12 KiB
API Documentation
Endpoint reference for the Marketplace Backoffice.
Base URL: https://your-api-domain.com/api
🇷🇺 Документация на русском языке: API.ru.md
Projects
Get All Projects
GET /api/projects
Response 200:
[
{
"id": "dexar",
"name": "dexar",
"displayName": "Dexar Marketplace",
"active": true,
"logoUrl": "https://..."
}
]
Categories
Get Categories for Project
GET /api/projects/:projectId/categories
Response 200:
[
{
"id": "cat1",
"name": "Electronics",
"visible": true,
"priority": 1,
"img": "https://...",
"projectId": "dexar",
"subcategories": [ ...Subcategory[] ]
}
]
Get Single Category
GET /api/categories/:categoryId
Response 200:
{
"id": "cat1",
"name": "Electronics",
"visible": true,
"priority": 1,
"img": "https://...",
"projectId": "dexar",
"subcategories": [ ...Subcategory[] ]
}
Create Category
POST /api/projects/:projectId/categories
Body:
{
"name": "New Category", // required
"visible": true,
"priority": 10,
"img": "https://..."
}
Response 201: (created category object)
Update Category
PATCH /api/categories/:categoryId
Body: (any subset of fields)
{
"name": "Updated Name",
"visible": false,
"priority": 5
}
Response 200: (updated category object)
Delete Category
DELETE /api/categories/:categoryId
Response 204 No Content
Note: Cascades and deletes all subcategories and items under this category
Subcategories
Subcategories are recursive - they can be nested under a root Category or under
another Subcategory. The nesting depth is unlimited, with one constraint:
a subcategory that already contains items cannot have child subcategories (and vice versa).
Subcategory Object
{
"id": "sub1",
"name": "Smartphones",
"visible": true,
"priority": 1,
"img": "https://...",
"categoryId": "cat1", // always the ROOT category ID
"parentId": "cat1", // direct parent ID (category OR subcategory)
"itemCount": 15,
"hasItems": true,
"subcategories": [] // nested children (empty when hasItems = true)
}
categoryIdis always the root-level category this subtree belongs to.parentIdis the direct parent - it can be either a category ID or a subcategory ID.
Get Subcategories Under a Category
GET /api/categories/:categoryId/subcategories
Response 200: Subcategory[] (nested subcategories populated recursively)
Get Single Subcategory
GET /api/subcategories/:subcategoryId
Response 200: Subcategory object (with nested subcategories if any)
Create Subcategory Under a Category (level 1)
POST /api/categories/:categoryId/subcategories
Body:
{
"id": "custom-id", // optional, auto-generated if omitted (used in URL routing)
"name": "Smartphones", // required
"visible": true,
"priority": 10
}
Response 201: (created subcategory object)
Error 400: if category does not exist
Create Subcategory Under a Parent Subcategory (level 2+, nested)
POST /api/subcategories/:parentSubcategoryId/subcategories
Body:
{
"id": "custom-id", // optional, auto-generated if omitted (used in URL routing)
"name": "Apple", // required
"visible": true,
"priority": 10
}
Response 201: (created subcategory object)
Error 400: if parent subcategory has items (hasItems = true)
Error 404: if parent subcategory does not exist
Update Subcategory
PATCH /api/subcategories/:subcategoryId
Body: (any subset of fields)
{
"id": "new-slug", // ID is editable - used for marketplace URL routing
"name": "Updated Name",
"visible": false,
"priority": 3
}
Response 200: (updated subcategory object)
Delete Subcategory
DELETE /api/subcategories/:subcategoryId
Response 204 No Content
Note: Cascades and deletes all nested subcategories and items under this subcategory
Items
Items always belong to the deepest subcategory in the hierarchy (a leaf node).
A subcategory with at least one item has hasItems: true and cannot have child subcategories.
Get Items (Paginated)
GET /api/subcategories/:subcategoryId/items
Query Params:
page number (default: 1)
limit number (default: 20)
search string optional - filters by name (case-insensitive)
visible boolean optional - filter by visibility
tags string optional - comma-separated tag list
Response 200:
{
"items": [
{
"id": "item1",
"name": "iPhone 15 Pro",
"visible": true,
"priority": 1,
"quantity": 50,
"price": 1299,
"currency": "USD",
"imgs": ["https://...", "https://..."],
"tags": ["new", "featured"],
"badges": ["new", "exclusive"],
"simpleDescription": "Latest iPhone...",
"description": [
{ "key": "Color", "value": "Black" },
{ "key": "Storage", "value": "256GB" }
],
"subcategoryId": "sub1",
"translations": {
"ru": {
"name": "iPhone 15 Про",
"simpleDescription": "Последний iPhone...",
"description": [
{ "key": "Цвет", "value": "Чёрный" },
{ "key": "Память", "value": "256 ГБ" }
]
}
},
"comments": [
{
"id": "c1",
"text": "Great product!",
"author": "John Doe",
"stars": 5,
"createdAt": "2024-01-10T10:30:00Z"
}
]
}
],
"total": 150,
"page": 1,
"limit": 20,
"hasMore": true
}
Get Single Item
GET /api/items/:itemId
Response 200: (full item object)
Create Item
POST /api/subcategories/:subcategoryId/items
Body:
{
"name": "New Product", // required
"visible": true,
"priority": 10,
"quantity": 100,
"price": 999,
"currency": "USD", // USD | EUR | RUB | GBP | UAH
"imgs": ["https://..."],
"tags": ["new"],
"badges": ["new", "exclusive"], // optional - predefined or custom badge labels
"simpleDescription": "Short description",
"description": [
{ "key": "Size", "value": "Large" }
],
"translations": { // optional - localized content for marketplace
"ru": {
"name": "Новый товар",
"simpleDescription": "Краткое описание",
"description": [
{ "key": "Размер", "value": "Большой" }
]
}
}
}
Response 201: (created item object)
Side effect: Sets hasItems = true on the subcategory.
The subcategory can no longer receive child subcategories.
Update Item
PATCH /api/items/:itemId
Body: (any subset of fields)
{
"name": "Updated Name",
"price": 899,
"quantity": 80,
"visible": false
}
Response 200: (updated item object)
Updating images - always send the full array:
PATCH /api/items/:itemId
Body:
{
"imgs": ["https://new1.jpg", "https://new2.jpg"]
}
Response 200: (updated item object)
Delete Item
DELETE /api/items/:itemId
Response 204 No Content
Side effect: If no items remain in the subcategory, sets hasItems = false.
The subcategory can then receive child subcategories again.
Bulk Update Items
PATCH /api/items/bulk
Body:
{
"itemIds": ["item1", "item2", "item3"],
"data": {
"visible": true
}
}
Response 204 No Content
Upload
Upload Image
POST /api/upload
Body: multipart/form-data
image: File
Response 201:
{
"url": "https://cdn.example.com/uploads/abc123.jpg"
}
Notes
- All responses are JSON.
- Use
PATCHfor partial updates - send only the fields you want to change. priority: lower number = appears first in the list.currencysupported values:USD,EUR,RUB,GBP,UAH.badges: optional string array. Predefined values with UI colors:new,sale,exclusive,hot,limited,bestseller,featured. Custom strings are also allowed.imgs: always send the complete array on update, not individual images.description: array of{ key, value }pairs - free-form attributes per item.translations: optional object keyed by language code ("ru","en", etc.) — each value may containname,simpleDescription,description[]. The marketplace frontend should use these when rendering in the corresponding language, falling back to the default fields if a translation is absent.- Auto-save from the backoffice fires
PATCHwith a single field every ~500 ms.
Business Rules
Nested Subcategories
The hierarchy works like this:
Category (e.g. Electronics)
Subcategory L1 (e.g. Kitchen) <- can add more children OR items, not both
Subcategory L2 (e.g. Big Kitchen) <- same rule
Subcategory L3 (e.g. Ovens) <- if this has items, it is a leaf
Items...
Rules:
- A category can always receive new subcategories (categories never hold items directly).
- A subcategory that has items (
hasItems: true) cannot receive child subcategories.POST /api/subcategories/:id/subcategorieson a node withhasItems: true->400 Bad Request.
- A subcategory that has children cannot have items added to it (items only go to leaf nodes).
- When the first item is created in a subcategory ->
hasItemsbecomestrue. - When the last item is deleted ->
hasItemsbecomesfalse; child subcategories can be added again.
URL Structure (Marketplace Frontend)
Subcategory and item id fields are used directly in the marketplace URL path:
/{categoryId}/{sub1Id}/{sub2Id}/.../{itemId}
Examples:
/electronics/smartphones/iphone-15
/electronics/smartphones/apple/iphone-15-pro
/furniture/living-room/sofas/corner-sofa-modelo
The id field on subcategories is editable via PATCH to allow renaming slugs.
Comments
starsis optional, integer 1-5.authoris optional (anonymous comments allowed).createdAtis ISO 8601 string.
Cascade Deletes
| Delete target | Also deletes |
|---|---|
| Category | all subcategories (recursive) and their items |
| Subcategory | all nested subcategories (recursive) and their items |
| Item | nothing else |
Internationalization (i18n)
The API supports localized content for items and categories via the translations field.
translations object structure
Any entity that supports translations accepts the same nested shape:
{
"translations": {
"ru": {
"name": "Название",
"simpleDescription": "Краткое описание",
"description": [
{ "key": "Цвет", "value": "Чёрный" }
]
}
}
}
- Keys are ISO 639-1 language codes (
"ru","en", etc.). - All sub-fields are optional — omit what you don't need.
- Currently supported in the backoffice:
ru(Russian).
Requesting a Specific Language
Pass ?lang=ru to any GET endpoint. The backend will:
- Return the default top-level fields as-is.
- Merge the matching
translations[lang]values into the response, overwriting the default fields. - Fall back gracefully to the default language if no translation exists for the requested lang.
GET /api/items/:itemId?lang=ru
GET /api/subcategories/:subcategoryId/items?lang=ru
Alternatively, you can use the Accept-Language header:
Accept-Language: ru
Fallback Behaviour
| Scenario | Returned value |
|---|---|
| Translation exists for requested lang | Translated value |
| Translation missing for requested lang | Default (base) field value |
lang param omitted |
Default (base) field value |
🇷🇺 See API.ru.md for full documentation in Russian.