Walkthrough: estimate a part
This end-to-end guide ties the individual endpoints together into a realistic flow: upload a part, run a program against it, and read back a cost-and-time estimate.
Overview
POST /parts → create a part, get a presigned upload URL
PUT <upload.url> → upload the part file directly to S3
POST /parts/{id}/complete → start processing
GET /parts/{id} → poll until status: ready, returns currentProgramId
GET /parts/{id}/programs → list all programs on the part
GET /parts/{id}/programs/{programId}/estimate → read cost & time
Creating a part automatically creates a program for it, so there's no separate
program-creation step: once GET /parts/{id} reports ready it hands back the
currentProgramId, which is the selected/default program pointer for that part.
Pass the part id and program id to the nested estimate endpoint. By default,
that program uses the API key creator's default cut config. To use specific cut
configs, first call GET /cut-configs, then pass one or more ids as
cutConfigIds when creating the part. Toolpath creates one program per listed
cut config.
Every create call is asynchronous: the Engine processes the work in the
background, so you submit the request, then poll the corresponding GET
endpoint until status transitions from processing to ready (or failed).
The conventions are uniform across endpoints: every success response wraps its
payload in a top-level data object, and polls return 202 while the Engine
is still working and 200 once the resource is ready or failed. All money
values are integers in USD cents; all durations are integers in
seconds.
1. Create a part
Uploading a part is a three-step flow: create the part to get a short-lived
presigned URL, PUT the part file bytes straight to S3, then call complete
to start processing. Keeping the geometry out of the request body avoids
body-size limits on large CAD files. Writes require an
Idempotency-Key so retries are safe.
First, create the part with its metadata:
curl -X POST https://app.toolpath.com/api/public/v0/parts \
-H "Authorization: Bearer tp_live_xxxxxxxxxxxx" \
-H "Idempotency-Key: 5f3c9b2a-1d4e-4f8a-9c2b-7e1a3b6d8c40" \
-H "Content-Type: application/json" \
-d '{
"name": "bracket",
"units": "mm",
"fileName": "bracket.step"
}'
| Field | Required | Notes |
|---|---|---|
name | yes | Display name for the part. |
units | yes | mm or in. |
fileName | no | Original file name including its extension; the extension determines the upload format. Supported: STEP (.step/.stp/.step_solid_json), IGES (.igs/.iges), Parasolid (.x_t/.x_b/.xmt_txt/.xmt_bin), SolidWorks (.sldprt), CATIA (.catpart), NX (.prt). Defaults to <name>.step when omitted. stepFileName is a deprecated alias. |
autoCreateProgram | no | Defaults to true. Set false to skip auto-creating a program; the part still becomes ready (with currentProgramId null) and you create one yourself via POST /parts/{partId}/programs. |
cutConfigIds | no | One or more cut config ids from GET /cut-configs. Only valid when autoCreateProgram is true or omitted. Each id creates one program after feature detection completes. |
projectId | no | Attach the part to an existing project from POST /projects; 404 if the id is unknown or owned by another team. If the project has a default cut config, the auto-created program uses it unless cutConfigIds overrides. When omitted, the part is added to an auto-created project named API: <name> (<timestamp>). |
You get back 201 Created. The data object describes the new part and the
upload object carries a short-lived presigned PUT URL for the part file:
{
"data": {
"id": "abcd1234",
"status": "processing",
"name": "bracket",
"units": "mm",
"currentProgramId": null,
"failureCode": null,
"failureReason": null,
"createdAt": "2026-06-10T17:00:00.000Z"
},
"upload": {
"url": "https://s3.amazonaws.com/...",
"method": "PUT",
"expiresAt": "2026-06-10T17:15:00.000Z"
}
}
Upload the part file
PUT the raw part file to upload.url before expiresAt. No Content-Type
is required, so any HTTP client works. Copy upload.url from the response into
$UPLOAD_URL, then:
curl -X PUT "$UPLOAD_URL" --data-binary @bracket.step
Then call complete to confirm the upload and start feature detection:
curl -X POST https://app.toolpath.com/api/public/v0/parts/abcd1234/complete \
-H "Authorization: Bearer tp_live_xxxxxxxxxxxx" \
-H "Idempotency-Key: 6a4d0c3b-2e5f-4a02-9d1c-3e6f9a2b5d04"
complete returns 202 once processing starts, or 409 if the file has not
been uploaded yet. It is idempotent, so re-calling it on a part that is already
processing is a no-op.
By default, creating a part adds it to a Project named
API: <part-name> (<timestamp>), where the timestamp is the part's creation
time, so it shows up in the Toolpath web app. To group parts under a name you
choose (for example an internal quote number), create a project first with
POST /projects and pass its id as
projectId above. Either way, any program for the part, whether auto-created or
one you create yourself via POST /parts/{partId}/programs, attaches to that
same Project.
2. Poll the part until it's ready
curl https://app.toolpath.com/api/public/v0/parts/abcd1234 \
-H "Authorization: Bearer tp_live_xxxxxxxxxxxx"
Returns 202 while the Engine is still detecting features and 200 once the
part is ready (or failed). Poll with backoff (see
Rate limits) until you see:
{
"data": {
"id": "abcd1234",
"status": "ready",
"currentProgramId": "efgh5678",
"name": "bracket",
"units": "mm",
"failureCode": null,
"failureReason": null,
"createdAt": "2026-06-10T17:00:00.000Z"
}
}
status: "ready" means feature detection finished and any auto-created programs
have been queued. currentProgramId is the selected/default program pointer.
For the canonical program collection, call GET /parts/{id}/programs. If you
supplied multiple cutConfigIds, use that list to choose the program you want.
currentProgramId stays null until the part is ready, so read it from the
ready response rather than an earlier poll.
If feature detection fails, the part comes back 200 with status: "failed"
and a failureCode of feature_detection_failed plus a human-readable
failureReason; both fields are null while the part is processing or ready.
3. Request the estimate
Creating the part already created a program for it in the background, so there's
no program to create here. If you supplied multiple cutConfigIds, choose the
program id you want from GET /parts/{id}/programs. If you
created the part with autoCreateProgram: false, create one program yourself
first with POST /parts/{partId}/programs. Omit the body to use the API key
creator's default cut config, or pass one cutConfigId to choose a specific
cut config. Then use the returned program id below. Pass the currentProgramId
from the ready part to the estimate endpoint and poll it: it
returns 202 with an ETA while the Engine computes the strategy and 200 once
the estimate is ready.
To see the program's setups and features, call
GET /parts/{partId}/programs/{programId}. That endpoint accepts the same
?timings=fast|refined query param: it selects whether each setup's
machiningTimeSeconds reflects the fast heuristic times or the refined
simulation times, returning 202 while the refined pass is still computing. To
evaluate the same part against different settings after the part is ready, create
additional programs with POST /parts/{partId}/programs; omit the body for the
default cut config, or send { "cutConfigId": "cfg00001" }.
curl "https://app.toolpath.com/api/public/v0/parts/abcd1234/programs/efgh5678/estimate?timings=fast" \
-H "Authorization: Bearer tp_live_xxxxxxxxxxxx"
The timings query param selects the estimate variant:
fast(default): heuristic estimate, available as soon as the program is ready.refined: simulation-based estimate; may still be computing afterfastis ready.
While the Engine is still computing, this endpoint returns 202 with a
processing envelope carrying an ETA instead of the estimate; once the
estimate is ready it returns 200 with the estimate in data (see step 4).
A 422 means the Engine reported an error for this program: the same
program_generation_failed condition you'd see on
GET /parts/{partId}/programs/{programId}.
{
"data": {
"programId": "efgh5678",
"status": "processing",
"etaSeconds": 30
}
}
Poll with backoff until you get a 200 back.
4. Read the result
A ready estimate comes back with 200, wrapped in data like every other
response. Costs are integer USD cents; times are integer seconds.
{
"data": {
"programId": "efgh5678",
"status": "ready",
"currency": "USD",
"timings": "fast",
"totalCost": 4250,
"subtotalCost": 4250,
"discountCost": 0,
"quantity": 1,
"unitCost": 4250,
"cycleTimeSeconds": 480,
"breakdown": {
"machineTimeSeconds": 420,
"toolChangeSeconds": 60,
"setupTimeSeconds": 60
},
"costBreakdown": {
"machiningCost": 3000,
"setupCost": 750,
"materialCost": 400,
"otherCost": 100
},
"assumptions": {
"material": "6061 Aluminum",
"stock": [3.94, 3.15, 0.98],
"machineId": null
}
}
}
Key fields:
totalCost/unitCost: price in cents (4250= $42.50).unitCostistotalCostdivided byquantity; the quantity comes from your estimate preset, not from the request.cycleTimeSeconds: machining cycle time (machine time plus tool changes).setupTimeSecondsappears inbreakdownbut is not included incycleTimeSeconds.costBreakdown: where the cost comes from (machining, setup, material, other).assumptions: the material and stock dimensions[x, y, z]the estimate was computed against. Stock dimensions are always in inches in v0, regardless of the part'sunits.machineIdis reserved and currently alwaysnull.