23 KiB
Complete Backend API Documentation
Last updated: February 2026 Frontend: Angular 21 · Dual-brand (Dexar + Novo) Covers: Catalog, Cart, Payments, Reviews, Regions, Auth, i18n, BackOffice
Base URLs
| Brand | Dev | Production |
|---|---|---|
| Dexar | https://api.dexarmarket.ru:445 |
https://api.dexarmarket.ru:445 |
| Novo | https://api.novo.market:444 |
https://api.novo.market:444 |
Global HTTP Headers
The frontend automatically attaches two custom headers to every API request via an interceptor. The backend should read these headers and use them to filter/translate responses accordingly.
| Header | Example Value | Description |
|---|---|---|
X-Region |
moscow |
Region ID selected by the user. Absent = global (all). |
X-Language |
ru |
Active UI language: ru, en, or hy. |
Backend behavior
X-Region: If present, filter items/categories to only those available in that region. If absent, return everything (global catalog).X-Language: If present, return translatedname,description, etc. for categories/items when translations exist. If absent orru, use russians defaults.
CORS requirements for these headers
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
1. Health Check
GET /ping
Simple health check.
Response 200:
{ "message": "pong" }
2. Catalog — Categories
GET /category
Returns all top-level categories. Respects X-Region and X-Language headers.
Response 200:
[
{
"categoryID": 1,
"name": "Электроника",
"parentID": 0,
"icon": "https://...",
"wideBanner": "https://...",
"itemCount": 42,
"priority": 10,
"id": "cat_abc123",
"visible": true,
"img": "https://...",
"projectId": "proj_xyz",
"subcategories": [
{
"id": "sub_001",
"name": "Смартфоны",
"visible": true,
"priority": 5,
"img": "https://...",
"categoryId": "cat_abc123",
"parentId": "cat_abc123",
"itemCount": 20,
"hasItems": true,
"subcategories": []
}
]
}
]
Category object:
| Field | Type | Required | Description |
|---|---|---|---|
categoryID |
number | yes | Legacy numeric ID |
name |
string | yes | Category display name (translated if X-Language) |
parentID |
number | yes | Parent category ID (0 = top-level) |
icon |
string | no | Category icon URL |
wideBanner |
string | no | Wide banner image URL |
itemCount |
number | no | Number of items in category |
priority |
number | no | Sort priority (higher = first) |
id |
string | no | BackOffice string ID |
visible |
boolean | no | Whether category is shown (true default) |
img |
string | no | BackOffice image URL (maps to icon) |
projectId |
string | no | BackOffice project reference |
subcategories |
Subcategory[] | no | Nested subcategories |
Subcategory object:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Subcategory ID |
name |
string | yes | Display name |
visible |
boolean | no | Whether visible |
priority |
number | no | Sort priority |
img |
string | no | Image URL |
categoryId |
string | yes | Parent category ID |
parentId |
string | yes | Direct parent ID |
itemCount |
number | no | Number of items |
hasItems |
boolean | no | Whether has any items |
subcategories |
Subcategory[] | no | Nested children |
GET /category/:categoryID
Returns items in a specific category. Respects X-Region and X-Language headers.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
count |
number | 50 |
Items per page |
skip |
number | 0 |
Offset for paging |
Response 200: Array of Item objects.
3. Items
GET /item/:itemID
Returns a single item. Respects X-Region and X-Language headers.
Response 200: A single Item object.
GET /searchitems
Full-text search across items. Respects X-Region and X-Language headers.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
search |
string | — | Search query (required) |
count |
number | 50 |
Items per page |
skip |
number | 0 |
Offset for paging |
Response 200:
{
"items": [ /* Item objects */ ],
"total": 128,
"count": 50,
"skip": 0
}
GET /randomitems
Returns random items for carousel/recommendations. Respects X-Region and X-Language headers.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
count |
number | 5 |
Number of items to return |
category |
number | — | Optional: limit to this category |
Response 200: Array of Item objects.
Item Object
The backend can return items in either legacy format or BackOffice format. The frontend normalizes both.
{
"categoryID": 1,
"itemID": 123,
"name": "iPhone 15 Pro",
"photos": [{ "url": "https://..." }],
"description": "Описание товара",
"currency": "RUB",
"price": 89990,
"discount": 10,
"remainings": "high",
"rating": 4.5,
"callbacks": [
{
"rating": 5,
"content": "Отличный товар!",
"userID": "user_123",
"timestamp": "2026-02-01T12:00:00Z"
}
],
"questions": [
{
"question": "Есть ли гарантия?",
"answer": "Да, 12 месяцев",
"upvotes": 5,
"downvotes": 0
}
],
"id": "item_abc123",
"visible": true,
"priority": 10,
"imgs": ["https://img1.jpg", "https://img2.jpg"],
"tags": ["new", "popular"],
"badges": ["bestseller", "sale"],
"simpleDescription": "Краткое описание",
"descriptionFields": [
{ "key": "Процессор", "value": "A17 Pro" },
{ "key": "Память", "value": "256 GB" }
],
"subcategoryId": "sub_001",
"translations": {
"en": {
"name": "iPhone 15 Pro",
"simpleDescription": "Short description",
"description": [
{ "key": "Processor", "value": "A17 Pro" }
]
},
"hy": {
"name": "iPhone 15 Pro",
"simpleDescription": "Կարcheck check check"
}
},
"comments": [
{
"id": "cmt_001",
"text": "Отличный товар!",
"author": "user_123",
"stars": 5,
"createdAt": "2026-02-01T12:00:00Z"
}
],
"quantity": 50
}
Full Item fields:
| Field | Type | Required | Description |
|---|---|---|---|
categoryID |
number | yes | Category this item belongs to |
itemID |
number | yes | Legacy numeric item ID |
name |
string | yes | Item display name |
photos |
Photo[] | no | Legacy photo array [{ url }] |
description |
string | yes | Text description |
currency |
string | yes | Currency code (default: RUB) |
price |
number | yes | Price in the currency's smallest display unit |
discount |
number | yes | Discount percentage (0–100) |
remainings |
string | no | Stock level: high, medium, low, out |
rating |
number | yes | Average rating (0–5) |
callbacks |
Review[] | no | Legacy reviews (alias for reviews) |
questions |
Question[] | no | Q&A entries |
id |
string | no | BackOffice string ID |
visible |
boolean | no | Whether item is visible (true default) |
priority |
number | no | Sort priority (higher = first) |
imgs |
string[] | no | BackOffice image URLs (maps to photos) |
tags |
string[] | no | Item tags for filtering |
badges |
string[] | no | Display badges (bestseller, sale, etc.) |
simpleDescription |
string | no | Short plain-text description |
descriptionFields |
DescriptionField[] | no | Structured [{ key, value }] descriptions |
subcategoryId |
string | no | BackOffice subcategory reference |
translations |
Record | no | Translations keyed by lang code (see below) |
comments |
Comment[] | no | BackOffice comments format |
quantity |
number | no | Numeric stock count (maps to remainings on frontend) |
Nested types:
| Type | Fields |
|---|---|
Photo |
url: string, photo?: string, video?: string, type?: string |
DescriptionField |
key: string, value: string |
Comment |
id?: string, text: string, author?: string, stars?: number, createdAt?: string |
Review |
rating?: number, content?: string, userID?: string, answer?: string, timestamp?: string |
Question |
question: string, answer: string, upvotes: number, downvotes: number |
ItemTranslation |
name?: string, simpleDescription?: string, description?: DescriptionField[] |
4. Cart
POST /cart — Add item to cart
Request body:
{ "itemID": 123, "quantity": 1 }
Response 200:
{ "message": "Added to cart" }
PATCH /cart — Update item quantity
Request body:
{ "itemID": 123, "quantity": 3 }
Response 200:
{ "message": "Updated" }
DELETE /cart — Remove items from cart
Request body: Array of item IDs
[123, 456]
Response 200:
{ "message": "Removed" }
GET /cart — Get cart contents
Response 200: Array of Item objects (each with quantity field).
5. Payments (SBP / QR)
POST /cart — Create payment (SBP QR)
Note: Same endpoint as add-to-cart but with different body schema.
Request body:
{
"amount": 89990,
"currency": "RUB",
"siteuserID": "tg_123456789",
"siteorderID": "order_abc123",
"redirectUrl": "",
"telegramUsername": "john_doe",
"items": [
{ "itemID": 123, "price": 89990, "name": "iPhone 15 Pro" }
]
}
Response 200:
{
"qrId": "qr_abc123",
"qrStatus": "CREATED",
"qrExpirationDate": "2026-02-28T13:00:00Z",
"payload": "https://qr.nspk.ru/...",
"qrUrl": "https://qr.nspk.ru/..."
}
GET /qr/payment/:qrId — Check payment status
Response 200:
{
"additionalInfo": "",
"paymentPurpose": "Order #order_abc123",
"amount": 89990,
"code": "SUCCESS",
"createDate": "2026-02-28T12:00:00Z",
"currency": "RUB",
"order": "order_abc123",
"paymentStatus": "COMPLETED",
"qrId": "qr_abc123",
"transactionDate": "2026-02-28T12:01:00Z",
"transactionId": 999,
"qrExpirationDate": "2026-02-28T13:00:00Z",
"phoneNumber": "+7XXXXXXXXXX"
}
paymentStatus values |
Meaning |
|---|---|
CREATED |
QR generated, not paid |
WAITING |
Payment in progress |
COMPLETED |
Payment successful |
EXPIRED |
QR code expired |
CANCELLED |
Payment cancelled |
POST /purchase-email — Submit email after payment
Request body:
{
"email": "user@example.com",
"telegramUserId": "123456789",
"items": [
{ "itemID": 123, "name": "iPhone 15 Pro", "price": 89990, "currency": "RUB" }
]
}
Response 200:
{ "message": "Email sent" }
6. Reviews / Comments
POST /comment — Submit a review
Request body:
{
"itemID": 123,
"rating": 5,
"comment": "Great product!",
"username": "john_doe",
"userId": 123456789,
"timestamp": "2026-02-28T12:00:00Z"
}
Response 200:
{ "message": "Review submitted" }
7. Regions
GET /regions — List available regions
Returns regions where the marketplace operates.
Response 200:
[
{
"id": "moscow",
"city": "Москва",
"country": "Россия",
"countryCode": "RU",
"timezone": "Europe/Moscow"
},
{
"id": "spb",
"city": "Санкт-Петербург",
"country": "Россия",
"countryCode": "RU",
"timezone": "Europe/Moscow"
},
{
"id": "yerevan",
"city": "Ереван",
"country": "Армения",
"countryCode": "AM",
"timezone": "Asia/Yerevan"
}
]
Region object:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Unique region identifier |
city |
string | yes | City name (display) |
country |
string | yes | Country name |
countryCode |
string | yes | ISO 3166-1 alpha-2 |
timezone |
string | no | IANA timezone |
Fallback: If this endpoint is down, the frontend uses 6 hardcoded defaults: Moscow, SPB, Yerevan, Minsk, Almaty, Tbilisi.
8. Authentication (Telegram Login)
Authentication is Telegram-based with cookie sessions (HttpOnly, Secure, SameSite=None).
All auth endpoints must include withCredentials: true CORS support.
Auth flow
1. User clicks "Checkout" → not authenticated → login dialog shown
2. User clicks "Log in with Telegram" → opens https://t.me/{bot}?start=auth_{callback}
3. User starts the bot in Telegram
4. Bot sends user data → backend /auth/telegram/callback
5. Backend creates session → sets Set-Cookie
6. Frontend polls GET /auth/session every 3s
7. Session detected → dialog closes → checkout proceeds
GET /auth/session — Check current session
Request: Cookies only (session cookie set by backend).
Response 200 (authenticated):
{
"sessionId": "sess_abc123",
"telegramUserId": 123456789,
"username": "john_doe",
"displayName": "John Doe",
"active": true,
"expiresAt": "2026-03-01T12:00:00Z"
}
Response 200 (expired):
{
"sessionId": "sess_abc123",
"telegramUserId": 123456789,
"username": "john_doe",
"displayName": "John Doe",
"active": false,
"expiresAt": "2026-02-27T12:00:00Z"
}
Response 401 (no session):
{ "error": "No active session" }
AuthSession object:
| Field | Type | Required | Description |
|---|---|---|---|
sessionId |
string | yes | Unique session ID |
telegramUserId |
number | yes | Telegram user ID |
username |
string? | no | Telegram @username (can be null) |
displayName |
string | yes | User display name (first + last) |
active |
boolean | yes | Whether session is valid |
expiresAt |
string | yes | ISO 8601 expiration datetime |
GET /auth/telegram/callback — Telegram bot auth callback
Called by the Telegram bot after user authenticates.
Request body (from bot):
{
"id": 123456789,
"first_name": "John",
"last_name": "Doe",
"username": "john_doe",
"photo_url": "https://t.me/i/userpic/...",
"auth_date": 1709100000,
"hash": "abc123def456..."
}
Response: Must set a session cookie and return:
{
"sessionId": "sess_abc123",
"message": "Authenticated successfully"
}
Cookie requirements:
| Attribute | Value | Notes |
|---|---|---|
HttpOnly |
true |
Not accessible via JS |
Secure |
true |
HTTPS only |
SameSite |
None |
Required for cross-origin (API ≠ frontend) |
Path |
/ |
|
Max-Age |
86400 (24h) |
Or as needed |
POST /auth/logout — End session
Request: Cookies only, empty body {}
Response 200:
{ "message": "Logged out" }
Must clear/invalidate the session cookie.
Session refresh
The frontend re-checks the session 60 seconds before expiresAt. If the backend supports sliding expiration, it can reset the cookie's Max-Age on each GET /auth/session.
9. i18n / Translations
The frontend supports 3 languages: Russian (ru), English (en), Armenian (hy).
The active language is sent via the X-Language HTTP header on every request.
What the backend should do with X-Language
-
Categories & items: If
translationsfield exists for the requested language, return the translatedname,description, etc. OR the backend can apply translations server-side and return already-translated fields. -
The
translationsfield on items (optional approach):{ "translations": { "en": { "name": "iPhone 15 Pro", "simpleDescription": "Short desc in English", "description": [{ "key": "Processor", "value": "A17 Pro" }] }, "hy": { "name": "iPhone 15 Pro", "simpleDescription": "Կarcheck check" } } } -
Recommended approach: Read
X-Languageheader and return thename/descriptionin that language directly. If no translation exists, return the Russian default.
10. CORS Configuration
For auth cookies and custom headers to work, the backend CORS config must include:
Access-Control-Allow-Origin: https://dexarmarket.ru (NOT wildcard *)
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Important:
Access-Control-Allow-Origincannot be*whenAllow-Credentials: true. Must be the exact frontend origin.
Allowed origins:
https://dexarmarket.ruhttps://novo.markethttp://localhost:4200(dev)http://localhost:4201(dev, Novo)
11. Telegram Bot Setup
Each brand needs its own bot:
- Dexar:
@dexarmarket_bot - Novo:
@novomarket_bot
The bot should:
- Listen for
/start auth_{callbackUrl}command - Extract the callback URL
- Send the user's Telegram data (
id,first_name,username, etc.) to that callback URL - The callback URL is
{apiUrl}/auth/telegram/callback
Complete Endpoint Reference
New endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
GET |
/regions |
List available regions | No |
GET |
/auth/session |
Check current session | Cookie |
GET |
/auth/telegram/callback |
Telegram bot auth callback | No (bot) |
POST |
/auth/logout |
End session | Cookie |
Existing endpoints
| Method | Path | Description | Auth | Headers |
|---|---|---|---|---|
GET |
/ping |
Health check | No | — |
GET |
/category |
List categories | No | X-Region, X-Language |
GET |
/category/:id |
Items in category | No | X-Region, X-Language |
GET |
/item/:id |
Single item | No | X-Region, X-Language |
GET |
/searchitems |
Search items | No | X-Region, X-Language |
GET |
/randomitems |
Random items | No | X-Region, X-Language |
POST |
/cart |
Add to cart / Payment | No* | — |
PATCH |
/cart |
Update cart quantity | No* | — |
DELETE |
/cart |
Remove from cart | No* | — |
GET |
/cart |
Get cart contents | No* | — |
POST |
/comment |
Submit review | No | — |
GET |
/qr/payment/:qrId |
Check payment status | No | — |
POST |
/purchase-email |
Submit email after pay | No | — |
* Cart/payment endpoints may use the session cookie if available for order association, but don't strictly require auth. The frontend enforces auth before checkout.