Integrate
Errors & rate limits
The error envelope, every error code with its HTTP status, retry guidance and request limits.
Last updated
Every error uses the same envelope, with a stable machine-readable code and a human-readable message.
Error envelope
JSON
{
"success": false,
"error": {
"code": "unserviceable",
"message": "We don't deliver to Kebbi yet"
}
}
| Field | Type | Description |
|---|---|---|
success | boolean | Always false for errors |
error.code | string | One of the codes below. Branch your logic on this |
error.message | string | A readable explanation. Its wording can change, so log or display it but don't parse it |
Error codes
| HTTP | error.code | When |
|---|---|---|
| 400 | invalid_request | Missing or invalid field, expired quote, cancel not allowed now |
| 401 | authentication_error | Missing, invalid or revoked API key |
| 401 | environment_mismatch | Key environment doesn't match the resource |
| 402 | insufficient_funds | Wallet balance too low |
| 403 | permission_error | Production not enabled (KYB pending), merchant suspended |
| 404 | not_found | Unknown shipment / resource |
| 409 | invalid_request | Conflict (e.g. shipment changed concurrently, or can no longer be cancelled) |
| 422 | unserviceable | We don't deliver to that state or address yet |
| 429 | rate_limited | Too many requests |
| 503 | service_unavailable | Delivery network temporarily unavailable; retry with backoff |
| 500 | api_error | Unexpected error |
Handling errors
| Code | What to do |
|---|---|
invalid_request | Fix the request. For an expired or used quote, request a new one |
authentication_error, environment_mismatch | Check that you are sending the right key for the environment, and that it hasn't been revoked |
insufficient_funds | Fund your wallet, then request a new quote. Nothing was created or charged |
permission_error | Finish business verification, or contact support if your account is suspended |
not_found | Check the ID, and that you are using a key from the environment the resource was created in |
unserviceable | Ask your customer for a different address. Nothing was charged |
rate_limited | Slow down and retry after a short wait |
service_unavailable | Retry with exponential backoff. On POST /shipments, reuse the same Idempotency-Key |
api_error | Retry with backoff. Contact support if it persists |
Rate limits
| Scope | Limit | Keyed by |
|---|---|---|
| Authenticated API | 300 requests per minute | API key |
Public POST /rate-check | 8 requests per minute | IP address |
Public GET /track/:trackingCode and GET /status | 30 requests per minute | IP address |
Authenticated limits are counted per key, not per IP, so several servers sharing one key share its limit. Responses include standard RateLimit-* headers so you can see how much of the window is left. Over the limit, the API returns 429:
JSON
{
"success": false,
"error": {
"code": "rate_limited",
"message": "Too many requests. Please slow down and retry after a moment."
}
}
Public endpoints
These endpoints need no API key:
| Method | Path | Purpose |
|---|---|---|
GET | /track/:trackingCode | Public tracking. See Tracking |
POST | /rate-check | A public price check with the same fields as POST /rates/estimate, priced like production. No quote is saved, so it returns no quoteId |
GET | /status | Public system status, the same data as 7feastr.com/status |
Use the authenticated POST /rates/estimate in your integration. /rate-check is meant for quick price checks, such as a calculator on your website.