Skip to main content

Formats and conventions

These rules hold across the whole API. Learn them once and every new endpoint behaves the way you expect.


1. The response envelope​

Every response is a ResponseData<T>. A bare array or scalar is never returned.

{
"data": { },
"message": "Success",
"error": null,
"accept": true,
"errors": [],
"requestId": null
}
FieldTypeMeaning
dataT | nullThe payload; null on error
messagestring"Success" on success, a human-readable message (usually Uzbek) on error. Not always "Success" — see the warning below
errorobject | null{ messageCode, args } when present. Most errors leave it null — see Errors § 3
acceptbooleantrue on success, false on error
errorsarrayField-level validation errors; [] on success (null in the filter-produced 401/429)
requestIdstring | nullSet by asynchronous write operations (section 6)

:::tip Branch on the status, never on text 200 plus accept: true means success. Base your logic on the HTTP status code; treat error.messageCode as extra detail that is often absent, and never parse message. :::

:::warning message is not a constant on success Within 7 days of a token's expiry the platform replaces "Success" on every successful response with an English warning — "Token will expire in 5 days. Please renew it.". It is a deliberate nudge, not an error (accept stays true, data is unaffected), and it is another reason not to compare message against "Success". See Authentication § 5.1. :::

Two endpoints step outside the envelope: POST /v1/open-api/oauth/token returns the RFC 6749 token shape directly (its errors still use the envelope), and GET /v1/open-api/address/{uuid} returns the resolved address object on its own. Everything else — including POST /v1/open-api/oauth/revoke — is wrapped.


2. Pagination​

Paginated responses put a PageableData<T> inside data:

{
"data": {
"totalPages": 27,
"currentPage": 1,
"totalElements": 398,
"data": [ ]
},
"accept": true
}
FieldTypeMeaning
totalPagesintegerTotal number of pages
currentPageintegerCurrent page — 1-based
totalElementsinteger (int64)Total number of rows
dataarrayThe rows on this page

:::warning Pages start at 1 Many APIs start at 0; this one starts at 1. Sending page: 0 returns 400. :::

There is no last flag — detect the end with currentPage >= totalPages.


3. Filtering and sorting​

Every POST .../filter endpoint accepts the same trio:

FieldTypeMeaning
pageintegerPage number, min 1. Default 1
sizeintegerPage size, min 1. The maximum and the default differ per endpoint — see 3.2
ordersobject<Domain>OrderField → ASC | DESC
{
"page": 1,
"size": 100,
"statuses": ["ACTIVE"],
"orders": { "CONTRACT_DATE": "DESC", "ID": "ASC" }
}

Key order in orders is the ORDER BY column order. Above, rows are sorted by CONTRACT_DATE DESC first, then ID ASC. Keys come from each domain's allow-listed enum (for contracts: ID, NUMBER, CONTRACT_DATE, AMOUNT, …); anything else returns 400. With an empty orders, the domain's historical default applies (usually ID DESC).

3.1 Filter semantics​

RuleMeaning
null, a missing key and an empty arrayAll mean "do not filter". An empty array does not mean "match nothing"
Values within one fieldOR ("statuses": ["ACTIVE","CANCELED"] matches either)
Different fieldsAND
Ranges (...From / ...To)Inclusive on both ends
Deleted rowsExcluded unless the filter asks for them (includeDeleted / deletedOnly)
Company scopeEverything is scoped to the token's company — you never pass a company id

3.2 Page size is per endpoint​

There is no single global maximum. Sending size: 100 to an endpoint that caps at 50 is a 400 (errors: [{ "field": "size", "reason": "MAX" }]), so read the cap for the endpoint you are calling:

Endpointsize maxDefault
POST /v1/open-api/lead/filter5010
POST /v1/open-api/lead-task/filter3015
POST /v1/open-api/lead-event/filter10020
POST /v1/open-api/contract/filter10015
POST /v1/open-api/flat/filter10015
POST /v1/open-api/monthly-payment/filter10015
POST /v1/open-api/payment/filter10010
POST /v1/open-api/call-history/filter10010
POST /v1/open-api/employee/filter5010
GET /v1/open-api/contract/{id}/payment5020
GET /v1/open-api/contract-payment/{contractId} (deprecated)—none: page and size are required, and size must be at least 20

Two shapes of pagination input exist: the POST .../filter endpoints take page / size in the JSON body, while the two contract-payment reads take them as query parameters.

