API Integration Overview
This page explains how Bione's HTTP APIs are structured, how authentication works, and what to expect from responses. The API Reference documents every endpoint; this page gives you the landscape so you know where to look and what conventions to follow.
What the API is for
Bione is a merchant platform: companies run their shops, customers earn cashback, and integration partners connect to exchange data. Four separate API surfaces serve four audiences, each with its own authentication strategy. You are reading about the Integration surface — the one external partners use — but understanding all four helps you reason about data ownership and access boundaries.
The four surfaces
| Surface | URL prefix | Auth | Who uses it |
|---|---|---|---|
| Customer | /v1/customer/* | JWT cookies or body tokens | Mobile app, website, Telegram Mini App |
| Company | /v1/company/* | JWT + OrganizationMember | Company admin web |
| Platform | /v1/platform/* | JWT + platformRole + 2FA | Platform admin web |
| Integration | /integration/v1/* | API key in api-key header | External partner systems |
The Integration surface lives at /integration/v1/* — outside the global /v1 prefix — so it can be versioned and deprecated independently from Bione's internal releases.
Authentication
Browser surfaces (Customer, Company, Platform)
All three use httpOnly cookie sessions (bione_at / bione_rt). Tokens are never in response bodies for browser clients; they are set via Set-Cookie and sent automatically. Refresh happens through POST /<surface>/auth/refresh.
Native mobile (Customer)
The Kotlin Multiplatform mobile app is the only client permitted to receive tokens in the response body. It sends X-Client-Type: native on auth requests, stores tokens in platform-secured storage, and sends them as Authorization: Bearer <accessToken> on subsequent calls.
Integration (external partners)
Send your API key in the api-key header:
api-key: bik_your_key_hereThere is no OAuth flow, no token exchange, and no refresh. The key is the credential until the merchant revokes it. Bione stores only an argon2 hash of the key — a lost key cannot be recovered and must be reissued.
See Authentication for key scopes, rotation, and expiry.
Response envelope
Every response — success or error — is wrapped in the same envelope. This is a Bito-compatible shape, designed so that clients which also integrate with the Bito ERP API can share dispatch logic.
Dispatch on code, not on the HTTP status. 0 is success; anything else is an error. The numeric code is stable across releases; the prose message may be reworded.
{
"code": 0,
"message": "Success",
"data": {
"id": "prod_1",
"name": "Kofe",
"price": "9900.00"
},
"messages": {
"uz": "Muvaffaqiyatli",
"ru": "Успешно",
"en": "Success"
},
"status_code": 200,
"time": "2026-08-25T08:32:00.000Z"
}Key properties:
code—0means success. Non-zero is an error; see Error Codes.data— the payload. On error this isnullor carries validation context. A controller returning nothing yieldsdata: "OK"instead ofnull.messages— the same explanation in all three locales Bione ships (uz / ru / en), so you can localise without re-fetching.status_code— the real HTTP status, echoed in the envelope.- Every success is HTTP 200, regardless of verb. A
POSTthat creates a resource answers 200, not 201.
Paginated responses
Lists nest under data with pagination metadata:
{
"code": 0,
"message": "Success",
"data": {
"items": [...],
"total": 42,
"page": 1,
"pageSize": 20,
"totalPages": 3
},
"messages": { "uz": "...", "ru": "...", "en": "..." },
"status_code": 200,
"time": "2026-08-25T08:32:00.000Z"
}Money on the wire
All monetary values are Decimal(14,2) serialised as strings, not numbers. You will see "5000.00", not 5000.
- Never parse money strings with
Number()orparseFloat(). - Format at display time with
Intl.NumberFormator your locale's equivalent.
This avoids floating-point rounding and keeps precision consistent between your system and Bione's.
Standard REST verbs
Bione uses standard HTTP verbs — it is not a POST-only RPC:
| Verb | When | Example |
|---|---|---|
GET | Read (list, get-by-id) | GET /integration/v1/products |
POST | Create or perform an action | POST /integration/v1/orders |
PATCH | Partial update | PATCH /integration/v1/orders/:id |
DELETE | Soft-delete | DELETE /integration/v1/products/:id |
Use the verb that matches your operation. The backend validates and rejects incorrect usage.
Idempotency
Safe retries are built in. Send an Idempotency-Key header on any non-GET request to opt in.
| Situation | Result |
|---|---|
| First request with this key | Handler runs; response is stored on 2xx |
| Retry — same key, same body, original done | Stored response replayed verbatim |
| Retry — same key, different body | 409 idempotency mismatch |
| Retry — first request still in flight | 425 request in progress |
Idempotency records live in Redis for 24 hours. A non-2xx response deletes the record, so a failed call may be retried with the same key.
Tip: The body hash is compared after parsing, not raw bytes. Two requests with the same JSON but different key ordering are considered the same body.
Pagination
List endpoints accept standard query parameters and return a Paginated<T> shape:
GET /integration/v1/products?page=1&pageSize=20&q=kofe| Parameter | Default | Description |
|---|---|---|
page | 1 | 1-indexed page number |
pageSize | 20 | Items per page |
q | — | Free-text search filter |
The response data object contains items, total, page, pageSize, and totalPages.
Error handling
Errors use the same envelope as successes. The code field carries a numeric error code from Bione's namespaced enum; 0 is always success.
{
"code": 10012,
"message": "X-Audit-Reason header is required for this action",
"data": null,
"messages": {
"uz": "Audit sababini ko'rsating",
"ru": "Укажите причину для аудита",
"en": "X-Audit-Reason header is required for this action"
},
"status_code": 400,
"time": "2026-08-25T08:32:00.000Z",
"path": "/v1/platform/organizations/abc/suspend"
}codeis the stable, machine-readable identifier. Map it in your client rather than matching on message text.messagesgives you all three locales in one response, so you can localise without a second call.status_codeis the real HTTP status (400, 403, 404, …).
See Error Codes for the full list.
Your first call
Assuming you already have an API key from the merchant:
1. Health check
This route needs no key. It separates "wrong network path" from "wrong key":
curl https://api.bione.uz/integration/v1/health{
"code": 0,
"message": "Success",
"data": { "status": "ok", "version": "1" },
"messages": { "uz": "Muvaffaqiyatli", "ru": "Успешно", "en": "Success" },
"status_code": 200,
"time": "2026-08-25T08:32:00.000Z"
}2. List products
curl -H "api-key: bik_your_key_here" \
https://api.bione.uz/integration/v1/productsYou will get back the envelope with data.items containing the merchant's product catalogue.
3. Next steps
From here you can browse the API Reference for the full list of endpoints, request schemas, and response shapes.
What's NOT in the API
The Integration surface is intentionally scoped. Things it does not expose:
- No webhooks. Bione v1 is pull-only. Poll for changes or use the health and status endpoints to stay current.
- No OAuth. Authentication is API key only. There is no token exchange, no authorization URL, and no refresh cycle.
- No order cancellation, refunds, or cashback adjustments. These are merchant-staff operations performed through the Company admin surface.
- No cross-merchant access. One API key belongs to one merchant. There is no way to query across merchants with a single key.
These constraints are by design. If you need webhook delivery or cross-merchant aggregation, reach out — these are candidates for future versions.
Stability and versioning
The Integration surface lives at /integration/v1/*. Within a major version:
- Fields may be added to responses without notice.
- Fields will not be removed or renamed within
v1. - Endpoints will not be removed or renamed within
v1.
Breaking changes — removals, renames, or semantic shifts — bump the prefix to /integration/v2/*. The old version continues to work during a deprecation window.
See Versioning for the full policy.