Skip to main content

Error Codes

Every non-2xx response from the DialStack REST API shares the same shape:

JSON
{
"error": "Human-readable message describing what went wrong.",
"code": "machine_readable_code",
"details": { "field_name": "reason" }
}

error is always present. code is present on errors that have a stable machine-readable identifier (listed below). details is optional and carries field-level validation context when applicable.

info

Branch on code, not on error. The error string may be rephrased without notice; code values are stable and versioned. When no code is present, fall back to the HTTP status.

HTTP status codes

StatusMeaningRetry?
400Malformed request — invalid JSON, missing required fields.No — fix the request.
401Missing or invalid API key (Authorization: Bearer ...).No — rotate / check the key.
403Valid key, but no access to this account or resource.No — check DialStack-Account.
404Resource does not exist on this account.No.
409Conflict — e.g., duplicate handle, already assigned.No.
422Validation error — payload is well-formed but semantically wrong.No — fix the payload.
429Rate limit exceeded. Retry-After header tells you when to retry.Yes, after Retry-After.
5xxServer error. Transient unless documented otherwise.Yes, with exponential backoff.

Stable error codes

The following code values are stable and safe to branch on. Codes not listed here are either legacy or platform-internal; do not rely on them.

CodeHTTPMeaning
validation_error400The request body failed validation. See details for field-level reasons.
invalid_pagination400The pagination cursor is malformed or expired. Restart from the first page.
invalid_sandbox_phone_number400The supplied phone number is not a valid sandbox number for this platform.
sandbox_feature_unsupported422The requested feature is not available in sandbox mode for this platform.

Phone numbers

CodeHTTPMeaning
phone_number_limit_exceeded409The request would take the account past its phone number limit. Raise the limit and retry.
phone_numbers_already_claimed409One or more of the requested numbers are already in service and cannot be ordered. The error string names them.
port_order_invalid_state409The port order's current status does not allow the action.

phone_number_limit_exceeded is returned when buying numbers, when creating or editing a port order, and when recovering a released number, since each of those grows the account's count. Raising the account's limit — or releasing numbers it no longer needs — clears it.

phone_numbers_already_claimed names the numbers that are unavailable — in the error string for a human, and in a details object for your code:

JSON
{
"code": "phone_numbers_already_claimed",
"error": "phone number(s) already claimed — already on this account: +17702126011",
"details": {
"already_on_account": ["+17702126011"],
"in_service_elsewhere": []
}
}

This is the only code that carries details, because which of your numbers conflicted is the one thing here you cannot get from any other request. Both keys are always present; an empty group is []. The two are separated because they call for different actions:

  • Already on your account. The number is yours; it does not need to be ordered again. Take it off the request.
  • Already in service elsewhere. The number is live on another account with the same carrier, so there is no port to run — moving it is an intra-carrier transfer, which support arranges. Contact support rather than retrying.

A number counts as claimed only while it is live — active or inactive — on any account. A number you released is not claimed, so it will not be reported here; request it back with POST /v1/phone-numbers/{phone_number_id} and status: active instead, which is the one case that can return phone_number_limit_exceeded on an update.

Note that a conflict confirms the number is in service with this carrier. That is not treated as a disclosure boundary: you supplied the number in the request, so the response tells you nothing about it you did not already know, beyond whether it is available — which is the question you asked. Abuse of this endpoint to enumerate numbers is addressed by rate limiting, not by withholding the answer.

port_order_invalid_state is returned when the action does not fit the order's current status — editing an order that has moved past the point where changes are accepted, cancelling one in a terminal status, submitting one that is not approved, approving one that is no longer a draft, or uploading a document to an order that is neither a draft nor in exception. The error message names both the status and the attempted action, so it reads as port order is in "submitted" status, cannot update.

The code is the whole machine-readable contract on these responses. There is no details object — everything one could carry is either already known to the caller (the numbers they sent, the endpoint they called, their own cap) or not theirs to see. Read error for a human-readable description.

Some conflicts on these endpoints carry no code, because there is no action a client can take automatically: a caller ID change already being processed, an account that already has a directory listing on another number, a number that is not in a recoverable state, a recovery the carrier declined, and a disconnect refused inside the 30-day minimum hold. Branch on the status and surface the error message for those.

Device provisioning

CodeHTTPMeaning
unsupported_vendor400The phone vendor/model is not supported for provisioning.
invalid_configuration400The provisioning request is missing required device fields.
credentials_not_found404No SIP credentials exist for this endpoint.
generation_failed500The provisioning file could not be generated. Safe to retry.

Handling errors

  • Treat missing code as an unstable error. Log the error string for debugging, but make a business decision off HTTP status alone.
  • Do not parse the error string. It is subject to i18n and rephrasing.
  • On 429, respect Retry-After. Typical values are small (≤ 60s).
  • On 5xx, use exponential backoff. Start at 1s, double up to ~60s, cap at ~5 attempts. Every DialStack mutation is keyed by an operation ID so retries are safe — idempotency is built into the API.