Skip to main content
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

Every error response returns a JSON body with a code and a human-readable message. Some errors include a details object with field-level information.

// 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."
  }
}

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).
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).

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 requests per minute for your tier.
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.
// 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