Errors & Rate Limits
The IDeliver API uses standard HTTP status codes and returns machine-readable JSON error bodies so you can handle failures gracefully in your integration.
Error response format
All errors return a JSON body with at minimum an error code and a human-readable message:
{
"error": "validation_error",
"message": "customer_phone is required.",
"details": {
"field": "standardized.customer_phone"
}
}HTTP status codes
| Code | Meaning | What to do |
|---|---|---|
| 200 / 201 | Success | Order created or retrieved successfully |
| 400 | Bad request | Fix the request body — check error and message for the specific issue |
| 401 | Unauthorized | Your API key is missing, expired, or invalid |
| 403 | Forbidden | Your key lacks the required scope, or your account is blocked (e.g. billing issue, pending KYB) |
| 404 | Not found | The order ID or endpoint does not exist |
| 409 | Conflict | Duplicate external_order_id with a different payload |
| 429 | Rate limited | Slow down — back off and retry after the Retry-After header value |
| 500 / 503 | Server error | Retry with exponential backoff; if persistent, check status.ideliver.ng |
Common error codes
error value | What it means |
|---|---|
unauthorized | Missing or invalid API key |
forbidden | Valid key, but insufficient scope or account restriction |
validation_error | One or more required fields are missing or invalid |
OUT_OF_COVERAGE_AREA | Pickup or dropoff coordinates fall outside all active dispatch zones. Includes point (pickup/dropoff), lat, lng. Use GET /v1/coverage/check first |
duplicate_order | external_order_id already exists |
outside_coverage | Coordinates / address outside platform coverage (e.g. public pricing preview) |
pickup_outside_coverage | Pickup outside merchant / pricing zone (internal estimation paths) |
wallet_insufficient | Your wallet balance is too low to fund this delivery |
idempotency_conflict | Idempotency-Key reused with a different request body |
missing_pickup_time | standardized.scheduled_pickup_at is required |
missing_delivery_window | standardized.delivery_window_start and delivery_window_end are required |
invalid_pickup_time | Pickup time must be in the future |
invalid_delivery_window | Delivery window start must be before end |
cannot_cancel | Order is in a terminal or protected status and cannot be cancelled |
kyb_required | Account requires identity verification before placing orders |
rate_limited | Too many requests — slow down |
Rate limits
Rate limits are applied per API key:
| Tier | Requests per minute |
|---|---|
Sandbox (ilv_test_…) | 60 |
Production (ilv_live_…) | 300 |
When you are rate limited, the API returns HTTP 429 with a Retry-After header indicating how many seconds to wait before retrying.
HTTP/1.1 429 Too Many Requests
Retry-After: 5Retrying requests
For server errors (5xx), use exponential backoff with jitter:
async function createOrderWithRetry(client, payload, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await client.orders.create(payload);
} catch (err) {
if (err.status >= 500 && attempt < maxAttempts) {
const delay = Math.pow(2, attempt) * 500 + Math.random() * 200;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw err;
}
}
}Do not retry 4xx errors — they indicate a problem with your request that must be fixed before retrying.