Skip to main content
Staffify Docs
Updated
Docs/API Reference/Error Codes
API Reference

Error Codes

All Staffify API errors follow a consistent format. This page covers HTTP status codes, authentication errors, call lifecycle errors, and validation errors across every endpoint.

Error response format

There are two error envelope shapes across the API. Most REST v1 endpoints use a flat envelope. The OpenAI-compatible /v1/chat/completions endpoint uses a nested envelope that matches the OpenAI SDK error shape so client libraries parse it natively.

Flat envelope (REST v1)

Used by every endpoint under /api/v1/* except /chat/completions. Body includes a code, a human-readable message, and optionally a details object with field-level info.

// Standard error
HTTP/1.1 401 Unauthorized
{
  "code":    "INVALID_API_KEY",
  "message": "The API key provided is invalid or has been revoked."
}

// Validation error with field details
HTTP/1.1 400 Bad Request
{
  "code":    "VALIDATION_ERROR",
  "message": "Request validation failed.",
  "details": {
    "language": "Must be a valid BCP-47 language code.",
    "max_duration": "Must be between 60 and 7200."
  }
}

Nested envelope (chat completions)

Used by POST /api/v1/chat/completions. Every field is present on every error. Every response also includes an X-Request-Id header that matches error.request_id. Include that value when you contact support.

HTTP/1.1 402 Payment Required
X-Request-Id: chatreq-8a1e6b9f-3c2d-4d5f-9e2b-1a0b2c3d4e5f

{
  "error": {
    "message": "Insufficient credits. Estimated worst-case cost €0.0362 exceeds balance €0.0100.",
    "type": "invalid_request_error",
    "code": "INSUFFICIENT_CREDITS",
    "param": null,
    "request_id": "chatreq-8a1e6b9f-3c2d-4d5f-9e2b-1a0b2c3d4e5f"
  }
}

type is one of invalid_request_error, authentication_error, permission_error, rate_limit_error, upstream_error, server_error. See the full list of chat completions codes on the chat completions page.

Always check the code field programmatically. Never match on the message string, which may change between releases.

HTTP status codes

StatusMeaningWhen it occurs
200OKRequest succeeded. Body contains the response data.
201CreatedResource created successfully (e.g. POST /v1/agents, POST /v1/webhooks).
400Bad RequestRequest validation failed. Check the details object for field-level errors.
401UnauthorizedMissing, invalid, or revoked API key. See authentication errors below.
402Payment RequiredInsufficient credits to complete the action.
403ForbiddenThe key does not have permission for this action (e.g. read-only key on a write endpoint, or project key on an org endpoint).
404Not FoundThe requested resource does not exist, or belongs to a different project.
409ConflictOperation cannot be completed due to current resource state (e.g. deleting an agent with active calls, or an Idempotency-Key reused with a different body).
413Payload Too LargeRequest body exceeds the 2 MB cap on chat completions, or the prompt exceeds the per-tier input token ceiling.
422Unprocessable EntityThe request was well-formed but the action failed downstream (e.g. a phone transfer rejected by the carrier).
429Too Many RequestsRate limit exceeded. Check X-RateLimit-Remaining and X-RateLimit-Reset headers.
500Internal Server ErrorUnexpected server error. Retry with exponential back-off. Contact support if persistent.

Authentication errors

CodeHTTPDescription
MISSING_API_KEY401No Authorization or X-Api-Key header was provided.
INVALID_API_KEY401The API key does not exist or has been revoked.
INSUFFICIENT_CREDITS402Your credit balance is zero or below the minimum required for this action.
READ_ONLY_KEY403The API key is read-only and cannot be used for write operations (POST, PATCH, DELETE).
ORG_KEY_REQUIRED403This endpoint requires an org-scoped key (sfy_org_...), but a project key was provided.
PROJECT_NOT_FOUND404When using an org key, the X-Project-Id header references a project that does not exist or does not belong to this org.
RATE_LIMIT_EXCEEDED429Requests per minute limit reached for your tier. See X-RateLimit-Reset for when the window resets.
AUTH_RATE_LIMITED429Too many failed authentication attempts from this IP (100 failures in 15 minutes triggers a temporary block).

Chat completions error codes

These codes are specific to POST /api/v1/chat/completions and use the nested envelope with a type field.

CodeHTTPTypeDescription
UNSUPPORTED_MODEL400invalid_request_errormodel field must be "staffi-1.0".
INVALID_MESSAGES400invalid_request_errormessages missing or not a non-empty array.
MODEL_NOT_IN_PLAN403permission_errorRequested model is not available on your tier.
WALLET_MISSING402invalid_request_errorNo credits wallet exists for this organization. Contact support.
INPUT_TOO_LARGE413invalid_request_errorPrompt exceeds the per-tier input token ceiling.
REQUEST_TOO_LARGE413invalid_request_errorRequest body larger than 2 MB.
AI_RATE_LIMIT_EXCEEDED429rate_limit_errorChat completions per-minute limit exceeded (stricter than the general REST RATE_LIMIT_EXCEEDED).
AI_CONCURRENT_LIMIT_EXCEEDED429rate_limit_errorToo many simultaneous in-flight requests. Reduce parallelism.
IDEMPOTENCY_KEY_TOO_LONG400invalid_request_errorIdempotency-Key header exceeds 128 characters.
IDEMPOTENCY_KEY_CONFLICT409invalid_request_errorSame Idempotency-Key already used with a different request body.
IDEMPOTENCY_STREAMING_REPLAY409invalid_request_errorSame Idempotency-Key was used on a streaming request. Streaming responses cannot be replayed.
IDEMPOTENCY_KEY_QUOTA_EXCEEDED429rate_limit_errorExceeded 10,000 unique Idempotency-Keys for this API key in 24 hours.
INVALID_REQUEST400invalid_request_errorUpstream provider rejected the request as invalid.
UPSTREAM_FORBIDDEN403permission_errorUpstream provider refused the request.
UPSTREAM_RATE_LIMIT429rate_limit_errorUpstream provider is throttling. Retry with backoff.
UPSTREAM_UNAVAILABLE502upstream_errorUpstream provider temporarily unavailable. Retry.
UPSTREAM_ERROR502upstream_errorUpstream provider returned an unexpected error.
UPSTREAM_AUTH_FAILURE502server_errorInternal issue reaching the upstream provider. Contact support.
UPSTREAM_NOT_FOUND502upstream_errorUpstream provider returned an unexpected 404.

For security reasons the message field on chat-completion errors never echoes fragments of your request. Include request_id when contacting support.

Rate limit headers

These headers are included on every API response so you can monitor your limit consumption before hitting a 429.

HeaderDescription
X-RateLimit-LimitMaximum general REST requests per minute for your tier (all /api/v1/* endpoints).
X-RateLimit-RemainingRequests remaining in the current 1-minute window.
X-RateLimit-ResetUnix timestamp (seconds) when the rate limit window resets.
X-RateLimit-TierYour current tier (payg, starter, growth, scale, enterprise).
X-Concurrent-Call-LimitMaximum simultaneous active calls allowed on your tier.
X-AI-RateLimit-LimitChat completions per-minute cap (stricter than the general REST cap). Only on /api/v1/chat/completions.
X-AI-RateLimit-RemainingChat completions requests remaining in the current 1-minute window.
X-AI-RateLimit-ResetUnix timestamp (seconds) when the chat completions window resets.
X-AI-RateLimit-TierTier used for the chat completions caps.
X-AI-Concurrent-LimitMaximum simultaneous in-flight chat completions requests.
X-AI-Concurrent-CurrentCurrent in-flight chat completions requests count.
X-Request-IdUnique per-request identifier. Present on chat completions responses. Include in support tickets.
// Handle 429 with exponential back-off
async function apiRequest(url, options, attempt = 0) {
  const res = await fetch(url, options);

  if (res.status === 429) {
    const reset = res.headers.get('X-RateLimit-Reset');
    const waitMs = reset
      ? (parseInt(reset) * 1000 - Date.now()) + 100
      : Math.min(1000 * 2 ** attempt, 30000);

    await new Promise(r => setTimeout(r, waitMs));
    return apiRequest(url, options, attempt + 1);
  }

  return res;
}

Call ended_reason values

The ended_reason field on a completed call tells you exactly why the call ended. Available in GET /v1/calls/:callId and in the call.ended webhook payload.

ended_reasonWhat happened
agent-hangupThe AI agent ended the call (e.g. used the end-call tool or end_call_enabled triggered).
caller-hangupThe caller hung up.
apiThe call was ended via POST /v1/calls/:callId/end.
transferThe call was transferred to another number via POST /v1/calls/:callId/transfer.
timeoutThe call reached its max_duration limit.
silence-timeoutThe inactivity_timeout was reached: the caller was silent for too long.
errorAn internal error occurred during the call (e.g. STT/TTS failure, LLM error). Check the call detail for more context.
max-durationAlias for timeout. The global maximum call duration was reached.

Call status and disposition

status

ringingCall connected, agent starting up.
in-progressActive live call.
completedCall ended normally.
failedCall ended due to an error.
busyDestination was busy (transfers only).
no-answerCall was not answered.
canceledCall canceled before connecting.
rejectedCall rejected (e.g. insufficient credits, concurrency limit).

disposition

answeredCall was answered and conversation took place.
no-answerNever answered (missed).
transferredCaller was transferred to another number.
busyLine was busy.
failedCall failed before connection.
voicemailReached voicemail (future).
rejectedCall rejected before ringing.

Common validation errors

ScenarioHTTPError message
Missing required field (e.g. name, server_url)400name is required.
Invalid language code400language must be a supported BCP-47 code.
Voice not available for language400Voice is not available for the selected language.
server_url is not HTTPS or not reachable400server_url must be a valid public HTTPS URL.
server_url blocked by SSRF filter (internal IP)400server_url blocked by SSRF filter.
max_duration out of range400max_duration must be between 60 and 7200.
knowledge_base_ids exceeds limit of 20400knowledge_base_ids cannot exceed 20 entries.
Deleting agent with active calls409Agent has active calls and cannot be deleted.
Purchasing number with no address (non-US/CA)400address_id is required for this country.
Phone number not in E.164 format400to must be a valid E.164 phone number.
SMS message exceeds 160 characters400message must not exceed 160 characters.
Transfer while call is not in-progress409Call is not in-progress and cannot be transferred.

Retrying requests

The following errors are safe to retry. All others should not be retried automatically without fixing the underlying cause first.

Safe to retry

  • 429: after the rate limit window resets
  • 500: with exponential back-off (max 3 attempts)
  • 503: service temporarily unavailable

Do not retry

  • 400: fix the request first
  • 401: fix or rotate the API key
  • 402: add credits first
  • 403: use the correct key type
  • 404: resource does not exist
  • 409: resolve the conflict first
Error Codes - Staffify API