Errors

Every Phone.inc API error has a JSON body and a meaningful HTTP status code. Most failures fall into a small handful of categories — bad input, missing or invalid auth, or a resource that doesn't exist. Use the status code to decide how to react and the body to figure out what to fix.


Status codes

  • Name
    200 OK
    Description

    The request succeeded. The body contains the resource.

  • Name
    400 Bad Request
    Description

    A required parameter is missing or malformed.

  • Name
    401 Unauthorized
    Description

    The credential is missing, malformed, expired, or revoked. For API keys, fix the key. For OAuth tokens, refresh and retry.

  • Name
    403 Forbidden
    Description

    The credential is valid but doesn't grant access to the requested resource.

  • Name
    404 Not Found
    Description

    The resource doesn't exist, or it belongs to a different company than the authenticated credential.

  • Name
    422 Unprocessable Entity
    Description

    The request was well-formed but failed validation. The body lists every field-level problem.

  • Name
    429 Too Many Requests
    Description

    Rate limit hit. Look at Retry-After for how long to wait before retrying.

  • Name
    5xx Server Error
    Description

    Something broke on our side. Retry with exponential backoff. If it persists, email [email protected].


Error response shapes

Single-message errors use an error key — most 400/401/403/404/429 responses look like this:

Single error

{
  "error": "Voicemail not found"
}

Validation errors (422) return an errors object keyed by field, mirroring the request body:

Validation errors

{
  "errors": {
    "listened": ["must be a boolean"]
  }
}

OAuth endpoints (/oauth/token, /oauth/authorize, /oauth/revoke) follow the OAuth 2.0 spec and use a different shape — see OAuth 2.0 for partners for those.


Authentication errors

A 401 Unauthorized from /api/v1/* always means the credential is the problem. The reason depends on whether you sent an API key or an OAuth bearer token.

API keys

Tokens that start with phk_ are looked up as API keys. If the lookup fails — wrong token, revoked key, typo — you get:

401 Invalid API key

{
  "error": "Invalid API key"
}

There is no expiry on API keys. A 401 means the key doesn't exist on our side, full stop. Don't retry — fix the key. Generate a new one at app.phone.inc/api_keys if needed.

OAuth bearer tokens

Tokens without the phk_ prefix are validated as OAuth access tokens. The response includes a WWW-Authenticate header with the specific reason:

WWW-Authenticate: Bearer realm="Doorkeeper", error="invalid_token", error_description="The access token expired"

Common reasons:

  • Name
    invalid_token
    Description

    The token doesn't exist or was malformed.

  • Name
    expired_token
    Description

    The token expired (access tokens last 1 hour). Use your refresh token to mint a new one.

  • Name
    revoked_token
    Description

    The token was revoked — either by your app calling /oauth/revoke or by an admin. The user has to re-authorize.

The right move is almost always: refresh the token and retry the request once. If the refresh itself fails, send the user back through the authorization flow.


Rate limits

The public API has one rate limit today: a defense against credential-stuffing on /api/v1/*. After 10 invalid-key attempts from the same IP within 60 seconds, that IP is blocked for 60 seconds. Subsequent requests get:

429 Too Many Requests

{
  "error": "Too many failed API key attempts. Wait a moment and try again."
}

The response includes a Retry-After: 60 header. Honour it. Valid keys are not throttled here — only failed lookups count toward the budget. If you're hitting this in production, your code is probably sending the wrong token; double-check what's in the X-Api-Key header before retrying.


Common mistakes

A few patterns trip up most integrators on day one:

  • Forgot the X-Api-Key header. Empty header = 401. Make sure your HTTP client is actually attaching the key.
  • Pasted a key with surrounding whitespace. Some shells add a trailing newline when copying. Strip whitespace before sending.
  • Using a non-E.164 phone number. Endpoints that accept a phone number (/api/v1/calls?phone_number=, /api/v1/caller_context?phone=) expect E.164 format like +14155551234. Anything else is silently treated as no match.
  • Querying a resource that belongs to another company. API keys and OAuth tokens are scoped to a single company. A 404 here doesn't mean the ID is wrong globally — just that it's not visible under this credential.
  • OAuth: ignoring expires_in. Access tokens expire after 1 hour. Refresh proactively when there are fewer than ~5 minutes left, not on the first 401.

Retrying

5xx responses are safe to retry — use exponential backoff (e.g. 1s, 2s, 4s, capped at 30s) and a small jitter. 429 is also retryable — wait at least the Retry-After value before retrying. Don't retry 4xx responses; the request will fail the same way every time until you fix the input.