Errors & idempotency
Error format
Errors return a JSON body with a detail field — either a string or a
structured object:
{"detail": "Order in status 'completed' cannot be cancelled"}
{"detail": {"message": "Order is not ready",
"errors": ["requirement 'loa' is not fulfilled",
"requirement 'recent_invoice': document is older than 30 days; providers will reject it — upload a recent one"]}}
{"detail": {"message": "Numbers already on the platform or in an active port: +447912345678",
"existing_order_ids": ["a7c9…"]}}
Status codes
| Code | Meaning | Typical causes |
|---|---|---|
400 |
Malformed request | Invalid JSON |
401 |
Not authenticated | Missing/invalid/revoked API key; bad webhook signature |
403 |
Not allowed | Suspended account |
404 |
Not found | Wrong id, or the resource belongs to another account |
409 |
Conflict | Duplicate number port, illegal status transition, already-decided port-out, last-key revocation |
422 |
Validation failed | Bad E.164 numbers, unfulfilled requirements, stale documents, unsupported country, reused idempotency key with a different body |
500 |
Our fault | Retry with backoff; if persistent, contact support |
422 responses always tell you what to fix — surface them to your users.
Idempotency
POST /v2/porting_orders accepts an Idempotency-Key header (any unique
string, e.g. a UUID from your system):
- Same key + same body → the original response is replayed, nothing is created twice. Safe to retry on timeouts.
- Same key + different body →
422(this is a bug in your integration).
curl -X POST https://api.example.com/v2/porting_orders \
-H "Authorization: Bearer pk_live_YOUR_KEY" \
-H "Idempotency-Key: 2f4c1e9a-7b1d-4a53-9e1c-8f2a6d0b3c11" \
-H "Content-Type: application/json" \
-d '{"phone_numbers": ["+447912345678"]}'
Retry guidance
- Network errors / 5xx: retry with exponential backoff and the same
Idempotency-Key. - 409/422: do not blind-retry — read
detail, fix, then retry. - Webhook handlers: respond 2xx fast (queue the work); we retry failures on a 1m/5m/30m/2h/6h schedule.