Errors
A failed request returns an HTTP status and a JSON body with one of six error tags and a message a person can act on.
A failed request returns a 4xx or 5xx status and a JSON body with two fields: _tag, which
names the kind of error, and message, a sentence that says what went wrong and, where it can,
what to do about it.
json
{ "message": "An archived product already uses the slug storefront. Restore it or choose another slug.", "_tag": "Conflict" }
Branch on _tag or the status, never on the message: messages are written for people and can
change.
Error tags
| Status | _tag | Meaning |
|---|---|---|
400 | InvalidInput | The request is malformed, or a value is not allowed, such as a slug with capitals or a body missing a required field |
401 | Unauthorized | The request has no valid API key: it is missing, unknown or revoked |
403 | Forbidden | The key's user or role may not do this, such as a viewer creating a ticket or a member writing a production secret |
404 | NotFound | The workspace or object does not exist, or the caller cannot see it |
409 | Conflict | The change conflicts with current data, such as a slug that is already in use or an archived object |
500 | Internal | Clanker Ship failed to handle the request |
Each endpoint in the reference lists the tags it can return.
What each one means for you
InvalidInputnames the rule in its message, such asUse 2–48 lowercase letters, numbers or hyphens, …. A body that does not match the endpoint's schema at all, such as a missing required field or a value of the wrong type, gets the messageInvalid request payload. Fix the request before sending it again.Unauthorizedmeans the key itself failed. Check that you sent it, and that it was not revoked. See Authentication.Forbiddenmeans the key works but its role or kind does not allow the action, such as an account API key commenting on a ticket. Use a key with the right role.NotFoundcovers objects in other workspaces too: a workspace you do not belong to, or another workspace's product, returns404, never403, so the API does not reveal what exists.Conflictmeans the request was valid but the current data stops it: a taken name, an archived product, a test run that already finished, or a secret that changed since you read it. Read the object again, then decide. Repeating the same request gets the same answer.Internalmeans Clanker Ship failed. Retry after a short wait; before retrying a write that is not idempotent, such as creating a ticket, check whether the first attempt went through. If it keeps happening, report it; see Request IDs.
Handle errors in code
typescript
const response = await fetch("https://api.clankertest.com/v1/workspaces/acme/products", { method: "POST", headers: { Authorization: `Bearer ${process.env.CLANKER_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ slug: "storefront", name: "Storefront", type: "website" }), }); if (!response.ok) { const error = (await response.json()) as { _tag: string; message: string }; if (error._tag === "Conflict") { // Already there: read it instead of creating it. } else { throw new Error(`${response.status} ${error._tag}: ${error.message}`); } }
Other shapes
- A path that matches no endpoint returns
404with an empty body. - Test-run file uploads and downloads (
/test-runs/{run}/files/{path}) return their errors as{ "message": "…" }, without_tag. - The agent HTTP API and MCP use their own shape,
{ "code", "message", "requestId" }, with codes such asnot_foundandpermission_denied; see Agent HTTP API.