Skip to main content

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"
}'
FieldRequiredNotes
nameyesDisplay name for the part.
unitsyesmm or in.
fileNamenoOriginal 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.
autoCreateProgramnoDefaults 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.
cutConfigIdsnoOne 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.
projectIdnoAttach 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.

Parts appear in the web app as a Project

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.

Inspecting the program or creating more

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 after fast is 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). unitCost is totalCost divided by quantity; the quantity comes from your estimate preset, not from the request.
  • cycleTimeSeconds: machining cycle time (machine time plus tool changes). setupTimeSeconds appears in breakdown but is not included in cycleTimeSeconds.
  • 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's units. machineId is reserved and currently always null.