2026-02-20 00:59:46 +04:00
|
|
|
# API Documentation
|
2026-01-19 23:17:07 +04:00
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Endpoint reference for the Marketplace Backoffice.
|
|
|
|
|
Base URL: `https://your-api-domain.com/api`
|
|
|
|
|
|
|
|
|
|
---
|
2026-01-19 23:17:07 +04:00
|
|
|
|
|
|
|
|
## Projects
|
|
|
|
|
|
|
|
|
|
### Get All Projects
|
|
|
|
|
```
|
|
|
|
|
GET /api/projects
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200:
|
2026-01-19 23:17:07 +04:00
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"id": "dexar",
|
|
|
|
|
"name": "dexar",
|
|
|
|
|
"displayName": "Dexar Marketplace",
|
|
|
|
|
"active": true,
|
|
|
|
|
"logoUrl": "https://..."
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
---
|
|
|
|
|
|
2026-01-19 23:17:07 +04:00
|
|
|
## Categories
|
|
|
|
|
|
|
|
|
|
### Get Categories for Project
|
|
|
|
|
```
|
|
|
|
|
GET /api/projects/:projectId/categories
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200:
|
2026-01-19 23:17:07 +04:00
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"id": "cat1",
|
|
|
|
|
"name": "Electronics",
|
|
|
|
|
"visible": true,
|
|
|
|
|
"priority": 1,
|
|
|
|
|
"img": "https://...",
|
|
|
|
|
"projectId": "dexar",
|
2026-02-20 00:59:46 +04:00
|
|
|
"subcategories": [ ...Subcategory[] ]
|
2026-01-19 23:17:07 +04:00
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Get Single Category
|
|
|
|
|
```
|
|
|
|
|
GET /api/categories/:categoryId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200:
|
2026-01-19 23:17:07 +04:00
|
|
|
{
|
|
|
|
|
"id": "cat1",
|
|
|
|
|
"name": "Electronics",
|
|
|
|
|
"visible": true,
|
|
|
|
|
"priority": 1,
|
|
|
|
|
"img": "https://...",
|
|
|
|
|
"projectId": "dexar",
|
2026-02-20 00:59:46 +04:00
|
|
|
"subcategories": [ ...Subcategory[] ]
|
2026-01-19 23:17:07 +04:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Create Category
|
|
|
|
|
```
|
|
|
|
|
POST /api/projects/:projectId/categories
|
|
|
|
|
|
|
|
|
|
Body:
|
|
|
|
|
{
|
2026-02-20 00:59:46 +04:00
|
|
|
"name": "New Category", // required
|
2026-01-19 23:17:07 +04:00
|
|
|
"visible": true,
|
|
|
|
|
"priority": 10,
|
|
|
|
|
"img": "https://..."
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 201: (created category object)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Update Category
|
|
|
|
|
```
|
|
|
|
|
PATCH /api/categories/:categoryId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Body: (any subset of fields)
|
2026-01-19 23:17:07 +04:00
|
|
|
{
|
|
|
|
|
"name": "Updated Name",
|
|
|
|
|
"visible": false,
|
|
|
|
|
"priority": 5
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200: (updated category object)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Delete Category
|
|
|
|
|
```
|
|
|
|
|
DELETE /api/categories/:categoryId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 204 No Content
|
|
|
|
|
|
|
|
|
|
Note: Cascades and deletes all subcategories and items under this category
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
---
|
|
|
|
|
|
2026-01-19 23:17:07 +04:00
|
|
|
## Subcategories
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
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
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"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)
|
|
|
|
|
}
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
> `categoryId` is always the **root-level category** this subtree belongs to.
|
|
|
|
|
> `parentId` is the **direct parent** - it can be either a category ID or a subcategory ID.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### Get Subcategories Under a Category
|
|
|
|
|
```
|
|
|
|
|
GET /api/categories/:categoryId/subcategories
|
2026-01-22 00:41:13 +04:00
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200: Subcategory[] (nested subcategories populated recursively)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Get Single Subcategory
|
|
|
|
|
```
|
|
|
|
|
GET /api/subcategories/:subcategoryId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200: Subcategory object (with nested subcategories if any)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
### Create Subcategory Under a Category (level 1)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
POST /api/categories/:categoryId/subcategories
|
|
|
|
|
|
|
|
|
|
Body:
|
|
|
|
|
{
|
2026-02-20 00:59:46 +04:00
|
|
|
"id": "custom-id", // optional, auto-generated if omitted (used in URL routing)
|
|
|
|
|
"name": "Smartphones", // required
|
2026-01-19 23:17:07 +04:00
|
|
|
"visible": true,
|
|
|
|
|
"priority": 10
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 201: (created subcategory object)
|
|
|
|
|
Error 400: if category does not exist
|
|
|
|
|
```
|
2026-01-22 00:41:13 +04:00
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
### 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
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Update Subcategory
|
|
|
|
|
```
|
|
|
|
|
PATCH /api/subcategories/:subcategoryId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Body: (any subset of fields)
|
2026-01-19 23:17:07 +04:00
|
|
|
{
|
2026-02-20 00:59:46 +04:00
|
|
|
"id": "new-slug", // ID is editable - used for marketplace URL routing
|
2026-01-19 23:17:07 +04:00
|
|
|
"name": "Updated Name",
|
2026-02-20 00:59:46 +04:00
|
|
|
"visible": false,
|
|
|
|
|
"priority": 3
|
2026-01-19 23:17:07 +04:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200: (updated subcategory object)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Delete Subcategory
|
|
|
|
|
```
|
|
|
|
|
DELETE /api/subcategories/:subcategoryId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 204 No Content
|
|
|
|
|
|
|
|
|
|
Note: Cascades and deletes all nested subcategories and items under this subcategory
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
---
|
|
|
|
|
|
2026-01-19 23:17:07 +04:00
|
|
|
## Items
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
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.
|
|
|
|
|
|
2026-01-19 23:17:07 +04:00
|
|
|
### Get Items (Paginated)
|
|
|
|
|
```
|
2026-02-20 00:59:46 +04:00
|
|
|
GET /api/subcategories/:subcategoryId/items
|
2026-01-19 23:17:07 +04:00
|
|
|
|
|
|
|
|
Query Params:
|
2026-02-20 00:59:46 +04:00
|
|
|
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
|
2026-01-19 23:17:07 +04:00
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200:
|
2026-01-19 23:17:07 +04:00
|
|
|
{
|
|
|
|
|
"items": [
|
|
|
|
|
{
|
|
|
|
|
"id": "item1",
|
|
|
|
|
"name": "iPhone 15 Pro",
|
|
|
|
|
"visible": true,
|
|
|
|
|
"priority": 1,
|
|
|
|
|
"quantity": 50,
|
|
|
|
|
"price": 1299,
|
|
|
|
|
"currency": "USD",
|
|
|
|
|
"imgs": ["https://...", "https://..."],
|
|
|
|
|
"tags": ["new", "featured"],
|
|
|
|
|
"simpleDescription": "Latest iPhone...",
|
|
|
|
|
"description": [
|
|
|
|
|
{ "key": "Color", "value": "Black" },
|
|
|
|
|
{ "key": "Storage", "value": "256GB" }
|
|
|
|
|
],
|
|
|
|
|
"subcategoryId": "sub1",
|
2026-01-22 00:41:13 +04:00
|
|
|
"comments": [
|
|
|
|
|
{
|
|
|
|
|
"id": "c1",
|
|
|
|
|
"text": "Great product!",
|
|
|
|
|
"author": "John Doe",
|
|
|
|
|
"stars": 5,
|
|
|
|
|
"createdAt": "2024-01-10T10:30:00Z"
|
|
|
|
|
}
|
|
|
|
|
]
|
2026-01-19 23:17:07 +04:00
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"total": 150,
|
|
|
|
|
"page": 1,
|
|
|
|
|
"limit": 20,
|
|
|
|
|
"hasMore": true
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Get Single Item
|
|
|
|
|
```
|
|
|
|
|
GET /api/items/:itemId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200: (full item object)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Create Item
|
|
|
|
|
```
|
|
|
|
|
POST /api/subcategories/:subcategoryId/items
|
|
|
|
|
|
|
|
|
|
Body:
|
|
|
|
|
{
|
2026-02-20 00:59:46 +04:00
|
|
|
"name": "New Product", // required
|
2026-01-19 23:17:07 +04:00
|
|
|
"visible": true,
|
|
|
|
|
"priority": 10,
|
|
|
|
|
"quantity": 100,
|
|
|
|
|
"price": 999,
|
2026-02-20 00:59:46 +04:00
|
|
|
"currency": "USD", // USD | EUR | RUB | GBP | UAH
|
2026-01-19 23:17:07 +04:00
|
|
|
"imgs": ["https://..."],
|
|
|
|
|
"tags": ["new"],
|
2026-02-20 00:59:46 +04:00
|
|
|
"simpleDescription": "Short description",
|
2026-01-19 23:17:07 +04:00
|
|
|
"description": [
|
|
|
|
|
{ "key": "Size", "value": "Large" }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 201: (created item object)
|
|
|
|
|
|
|
|
|
|
Side effect: Sets hasItems = true on the subcategory.
|
|
|
|
|
The subcategory can no longer receive child subcategories.
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Update Item
|
|
|
|
|
```
|
|
|
|
|
PATCH /api/items/:itemId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Body: (any subset of fields)
|
2026-01-19 23:17:07 +04:00
|
|
|
{
|
|
|
|
|
"name": "Updated Name",
|
|
|
|
|
"price": 899,
|
|
|
|
|
"quantity": 80,
|
|
|
|
|
"visible": false
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200: (updated item object)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
**Updating images - always send the full array:**
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
2026-02-20 00:59:46 +04:00
|
|
|
PATCH /api/items/:itemId
|
2026-01-19 23:17:07 +04:00
|
|
|
|
|
|
|
|
Body:
|
|
|
|
|
{
|
2026-02-20 00:59:46 +04:00
|
|
|
"imgs": ["https://new1.jpg", "https://new2.jpg"]
|
2026-01-19 23:17:07 +04:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 200: (updated item object)
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Delete Item
|
|
|
|
|
```
|
|
|
|
|
DELETE /api/items/:itemId
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 204 No Content
|
|
|
|
|
|
|
|
|
|
Side effect: If no items remain in the subcategory, sets hasItems = false.
|
|
|
|
|
The subcategory can then receive child subcategories again.
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Bulk Update Items
|
|
|
|
|
```
|
|
|
|
|
PATCH /api/items/bulk
|
|
|
|
|
|
|
|
|
|
Body:
|
|
|
|
|
{
|
|
|
|
|
"itemIds": ["item1", "item2", "item3"],
|
|
|
|
|
"data": {
|
|
|
|
|
"visible": true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 204 No Content
|
2026-01-19 23:17:07 +04:00
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
---
|
|
|
|
|
|
2026-01-19 23:17:07 +04:00
|
|
|
## Upload
|
|
|
|
|
|
|
|
|
|
### Upload Image
|
|
|
|
|
```
|
|
|
|
|
POST /api/upload
|
|
|
|
|
|
|
|
|
|
Body: multipart/form-data
|
2026-02-20 00:59:46 +04:00
|
|
|
image: File
|
2026-01-19 23:17:07 +04:00
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
Response 201:
|
2026-01-19 23:17:07 +04:00
|
|
|
{
|
2026-02-20 00:59:46 +04:00
|
|
|
"url": "https://cdn.example.com/uploads/abc123.jpg"
|
2026-01-19 23:17:07 +04:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
---
|
|
|
|
|
|
2026-01-19 23:17:07 +04:00
|
|
|
## Notes
|
|
|
|
|
|
2026-02-20 00:59:46 +04:00
|
|
|
- All responses are JSON.
|
|
|
|
|
- Use `PATCH` for partial updates - send only the fields you want to change.
|
|
|
|
|
- `priority`: lower number = appears first in the list.
|
|
|
|
|
- `currency` supported values: `USD`, `EUR`, `RUB`, `GBP`, `UAH`.
|
|
|
|
|
- `imgs`: always send the **complete** array on update, not individual images.
|
|
|
|
|
- `description`: array of `{ key, value }` pairs - free-form attributes per item.
|
|
|
|
|
- Auto-save from the backoffice fires `PATCH` with 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/subcategories` on a node with `hasItems: 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 -> `hasItems` becomes `true`.
|
|
|
|
|
- When the **last item** is deleted -> `hasItems` becomes `false`; 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
|
|
|
|
|
|
|
|
|
|
- `stars` is optional, integer 1-5.
|
|
|
|
|
- `author` is optional (anonymous comments allowed).
|
|
|
|
|
- `createdAt` is 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 |
|