Skip to content

HTTP status codes

Complete reference for HTTP status codes the Zorio API returns and how your CRM should handle them.

2xx — Success

HTTPWhenBody
200 OKMost GET / PUT / DELETE successes{ "data": {} } or a paginated list
201 CreatedPOST creating a new resource{ "data": { "id": ..., "..." } }
202 AcceptedAsync action enqueued (e.g. click-to-call, lead import){ "data": { "call_uuid": "...", "status": "queued" } }

3xx — Redirect

HTTPWhenBody
302 Found/api/pbx/cdr/{uuid}/recording redirecting to a signed URLEmpty body, header Location: <signed URL>

4xx — Client errors

400 Bad Request

The request is invalid at the HTTP level (malformed JSON, missing required header, ...).

json
{ "message": "Malformed request body" }

Handling: check the JSON is correct and the Content-Type: application/json header is present.

401 Unauthorized

Token missing / invalid / expired.

json
{ "message": "Unauthenticated." }

Handling: redirect the user to login for a fresh token. DO NOT hardcode tokens in the frontend.

403 Forbidden

Token is valid but missing the required permission.

json
{ "message": "This action is unauthorized." }

Handling: log it and ask an admin to grant the missing permission to the user (e.g. pbx_api_access, telesales_api_access).

404 Not Found

The resource doesn't exist in your account's data.

json
{ "message": "Resource not found" }

Security note

When a token is used against the wrong account (e.g. account A asks for data from account B), Zorio also returns 404 rather than 403 so it does not leak existence. Your CRM can't distinguish the two cases.

409 Conflict

State-machine violation or unique-constraint conflict.

json
{ "message": "Extension already exists in this queue." }

Handling: refresh local state and notify the user.

422 Unprocessable Entity

Validation error — the body parsed correctly, but one or more field values are invalid.

json
{
  "message": "The given data was invalid.",
  "errors": {
    "extension_number": ["Extension number format is invalid."],
    "outbound_caller_id": ["The selected outbound caller id is invalid."]
  }
}

Handling:

  • Iterate over errors, show each message next to its field in the UI.
  • For form submits — don't refresh the page; keep the user's current state.

429 Too Many Requests

Rate-limit exceeded.

json
{ "message": "Too many requests. Retry after 25 seconds." }

Response headers:

Retry-After: 25
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782706011

Handling: back off per Retry-After. Implement exponential backoff + jitter to avoid a thundering herd.

5xx — Server errors

500 Internal Server Error

Unexpected backend error.

json
{ "message": "Internal server error" }

Handling: retry with exponential backoff (3 attempts, 1s / 4s / 16s). If 500 persists → contact Zorio support with X-Request-Id (if available) or the request timestamp.

502 Bad Gateway

Lower-tier failure — most common cases:

  • Click-to-call: Zorio PBX did not accept the originate (the PBX may have just restarted).
json
{ "message": "PBX did not accept the originate. Please try again." }

Handling: retry after a few seconds. If 502 persists → escalate.

503 Service Unavailable

Maintenance or temporary overload.

json
{ "message": "Service temporarily unavailable" }

The Retry-After: <seconds> header advises the suggested retry delay.

504 Gateway Timeout

Upstream timeout. Rare.

Best practices

Always handle 4xx + 5xx

Every API call MUST have an error handler. DO NOT assume "200 OK by default".

Distinguish retry-able vs non-retry-able

StatusRetry?Reason
4xx (except 408, 429)NoClient-side error — retry won't change the result
408 Request TimeoutYes (1-3 times)May be a network blip
429 Too Many RequestsYes (after Retry-After)Back off per the header
500, 502, 503, 504Yes (exponential backoff)Temporary server-side errors

Trace requests

Response header: X-Request-Id: <uuid> — paste it into your support ticket; the Zorio team uses it to trace root cause quickly.

Network error vs HTTP error

A network error (DNS failure, connection refused, TLS error) is NOT an HTTP status code — there is no body. Catch it separately:

js
try {
  const res = await fetch(url, opts);
  if (!res.ok) {
    // HTTP error 4xx / 5xx
  }
} catch (err) {
  // Network error
}

Cấp phép theo điều khoản sử dụng của Zorio.