Start
Errors
Every refusal has the same shape: a list of problems, each naming its field in the web form's words, and the plan that lifts a cap.
A call to the REST API that does not succeed answers with a status in the 4xx or 5xx range and one body shape, whichever call it was. The MCP server answers its refusals as JSON-RPC errors instead, and a 405 with an empty body; Tools lists them.
The shape
{
"errors": [
{ "field": "parts[2].length", "message": "Part 3 length is required.", "upgrade": null },
{ "field": null, "message": "This job has 640 cuts; the limit is 500 a job.", "upgrade": "bench" }
]
}
fieldis where in the request the problem is, written as a path likeparts[2].lengthorsheets[0].trim_left, ornullwhen it is about the whole request.messageis the problem in the words the web form uses, so a person who knows the optimizer recognises it. Rows are counted from 1 in the words and from 0 in the path.upgradeis the lowest plan that takes the request, when a cap or a plan-only option is the reason, elsenull.
An option the workspace's tier does not include is answered on its own: those problems come back alone, with 403, before the request's rules are checked.
Status codes
| Status | When |
|---|---|
400 |
The Idempotency-Key is not 1 to 255 characters. On the MCP server also: an MCP-Protocol-Version it does not speak, or a body that is not one JSON-RPC 2.0 message. |
401 |
No live API key. |
403 |
The workspace's tier does not include an option or feature the request asks for: a plan-only option, nesting, linked load stages, or the deep lane. upgrade names the plan that does. Also a sixth load in a month on Free, with upgrade naming the Bench plan. On the MCP server also: a browser request from an origin other than the MCP server's or the portal's. |
404 |
No job, plan or export at that id, or one the workspace's tier cannot read; or no such route. |
405 |
The MCP server was asked with anything but POST. The body is empty. |
409 |
A request with this Idempotency-Key is still running. |
413 |
The body is over 4 MB. |
422 |
The request breaks a rule, each problem naming its field; a job past its tier's cap on cuts, stock lengths, sheet sizes or nest parts, with upgrade set; a body that is not one JSON object, or holds more rows or longer text than the API takes; or the Idempotency-Key was used for another request. |
429 |
Over a per-minute budget; every solve slot of the workspace is busy ("The optimizer is busy right now."); or the workspace already has 5 nests, or 5 loads, waiting. |
500 |
Something went wrong on our side. Retry with the same Idempotency-Key. |
Checks answer 200
POST /v1/linear/validate and POST /v1/sheet/validate read the body as a solve does, so they refuse what a solve refuses before any rule is checked: an option the tier does not include (403), a body that is too large or not one JSON object (422), and a spent free-call budget (429). A cut list they can read is answered 200 with a verdict and the same problem list, so a client can show the problems inline:
{ "valid": false, "errors": [{ "field": "parts[0].quantity", "message": "Part 1 quantity is required.", "upgrade": null }] }
Checks are free calls and do not count as solves.
Handling them
- Show
messageto the person and point atfield. The words already match the web. - When
upgradeis set, offer the plan; the request is right, the tier is short. - Treat
404on a plan or export as "no such plan": the id is wrong, or belongs to another workspace. - Treat
409,429and500as retryable,4xxotherwise as a request to fix. Idempotency and retries has the pattern.