Errors

Every failure returns the same envelope, whatever the endpoint:

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "AGENT_NOT_FOUND",
    "message": "Agent not found"
  },
  "request_id": "req_a1b2c3d4"
}

Branch on type for broad handling and code for specific cases. message is written for a human reading a log — do not parse it, and do not show it to your end users verbatim.

Error types

TypeStatusWhat it means
invalid_request_error400The request was malformed or failed validation
authentication_error401The API key is missing, malformed, revoked, or expired
permission_error403The key is valid but lacks the required scope
not_found_error404No such resource in your organization
conflict_error409The resource is not in a state that allows this action
rate_limit_error429Too many requests for this key — see rate limits
api_error5xxSomething went wrong on our side

A 404 may mean "not yours"

Requesting a resource that exists but belongs to another organization returns 404, not 403. This is deliberate: a 403 would confirm the id exists, letting anyone probe for valid ids across tenants. Treat 404 as "no such resource for this key".

Handling failures well

Retry 429 and 5xx, never 4xx. A 400 will fail identically no matter how many times you send it. Back off exponentially and cap your attempts.

Treat 409 as a state problem, not a transient one. CALL_NOT_ACTIVE means the call already ended; retrying will not change that. Re-read the resource and decide again.

javascript
async function callApi(path, init, attempt = 0) {
  const response = await fetch(`https://api.rabbitt.ai/api/v1${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.RABBITT_API_KEY}`,
      "Content-Type": "application/json",
      ...init?.headers,
    },
  });

  if (response.ok) return response.json();

  const { error, request_id } = await response.json();

  // Only transient failures are worth retrying.
  const transient = response.status === 429 || response.status >= 500;
  if (transient && attempt < 4) {
    await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
    return callApi(path, init, attempt + 1);
  }

  throw new Error(`${error.code}: ${error.message} (request_id: ${request_id})`);
}

Common codes

CodeStatusUsually means
INVALID_API_KEY401Check the Authorization header and that the key is not revoked
PERMISSION_DENIED403The key needs an additional scope
AGENT_NOT_FOUND404Wrong agent id, or it belongs to another organization
ORGANIZATION_AGENT_LIMIT409Your plan's agent quota is full — delete one or upgrade
INSUFFICIENT_CREDITS402Top up before placing more calls
CALL_NOT_ACTIVE409The call already ended
INVALID_AGENT_TYPE400Interviews need a voice agent; calls need a phone agent
NO_ALLOWED_ORIGINS403Add embed origins to the API key before minting embed tokens
BYOK_NOT_READY400Creating a BYOK key before you have a verified provider key for the voice LLM, speech-to-text, and text-to-speech engines — add them in billing settings
BYOK_PROVIDER_KEY_REQUIRED400An agent was set to a provider your organization has no BYOK key for; add the key or choose a configured provider
MISSING_BYOK_CREDENTIAL400An interview or call tried to run on a BYOK key whose provider credential is missing or was removed after the agent was configured

Dashboard Welcome