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-Afterfor 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/revokeor 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-Keyheader. 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
404here 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 first401.
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.