Error codes & handling
Every error envelope the REST and Mobile APIs return, the HTTP status codes behind them, and what to do about each.
Both APIs return predictable JSON errors. The envelopes differ between the REST API and the Mobile API — parse the right one.
Who this is for
- Developer — writing retry/backoff logic or mapping errors to user messages.
- Support — reading an integration's logs and telling apart auth, scope and quota problems.
Prerequisites: basic familiarity with HTTP status codes.
The two envelopes
REST API (/api/v1/*)
{
"status": "error",
"message": "The given data was invalid.",
"code": "VALIDATION_FAILED",
"errors": { "first_name": ["The first name field is required."] }
}
statusis always"error"on failure.codeis a stable machine-readable token (table below).errorsappears only on 422 validation failures.
Mobile API (/api/v1/mobile/*)
{ "error": { "message": "Invalid email or password.", "code": "INVALID_CREDENTIALS" } }
- Success is
{"data": ...}; failure is{"error": {"message", "code"}}. - A few mobile endpoints have their own minimal shapes (for example the capability push returns a literal
{"ok":true}) — the endpoint docs flag these.
Status codes at a glance
| Status | Code(s) | Meaning | Typical fix |
|---|---|---|---|
| 400 | invalid signature / invalid payload |
Webhook body failed signature verification (payment webhooks) | Check the provider secret in Super Admin → Billing → Providers |
| 401 | UNAUTHORIZED, INVALID_CREDENTIALS, otp_invalid |
Missing/invalid credential | New API key or re-login on mobile |
| 402 | LEAD_LIMIT_REACHED |
Workspace lead quota for the plan is exhausted | Upgrade the plan or archive old leads |
| 403 | FORBIDDEN, Insufficient scope…, ACCOUNT_SUSPENDED, WORKSPACE_INACTIVE, login_method_disabled |
Authenticated but not allowed | Grant the scope / reactivate workspace / enable the login method |
| 404 | NOT_FOUND, CALL_NOT_FOUND, user_not_found, FILE_MISSING |
Resource missing or outside your workspace | Check the id; cross-workspace ids deliberately 404 |
| 410 | GONE |
Resource existed but is no longer available (expired recordings) | Nothing to retry — the file was purged |
| 422 | VALIDATION_FAILED, NUMBER_EXCLUDED, INVALID_MIME, CALL_NOT_LINKED, INVALID_IDENTIFIER |
Request understood but rejected by rules | Read the errors map / fix the payload |
| 429 | RATE_LIMITED, ACCOUNT_LOCKED |
Rate limit exceeded, or password login locked after 5 failed attempts | Wait for Retry-After / the retry time, then slow down |
| 500 | SERVER_ERROR |
Unhandled server-side failure | Check storage/logs/laravel.log |
| 503 | — | Health check found a degraded subsystem | See Monitoring & alerts |
REST API codes in detail
Defined in App\Http\Responses\ApiErrorResponse:
| Code | HTTP | Message you will see | Resolution |
|---|---|---|---|
UNAUTHORIZED |
401 | API key required. Provide Authorization: Bearer <key> header. |
Send the Bearer header |
UNAUTHORIZED |
401 | Invalid or expired API key. |
Key revoked/expired — create a new one |
FORBIDDEN |
403 | This workspace is not active. Renew or reactivate the subscription to use the API. |
Billing issue on the workspace |
FORBIDDEN |
403 | Insufficient scope. Required: <scope> |
Add the scope to the key |
NOT_FOUND |
404 | Resource not found. |
Wrong id, or the record belongs to another workspace |
VALIDATION_FAILED |
422 | The given data was invalid. |
Fix the fields listed in errors |
LEAD_LIMIT_REACHED |
402 | Lead limit reached for your plan. |
Upgrade or clean up leads |
RATE_LIMITED |
429 | Rate limit exceeded. Limit: N requests/hour. |
Honour Retry-After; raise the key limit if legitimate |
SERVER_ERROR |
500 | Something went wrong. Please try again. |
Check the Laravel log |
A real 422 example
Creating a lead without first_name (required):
curl -s -X POST "https://crm.zodiactechsoft.in/api/v1/leads" \
-H "Authorization: Bearer lh_yourKeyHere" \
-H "Content-Type: application/json" \
-d '{"email":"lead@example.com"}'
{
"status": "error",
"message": "The given data was invalid.",
"code": "VALIDATION_FAILED",
"errors": {
"first_name": ["The first name field is required."]
}
}
Mobile API codes in detail
| Code | HTTP | Message / meaning |
|---|---|---|
INVALID_CREDENTIALS |
401 | Invalid email or password. |
IDENTIFIER_REQUIRED |
422 | Login/OTP called without an identifier |
invalid_identifier |
422 | Identifier is neither a valid e-mail nor phone |
user_not_found |
404 | OTP requested for an unknown user |
ACCOUNT_SUSPENDED |
403 | The user account is suspended |
NO_WORKSPACE |
403 | User has no workspace membership |
WORKSPACE_INACTIVE |
403 | This workspace is not active... |
login_method_disabled |
403 | Password/OTP login disabled for this workspace |
otp_invalid |
401 | Wrong or expired OTP code |
code_required |
422 | OTP verify called without a code |
ACCOUNT_LOCKED |
429 | Password login locked after 5 failed attempts; message includes a retry time |
RATE_LIMITED |
429 | otp/start includes retry_after (seconds) in the payload; login/otp-verify 429s are route-throttle only |
CALL_NOT_LINKED |
422 | Editing a call that is not matched to a lead |
NUMBER_EXCLUDED |
422 | Recording/upload for an excluded number |
INVALID_MIME |
422 | Recording upload is not an accepted audio type |
CALL_NOT_FOUND / FILE_MISSING |
404 | Recording target missing |
GONE |
410 | Recording expired (30-day retention) |
Handling 429 correctly
- Read
Retry-After(seconds) and sleep at least that long. - Back off exponentially for repeated hits.
- If your workload legitimately needs more, raise the key's
rate_limit_per_hour(up to 100000) — but remember the workspace ceiling is a multiple of the per-key limit.
Common mistakes
- Retrying 4xx errors without changes — only 429 and 5xx deserve retries.
- Ignoring the
errorsmap on 422 and showing only the generic message. - Treating a 404 on a cross-workspace id as a bug — it is intentional isolation.
- Parsing the wrong envelope (e.g. expecting
{"status":"error"}from the mobile API).
Troubleshooting
| Symptom | Check |
|---|---|
| Every call 401 | Header present? key revoked? clock skew does not affect keys (no timestamps used) |
| 402 on lead create only | Workspace plan lead quota — see Billing → overview |
422 with empty errors |
Some endpoints validate fields payloads dynamically; re-read the endpoint doc |
| 500 consistently | storage/logs/laravel.log — often a missing storage permission or DB connection |
Technical details
- REST error builder:
App\Http\Responses\ApiErrorResponse(UNAUTHORIZED,FORBIDDEN,NOT_FOUND,RATE_LIMITED,VALIDATION_FAILED,SERVER_ERROR; quota helper emits 402LEAD_LIMIT_REACHED). - Mobile login codes:
App\Http\Controllers\Api\Mobile\AuthController. - Unhandled API exceptions are rendered with the same error shape by the framework's JSON exception handling (
Accept: application/jsonrequired). - Rate-limit headers and messages:
App\Http\Middleware\ApiKeyAuthentication.
Related guides
Was this page helpful?
Thanks — noted. ✦