OpenAPI 3.1REST · v1
Dona API
Подключите магазин к своей системе: товары, остатки и цены, заказы, здоровье аккаунта и события — через один REST API. Для 1С, МойСклад, BILLZ, SmartUp, Linko и AI-агентов.
Ключ создаёт владелец магазина в кабинете продавца: Настройки › API. Ключ показывается один раз.
- Базовый адрес
https://api.dona.im/seller-api/v1- Типы ключей
dona_sk_live_…ключ интеграции — для ERP, 1С, POS и вашей системыdona_ak_live_…ключ AI-агента — только через MCPdona_it_live_…токен установки приложения — скоро
API открывается поэтапно. Что работает сейчас — на странице «Статус», что добавлено — в «Изменениях».
/mecurl -sS https://api.dona.im/seller-api/v1/me \
-H "Authorization: Bearer $DONA_KEY"{
"key": { … },
"shop": { … },
"tier": "basic",
"api_access": { … },
"writes_enabled": false,
"limits": {
"requests_per_min": 300,
"burst": 60,
"requests_per_day": 100000,
"writes_per_min": 60,
"writes_per_day": 20000,
"in_flight": 4,
"seller_requests_per_min": 900,
"remaining": { … }
},
"attention": { … },
"docs_url": "https://dona.uz/uz/developers"
}Содержание
0108
Быстрый старт
4 шагаЧетыре шага: ключ, первый запрос, безопасная пробная запись и лента событий.
Создайте ключ
Кабинет продавца › Настройки › API › Создать ключ. Отметьте только нужные права (scope). Перед созданием первого ключа владелец магазина должен принять условия Приложения 2 (API и автоматизированный доступ) в кабинете продавца.
Пока владелец магазина не примет действующее Приложение 2, каждый вход — REST, MCP и OAuth — отвечает 403 agreement_required с
accept_url: страницей кабинета, где его принимают.Проверьте, кто вы
GET /meвозвращает ключ, магазин, уровень (Базовый/Расширенный), права и текущие лимиты.GET /me curl -sS https://api.dona.im/seller-api/v1/me \ -H "Authorization: Bearer $DONA_KEY"Ответ
GET /me(пример из спецификации)200 { "key": { "id": "0199760c-2f4b-7a3e-9d21-5c8e4b1f0a37", "name": "billz-prod", "kind": "sk", "prefix": "dona_sk_live_3fK", "scopes": [ "catalog:read", "catalog:stock", "orders:read" ], "expires_at": "2027-09-24T00:00:00+05:00", "ip_allow": [] }, "shop": { "id": "0198f3a2-6c1d-7b40-8e55-2a9c3d4e5f60", "name": "Bahor Textile", "status": "approved", "documents_status": "approved", "kyc_status": "approved", "can_sell": true }, "tier": "basic", "api_access": { "state": "enabled", "reason": null }, "writes_enabled": false, "limits": { "requests_per_min": 300, "burst": 60, "requests_per_day": 100000, "writes_per_min": 60, "writes_per_day": 20000, "in_flight": 4, "seller_requests_per_min": 900, "remaining": { "requests_per_min": 287, "requests_per_day": 99412, "writes_per_min": 60, "writes_per_day": 20000 } }, "attention": { "open": 3, "action_required": 2 }, "docs_url": "https://dona.uz/uz/developers" }Пробная запись остатков
С
Dona-Dry-Run: trueзапись проверяется, результат считается — и ничего не сохраняется.Пока запись закрыта (
writes_enabled), сервер отвечает503 limits_unavailable— это не ошибка ключа.POST /stock curl -sS -X POST https://api.dona.im/seller-api/v1/stock \ -H "Authorization: Bearer $DONA_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Dona-Dry-Run: true" \ -H "Content-Type: application/json" \ -d '{"items":[{"seller_sku":"A-100","quantity":12}]}'Получайте изменения
GET /events?cursor=…— основной канал. Вебхуки присылают те же события.GET /events curl -sS "https://api.dona.im/seller-api/v1/events?cursor=$LAST_EVENT_ID&limit=200" \ -H "Authorization: Bearer $DONA_KEY"
0208
Справочник
14 групп · 61 операцияКаждая операция отрисована из контракта OpenAPI при сборке сайта. Значки показывают уровень ключа и право, нужные для операции.
Справочник — на английском, как и сам контракт.
- Базовый
- Любой действующий ключ
- Расширенный
- Подтверждены личность, документы и магазин, нет санкций
- Открытый
- Без ключа
Общие параметры (в операциях указаны по имени)11
Idempotency-Keyheader · string1–255 chars. Scope = this key × route. Terminal 2xx/4xx replayed byte-identical for 24 h; a 5xx is never stored. Missing ⇒
400 invalid_bodywithdetails[{field:"Idempotency-Key",code:"required"}].Accept-Languageheader · stringLocalises
messagein error bodies and single-language renderings. Defaultuz.X-Dona-Integrationheader · stringname/versionof the calling integration; stored (≤ 128 chars) and searchable in the request journal.Dona-Dry-Runheader · stringtrue⇒ validate + guard + compute the effect, then roll back. Zero side-effect rows (no events, no activity, no counters). Same as?dry_run=true. Only the literaltrue/false; any other value ⇒400 invalid_body+details[{field:"Dona-Dry-Run", code:"invalid"}](C51).dry_runquery · stringAlias of the
Dona-Dry-Runheader. Only the literal stringstrue/false—1,0,TRUE,yes, an empty value ⇒400 invalid_body+details[{field:"dry_run", code:"invalid"}](C51). A string enum, not a boolean, so no SDK generator serialises it as1/0.Dona-Sellerheader · string (uuid)Vendor-app install keys only (
dona_it_live_…, S6) — and then REQUIRED on every request, public routes included: the id of the shop the install key belongs to. Missing ⇒400 invalid_body+details[{field:"Dona-Seller", code:"required"}]; sent more than once, or not ONE id in the canonical form the API prints (lower-case, 36 characters — no braces, nourn:uuid:, no padding) ⇒400 invalid_body+details[{field:"Dona-Seller", code:"invalid"}]; naming any other shop — even one that installed the same app ⇒404 not_found(never 403; nothing is read). Ignored on a seller key (dona_sk_).If-None-Matchheader · stringAn
ETagfrom a previous identical request ⇒304with rate headers (cost 0.5).cursorquery · stringOpaque keyset cursor from
next_cursor.limitquery · integerPage size, default 50, max 100 (clamped, with
Dona-API-Warn).limit > 50costs1 + ceil(limit/50).updated_sincequery · stringISO 8601 with offset, or epoch milliseconds. The visible high-water mark is capped at
now() − 10 sso a slow transaction is never skipped.pagequery · integer0-based page alias (Uzum parity) — slower than
cursor, ≤ 1 000 pages; mutually exclusive withcursor.
0308
Лимиты
Лимиты считаются по подтверждённому ключу и магазину, а не по IP. Ниже — значения по умолчанию; действующие для вашего ключа — в GET /me → limits.
| Лимит | Базовый sk | Расширенный sk | Агент ak |
|---|---|---|---|
| Запросов / мин (ключ) | 300 · burst 60 | 600 · burst 120 | 60 · burst 20 |
| Запросов / сутки (ключ) | 100 000 | 250 000 | 20 000 |
| Записей / мин | 60 | 120 | 10 |
| Записей / сутки | 20 000 | 50 000 | 2 000 |
| Одновременных запросов | 4 | 8 | 4 |
| Запросов / мин (магазин, все ключи) | 900 | 1 800 | — |
- Остаток или цена одного товара — не чаще 1 записи в 10 секунд (
object_cooldown). - Пакетная запись ≤ 1 000 строк; тело ≤ 256 КиБ (пакетная — 4 МиБ). Рекомендуем 100–300 строк.
- Страница
limit≤ 100 (на/events≤ 200).If-None-Match→304за полцены. - Наши 429/503 и 5xx не списываются и не приостанавливают ключ.
Значения Dona-Rate-Limited-Reason:
key-ratewrite-rateseller-rateconcurrencyobject-cooldownapp-rateglobalsuspended
0408
Коды ошибок
59 кодовЛюбая ошибка — один конверт: error — стабильный код, message — перевод по Accept-Language.
Ветвитесь по коду, никогда — по message.
{
"error": "insufficient_scope",
"message": "Bu amal uchun ruxsat yetarli emas.",
"request_id": "req_01997b2e-9a8b-7c6d-8e5f-4a3b2c1d0e9f",
"details": [],
"doc_url": "https://dona.uz/uz/developers/errors#insufficient_scope",
"required_scope": "orders:write"
}202Accepted
approval_requiredthe MCP door — see MCP
400Bad Request
invalid_bodymalformed JSON, a missing
Idempotency-Key, validation (details[]carries field codes such asikpu_required,cursor_expired), adry_run/Dona-Dry-Runthat is not the literaltrue/false(details[{field, code:"invalid"}], C51), a vendor-app install key withoutDona-Seller(details[{field:"Dona-Seller", code:"required"}], S6) or with a repeated / non-canonical one (code:"invalid"), or an op-specific 400 (invalid_decline_reason)invalid_decline_reasonrequest_state_invalidthe MCP door — see MCP
401Unauthorized
api_key_malformedchecksum fails — refused before any DB read
api_key_expired+
rotate_url, no graceapi_key_revokedalso a vendor-app key whose install was uninstalled — S6
key_kind_not_acceptedan agent key
akon this treedownload_token_invaliddownload_token_expiredinvalid_tokenthe MCP door — see MCP
Также a seller/app/staff JWT on this tree
403Forbidden
insufficient_scope+
required_scope; also a scope indisabled_scopestier_required+
scope; derived on every request; also a key minted for a third-partyorders:piirecipient —details[pii_recipient: pii_third_party_pending_counsel], D7, C59key_suspended+
suspended_until,Retry-After,Dona-Rate-Limited-Reason: suspendedapi_blockedip_not_allowedjudged on the trusted client IP only
seller_not_approvedshop closed/blocked/suspended/restricted
not_a_sellernever on this tree; the portal's owner doors
app_frozenS6: every key of a frozen vendor app, from the next request
agreement_required+
accept_url: the shop owner has not accepted the CURRENT Annex 2 «API and automated access» — the version in force or, while none is, the earliest announced; accepted once in Settings › API, then every door admits the key again. Checked after the sanctions, before the scopesrequest_state_foreignthe MCP door — see MCP
404Not Found
not_foundevery missing OR foreign id (never 403: no existence oracle)
409Conflict
idempotency_in_progress+
Retry-After: 2idempotency_mismatchversion_conflictai_review_openpublish of a product whose Dona AI review case is open — the review decides its status
order_not_acceptableno_pickup_addressorder_not_shippableorder_awaiting_courierorder_not_cancellableorder_has_active_returnno_tracking_number+
ordersno_items_selectedall_items_delayedS4, C53
confirmation_declinednothing written · the MCP door — see MCP
request_state_mismatchthe MCP door — see MCP
request_state_replayedthe MCP door — see MCP
held_write_not_pendingan intent's fate · the MCP door — see MCP
Также a replay of a rejected held write
410Gone
request_state_expiredthe MCP door — see MCP
hold_expiredan intent's fate · the MCP door — see MCP
413Content Too Large
payload_too_largebody > 256 KiB (4 MiB on bulk stock/prices/batch), or > 1 000 lines
422Unprocessable Content
webhook_url_invalid+
details[].code∈timeout,status_<n>,tls,dns,private_address,schemewarehouse_unsupported
429Too Many Requests
rate_limitedwith
Dona-Rate-Limited-Reason: app-rate: a vendor app's bucket, shared by all its installs — S6write_rate_limitedconcurrency_limitedseller_rate_limitedobject_cooldownapi_busyintent_limitthe MCP door — see MCP
Также Always a JSON body + Retry-After from the origin (an edge 429 from Cloudflare has NO body — treat it as Retry-After: 60). Costs 0 and never counts toward the breaker. rate_limited is also the pre-auth ADDRESS guard, answered before the key is read: an address over 1 200 requests a minute that were not admitted (no key, a public route, a malformed, unknown or refused key — admitted requests never count), or one tarpitted at 60/min for 15 min after ≥ 20 unknown keys in a minute. No key is ever suspended by traffic that did not prove it
Также Cloudflare edge, per-IP 60/min on public routes. No JSON body (the edge eats it) — treat as Retry-After: 60
500Internal Server Error
internal_errordetails are in the redacted error copy, never here. Costs 0
503Service Unavailable
attention_busyan acknowledgement that resolves an item could not take the attention writer lock within 2 s — nothing was written; retry after
Retry-After: 2
Также Costs 0
0508
События и вебхуки
41 тип событияВсе изменения — в одной ленте. Для систем, которые умеют только инициировать запрос (как 1С), опрос ленты — основной канал.
Опрос ленты
get/eventscurl -sS "https://api.dona.im/seller-api/v1/events?cursor=$LAST_EVENT_ID&limit=200" \
-H "Authorization: Bearer $DONA_KEY"Вебхуки — подпись Standard Webhooks
Правила доставки
One event per POST (≤ 20 KB), at-least-once, unordered. Reply 2xx within 10 s (ack first, process after); 3xx is a failure; 410 Gone disables the endpoint. Retry ladder: immediate · 5 s · 5 m · 30 m · 2 h · 5 h · 10 h · 14 h · 20 h · 24 h (≈ 75 h, ±10 % jitter).
Заголовки
webhook-id- = the event id. Your idempotency key — dedupe for 24 h.
webhook-timestamp- Unix seconds. Reject if |now − ts| > 300 s. A redelivery carries a FRESH timestamp.
webhook-signature- Standard Webhooks: space-delimited
v1,<base64 HMAC-SHA256(base64decode(secret after whsec_), id + "." + ts + "." + rawBody)>; two entries during a 24 h rotation. Dona-Request-Id- Correlates with the delivery log.
Типы событий
order.createdorder.paidorder.acceptedorder.declinedorder.readyorder.shippedorder.deliveredorder.cancelledorder.line_cancelledorder.accept_due_soonorder.ship_overduereturn.requestedreturn.status_changedproduct.publishedproduct.heldproduct.rejectedproduct.demotedproduct.violationproduct.deletedstock.lowstock.outwrite.heldwrite.approvedwrite.rejectedwrite.expiredaccount.status_changedaccount.documents_decidedaccount.kyc_reopenedaccount.suppressedaccount.health_changedaccount.agreement_owedlive.access_changedlive.strikeattention.openedattention.resolvedkey.expiringkey.suspendedkey.unsuspendedwebhook.failingwebhook.disabledping
Подпись ниже настоящая: она вычислена при сборке сайта из показанного демо-секрета и тела, и код подтверждает её байт в байт.
POST https://example.uz/dona-webhook
webhook-id: 01997b2e-7f00-7123-8456-789abcdef012
webhook-timestamp: 1790223151
webhook-signature: v1,CFjQ7u8l+tPV5Wzhawj/Hx9iWYx9sM6mcFJu4wsQxpQ=
# demo secret — NOT a real one
# whsec_ZG9uYS1kZW1vLXNlY3JldC1ub3QtYS1yZWFsLW9uZSE=
{"id":"01997b2e-7f00-7123-8456-789abcdef012","type":"order.created","occurred_at":"2026-09-24T09:12:31+05:00","seller_id":"0198f3a2-6c1d-7b40-8e55-2a9c3d4e5f60","api_version":"v1","data":{"order_id":"01997b2e-41c0-7e6f-8a9b-0c1d2e3f4a5b","status":"paid","payment_status":"paid","payment_method":"card","scheme":"own_fleet","total_uzs":259000,"accept_deadline":"2026-09-25T09:12:00+05:00","ship_by_deadline":null,"updated_at":"2026-09-24T09:12:31+05:00"},"links":{"self":"/seller-api/v1/orders/01997b2e-41c0-7e6f-8a9b-0c1d2e3f4a5b"}}import { createHmac, timingSafeEqual } from 'node:crypto'
// secret = "whsec_…" from POST /webhooks; body = the RAW request body (a string, not parsed JSON)
export function verify(secret, headers, body) {
const id = headers['webhook-id'], ts = headers['webhook-timestamp']
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
const key = Buffer.from(secret.slice('whsec_'.length), 'base64')
const want = createHmac('sha256', key).update(`${id}.${ts}.${body}`).digest()
return headers['webhook-signature'].split(' ').some((entry) => {
const [v, sig] = entry.split(',')
const got = Buffer.from(sig ?? '', 'base64')
return v === 'v1' && got.length === want.length && timingSafeEqual(got, want)
})
}import base64, hashlib, hmac, time
def verify(secret: str, headers: dict, body: bytes) -> bool:
wid, ts = headers["webhook-id"], headers["webhook-timestamp"]
if abs(time.time() - int(ts)) > 300:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
want = hmac.new(key, f"{wid}.{ts}.".encode() + body, hashlib.sha256).digest()
for entry in headers["webhook-signature"].split(" "):
v, _, sig = entry.partition(",")
if v == "v1" and hmac.compare_digest(base64.b64decode(sig), want):
return True
return False0608
MCP — AI-агенты
Claude Code, Cursor, Codex и другие AI-агенты могут работать с вашим магазином через MCP. Каждый вызов журналируется.
С ключом агента агент читает магазин: товары, заказы, события, требующие внимания состояния, здоровье аккаунта и эту документацию. Инструменты записи — остатки, цены, отправка и отмена заказа — доступны только магазину расширенного уровня и только пока запись включена. С первого вызова ничего не записывается: изменение сначала подтверждается — в форме подтверждения самого агента или владельцем магазина в Настройки › API › Отложенные изменения.
- Endpoint
https://api.dona.im/seller-api/mcp- Метаданные защищённого ресурса (RFC 9728)
/.well-known/oauth-protected-resource/seller-api/mcp
Ключ агента (dona_ak_live_…, право mcp) создаётся в разделе Настройки › API.
claude mcp add --transport http dona https://api.dona.im/seller-api/mcp --header "Authorization: Bearer ${DONA_AGENT_KEY}"OAuth 2.1 — коннекторы claude.ai и ChatGPT
Коннекторы, в которые нельзя вставить ключ, входят через OAuth 2.1: подключение одобряет владелец магазина в кабинете продавца (Настройки › API › Подключённые приложения) и может отключить его в любой момент. Через OAuth агент может только читать.
- Сервер авторизации
https://auth.dona.im
0708
SDK
SDK для TypeScript, Python, Go, PHP и C# генерируются из этого же контракта.
- TypeScript
- Python
- Go
- PHP
- C#
- Предварительная версия 0.1.0
SDK лежат в открытом репозитории на GitHub. В реестрах (npm, PyPI, NuGet, Packagist) пакеты ещё не опубликованы — пока устанавливайте прямо из репозитория.
TypeScript
Требуется: Node ≥ 20Нужен клон репозитория
Установка git clone https://github.com/ozbuz/dona-api.git && cd dona-api/sdks/typescript && npm installУстановка npm install ../dona-api/sdks/typescriptPython
Требуется: ≥ 3.11Установка pip install "dona-api @ git+https://github.com/ozbuz/dona-api.git@main#subdirectory=sdks/python"Go
Требуется: ≥ 1.26Установка go get github.com/ozbuz/dona-api/sdks/go@mainPHP
Требуется: ≥ 8.1, Guzzle 7Нужен клон репозитория
Установка git clone https://github.com/ozbuz/dona-api.git && cd your-project && composer config repositories.dona-api path ../dona-api/sdks/php && composer require ozbuz/dona-api:*@devC#
Требуется: .NET 10Нужен клон репозитория
Установка git clone https://github.com/ozbuz/dona-api.git && dotnet add reference path/to/dona-api/sdks/csharp/src/Dona.Api/Dona.Api.csproj
0808
Изменения и статус
Каждое изменение публикуется с датой, типом и затронутыми правами.
Нужен ключ?
Ключ создаёт владелец магазина в кабинете продавца: Настройки › API. Ключ показывается один раз.
Вопрос как у покупателя или продавца? Загляните в центр помощи.