Skip to main content

Walkthrough: machinability report

This guide covers the shortest path to a machinability decision: upload a part, choose the program you want to evaluate, then poll that program's machinability endpoint until it is ready and read the machinability score. It is built for automated screening, where a part that scores 100 can skip manual review.

Overview

POST /parts → create the part, get a presigned upload URL
PUT <upload.url> → upload the part file directly to S3
POST /parts/{id}/complete → start feature detection
GET /parts/{id} → poll until status: ready, returns currentProgramId
GET /parts/{id}/programs/{programId}/machinability → read machinabilityScore

You do not need to create a program. Completing the upload automatically kicks off feature detection and, once that finishes, creates one or more programs in the background. By default the auto-created program uses the API key creator's default cut config. To screen against specific cut configs, first call GET /cut-configs, then pass one or more ids as cutConfigIds when creating the part. Each program has its own machinability result.

Every success response wraps its payload in a top-level data object. Polls return 202 while the Engine is still working and 200 once the report resolves to ready or failed. Durations are integers in seconds.

1. Create a part

Creating a part is a three-step upload: 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"
}'

You get back 201 Created with the new part and an upload block containing a short-lived presigned PUT URL. See the Estimate a part walkthrough for the complete field reference.

{
"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"
}
}

Hold on to the id (abcd1234 here). That is the part id you poll in step 2.

Next, copy upload.url from the response into $UPLOAD_URL and PUT the raw part file to it before expiresAt. No Content-Type is required:

curl -X PUT "$UPLOAD_URL" --data-binary @bracket.step

Finally, call complete so Toolpath starts 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>), so it shows up in the Toolpath web app alongside parts created in the UI. To group parts under a name you choose, create a project with POST /projects and pass its id as projectId on POST /parts.

2. Poll the part and choose a program

curl https://app.toolpath.com/api/public/v0/parts/abcd1234 \
-H "Authorization: Bearer tp_live_xxxxxxxxxxxx"

While feature detection is still running, the part endpoint returns 202 with status: "processing". Poll with backoff until the part is ready; the ready part includes currentProgramId, the selected/default program pointer.

{
"data": {
"id": "abcd1234",
"status": "ready",
"currentProgramId": "efgh5678",
"name": "bracket",
"units": "mm",
"failureCode": null,
"failureReason": null,
"createdAt": "2026-06-10T17:00:00.000Z"
}
}

Call GET /parts/{id}/programs to list the canonical program collection with metadata such as cut config id and name, especially when the part was created with multiple cutConfigIds.

3. Poll machinability and read the score

curl https://app.toolpath.com/api/public/v0/parts/abcd1234/programs/efgh5678/machinability \
-H "Authorization: Bearer tp_live_xxxxxxxxxxxx"

While the Engine is still building the program, the endpoint returns 202 with a processing envelope carrying an ETA in seconds.

{
"data": {
"programId": "efgh5678",
"status": "processing",
"etaSeconds": 30
}
}

Poll with backoff (see Rate limits) until you get a 200.

A ready report comes back with 200 and status: "ready":

{
"data": {
"partId": "abcd1234",
"programId": "efgh5678",
"status": "ready",
"machinabilityScore": 100,
"totalFeatures": 12,
"totalMachinableFeatures": 12,
"assessments": [],
"setups": [
{
"id": "ijkl9012",
"position": 1,
"isMultiAxis": false,
"machiningTimeSeconds": 420,
"features": [{ "type": "through_hole", "label": "hole 1" }]
}
]
}
}

Key fields:

  • machinabilityScore: an integer from 0 to 100. It is the percentage of the part's machinable surface area that the configured tools can reach. A score of 100 means every detected feature can be machined with the tools in the API key owner's default cut config.
  • totalFeatures / totalMachinableFeatures: how many features the Engine detected, and how many of those are machinable. When these two match, no feature was flagged as unreachable.
  • assessments: design-advisor findings grouped by category (see below). An empty array means nothing was flagged.
  • setups: the same setup and feature breakdown returned by GET /parts/{partId}/programs/{programId}, so you can see how the part would be machined. Each setup's machiningTimeSeconds here is always the fast (heuristic) estimate; this endpoint does not accept the ?timings parameter.

Assessments

When a part scores below 100, the assessments array explains why. Each entry is one category of finding:

{
"category": "challengingHole",
"label": "Challenging Hole",
"description": "Holes that have high L/D ratios or are deep in the part are challenging to machine.",
"severity": "error",
"affectedFeatureCount": 1,
"features": [{ "type": "blind_hole", "label": "hole 3" }]
}
  • category: a stable machine-readable key (for example toolAccessibility, challengingHole, sharpInteriorCorner).
  • label / description: human-readable text for the category.
  • severity: error for findings that block machining, warning for advisories.
  • affectedFeatureCount and features: how many features fall under this category and a short list of them, each with its type and optional label.

Failed reports

If program generation fails, machinability resolves to 200 with status: "failed" and an empty report body. The failureCode and failureReason fields tell you what failed; both are null on a ready report. Treat a failure as "could not assess," not "not machinable," and route the part to manual review.

{
"data": {
"partId": "abcd1234",
"programId": "efgh5678",
"status": "failed",
"machinabilityScore": null,
"totalFeatures": 0,
"totalMachinableFeatures": 0,
"assessments": [],
"setups": [],
"failureCode": "program_generation_failed",
"failureReason": "Program generation failed for this part."
}
}
  • failureCode: a stable, machine-readable value you can branch on. For machinability this is program_generation_failed.
  • failureReason: a short, human-readable summary of the failure, suitable for display and logging. Branch on failureCode, not on this text.
Tools come from the selected cut config

The report is built against the selected program's cut config and linked tool libraries. By default that is the API key owner's default cut config. To use specific tooling, pass cutConfigIds to POST /parts; each id creates a program with its own report inputs.