Skip to navigation

Errors

The error shape, the status codes the API uses, and how rate limits work.

The API uses ordinary HTTP status codes. A 2xx means success. Anything else comes with a JSON body that says what went wrong.

The error shape

Every error body has the same shape:

{
"statusCode": 403,
"error": "insufficient_scope",
"message": "This API key needs the experts:write scope.",
"requiredScopes": ["experts:write"],
"missingScopes": ["experts:write"],
"requestId": "req_01M726GS84892WE68HMF286YFH"
}
FieldWhat it is
statusCodeThe HTTP status, repeated in the body.
errorA short code. On the API’s own refusals it is a stable snake_case code, such as insufficient_scope or rate_limited, that you can branch on. On some generic errors it is the HTTP reason phrase instead, such as "Not Found".
messageA sentence for people. It can change; don’t parse it.
requestIdThe request’s id. It matches the X-Request-Id response header.

Some errors add fields of their own: requiredScopes and missingScopes on insufficient_scope, and retryAfterSeconds on rate_limited.

Branch on statusCode first, then on error.

Request ids

Every response, success or error, carries an X-Request-Id header such as req_01M726GS2P1N4DCP7FARE8MA4F. Quote it when you contact support. It also finds the request in Developers → Request log.

You can send your own X-Request-Id (up to 64 letters, digits, ., _, : or -). The API keeps it and returns it, so you can match API requests to your own logs. Anything else is replaced with a new id.

Server errors

An unexpected failure answers 500 with a generic message and an errorId, which is the same value as requestId:

{
"statusCode": 500,
"message": "Something went wrong. Please try again.",
"errorId": "req_01M726GS2P1N4DCP7FARE8MA4F",
"requestId": "req_01M726GS2P1N4DCP7FARE8MA4F"
}

The details stay in Rydhm’s logs under that id.

Status codes

StatusCodes in errorWhat it means and what to do
400variesThe request is not valid: a missing or malformed field. The message says which. Fix the request.
401invalid_api_keyThe key is unknown, wrong, or deleted.
401api_key_disabledThe key, or its service account, is disabled. An admin can enable it.
401api_key_expiredThe key has expired. Create a new one.
401UnauthorizedNo key was sent.
402payment_requiredThe workspace has run out of credits. See the note below.
403insufficient_scopeThe key lacks a scope the route needs. missingScopes lists them.
403expert_not_allowedThe key is limited to other experts.
403not_available_to_api_keysThe route can’t be called with a key at all: it is only for people signed in to the app.
403ip_not_allowedThe key’s IP allowlist doesn’t include your address.
403plan_limitThe workspace’s plan allows no more of this, such as experts or knowledge. See the note below.
403workspace_deletedThe workspace has been deleted.
404Not FoundNothing with that id exists in your workspace. Ids from other workspaces also answer 404.
409stale_edit (in code)On PATCH /v1/agents/{id} with baseUpdatedAt: someone saved the expert after you loaded it. Reload and try again. This one error puts its code in a code field instead of error.
409idempotency_in_progressA request with the same Idempotency-Key is still running.
422idempotency_key_reusedThe Idempotency-Key was already used with a different body.
429rate_limitedToo many requests. Wait Retry-After seconds.
429concurrency_limitOn voice sessions: the workspace already has as many calls running as it may. Try again when one ends.
5xxvariesA problem on Rydhm’s side. Retry with backoff, and quote the requestId if it keeps happening.

Rydhm doesn’t enforce credits or plan limits yet. When it does, routes that start paid work (chats, voice sessions, meetings) can answer 402 payment_required, and creating experts or adding knowledge can answer 403 plan_limit. Handle both now so your integration is ready.

Errors during a stream

A chat reply stream (GET /v1/chat/conversations/{id}/stream) sends its headers before the reply is generated. If the reply fails after that, the stream carries an error frame instead of an HTTP status:

data: {"type":"error","message":"Something went wrong. Please try again."}

Rate limits

Rate limits stop runaway loops and abuse. They are set well above normal use.

Each limited route counts requests in fixed windows, in two buckets at once:

  • the workspace, shared by everyone and every key in it;
  • the key, so one integration can’t use up the whole workspace’s budget.

A request is refused when either bucket is full.

Headers

Rate-limited routes return the state of the tightest budget the request touched:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the window resets.
Retry-AfterOn a 429 only: seconds to wait before retrying.
HTTP/1.1 201 Created
X-RateLimit-Limit: 240
X-RateLimit-Remaining: 233
X-RateLimit-Reset: 21

Routes without a limit, such as reading an expert, don’t send these headers.

When you hit a limit

The API answers 429:

{
"statusCode": 429,
"error": "rate_limited",
"message": "Too many chat requests. Try again in 12 seconds.",
"retryAfterSeconds": 12,
"requestId": "req_01M726GS2P1N4DCP7FARE8MA4F"
}

Wait Retry-After seconds, then retry. Don’t retry in a tight loop: every retry inside the window is refused too. The SDKs retry 429 responses for you with backoff.

Limits by route

RoutesPer workspacePer key
Chat: /v1/chat/conversations/… (start, send, stream, read, delete)240 per minute240 per minute
Knowledge: /v1/agents/{id}/kb/…, /v1/kb/collections/…, /v1/kb/docs/…, /v1/kb/crawls/…60 per minute60 per minute
Website imports: POST …/kb/crawl, POST /v1/kb/crawls/{id}/refresh10 per hour10 per hour
Website import preview: POST /v1/kb/crawl/preview30 per hour30 per hour
Voice sessions: POST /v1/voice/sessions60 per minute30 per minute
Meetings (reads and changes): /v1/meetings/…600 per minute600 per minute
Send to a meeting: POST /v1/meetings30 per minute and 300 per hour20 per minute and 300 per hour
Analytics: /v1/analytics/…300 per minute300 per minute
Analytics live view: GET /v1/analytics/live600 per minute600 per minute
Analytics CSV export: GET /v1/analytics/export.csv10 per minute10 per minute
Tool tests: POST /v1/workspace-tools/{id}/test, POST /v1/workspace-tools/test-draft30 per minute30 per minute
Tool from curl: POST /v1/workspace-tools/parse-curl60 per minute60 per minute
Tool drafts: POST /v1/workspace-tools/draft30 per hour30 per hour
MCP: POST /mcp/experts/{id}120 per minute60 per minute

The MCP endpoint also lets one key run at most 3 ask_… questions at the same time. See MCP.

Voice sessions are also capped by how many calls a workspace may run at once. Past the cap, a new session gets 429 with error: "concurrency_limit".

Public routes

The public routes under /v1/public/… need no key. They are limited per visitor IP address and per expert instead. A refused request gets 429 with a Retry-After header and the message “Too many requests. Please wait a moment and try again.” These routes don’t send the X-RateLimit-* headers.