Errors & Workflow Blockers
One error envelope; 409s explain exactly what business rule stopped the request and what would unblock it.
Every error, from any layer, has the same shape:
{
"error": {
"type": "conflict",
"code": "job_blocked",
"message": "This job cannot be started yet.",
"request_id": "req_4a13d1f7a5494e7b89249f32b4436d79",
"param": null,
"details": { … },
"blockers": [
{ "type": "authorization", "reason": "not_authorized",
"message": "The customer has not approved this work.",
"resolution": ["Send the estimate for approval from Shop Commander."] }
]
}
}
| Field | Meaning |
|---|---|
type |
Coarse class: invalid_request, authentication, permission, not_found, conflict, rate_limited, api_error |
code |
Stable machine code — branch on this, never on message |
message |
Human explanation, safe to log and show to an operator |
request_id |
Quote it to the shop or support; it is traceable end to end |
param |
For validation errors: the offending field (dotted path) |
details |
Structured extras (required_scope, supported values, retry_after_seconds, …) |
blockers |
For conflict: every business rule in the way, each with a reason and a list of possible resolutions |
Status codes
| Status | type |
When |
|---|---|---|
| 400 | invalid_request |
Malformed id, bad cursor, bad limit, missing Idempotency-Key |
| 422 | invalid_request |
Body validation failed (code: validation_failed, one entry per field in details.errors) |
| 401 | authentication |
Missing/invalid/revoked key |
| 403 | permission |
Missing scope, suspended integration, inactive shop |
| 404 | not_found |
No such resource for this shop — a record in another shop is indistinguishable from a nonexistent one |
| 409 | conflict / idempotency |
Stale expected_version (version_conflict), a reused Idempotency-Key with a different body (idempotency_key_reused), a duplicate, or a workflow blocker |
| 429 | rate_limited |
Budget exhausted; honour Retry-After |
| 5xx | api_error |
Ours; retry with backoff, then report the request_id |
Workflow blockers
Shop Commander enforces the shop's operating rules: work cannot start before the customer approves it, an appointment cannot be booked outside business hours or over a technician's capacity, a job cannot complete with unresolved parts, and so on. When a rule stops a write the API answers 409 with blockers listing every rule in the way, so you can show an operator the whole picture rather than discovering them one by one.
Blocker types you will meet: authorization, parts, shop_hours, technician_capacity, appointment, state. Each carries a reason code you can branch on and a resolution list written for a person.
The API also tells you in advance what is possible: appointments and jobs include available_actions, the list of domain actions valid right now for this caller. Prefer checking it to guessing.
Validation errors
422 invalid_request with code: "validation_failed" includes details.errors, one entry per field with its dotted param path and message. Unknown fields are rejected on purpose — a typo must not be silently ignored.
Retrying
- Retry
429afterRetry-Afterseconds, and5xx/network failures with exponential backoff. - Retry creates only with the same
Idempotency-Key— see Idempotency. - Do not blindly retry workflow/version/key-reuse
409s; read their code and blockers.idempotency_in_progressis the exception: retry the identical request after itsRetry-Afterdelay.