Errors
Errors arrive in the same response envelope as
successes — what differs is accept: false and a data of null:
{
"data": null,
"message": "Shartnoma topilmadi.",
"error": null,
"accept": false,
"errors": [],
"requestId": null
}
| Field | Meaning |
|---|---|
message | Human-readable text, usually in Uzbek. Do not branch on it — the wording can change |
error | { messageCode, args } when present — but most errors leave it null (section 3) |
errors[] | Field-level validation failures (section 5) |
data | null on error — a few 400s echo the underlying parser/validator message here instead |
:::warning Branch on the HTTP status first
error.messageCode is the stable identifier, but today it is only filled in for a handful of
generic errors. The reliable signal is the HTTP status code — read section 3 before you build
error handling around messageCode.
:::
On this page:
- Where an error comes from
- HTTP status codes
- When
messageCodeis present - How "not found" is reported
- Validation failures —
errors[] - Retry policy
- Known deviations
1. Where an error comes from
Two different layers can reject your request, and they answer in different shapes.
1.1 The authentication layer — 401 and 429
Token checks and rate limiting happen in a servlet filter, before your request reaches any
endpoint. Its body is a reduced envelope: no error, no requestId, and errors is null
rather than [].
{
"message": "Invalid or expired Open API token",
"accept": false,
"errors": null,
"data": null
}
{
"message": "Rate limit exceeded. Max 60 requests per minute.",
"accept": false,
"errors": null,
"data": null
}
Both messages are English and fixed, and neither carries a messageCode. Every reason for a
401 — no header, unknown token, expired token, revoked token, revoked connection, blocked
company — produces the same body, so the status is all you get. Use
GET /v1/open-api/token/info to find out which one it is.
1.2 The application layer — everything else
Once authentication passes, errors come from the endpoint itself and use the full envelope:
message, errors[], and sometimes error.
2. HTTP status codes
| Status | When | What to do |
|---|---|---|
200 | Success. Also the answer to an accepted async write (requestId, data: null) | — |
400 | Malformed JSON, bean validation (page < 1, size over the endpoint's maximum), unknown enum value, business validation (e.g. talk time greater than call duration), OAuth request errors | Fix the request; retrying won't help |
401 | Token problem or a blocked company (1.1). OAuth: unknown client_id / wrong client_secret | Check the token, then refresh or reissue it |
403 | Valid token without the required grant | Ask the company to grant the missing (permission, scope) |
404 | An unknown requestId, or a request-status entry that has expired | Don't poll a requestId older than 24 hours |
406 | A token-state error surfaced by an endpoint rather than the filter — rare | Treat it like 401 |
409 | Also how most "not found" cases are reported (section 4), plus genuine conflicts such as a duplicate value | Re-read the current state; on a not-found, stop |
429 | Per-minute limit exceeded (1.1) | Back off — see Rate limits |
500 | Our fault — and today also the answer for a few missing records (section 4) | Retry with backoff; on the paths listed in section 4, treat it as "not found" |
3. When messageCode is present
error.messageCode is filled in only by the generic handlers:
messageCode | Status | When |
|---|---|---|
3701 | 403 | Access denied — the token lacks the required grant |
3704 | 500 | Unhandled server error |
3705 | 400 | Malformed / unparsable JSON body |
Everything else — a missing lead, an invalid call duration, an OAuth failure, a rejected token —
comes back with error: null and only a message. The domain codes below exist inside the
platform and appear in logs and support tickets, so they are worth knowing, but do not build
client logic that waits for them in a response body:
| Code | Meaning | How you actually see it |
|---|---|---|
6201 / 6202 / 6204 | Token not found / expired / invalid | 401 from the filter, no code in the body |
6205 | Rate limit exceeded | 429 from the filter, no code in the body |
6206 | requestId not found | 404, message only |
2715 | Company blocked (licence expired) | 401 from the filter |
1020 | Contract not found | 500 today (section 4) |
1310 | Flat not found | 409, message only |
1719 | Lead not found | 409, message only |
6400 / 6401 | Invalid call duration / talk time above total duration | 400, message only |
6900–6909 | OAuth errors | 400 (or 401 for invalid client), message only |
:::note If your client already branches on messageCode
Keep that code path — it is where the API is heading — but make it fall through to
status-based handling when error is null. That way nothing breaks when the field starts
being populated.
:::
4. How "not found" is reported
This is the most surprising part of the API today: a missing record is usually a 409, not a
404 — and on a few paths still a 500.
| Resource | Missing record returns | Body |
|---|---|---|
Lead (GET/DELETE /v1/open-api/lead/{leadId}, notes, tasks, lead history) | 409 | message only |
Flat (GET /v1/open-api/flat/{id}) | 409 | message only |
Contract (GET /v1/open-api/contract/{id}, its payments and schedule) | 500 | generic system-error message, messageCode: 3704 |
Payment (GET /v1/open-api/payment/{id}) | 500 | generic system-error message, messageCode: 3704 |
Request status (GET /v1/open-api/request/{requestId}) | 404 | message only |
An id inside an async write (unknown leadId, employeeId, call uuid) | 200 at accept time | the failure shows up as status: FAILED on the requestId |
So: treat 409 on a read as "this record does not exist or is not visible to your company", and
on the two paths above treat 500 the same way rather than retrying it forever. A 409 on a
write is a genuine conflict (for example a duplicate value) — those are distinguishable by
which endpoint you called, not by the body.
:::tip Never-existed and no-longer-visible look the same An id belonging to another company, a deleted record and an id that never existed all produce the same response. That is deliberate — it keeps ids from leaking across tenants. :::
5. Validation failures — errors[]
When the request fails validation, the 400 response lists the offending fields. Note that
error is null here — the detail lives in errors[]:
{
"data": null,
"message": "So'rovda xatolik",
"error": null,
"accept": false,
"errors": [
{ "field": "size", "reason": "MAX" },
{ "field": "page", "reason": "MIN" }
]
}
The same shape covers body fields and path/query parameters (for example a uuid longer than
128 characters, or a size above the endpoint's maximum).
reason | Meaning |
|---|---|
NOT_NULL | The field is required |
NOT_BLANK | Must not be an empty string |
NOT_EMPTY / NOT_NULL_COLLECTION | The list must not be empty or null |
MIN / MAX | Numeric bound violated |
DECIMAL_MIN / DECIMAL_MAX | Decimal bound violated |
SIZE / LENGTH | Length or element count out of range |
NOT_FOUND | No record matches the given id |
CONFLICT | The value conflicts with the current state |
VALID_PHONE | Invalid phone number format |
VALID_EPOCH_SEC | The timestamp is not in epoch seconds (milliseconds were probably sent) |
NULLABLE_NOT_EMPTY | May be null, but must not be empty when present |
A malformed JSON body is different: it is the one 400 that does carry a code
(error.messageCode: 3705) and comes with an empty errors[].
6. Retry policy
| Error | Retry? |
|---|---|
429 | ✅ with exponential backoff and jitter |
5xx, network timeout | ✅ with backoff, 3–5 attempts — except the "not found" paths in section 4, where retrying can never succeed |
401 | ⚠️ only after refreshing the token (OAuth refresh_token); otherwise don't repeat |
400, 403, 409 | ❌ pointless until the request or the permissions change |
:::tip Be careful with writes
Write endpoints are asynchronous: once you have a 200 plus a requestId, resending may create a
second record. If the connection dropped before you saw the response, look the record up
first, then retry. POST /v1/open-api/call-history is the exception — it is idempotent on your
uuid, so resending it is safe.
:::
7. Known deviations
The docs describe reality — these behave as follows today:
| Deviation | Note |
|---|---|
⚠️ Missing contract or payment returns 500, not 404 | Being fixed; for now treat 500 on those paths as "not found" too |
⚠️ Missing lead / flat / note / task / history item returns 409, not 404 | Section 4 |
⚠️ error.messageCode is absent from most error bodies | Section 3 — branch on the status |
401 and 429 bodies use a reduced envelope (errors: null, no error) | Section 1.1 |
The contrac-field path typo | The canonical path is contract-field; the old spelling still works as an alias |
No X-RateLimit-* or Retry-After headers | Track your own usage |
No last flag in PageableData | Use currentPage >= totalPages |
A successful response's message can be a token-expiry warning instead of "Success" | Within 7 days of expiry — see Authentication |