Skip to content
Skip to Content
APIErrors

Errors

Every failure returns the same object.

{ "error": { "type": "permission_error", "code": "missing_scope", "message": "This key is missing the todos:write scope. Create a key with that scope to call createTodo.", "param": null, "docsUrl": "https://usedoozy.com/docs/api/errors#missing_scope", "specUrl": "https://usedoozy.com/openapi.json", "requestId": "req_8f2c1d4e5a6b7c8d9e0f1a2b" } }

Branch on code. It is stable. The message is written for a person reading a log and may change.

docsUrl points at the entry for that code further down this page, so an unfamiliar code is one click from an explanation.

specUrl is on every error and is always the same: the full OpenAPI document, served without a credential. It is there so that a program which has got something wrong can re-read the contract from the failure itself, rather than needing to know where the specification lives before it can recover.

requestId is on every response, error or not, in the X-Request-Id header. Quote it if you get in touch.

Types

type groups codes so you can decide what to do without knowing every code.

TypeStatusWhat to do
invalid_request_error400, 413, 415Fix the request. Retrying unchanged will fail the same way.
authentication_error401The credential is not usable. Check the key.
permission_error403The credential is real but not allowed this. Check its scopes.
not_found_error404Nothing with that id in this workspace.
conflict_error409The resource is not in a state that allows this.
rate_limit_error402, 429Wait and retry; Retry-After says how long. The exception is insufficient_minutes, which needs more Doozy Minutes rather than more patience.
api_error500, 504Ours. Retry with backoff; get in touch if it persists.

Codes

Authentication

missing_credentials — No credential was sent. Add Authorization: Bearer doozy_sk_....

invalid_api_key — The key is malformed, unknown, or revoked. All three return the same code deliberately: telling you a key exists but is revoked tells you the key exists.

expired_api_key — The key passed its expiry date. Create a new one; expiry cannot be extended.

invalid_session_token — The user session token is invalid or expired.

Permission

missing_scope — The key is real but was not granted a scope this operation needs. The message names it. See Scopes.

workspace_access_denied — The credential belongs to a different workspace, or the user is not a member of this one.

api_key_auth_required — You called a key management operation with an API key. Those need a signed-in user session.

Request

validation_failed — The body, path or query failed validation. param names the first field at fault.

invalid_cursor — The cursor was not issued by this endpoint. Start the list again without one.

payload_too_large — The body exceeded 500 KB.

unsupported_media_type — Send the body as application/json.

Not found

resource_not_found — No such record in this workspace. Ids are workspace-scoped, so a valid id from another workspace reads as missing.

unknown_operation — No operation is mounted at that method and path. Check the method as well as the path: several paths answer to more than one, and a POST to a path that only takes GET lands here rather than on a validation error.

Conflict

resource_conflict — The resource cannot do this right now. Sending to a chat whose agent is mid-turn, deleting a list that still holds todos, archiving the workspace default agent.

idempotency_key_reused — The same Idempotency-Key arrived with a different body. Use a fresh key for a different request.

idempotency_request_in_progress — The first request with this key is still running. Retry in a moment.

Limits

rate_limit_exceeded — Past the key’s per-minute budget. Retry-After gives the seconds. See Rate limits for the budget and the headers that track it.

insufficient_minutes — The workspace is out of Doozy Minutes, so agent work cannot start. Reading and writing records still works.

Ours

internal_error — Something failed on our side. Retry with backoff.

upstream_timeout — A blocking wait ran past its deadline. The work is still going; poll for the result.

Retrying safely

Reads are safe to retry. Writes need an Idempotency-Key:

curl -X POST https://api.usedoozy.com/api/v1/todos \ -H "Authorization: Bearer $DOOZY_API_KEY" \ -H "Idempotency-Key: 8f2c1d4e-5a6b-7c8d-9e0f-1a2b3c4d5e6f" \ -H "Content-Type: application/json" \ -d '{ "title": "Only one of me" }'

Retry with the same key and you get the first response, with Idempotency-Replayed: true. One todo exists, not two.

Keys are remembered for 24 hours, per credential. Use a fresh one per logical operation; a UUID is the easy choice.

Reusing a key with a different body is refused rather than answered from the cache, because answering would hide the bug.

Backing off

async function withRetry(request, attempts = 4) { for (let attempt = 0; attempt < attempts; attempt++) { const response = await request(); if (response.ok) return response; const body = await response.clone().json(); const type = body.error?.type; const retryable = type === 'rate_limit_error' || type === 'api_error'; if (!retryable || attempt === attempts - 1) return response; const retryAfter = Number(response.headers.get('Retry-After')); const delay = retryAfter ? retryAfter * 1000 : 2 ** attempt * 500 + Math.random() * 250; await new Promise((resolve) => setTimeout(resolve, delay)); } }

Retry on rate_limit_error and api_error. Everything else will fail the same way.

Last updated on