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.
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 from0to100. It is the percentage of the part's machinable surface area that the configured tools can reach. A score of100means 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 byGET /parts/{partId}/programs/{programId}, so you can see how the part would be machined. Each setup'smachiningTimeSecondshere is always the fast (heuristic) estimate; this endpoint does not accept the?timingsparameter.
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 exampletoolAccessibility,challengingHole,sharpInteriorCorner).label/description: human-readable text for the category.severity:errorfor findings that block machining,warningfor advisories.affectedFeatureCountandfeatures: how many features fall under this category and a short list of them, each with itstypeand optionallabel.
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 isprogram_generation_failed.failureReason: a short, human-readable summary of the failure, suitable for display and logging. Branch onfailureCode, not on this text.
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.