Zodiac Tech CRM Docs

Error codes & handling

Every error envelope the REST and Mobile APIs return, the HTTP status codes behind them, and what to do about each.

developer support admin Medium ⏱ 10 min Updated 2026-10-06

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."] }
}
  • status is always "error" on failure.
  • code is a stable machine-readable token (table below).
  • errors appears 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

  1. Read Retry-After (seconds) and sleep at least that long.
  2. Back off exponentially for repeated hits.
  3. 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 errors map 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 402 LEAD_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/json required).
  • Rate-limit headers and messages: App\Http\Middleware\ApiKeyAuthentication.

Was this page helpful?