API Error Codes
Last updated
All error responses follow a consistent format with an error code and human-readable message.
Error Format
{
"detail": {
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description of what went wrong"
}
}
}Authentication Errors
A missing, malformed, unknown or revoked API key returns 401 with the code UNAUTHORIZED. The message is one of "Missing API key", "Invalid API key format", "Invalid or revoked API key". Make sure your key starts with dsk_ and is passed as a Bearer token.
{
"detail": {
"error": {
"code": "UNAUTHORIZED",
"message": "Missing API key"
}
}
}Rate-Limit Errors
A request over its rate limit returns 429 with a different body: a plain error string and no error code. A 429 that carries MAX_RENTALS means the concurrent rental limit instead.
{
"error": "Rate limit exceeded: 60 per 1 minute"
}Validation Errors
A request with missing or malformed fields returns 422 with the standard FastAPI body, where detail is a list of problems.
{
"detail": [
{
"type": "missing",
"loc": ["body", "service"],
"msg": "Field required",
"input": {}
}
]
}Retries and Maintenance
Programs that buy numbers unattended should handle short outages, such as a maintenance, without losing orders or buying twice.
- Retry these:
429with a rate-limit body,502,503,504,520to524, and a network error or timeout. They are temporary: the same request works again shortly. - Wait before each retry: If the response has a
Retry-Afterheader, wait that many seconds. Otherwise wait longer each time (2, 4, 8, 16, 32, then 60 seconds) and add up to one random second, so your clients do not all retry at the same instant. Give up after about 10 minutes and alert yourself. - Do not retry these unchanged:
400,401,402,403,404,409,422and429 MAX_RENTALS. Repeating the same request gives the same answer: fix the cause first (top up, pick another service, wait for an order to finish). - A failed buy may still have gone through: After a timeout, a network error or a
502,504or52xonPOST /v1/ordersor a re-rent, the order may exist. Before buying again, list your newest orders (GET /v1/orders) and look for one for that service created in the last minutes. A503means no order was kept:MAINTENANCEstopped the request before anything happened, and afterSERVICE_UNAVAILABLEany charge is credited back automatically. - Maintenance:
503 MAINTENANCEusually pauses only new purchases and re-rents, for about a minute: reading orders, cancelling and code delivery keep working, except a few seconds that can answer502while the API restarts. Rarely the whole API is paused for a few minutes. Your balance and open orders are safe either way, and codes that arrive meanwhile are on the order when you read it again. - Keep your orders through an outage: Store each order
idas soon as you get it, and keep polling with these waits instead of dropping the order. An order with no code still expires and is credited back on its own. - Set a client timeout: At least 30 seconds per request, and 60 seconds for buys and re-rents, which wait for a number.
During a maintenance the API answers 503 with a Retry-After header (seconds) and this body:
{
"detail": {
"error": {
"code": "MAINTENANCE",
"message": "New purchases are paused for a short maintenance. Numbers you already rented still receive their codes. Your balance is safe. Please try again in a few minutes."
}
}
}A retry helper in Python that follows these rules:
import random
import time
from datetime import datetime
import requests
API = "https://api.diddysms.com/v1"
HEADERS = {"Authorization": "Bearer dsk_your_api_key_here"}
TEMPORARY = {429, 502, 503, 504, 520, 521, 522, 523, 524}
def is_temporary(resp):
if resp is None: # network error or timeout
return True
if resp.status_code == 429 and "MAX_RENTALS" in resp.text:
return False # not a rate limit: an order has to finish first
return resp.status_code in TEMPORARY
def wait(resp, attempt):
"""Retry-After when the API sends it, else 2, 4, 8 ... 60 s, plus jitter."""
header = resp.headers.get("Retry-After") if resp is not None else None
delay = int(header) if header and header.isdigit() else min(60, 2 ** (attempt + 1))
time.sleep(delay + random.random())
def request(method, path, **kwargs):
"""Reads, cancel and complete: safe to repeat."""
for attempt in range(12):
try:
resp = requests.request(
method, f"{API}{path}", headers=HEADERS, timeout=30, **kwargs
)
except requests.RequestException:
resp = None
if not is_temporary(resp):
return resp
wait(resp, attempt)
raise RuntimeError(f"{method} {path}: still failing, try again later")
def buy(service):
"""A buy is not safe to repeat blindly: look for the order first."""
started = time.time()
for attempt in range(12):
try:
resp = requests.post(
f"{API}/orders", headers=HEADERS, json={"service": service}, timeout=60
)
except requests.RequestException:
resp = None
if resp is not None and resp.status_code == 201:
return resp.json()["order"]
if not is_temporary(resp):
resp.raise_for_status() # 4xx: fix the cause, do not repeat
if resp is None or resp.status_code not in (429, 503):
# Timeout, 502, 504 or 52x: the order may exist. (If you run buys
# in parallel, also check another buy has not claimed it.)
for order in request("GET", "/orders").json()["orders"]:
created = datetime.fromisoformat(order["created_at"].replace("Z", "+00:00"))
if order["service"] == service and created.timestamp() >= started - 5:
return order
wait(resp, attempt)
raise RuntimeError(f"buy {service}: still failing, try again later")Error Code Reference
| Code | HTTP Status | Description | What to do |
|---|---|---|---|
UNAUTHORIZED | 401 | The API key is missing, malformed, unknown or revoked | Send your current key as a Bearer token. Generating a new key revokes the previous one. |
ACCOUNT_BANNED | 403 | The account has been permanently banned | The key cannot be used. Open a support ticket if you think this is a mistake. |
ACCOUNT_FROZEN | 403 | The account is temporarily frozen | Open a support ticket. |
ACCOUNT_MERGED | 403 | The account has been merged into another account | Use an API key from the account it was merged into. |
INSUFFICIENT_BALANCE | 402 | Your balance is too low for this purchase | Top up your balance, then retry. |
NOT_FOUND | 404 | The service or order does not exist, or the order belongs to another account | Check the service name or order ID. |
ORDER_ERROR | 400 | Invalid order operation, e.g. cancelling or completing an order that is no longer active | Fetch the order to check its status before trying again. |
COOLDOWN | 400 | Cannot cancel within 60 seconds of purchase | Wait until the time in the message has passed, then cancel again. |
OUT_OF_STOCK | 409 | No numbers available right now: for the requested carrier or the service (buy), or for that same number (re-rent). On a buy, detail.error.scope says which: "carrier" or "service". Nothing is charged. | scope "carrier": retry without carrier, or send carrier_fallback "any". scope "service": retry later or pick another service. For a re-rent, buy a new number. |
MAX_RENTALS | 429 | Maximum 100 concurrent active rentals reached | Wait for an active order to complete or expire, or cancel one, then retry. |
SERVICE_UNAVAILABLE | 503 | Temporarily unable to process the request. A failed buy or re-rent keeps no order; any charge is credited back automatically. | Retry after a few seconds, waiting longer each time (see Retries and maintenance). |
MAINTENANCE | 503 | A short maintenance. Usually only new purchases and re-rents are paused, for about a minute; rarely the whole API, for a few minutes. Nothing was done and nothing was charged. | Wait the seconds in the Retry-After header, then retry. Keep your open orders: they keep receiving codes. |
HTTP Status Summary
| Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (new order, re-rented order or API key) |
| 400 | Invalid operation (ORDER_ERROR, COOLDOWN) |
| 401 | Missing or invalid API key (UNAUTHORIZED) |
| 402 | Insufficient balance |
| 403 | Account banned, frozen or merged |
| 404 | Service or order not found |
| 409 | Out of stock |
| 422 | Request validation failed (missing or malformed fields) |
| 429 | Rate limit or concurrent rental limit exceeded |
| 502 | The API was unreachable for a moment (for example while it restarts); the body may not be JSON. Retry; after a buy, check your orders first |
| 503 | Temporarily unavailable (SERVICE_UNAVAILABLE) or a short maintenance (MAINTENANCE); retry |
| 504 | The API did not answer in time; the body may not be JSON. Retry; after a buy, check your orders first |