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.

Hardware orders​

POST /v1/hardware-orders/{order_id}/checkout places the order and debits your bank account for it. It refuses with 409 for several unrelated reasons, and the fix for each is somewhere different, so branch on code to decide where to send the operator.

CodeHTTPMeaning
order_not_draft409The order is not in a state checkout can place: a rejected order, which cannot be resumed, or an order DialStack placed on your behalf, whose payment DialStack collects outside checkout.
shipping_not_quoted409No shipping service has been chosen. Create a shipping quote, choose a service from it on the order, and retry — an order with no freight price is not a free-shipping one.
ship_to_incomplete409The order has no confirmed shipping address, or no ship_to_recipient and an account with no name for the label. Set ship_to_address (and a recipient or the account's name) and retry.
shipping_quote_expired409The shipping quote the service was chosen from has expired. Create a new shipping quote, choose a service from it on the order, and retry.
no_debit_authority409You have no verified bank account with a live ACH authorization. Finish bank setup and retry; the order is left as a draft, not placed.
no_default_payment_method409You have a verified, authorized bank account, but none is set as the default. Set one as the default and retry; the order is left as a draft, not placed.
payment_method_pending_verification409Your bank account is still being verified (microdeposits). Complete verification and retry; the order is left as a draft, not placed.
catalog_item_not_priced409A line on the order is unpriced or withdrawn from the catalog since the draft was built. Remove or replace it.
hardware_line_backordered409Our distributor is holding part of this order and publishes no restock date, so we will not charge for goods we cannot ship. The shipping quote you chose from lists the held lines in backordered_lines. Remove or reduce the line, choose a service from a new quote, and retry; the draft survives.
hardware_order_already_debited409A debit already exists for this order, so it is no longer checkout-able. Poll the order for its payment status.
checkout_resume_window_elapsed409The order was placed more than 12 hours ago without a debit. Past that window a retry is no longer guaranteed against charging twice — contact support.
resale_certificate_required409You have no accepted, unexpired resale certificate on file. Hardware is sold for resale, so one is required before ordering. Upload or renew it and retry; the order is left as a draft.
debit_parameters_changed409Your bank account or authorization changed between the first attempt and this one, so the debit cannot be retried under the same key. Contact support.
hardware_checkout_live_only409The order ships to a sandbox or demo account. Checkout debits real money and orders real hardware, so it is only available for live accounts; drafts and shipping quotes still work.

no_debit_authority, no_default_payment_method, payment_method_pending_verification and hardware_checkout_live_only are raised before the order is placed, deliberately: placement freezes the prices and cannot be repeated, so discovering a missing authorization afterwards would strand the order rather than let you retry it.

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.