Error Codes and Handling
Every non-2xx response from the Loomstack API uses the same envelope, so one error handler covers the whole API. This reference lists each error code, its cause and fix, and — critically — which errors are retryable and which mean your request will never succeed as written.
The Error Envelope
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "title must be between 1 and 300 characters",
"field": "title",
"request_id": "req_a81f42"
}
}
code— stable, machine-readable string; branch your logic on this, never onmessagefield— present on validation errors, naming the offending fieldrequest_id— include this when contacting support; it lets the team find your exact request in server logs
4xx Errors: Fix the Request
These indicate a problem with your request. Do not retry unchanged — the result will be identical.
| Status | Code | Cause and fix |
|---|---|---|
400 | VALIDATION_FAILED | A field failed validation; see field and message |
400 | MALFORMED_JSON | Body isn’t valid JSON — check quoting and trailing commas |
401 | INVALID_API_KEY | Key is wrong, revoked, or missing the Bearer prefix |
401 | KEY_EXPIRED | Key passed its expiry date; generate a new one |
403 | INSUFFICIENT_SCOPE | Key lacks the required scope (e.g., calling POST /v1/projects without write:projects) — check with GET /v1/me |
403 | PLAN_LIMIT | Feature not on your plan (e.g., SSO endpoints on Pro) |
404 | PROJECT_NOT_FOUND, TASK_NOT_FOUND, USER_NOT_FOUND | Bad ID, or the key’s user can’t see that resource — private projects return 404, not 403, to avoid leaking existence |
409 | PROJECT_ARCHIVED | Writes against an archived project; restore it first |
409 | NO_SEATS_AVAILABLE | Invite would exceed the seat count; add seats or free one |
409 | DUPLICATE_WEBHOOK | A subscription with the same URL and events already exists |
422 | STATE_NOT_IN_WORKFLOW | The state value doesn’t exist in that project’s workflow — fetch valid states via GET /v1/projects/{projectId} |
429 and 5xx Errors: Safe to Retry
| Status | Code | Behavior |
|---|---|---|
429 | RATE_LIMIT_EXCEEDED | Wait the Retry-After seconds, then retry |
500 | INTERNAL_ERROR | Transient server fault — retry with backoff |
503 | SERVICE_UNAVAILABLE | Maintenance or overload — retry with backoff, check status.loomstack.com |
A solid retry policy: retry only 429, 500, and 503; use exponential backoff with jitter (e.g., 1s, 2s, 4s, 8s); cap at 5 attempts; and make write retries idempotent by passing an Idempotency-Key header:
curl -X POST https://api.loomstack.com/v1/projects/proj_884/tasks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: 7f3a9c1e-order-4402" \
-H "Content-Type: application/json" \
-d '{"title": "Investigate alert #4402"}'
If a retry re-sends the same Idempotency-Key within 24 hours, the API returns the original result instead of creating a duplicate.
Handling Partial Failures in Batch Requests
Batch endpoints (POST /v1/projects/{projectId}/tasks/batch) return 207 when some items fail:
{
"success": false,
"results": [
{ "index": 0, "success": true, "data": { "id": "task_901" } },
{ "index": 1, "success": false, "error": { "code": "VALIDATION_FAILED", "field": "due_date" } }
]
}
Iterate results and re-submit only the failed indexes after fixing them — never re-send the whole batch, or you’ll duplicate the successful items.
Logging Recommendations
- Log
code,request_id, and the endpoint for every error — not the full request body, which may contain sensitive content - Alert on sustained
401/403(a rotated or revoked key) and on5xxrates above ~1% - Treat a sudden spike of
404s after a deploy as a likely ID-mapping bug on your side
Summary
Branch on error.code, never on message text. Treat 4xx as permanent until the request changes, retry 429/500/503 with jittered backoff, and use Idempotency-Key so retried writes can’t duplicate. For batch calls, handle per-item results, and always keep the request_id — it’s your fast path through support.