Getting started
Errors
Failures use conventional HTTP status codes and always return the same JSON shape, with a stable code to switch on and a message you can show to people.
The error object
Every failed request returns this shape with a matching HTTP status. code never changes once published; message may be reworded. details is present when there is more to say.
{
"error": {
"code": "insufficient_balance",
"message": "Your balance is too low for this purchase. Add funds and try again.",
"details": {
"balance": "0.05",
"balance_micros": 50000,
"required": "0.895",
"required_micros": 895000
}
}
}HTTP status codes
- 200 · 201
- Success. 201 when something was created.
- 400
- The request is malformed or a parameter is invalid.
- 401
- The API key is missing or invalid.
- 402
- Your balance doesn't cover the purchase.
- 403
- Not allowed for this account right now.
- 404
- The resource or endpoint doesn't exist (or isn't yours).
- 405
- The endpoint exists but not with that method. The Allow header lists the ones it takes.
- 409
- Conflicts with the current state: no stock, over your max price, wrong status, reused key.
- 413
- The request body is too large.
- 429
- Rate limited. Wait for Retry-After.
- 5xx
- Something failed on our side or upstream. Safe to retry with the same Idempotency-Key.
Error codes
Every code the API returns. Retryable ones can succeed later without changing the request.
CodeStatusMeaningRetry
- invalid_requestHTTP status 400A parameter is missing, malformed or out of range.
details.fieldsnames each one.no — not retryable - insufficient_balanceHTTP status 402Your balance doesn't cover the price.
detailshasbalance_microsandrequired_micros.no — not retryable - account_disabledHTTP status 403The API key is valid but its account is disabled. Contact support@passcode.sh.no — not retryable
- not_availableHTTP status 403Real carrier numbers aren't on sale to your account yet. Demo inventory still works.no — not retryable
- country_not_foundHTTP status 404We don't sell numbers in that country. List them with GET /countries.no — not retryable
- not_foundHTTP status 404No endpoint has that path.no — not retryable
- order_not_foundHTTP status 404No order with that id belongs to your account.no — not retryable
- rental_not_foundHTTP status 404No rental with that id belongs to your account.no — not retryable
- service_not_foundHTTP status 404No enabled service has that slug. List them with GET /services.no — not retryable
- user_not_foundHTTP status 404The account no longer exists.no — not retryable
- method_not_allowedHTTP status 405The path exists but doesn't take that HTTP method, e.g.
GETon a cancel endpoint. TheAllowheader anddetails.allowedlist the methods it does take.no — not retryable - idempotency_conflictHTTP status 409The
Idempotency-Keywas already used with a different body.details.order_id(ordetails.rental_id) names what it created.no — not retryable - invalid_stateHTTP status 409The order or rental can't make that transition, e.g. cancelling after a code arrived.
details.statusis the current status.no — not retryable - no_inventoryHTTP status 409Nothing is in stock for that service and country right now. If an order had already been charged, it was refunded and
details.order_idnames it.retry — retryable - price_above_maxHTTP status 409The best available price is above your
max_price.details.price_microsis the lowest price on offer.retry — retryable - payload_too_largeHTTP status 413The JSON body is larger than 16 KB.no — not retryable
- rate_limitedHTTP status 429Too many requests. Wait for
Retry-Afterseconds.retry — retryable - internal_errorHTTP status 500Something failed on our side. Retry with the same
Idempotency-Key; contact support with theX-Request-Idif it persists.retry — retryable - provider_errorHTTP status 502The upstream carrier failed. Safe to retry.retry — retryable
Handling errors
Branch on error.code, not on the message or the status alone — several codes share a status.
Handle a failed order
const res = await fetch("https://passcode.sh/api/v1/orders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PASSCODE_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ service: "whatsapp", country: "US", max_price: "1.00" }),
});
if (!res.ok) {
const { error } = await res.json();
switch (error.code) {
case "insufficient_balance":
// Top up, then retry with the same Idempotency-Key.
break;
case "no_inventory":
case "price_above_max":
// Try another country, or raise max_price.
break;
case "rate_limited":
// Wait Retry-After seconds.
break;
default:
throw new Error(`${error.code}: ${error.message} (${res.headers.get("x-request-id")})`);
}
}Request IDs
Every response carries an X-Request-Id header (req_…). Log it with failures and include it when you email support@passcode.sh — it lets us find the exact request.