# DiddySMS API Documentation > Complete REST API reference for DiddySMS, the US non-VoIP SMS verification number service. Rent temporary US phone numbers and receive SMS verification codes programmatically. ## Contents - [API overview](https://diddysms.com/docs): Base URL, authentication, quickstart, rate limits and response format - [POST /v1/auth/api-key](https://diddysms.com/docs#post-v1-auth-api-key): Rotate your API key: get a new key and revoke the current one - [Services API](https://diddysms.com/docs/services): List services with price, stock and code window - [GET /v1/services](https://diddysms.com/docs/services#get-v1-services): List services with price, carrier price, stock and code window - [GET /v1/services/{service_name}](https://diddysms.com/docs/services#get-v1-services-service-name): Get the price, stock and code window of one service - [Orders API](https://diddysms.com/docs/orders): Rent a number, poll for the SMS code, cancel, complete or re-rent - [POST /v1/orders](https://diddysms.com/docs/orders#post-v1-orders): Rent a US phone number for a service - [GET /v1/orders](https://diddysms.com/docs/orders#get-v1-orders): List your orders, or only the active ones - [GET /v1/orders/{order_id}](https://diddysms.com/docs/orders#get-v1-orders-order-id): Get one order, including its SMS code once it arrives - [POST /v1/orders/{order_id}/cancel](https://diddysms.com/docs/orders#post-v1-orders-order-id-cancel): Cancel an active order and get its price credited back to your balance - [POST /v1/orders/{order_id}/complete](https://diddysms.com/docs/orders#post-v1-orders-order-id-complete): Check an active order for its SMS code right now - [POST /v1/orders/{order_id}/re-rent](https://diddysms.com/docs/orders#post-v1-orders-order-id-re-rent): Rent the phone number from a previous order again - [Balance & Transactions API](https://diddysms.com/docs/balance): Your balance in USD and your transaction history - [GET /v1/balance](https://diddysms.com/docs/balance#get-v1-balance): Get your account balance in USD - [GET /v1/transactions](https://diddysms.com/docs/balance#get-v1-transactions): List your deposits, purchases and refunds - [API error codes](https://diddysms.com/docs/errors): Every error code with its HTTP status, meaning and what to do This file is the whole reference in one place: every endpoint, field, rate limit, business rule and error code. A short index for machines is at https://diddysms.com/llms.txt and an OpenAPI description at https://diddysms.com/openapi.json. ## Base URL ``` https://api.diddysms.com/v1 ``` All endpoints are prefixed with this base URL. **Transport security:** all requests must use HTTPS with TLS 1.2 or newer (TLS 1.3 supported and preferred). TLS 1.0 and 1.1 are refused. Always use `https://`: a plain `http://` request is only redirected, and its `Authorization` header (your API key) would already have travelled unencrypted. ## Authentication Every request requires an API key in the `Authorization` header as a Bearer token: ``` Authorization: Bearer dsk_your_api_key_here ``` API keys start with `dsk_` and are 52 characters long. Keys are shown once at generation and cannot be retrieved later. Generating a new key revokes the previous one. A missing, malformed, unknown or revoked key returns `401` with the error code `UNAUTHORIZED`: ```json { "detail": { "error": { "code": "UNAUTHORIZED", "message": "Missing API key" } } } ``` The `message` is one of: "Missing API key", "Invalid API key format", "Invalid or revoked API key". ## Getting Your API Key 1. Sign up at https://diddysms.com/signup (or sign in at https://diddysms.com/signin) 2. Go to **Account → API keys** (https://diddysms.com/account/api-keys) and click **Generate API key** 3. Copy the key immediately — it will not be shown again Generating a new key automatically revokes any previous key on your account. ### POST /v1/auth/api-key Rotate your API key: authenticate with your current key to receive a new one. Every previous key on the account is revoked at once, and the new key appears only in this response. Your first key is created in your account (see above). **Request Body (JSON, optional):** | Name | Type | Required | Description | |------|------|----------|-------------| | name | string | No | Label for the key, up to 100 characters (default: "default") | **Example:** ```bash curl -X POST https://api.diddysms.com/v1/auth/api-key \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (201 Created):** ```json { "api_key": "dsk_your_new_api_key_here", "prefix": "dsk_your_new", "name": "default", "message": "Store this key securely. It will not be shown again." } ``` ## Quickstart Three API calls to buy a number and receive an SMS code: ### Step 1 — List available services ```bash curl https://api.diddysms.com/v1/services \ -H "Authorization: Bearer dsk_your_api_key_here" ``` ### Step 2 — Buy a number ```bash curl -X POST https://api.diddysms.com/v1/orders \ -H "Authorization: Bearer dsk_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"service": "google"}' ``` ### Step 3 — Poll for the SMS code ```bash curl https://api.diddysms.com/v1/orders/{order_id} \ -H "Authorization: Bearer dsk_your_api_key_here" # Repeat every 3 seconds until sms_code is set, or until status is no # longer "active": an order with no code expires when its code window ends and is credited back. # A 429, 502, 503 or 504 (for example a short maintenance): wait and poll again. ``` ### Step 4 — Cancel if you stop waiting If your flow stops waiting for the code (the app never sent it, your user left, or you give up), cancel the order instead of leaving it to expire: ```bash curl -X POST https://api.diddysms.com/v1/orders/{order_id}/cancel \ -H "Authorization: Bearer dsk_your_api_key_here" ``` You can cancel from 60 seconds after purchase. The price comes back to your balance at once, the order stops counting toward your 100 active rentals, and a code that arrives later can no longer complete it and charge you. If the answer is `400 ORDER_ERROR`, the code arrived first: read `sms_code` from the order. --- ## Services ### GET /v1/services Get a paginated list of available SMS verification services. **Query Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | search | string | No | Filter services by name | | page | integer | No | Page number (default: 1) | | per_page | integer | No | Results per page, 1–100 (default: 20) | **Example:** ```bash curl "https://api.diddysms.com/v1/services?search=google" \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** ```json { "services": [ { "name": "google", "display_name": "Google", "price": 1.50, "carrier_price": 1.80, "stock": 142, "ttl_minutes": 10, "code_window_seconds": 540 } ], "pagination": { "page": 1, "per_page": 20, "total_pages": 1 } } ``` **Response Fields:** | Field | Type | Description | |-------|------|-------------| | name | string | Service identifier; use this value when placing orders | | display_name | string | Human-readable service name | | price | number | Price in USD per number rental | | carrier_price | number | Price in USD when you choose a specific carrier (AT&T or T-Mobile): about 20% more | | stock | integer | Numbers available right now. 0 means temporarily out of stock; the service is still listed. | | ttl_minutes | integer | Minutes the number is reserved for this service. Not the order deadline; see code_window_seconds. | | code_window_seconds | integer | Seconds you have to receive the code after renting. It can differ from one service to another. | **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 503 | SERVICE_UNAVAILABLE | Temporarily unable to load services. Please try again. | ### GET /v1/services/{service_name} Get the price, stock and code window of one service. **Path Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | service_name | string | Yes | Service identifier (e.g. "google") | **Example:** ```bash curl https://api.diddysms.com/v1/services/google \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** the service object, with the fields listed above. ```json { "name": "google", "display_name": "Google", "price": 1.50, "carrier_price": 1.80, "stock": 142, "ttl_minutes": 10, "code_window_seconds": 540 } ``` **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 404 | NOT_FOUND | Service does not exist | | 503 | SERVICE_UNAVAILABLE | Temporarily unable to load the service. Please try again. | --- ## Orders An order starts `active` and waits for its SMS code. When the code arrives, the order becomes `completed` and `sms_code` is set. No code arrives after `expires_at`, the service's `code_window_seconds` after purchase. An order with no code becomes `expired` at that time or, for a service with a very short code window, a few minutes after purchase, and its price is credited back to your balance. You can cancel an active order 60 seconds after purchase for the same credit. ### POST /v1/orders Purchase a temporary phone number for a specified service. **Request Body (JSON):** | Name | Type | Required | Description | |------|------|----------|-------------| | service | string | Yes | Service name from GET /v1/services (e.g. "google") | | carrier | string | No | Optional: choose "at&t" or "tmobile", for about 20% more. You get a number on exactly that carrier; if it has none, the purchase fails with 409 OUT_OF_STOCK (scope "carrier") and nothing is charged. Omit to get whichever carrier is available. | | carrier_fallback | string | No | Only with carrier. "none" (default): fail when that carrier has no numbers. "any": buy a number on whichever carrier is available instead, at the normal price; the order then has carrier null and requested_carrier set to the carrier you asked for. | **Example:** ```bash curl -X POST https://api.diddysms.com/v1/orders \ -H "Authorization: Bearer dsk_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"service": "google", "carrier": "at&t"}' ``` **Response (201 Created):** ```json { "order": { "id": 200, "service": "google", "service_display_name": "Google", "phone_number": "2025551234", "status": "active", "price": 1.80, "carrier": "at&t", "requested_carrier": "at&t", "sms_code": null, "sms_message": null, "messages": [], "expires_at": "2026-02-26T22:49:00Z", "created_at": "2026-02-26T22:40:00Z", "completed_at": null } } ``` **If that carrier has no numbers (409 Conflict), nothing is charged:** ```json { "detail": { "error": { "code": "OUT_OF_STOCK", "message": "T-Mobile numbers for Google are out of stock right now: choose Any as the carrier, or try again in a few minutes.", "scope": "carrier" } } } ``` **Fall back to whichever carrier is available, at the normal price,** with `"carrier_fallback": "any"`. The order then has `carrier: null` and `requested_carrier` keeps your choice: ```bash curl -X POST https://api.diddysms.com/v1/orders \ -H "Authorization: Bearer dsk_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"service": "google", "carrier": "tmobile", "carrier_fallback": "any"}' ``` ```json { "order": { "id": 201, "service": "google", "service_display_name": "Google", "phone_number": "2025551234", "status": "active", "price": 1.50, "carrier": null, "requested_carrier": "tmobile", "sms_code": null, "sms_message": null, "messages": [], "expires_at": "2026-02-26T22:49:00Z", "created_at": "2026-02-26T22:40:00Z", "completed_at": null } } ``` **Order Fields:** | Field | Type | Description | |-------|------|-------------| | id | integer | Unique order ID | | service | string | Service identifier | | service_display_name | string | Human-readable service name | | phone_number | string | The rented US number as 10 digits, with no +1 country code and no formatting (e.g. "2025551234"). Add +1 yourself if a form needs E.164. | | status | string | One of: active, completed, cancelled, expired | | price | number | Amount charged in USD | | carrier | string or null | The carrier of the number ("at&t" or "tmobile") when you chose one, or null when you got whichever carrier was available | | requested_carrier | string or null | The carrier you asked for. Equals carrier, except when carrier_fallback "any" bought a number on whichever carrier was available, at the normal price: then carrier is null and this keeps your choice. | | sms_code | string or null | Received SMS code (null until received). Digits come without spaces. For a few services the code is a link plus an ID rather than digits. | | sms_message | string or null | The full SMS text the code came in, as plain text (null until received). It may contain a link: treat it as untrusted text. Set to null 30 days after the SMS arrived; sms_code stays. | | messages | object[] | Every SMS received on the order, oldest first: the one with sms_code, then any follow-up codes. Each item has code, text (the full SMS, or null) and received_at (ISO 8601). Empty until the code arrives. text becomes null 30 days after the SMS arrived; code stays. | | expires_at | string | Deadline for the SMS code: no code arrives after it. An order with no code expires and is credited back at this time or, for a service whose code window is shorter than most, a few minutes later. ISO 8601. | | created_at | string | ISO 8601 creation timestamp | | completed_at | string or null | ISO 8601 completion timestamp | | followup_codes | string[] | Additional codes received after the order completed. Only present when a follow-up code has actually arrived; the key is omitted from every other response. Available for a small number of services that send more than one message to the same number after completion; codes may be alphanumeric. Best effort, no guarantee: delivery depends on the number remaining active. Re-fetch this endpoint later to check. Each follow-up code is also in messages, with its full text. | **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 400 | ORDER_ERROR | The purchase was rejected; the message says why. Any charge is credited back automatically. | | 402 | INSUFFICIENT_BALANCE | Not enough funds to purchase | | 404 | NOT_FOUND | Service does not exist | | 409 | OUT_OF_STOCK | No numbers left right now. scope "carrier": none on the chosen carrier (retry without carrier, or send carrier_fallback "any"); scope "service": none for the service. Nothing is charged. | | 429 | MAX_RENTALS | Maximum 100 concurrent active rentals reached | | 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable. Please try again. | ### GET /v1/orders List your orders with optional filtering. **Query Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | status | string | No | Filter by "active" or omit for all | | limit | integer | No | Max results, 1–100 (default: 20) | | offset | integer | No | Results offset (default: 0) | **Example:** ```bash curl "https://api.diddysms.com/v1/orders?status=active" \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** ```json { "orders": [ { "id": 200, "service": "google", "service_display_name": "Google", "phone_number": "2025551234", "status": "active", "price": 1.50, "carrier": null, "requested_carrier": null, "sms_code": null, "sms_message": null, "messages": [], "expires_at": "2026-02-26T22:49:00Z", "created_at": "2026-02-26T22:40:00Z", "completed_at": null } ], "pagination": { "limit": 20, "offset": 0, "total": 1 } } ``` ### GET /v1/orders/{order_id} Get full details for a specific order, including the SMS code. Order status updates in real time as soon as a message arrives. **Path Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | order_id | integer | Yes | Order ID | **Example:** ```bash curl https://api.diddysms.com/v1/orders/200 \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** an order whose code has arrived. ```json { "order": { "id": 200, "service": "google", "service_display_name": "Google", "phone_number": "2025551234", "status": "completed", "price": 1.50, "carrier": null, "requested_carrier": null, "sms_code": "123456", "sms_message": "123456 is your Google verification code.", "messages": [ { "code": "123456", "text": "123456 is your Google verification code.", "received_at": "2026-02-26T22:40:48Z" } ], "expires_at": "2026-02-26T22:49:00Z", "created_at": "2026-02-26T22:40:00Z", "completed_at": "2026-02-26T22:40:48Z" } } ``` > **Tip:** Poll this endpoint every 3 seconds to check for incoming SMS codes. The `sms_code` field will be `null` until a code is received. Stop when `status` is no longer `active`. **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 404 | NOT_FOUND | Order not found | ### POST /v1/orders/{order_id}/cancel Cancel an active order and receive a full refund to your account balance. Recommended whenever you stop waiting for a code: the credit is immediate and the order stops counting toward your active rentals. **Path Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | order_id | integer | Yes | Order ID | **Example:** ```bash curl -X POST https://api.diddysms.com/v1/orders/200/cancel \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** Returns the updated order object with `status: "cancelled"`. If the SMS code arrives while you cancel, the order completes instead: the response is `400 ORDER_ERROR` and the order is charged like any order that got its code. Fetch the order to read `sms_code`. **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 400 | COOLDOWN | Cannot cancel within 60 seconds of purchase | | 400 | ORDER_ERROR | Order is not in "active" status, or its SMS code arrived first (the order completed with the code) | | 404 | NOT_FOUND | Order not found | | 503 | SERVICE_UNAVAILABLE | Could not cancel right now. Please try again. | ### POST /v1/orders/{order_id}/complete Check an active order for its SMS code right now. If the code has arrived, the order is returned `completed` with `sms_code` and `completed_at` set; if not, it is returned unchanged and still `active`. Orders complete on their own as soon as the code arrives, so polling GET /v1/orders/{order_id} is enough; call this only to force an immediate check. **Path Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | order_id | integer | Yes | Order ID | **Example:** ```bash curl -X POST https://api.diddysms.com/v1/orders/200/complete \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** Returns the order object. **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 400 | ORDER_ERROR | Order is no longer active (already completed, cancelled or expired) | | 404 | NOT_FOUND | Order not found | | 503 | SERVICE_UNAVAILABLE | Could not check the order right now. Please try again. | ### POST /v1/orders/{order_id}/re-rent Rent the same phone number again from a previous order. Creates a new order with a new ID but the same phone number. Useful when you need to receive another code on the same number. **Path Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | order_id | integer | Yes | Original order ID | **Example:** ```bash curl -X POST https://api.diddysms.com/v1/orders/200/re-rent \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (201 Created):** Returns a new order object with a new ID but the same phone number. **Error Responses:** | Status | Code | Description | |--------|------|-------------| | 400 | ORDER_ERROR | Cannot re-rent this order | | 402 | INSUFFICIENT_BALANCE | Not enough funds | | 404 | NOT_FOUND | Order not found | | 409 | OUT_OF_STOCK | That number can't be rented again right now. Any charge is credited back automatically; buy a new number instead. | | 429 | MAX_RENTALS | Maximum 100 concurrent active rentals reached | | 503 | SERVICE_UNAVAILABLE | Could not re-rent right now. Please try again. | --- ## Balance & Transactions ### GET /v1/balance Get your current account balance. **Example:** ```bash curl https://api.diddysms.com/v1/balance \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** ```json { "balance": 48.50, "currency": "USD" } ``` ### GET /v1/transactions Get your transaction history — deposits, purchases, and refunds. **Query Parameters:** | Name | Type | Required | Description | |------|------|----------|-------------| | limit | integer | No | Max results, 1–100 (default: 20) | | offset | integer | No | Results offset (default: 0) | **Example:** ```bash curl "https://api.diddysms.com/v1/transactions?limit=10" \ -H "Authorization: Bearer dsk_your_api_key_here" ``` **Response (200 OK):** ```json { "transactions": [ { "id": 1, "type": "purchase", "amount": -1.50, "balance_after": 48.50, "description": "Number for Google", "reference_id": 200, "created_at": "2026-02-26T22:40:00Z" } ], "pagination": { "limit": 10, "offset": 0, "total": 1 } } ``` **Transaction Types:** | Type | Direction | Description | |------|-----------|-------------| | purchase | negative | Charged when buying a number | | refund | positive | Credited when an order is cancelled, expires without a code, or cannot be rented | | deposit | positive | Funds added to your account | Other types (for example referral credits or balance adjustments) can appear; read the sign of `amount`. --- ## Rate Limits Rate limits are enforced per API key. Exceeding a limit returns `429 Too Many Requests` (see Error Codes for the body). | Endpoint | Limit | |----------|-------| | POST /v1/auth/api-key | 33/hour | | GET /v1/services | 369/min | | GET /v1/services/{service_name} | 1000/min | | POST /v1/orders | 60/min | | GET /v1/orders | 1000/min | | GET /v1/orders/{order_id} | 369/min | | POST /v1/orders/{order_id}/cancel | 33/min | | POST /v1/orders/{order_id}/complete | 1000/min | | POST /v1/orders/{order_id}/re-rent | 1000/min | | GET /v1/balance | 369/min | | GET /v1/transactions | 33/min | ## Business Rules - **Maximum concurrent rentals:** 100 active orders per account at any time. - **Cancellation cooldown:** Orders cannot be cancelled within 60 seconds of purchase. - **Auto-expiry:** An order that has not received a code expires when its code window ends and its price is credited back to your balance automatically. If a service has a very short code window, no code arrives after `code_window_seconds` on the service (`expires_at` on the order), but the order still expires, and is credited back, a few minutes after purchase. - **Refunds:** Cancelled and expired orders are credited back to your DiddySMS balance automatically. - **Carrier selection:** Every number is on AT&T or T-Mobile. Omit `carrier` to get whichever is available. Choose one for about 20% more and you get exactly that carrier: if it has no numbers, the purchase fails with `409 OUT_OF_STOCK` (`scope: "carrier"`) and nothing is charged. Retry without `carrier`, or send `carrier_fallback: "any"` to get whichever carrier is available, at the normal price, in the same request. ### Recommended integration 1. Buy: `POST /v1/orders`, and keep the order `id`. If the buy times out or answers `502`/`504`, list your newest orders before buying again (see Retries and maintenance). 2. Poll `GET /v1/orders/{order_id}` every 3 seconds. 3. When `sms_code` is set, use it. The order is complete; nothing else to do. 4. When you stop waiting, cancel with `POST /v1/orders/{order_id}/cancel` (from 60 seconds after purchase): - `200`: cancelled and credited back. - `400 ORDER_ERROR`: the order is no longer active. If the code arrived first, it is in `sms_code`. - `503`: try again after a short wait (see Retries and maintenance). 5. Don't just stop polling an order you no longer need. It stays active until `expires_at`, counts toward your active rentals, and a code that arrives in the meantime completes it and is charged. --- ## Error Codes All error responses use this format: ```json { "detail": { "error": { "code": "ERROR_CODE", "message": "Human-readable description of what went wrong" } } } ``` Two exceptions: a rate-limited request (429) returns `{"error": "Rate limit exceeded: ..."}` with no `detail.error.code`, so tell it apart from `MAX_RENTALS` by the body; and a request that fails validation (422) returns FastAPI's standard body, where `detail` is a list of problems: ```json { "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:** `429` with a rate-limit body, `502`, `503`, `504`, `520` to `524`, 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-After` header, 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`, `422` and `429 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`, `504` or `52x` on `POST /v1/orders` or 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. A `503` means no order was kept: `MAINTENANCE` stopped the request before anything happened, and after `SERVICE_UNAVAILABLE` any charge is credited back automatically. - **Maintenance:** `503 MAINTENANCE` usually 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 answer `502` while 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 `id` as 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: ```json { "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: ```python 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 |