Files
market-backOfficce/API.md
2026-02-20 09:01:02 +04:00

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)
}

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

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 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.
  • 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 contain name, 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 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

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:

  1. Return the default top-level fields as-is.
  2. Merge the matching translations[lang] values into the response, overwriting the default fields.
  3. 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.