:::note Not every list is paginated Reference reads return a plain array inside data, with no PageableData and no paging at all: GET /v1/open-api/pipe/all, /crm-field, /contract-field, /currency, /house/all, /reason-for-refusal/all, /lead-task/types, /lead-note/{leadId}/list, /contract/{id}/monthly-payment, the four /address/* endpoints and the deprecated GET /v1/open-api/employee. :::

3.3 Stable pagination​

To keep rows from being skipped or repeated across pages, the server always appends an internal primary-key tie-breaker to ORDER BY. You never see it in the response, but it is why paging stays consistent when a sort column has ties.

For bulk exports, the most reliable approach is to sort by a stable field ({"ID": "ASC"}) and walk the pages in order.


4. Dates and times​

ConceptNamingFormatExample
Point in time*Timestamp, *Atepoch seconds (not milliseconds)1766131200
Calendar day*Datedd.MM.yyyy31.12.2026

The two forms are never mixed for the same concept.

:::danger Seconds, not milliseconds Date.now() in JavaScript returns milliseconds. Divide before sending: Math.floor(Date.now() / 1000). Otherwise your timestamp lands somewhere in the year 55,000 and the filter comes back empty. :::


5. Types and naming​

TypeRule
EnumUPPER_SNAKE_CASE, serialized by name ("ACTIVE"). Unknown values return 400
CurrencyISO code ("UZS", "USD")
MoneyA JSON number, unrounded. Use a decimal type in your code, not a binary float
Field namescamelCase
Absent fieldsSome fields are omitted entirely when null — treat "key missing" the same as null
Identifiersid is an integer or int64; leads and contract payments use int64. In JavaScript, keep large ids as strings

5.1 New enum values are not a breaking change​

Enums can gain new values. Always write a default branch and never crash on an unknown value. Existing value names do not change within v1.


6. Writes are asynchronous​

Write endpoints (for example POST /v1/open-api/lead, POST /v1/open-api/payment) accept the request and queue it. The response carries a requestId instead of data:

{ "data": null, "accept": true, "requestId": "1f0c7a5e-3b62-4a91-8f0d-2e6b9c4a7d13" }

Poll for the outcome:

curl -s https://api.service.app.uysot.uz/v1/open-api/request/1f0c7a5e-3b62-4a91-8f0d-2e6b9c4a7d13 \
-H "X-Open-Api-Token: uysot_pat_..."
{
"accept": true,
"data": {
"requestId": "1f0c7a5e-3b62-4a91-8f0d-2e6b9c4a7d13",
"status": "SUCCESS",
"exception": null,
"createdAt": 1766131200,
"failedAt": null
}
}
statusMeaning
PENDINGStill being processed — wait a moment and poll again
SUCCESSApplied
FAILEDFailed — see exception for the reason and failedAt for the time
FieldMeaning
requestIdThe id you polled
statusPENDING | SUCCESS | FAILED
exceptionWhy it failed; null otherwise
createdAtWhen the request was accepted, epoch seconds
failedAtWhen it failed, epoch seconds; null otherwise

:::note Statuses live for 24 hours A request status is kept for 24 hours after it was accepted, then disappears — polling it later returns 404. That is a generous window, but it is not a job history: read the outcome soon after the write, and don't poll every 100 ms (each poll spends rate limit budget). :::

:::danger A requestId belongs to the token that created it The status is readable only with the exact same token value. Any other token — including a refreshed OAuth access token for the same company, or a newly issued PAT for the same application — gets a plain 404, indistinguishable from an expired status. So finish polling before you rotate or refresh the token, and never hand a requestId to a different worker holding a different token. :::

:::warning An accepted write is not a successful write 200 + requestId means "validated and queued". Business failures — an unknown leadId, an employee that doesn't exist, a value the CRM rejects — surface only as status: FAILED on the requestId. Code that ignores the requestId will silently lose writes. :::


7. Versioning and compatibility​

What v1 guarantees:

  • Fields may be added — ignore unknown fields rather than failing on them.
  • Enums may gain new values.
  • Existing field names are never renamed or removed; deprecated ones keep working.
  • Breaking changes ship under /v2.

So keep your JSON parser non-strict: an unfamiliar field must not break your client.

7.1 How a deprecation is signalled​

An endpoint on its way out is marked in three places, and it keeps working the whole time:

  • its reference page says Deprecated and names the replacement;
  • every response carries Deprecation: true and, when a removal date is set, Sunset: <HTTP-date> (RFC 8594) — for example Sunset: Fri, 12 Feb 2027 00:00:00 GMT on GET /v1/open-api/contract-payment/{contractId};
  • a deprecated field stays in the payload and keeps being filled until /v2.

Log the Sunset header when you see one — that date is your migration deadline, and it is the only place the API states it. Currently deprecated: GET /v1/open-api/employee (use POST /v1/open-api/employee/filter), GET /v1/open-api/contract-payment/{contractId} (use GET /v1/open-api/contract/{id}/payment) and the misspelled GET /v1/open-api/contrac-field alias.