Skip to main content

Errors

Every failed request returns the failure envelope with an error.code. Each code has its own page with causes and fixes.

Handling errors​

  • Branch on the HTTP status and error.code. Never branch on error.message, which can change.
  • For 422 INVALID_REQUEST, read error.fields. It lists each invalid field and why.
  • Log doc_url with the error. It links to the page for that code.
  • Don't treat available: false on a quote as an error. See quote reason codes.
Handling an error
const res = await fetch(url, options);
const body = await res.json();

if (body.status === 'failed') {
const { code, fields } = body.error;
if (code === 'INVALID_REQUEST') showFieldErrors(fields);
else if (code === 'INSUFFICIENT_BALANCE') promptToFundWallet();
else if (res.status >= 500 || res.status === 429) retryLater();
else logError(code, body.error.doc_url);
}

Error codes​

Every error response carries an error.code, including 400 and 5xx.

CodeHTTPWhen
INVALID_JSON400The body isn't valid JSON
IDEMPOTENCY_KEY_REQUIRED400A request that needs an Idempotency-Key arrived without one
INVALID_REQUEST422A field is missing or invalid
UNAUTHENTICATED401Missing, invalid or revoked access token
TOKEN_EXPIRED401The access token is older than an hour; get a new one from /oauth/token
INSUFFICIENT_SCOPE403The token doesn't carry the scope this endpoint needs
ACCOUNT_ON_HOLD403Invoice overdue or account suspended; new bookings refused
IP_NOT_ALLOWED403The account has the optional live IP allowlist on, and a live token was used from an IP address that isn't on it
TEST_MODE_ONLY403A sandbox endpoint was called with a live token, or a live token was sent from a web page (the API Reference or the dashboard's API console)
NOT_FOUND404No such object for this account
ADDRESS_NOT_SERVICEABLE422Creating a draft with two verified addresses when one is outside Reeds' coverage. (A quote returns available: false with this reason instead.)
INTERSTATE_NOT_AVAILABLE422Creating a draft from Lagos to the FCT: inter-state delivery is not available through the API. (A quote returns it as a reason.)
PICKUP_DATE_OUT_OF_RANGE422pickup_date is in the past or more than 7 days ahead
QUOTE_EXPIRED409The quote is older than 24 hours
QUOTE_ALREADY_USED409The quote already booked a delivery; error.delivery_id is that delivery
INSUFFICIENT_BALANCE402The wallet doesn't cover the total. Fund it and retry with the same Idempotency-Key.
CREDIT_LIMIT_REACHED402Invoice account: what it owes (unbilled deliveries plus unpaid invoices) plus this delivery exceeds the limit
ACTION_NOT_ALLOWED409The action conflicts with the object's state, for example booking a draft that isn't priced, or marking a delivered item ready. The error page lists every case.
ADDRESS_IN_USE409Deleting an address that a draft still uses; cancel the draft first
CANCEL_NOT_ALLOWED409The rider has already set off for the pickup
SHIPPER_REQUIRED422A platform account created a delivery without shipper (the merchant it is for)
PARCEL_TOO_HEAVY422package.weight_kg is over 30 kg
PARCEL_TOO_LARGE422The longest side in package.dimensions_cm is over 120 cm
PROHIBITED_ITEM422The description names something Reeds can't carry: cash, weapons, drugs, hazardous goods or perishable food
IDEMPOTENCY_KEY_REUSED422Same Idempotency-Key, different request body
REQUEST_IN_PROGRESS409Same Idempotency-Key while the first request is still running
RATE_LIMITED429Too many requests, or the account has used today's allowance for address lookups and pricing
INTERNAL_ERROR500Something failed inside Reeds; safe to retry with the same Idempotency-Key
SERVICE_UNAVAILABLE503Reeds is busy (any endpoint, with Retry-After), or a service Reeds depends on (maps, payments) is unavailable; retry later

The token endpoint (/oauth/token) follows the OAuth 2.0 standard instead: a wrong id or secret returns 401 with { "error": "invalid_client" }. A live client id used from an IP address that isn't on the account's IP allowlist, or sent from a web page, returns 400 with { "error": "unauthorized_client" }. See Authentication errors. When Reeds is busy it returns 503 with { "error": "temporarily_unavailable" } and Retry-After.

Quote reason codes​

When Reeds can't serve a route, POST /quotes returns 200 with available: false and a reason. This is not an error: a platform asks many couriers at once and simply leaves Reeds out.

reason.codeWhen
ADDRESS_NEEDS_CONFIRMATIONAn address matched only approximately, or not at all. data.suggestion is Reeds' best match; quote again with coordinates or a place id, or create a draft.
ADDRESS_NOT_SERVICEABLEAn address is outside Reeds' coverage, or there is no road route between the two
INTERSTATE_NOT_AVAILABLEThe drop-off is in another state. Inter-state delivery is not available through the API.

What to retry​

ResponseRetry?
401 TOKEN_EXPIREDGet a new token, then retry the same request.
402 INSUFFICIENT_BALANCE, 402 CREDIT_LIMIT_REACHEDFix the cause (fund the wallet, pay an invoice), then retry with the same Idempotency-Key.
403 ACCOUNT_ON_HOLDResolve the hold, then retry with the same key.
403 IP_NOT_ALLOWEDAdd the address in the dashboard (or call from an address that is on the list), then retry with the same key.
403 TEST_MODE_ONLYUse test credentials, or make the call from your server, then retry with the same key.
409 REQUEST_IN_PROGRESSWait a moment, then retry with the same key.
409 for a state conflict (QUOTE_EXPIRED, CANCEL_NOT_ALLOWED)Don't retry as is. Get a fresh quote, or change your approach.
429Wait for Retry-After, then retry with the same key.
500, 503Retry with backoff and the same key. On a 503, wait for Retry-After first when it is present.
400, 404, 422Don't retry. Fix the request and send it with a new key.

The full rules are in Idempotency.

Next steps​