Errors
The error response shape the ClientSphere API returns, and what each error code means.
Every failure returns a JSON body with the same shape, whatever went wrong:
Branch on error.code. It is stable and meant to be read by your code.
message is meant for a human reading a log and may be reworded.
Codes
| Status | error.code | Meaning |
|---|---|---|
400 | VALIDATION_ERROR | The body failed validation. Carries a details array naming the fields. |
400 | INVALID_ID | A path parameter is not a well-formed UUID. Note this is not a 404. |
400 | INVALID_EMAIL | An email-shaped path parameter or field is not a valid address. |
400 | INVALID_CONTACT_ID | A contactId in the body is not a well-formed UUID. |
401 | UNAUTHORIZED | No API key was supplied at all. |
401 | INVALID_KEY | The key is unknown, deactivated, or its prefix does not match its mode. |
401 | KEY_EXPIRED | The key was valid but has passed its expiry date. |
403 | FORBIDDEN | The key is valid but lacks the permission this endpoint needs. |
404 | NOT_FOUND | No such record in this workspace. |
409 | NO_ELIGIBLE_ACTOR | Creating a widget found nobody to attribute it to — see below. |
429 | RATE_LIMITED | Too many requests — see Rate limits. |
500 | INTERNAL_ERROR | Something failed inside the API. |
Sending transactional email
adds a few statuses of its own — 202, 409, 413, 422 and 502 — which
are documented on that endpoint rather than here.
NO_ELIGIBLE_ACTOR
Widgets record who created them, and that column cannot be null. An API key is not a person, so the API attributes the widget to a real user: the one who created the key, failing that the workspace owner, failing that any active user.
If none of those exist — typically because the person who created the key has
since left and no other active user remains — the request is refused with 409
rather than writing a row it cannot attribute. Reactivate a user in the
workspace and retry; the request is unchanged and safe to send again.
A bad id is a 400, not a 404
This is the one that surprises people. An id that is not a well-formed UUID is
rejected before anything is looked up, so it comes back 400 INVALID_ID:
404 NOT_FOUND means the id was well-formed and simply matched nothing. So if
you are threading ids through from somewhere else, a 400 points at the
plumbing — a truncated value, a slug where a UUID belongs — and a 404 points
at the record.
401 and 403 are different problems
They are easy to conflate and they need opposite fixes.
401 is about the key itself: absent (UNAUTHORIZED), unknown or deactivated
or carrying the wrong prefix for its mode (INVALID_KEY), or past its expiry
(KEY_EXPIRED). 403 means the key authenticated perfectly well and is simply
not allowed to do this — the fix is the key's permissions, in
Settings → API Keys, not the credential.
The 403 message names the permission that was missing, so you rarely have to
guess which one to grant.
Validation errors
A 400 VALIDATION_ERROR includes a details array identifying what failed, so
you can surface it rather than showing a generic message. Each entry names the
field in path and explains the problem in message:
path is an array because it addresses nested fields — a problem inside an
array element arrives as ["items", 0, "quantity"]. Joining it with . is
usually enough to point a user at the right input.
500s
INTERNAL_ERROR means the failure was ours, not your request's. A read is
always safe to retry. For a write, the request may or may not have taken
effect, so re-check the record's state before sending it again rather than
assuming it was lost.
404 is workspace-scoped
Every request is scoped to the workspace its key belongs to. A 404 means no
such record in that workspace — an id that is perfectly real in another
workspace, or on the other side of the test/live divide, still returns 404.
When a record you know exists returns 404, check you are not reading with a
sk_test_ key something written with a sk_live_ one.