{
  "info": {
    "_postman_id": "3b9d7c52-1e4f-4a86-9c20-7f5e1d3a8b64",
    "name": "PosFix — Public Integration API",
    "description": "The API through which a merchant's external systems (online shop, ERP, accounting) read and write the merchant's data: catalogue, stock, prices, customers, orders, sales (Z reports and all sales by day and channel), invoice recognition, the loyalty programme and events via webhooks.\n\nFor the API on the cash register (fiscal receipts, cash, printed X/Z reports) there is the separate **PosFix — Terminal API** collection.\n\n## The key\n\nThe **“Integration API”** module is a paid service: PosFix enables it for the merchant and issues the key. The key (`rk_…`) is shown **only once**; PosFix keeps only its fingerprint, so a lost key is replaced, not recovered. After replacement, the old key keeps working for 24 hours.\n\nSent with every request:\n\n```\nX-Posfix-Api-Key: rk_…\n```\n\nThe merchant is taken **from the key**. There is no merchant parameter, so a key cannot read anyone else's data. A key can be limited to certain cash registers: then it sees only their sales.\n\nStart with `GET /api/public/v1/ping`: it confirms the key and shows you its scopes.\n\n## Scopes\n\nEach key gets only the permissions chosen when it was issued. The scope a request needs is written at the start of its description.\n\n| Scope | What it allows |\n|---|---|\n| `products:read` | Catalog, categories, attributes, content blocks, price lists, prices. |\n| `inventory:read` | A product's stock. |\n| `customers:read` / `customers:write` | Read / create customers. |\n| `orders:read` / `orders:write` | Read / create orders, set the delivery details. |\n| `sales:read` | Cash registers, Z reports, receipts. |\n| `docai:catalog` | Your own Nomenclature for invoice recognition. |\n| `docai:recognize` | Send invoices (PDF, photos) for recognition — paid. |\n| `docai:read` | Read recognized invoices and confirm you have picked them up. |\n| `loyalty:read` | The loyalty programmes, the members with their balance, the card history. Also needs the “Loyalty Programs” module. |\n| `loyalty:write` | Enrols members, changes their name or email. Also needs the “Loyalty Programs” module. |\n| `loyalty:earn` | Accrues on paid orders and reverses on a return. Also needs the “Loyalty Programs” module. |\n| `webhooks:manage` | Event subscriptions. |\n\nWithout the required scope, or with the “Integration API” module inactive, the response is `403`, **without a body**. The `loyalty:*` scopes also need the “Loyalty Programs” module active for the merchant. “Ping” needs neither a scope nor a module: if it works and a request gives `403`, the cause is a missing scope or an inactive module.\n\n## Limits\n\n- Per key: a reserve of **120 requests**, refilled at **2 per second** (~120 per minute at a steady pace).\n- Over the limit: `429` with `errorCode: RATE_LIMITED`, **without** a `Retry-After` header. Retry after a few seconds, with increasing pauses.\n- A second limit, **300 requests per minute**, is counted per IP address. Today the service sees only the gateway's address, so this limit is shared by all integrators: a `429` can come even when your key is under its limit.\n- The limit is checked before the key: a `429` can come even for a request with a wrong key.\n\n## Errors\n\nError responses come in several shapes. **Decide by the HTTP status**; `errorCode` and `errors` help where present, and `detail` is text for humans and may change.\n\n| HTTP | Body | When |\n|---|---|---|\n| `401`, `403` | **no body** | Missing or invalid key; missing scope or inactive module. The two `403` cases look the same: check them with “Ping”. |\n| `404` | no body, or simple format | The resource does not exist (or belongs to another merchant). |\n| `400` | `{\"statusCode\":400,\"message\":\"…\",\"errors\":{\"câmp\":[\"…\"]},\"errorCodes\":{\"câmp\":[{\"code\":\"COD\",\"params\":{…}}]}}` | A parameter or JSON of the wrong type, an invalid GUID, parameter validation (binding format). |\n| `400`, `404`, `409` | `{\"status\":…,\"title\":\"…\",\"detail\":\"…\",\"errorCode\":\"COD\",\"params\":{…}}` | Business-rule rejection with a message in `detail` (simple format). |\n| `400`, `422`, `5xx` | `{\"type\":…,\"title\":\"COD\",\"status\":…,\"detail\":\"…\",\"errorCode\":\"COD\",\"params\":{…},\"errors\":{…},\"errorCodes\":{…}}` | `VALIDATION` (with `errors` and `errorCodes` per field), a business rule's rejection with its own code (`422`, for example insufficient stock or a changed price), `SERVICE_UNAVAILABLE` (503), `INTERNAL_ERROR` (500). |\n| `429` | `{\"status\":429,\"title\":\"Too Many Requests\",\"detail\":\"…\",\"errorCode\":\"RATE_LIMITED\"}` | Over the limit. |\n\nA `503` means an internal service is not responding: retry. A `401`/`403` can also appear when the service that checks the key or the module is down, without anything being wrong on your side.\n\n## Conventions\n\n- JSON with field names in **camelCase**. Fields with no value come as `null`; they aren't omitted — except those the description says are absent (stock `reserved` / `available`, the new webhook fields).\n- Calendar dates have the form `yyyy-MM-dd`; timestamps are ISO 8601 UTC, with `Z`.\n- Amounts are JSON numbers, in lei unless stated otherwise.\n- **VAT:** `price` in the catalog is the till price, **including VAT**. In an order, `lines[].unitPrice` is treated as **excluding VAT** and VAT is added on top. Don't copy the catalog price directly into the order: send `price / (1 + vatRate/100)` with 4 decimals, or don't send the price at all and PosFix takes it from the till.\n- The version is in the path (`/api/public/v1/`). In v1 only new fields and events are added; existing ones do not change: **ignore fields you don't recognize**.\n- A list parameter is repeated (`?categoryCode=A&categoryCode=B`); boolean values are written as `true` / `false`.\n\n## Pagination\n\n- **With a cursor** — products, customers, loyalty members and history: you ask for `limit`, you get `nextCursor`; you request the next page with `cursor=<nextCursor>`, until it comes back `null`.\n- **Page-based** — orders, Z reports, receipts: `page` (from 1) and `pageSize`; the response includes the total.\n\n## Webhooks: how you receive events\n\nPosFix sends a JSON `POST` to your URL when something happens at the merchant. You subscribe with “Create webhook” and choose the events.\n\n| Event | When | `data` |\n|---|---|---|\n| `product.created` | New product in the Nomenclature. | `posfixId`, `code`, `name`, `description`, `barcode`, `price` (the default price from the Nomenclature, not the till price), `currency`, `vatRate`, `isService` |\n| `product.updated` | Product changed. | `posfixId`, `code`, `name`, `description`, `currency`, `vatRate`, `isActive` (no price or barcode), `occurredAt` (the moment of the change); `nameTranslations`, `descriptionTranslations` — translations into the active languages, key = language code, without the main language (that is `name` / `description`); an untranslated language is absent |\n| `inventory.updated` | A product's stock or reserved quantity changed in a warehouse; it may come with `quantity` unchanged. `available` is absent for dishes with a recipe and for service positions; a service position without a recipe does not send `inventory.updated`. | `posfixId`, `warehouseId`, `quantity` (goods in the warehouse), `value` and `averageCost` (the position's value and average cost at the moment `changedAt`; a cost recalculation also sends `inventory.updated`, with `quantity` unchanged), `changedAt`, `reserved` (promised to confirmed orders), `available` (`quantity − reserved`, what can still be sold; negative when more was reserved than is in stock) |\n| `order.created` | New order (including from the Control Panel or from the till). | `id`, `number`, `status`, `changeType`, `total`, `paidSum`, `currency`, `occurredAt` (the moment of the change); on an order that came via the API also `externalOrderId`, `externalNumber`, `externalSourceChannel`; after preparation `readyForPickupAt`, after hand-over to the carrier `shippedAt` and `trackingNumber` |\n| `order.confirmed` | The merchant confirmed the order. | same |\n| `order.ready_for_pickup` | The pickup order is ready: the buyer can come for it. The status stays `Confirmed`. | the same; `readyForPickupAt` = the moment of preparation |\n| `order.shipped` | The goods were handed over to the carrier (Nova Post or another courier); it comes again if the shipment number is corrected. The status stays `Confirmed` until delivery. If the hand-over is withdrawn, `order.confirmed` (or `order.fulfilling`) comes without `shippedAt`. | the same; `shippedAt` = the moment of hand-over, `trackingNumber` = the shipment number (TTN, AWB), when there is one |\n| `order.preparation_started` | Someone took the order into work (“Iau în lucru”): the goods are being prepared. The status stays `Confirmed`. | same |\n| `order.fulfilling` | Partially delivered. | same |\n| `order.fulfilled` | Fully delivered. | same |\n| `order.closed` | Closed manually. | same |\n| `order.cancelled` | Cancelled. | same, plus `reason` when the system cancelled it: `payment_deadline` = not paid at all by the payment deadline; `stock_refused` = the API order was refused on arrival for insufficient stock (its request received `422`); `confirmation_failed` = the order's confirmation failed for a reason other than stock (from the API, or from the till, for example when the cashier gives up); no `reason` = cancelled by the merchant |\n| `order.reopened` | A cancelled order was returned to draft. | same |\n| `order.partially_paid` | The paid amount increased but doesn't cover the total. | same; `paidSum` in lei |\n| `order.paid` | The order is fully paid. | same; `paidSum` in lei |\n| `docai.recognition.completed` | An invoice was recognized (sent via the API or uploaded in the Control Panel). | `id`, `status`, `source`, `externalRef`, `fileName`, `supplierName`, `supplierIdno`, `documentSeries`, `documentNumber`, `issueDate`, `total`, `currency`; full data — `GET /api/public/v1/docai/recognitions/{id}` |\n| `docai.recognition.failed` | Recognition failed (unreadable file, invoice too long, interruption). | the same, plus `errorCode` |\n| `docai.recognition.reviewed` | A person reviewed and corrected the invoice in the Control Panel. | the same; the reviewed data — by id |\n| `sale.created` | A new sale: receipt, till return, invoice, customer return or retail report. | `id`, `kind` (`receipt`, `refund`, `invoice`, `customer_return`, `retail_report`), `number`, `date`, `soldAt`, `channel` (`id`, `name`, `type`), `customer` (`kind`: `individual`/`company`, `partnerId`, `name`), `terminalId`, `netAmount`, `vatAmount`, `totalAmount`, `currency`, `lines[]` (`productId`, `productCode`, `name`, `quantity`, `netAmount`, `vatAmount`); amounts in MDL, without VAT, returns negative |\n| `sale.updated` | What a person sees on the sale changed: the amount, the channel, the customer or the lines. | same, with the new values |\n| `sale.deleted` | The sale left the records (the invoice was cancelled, for example). | same, with the last values |\n| `loyalty.member.enrolled` | A new member in the loyalty programme, enrolled by any route: via the API, in the Control Panel, at the till or on the enrolment page. | the current member, with the fields from `GET /api/public/v1/loyalty/members/{cardNumber}`, without `wallet`; if the member has been deleted in the meantime, only `cardNumber: null` |\n| `loyalty.balance.changed` | The member's balance changed: points, stamps, bonus in lei or tier (an earning, a spending, a return, an expiry). | the same; `balance` and `bonus` are those at the moment of delivery, not at the moment of the change |\n\nThe body has the same envelope for every event:\n\n```json\n{ \"id\": \"7c1e9a52-3b4d-4f6e-8a90-1b2c3d4e5f60\", \"type\": \"order.paid\", \"createdAt\": \"2026-09-25T08:12:03.4567891Z\", \"version\": \"1\",\n  \"data\": { \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\", \"number\": \"0000000042\", \"status\": \"Confirmed\", \"changeType\": \"paid\", \"total\": 745.8, \"paidSum\": 745.8, \"currency\": \"MDL\", \"occurredAt\": \"2026-09-25T08:12:02.9180344Z\" } }\n```\n\n- `id` is the delivery id: the same on every retry. **Deduplicate on it** — a delivery can arrive twice.\n- `createdAt` is the time it was queued, not the time of the event. Arrival order is not guaranteed.\n- Deliveries may arrive out of order and may be retried for hours (see “Retries”): apply a change only if its moment — `changedAt` for stock, `occurredAt` for products and orders — is newer than the one you already have.\n- A cost recalculation sends `inventory.updated` for every revalued position, and in a cascade the same position may receive two events, an intermediate one and then the final state: for the same position, the event with the newest `changedAt` is the right one.\n- For orders, `paidSum` comes on every `order.*`, in lei, as PosFix holds it on the order at the moment of the event (it is recalculated after each payment, so it may lag slightly): apply it only when `occurredAt` is newer than that of the last event you applied; the current amount is given by `GET /api/public/v1/orders/{id}`.\n- An order that came through the API carries `externalOrderId` (your key), `externalNumber` and `externalSourceChannel`. **Match orders by `id`**; `externalOrderId` is only for an `id` you do not know yet. An order refused on arrival comes as an `order.created` → `order.cancelled` pair (`reason` `stock_refused` or `confirmation_failed`) with the same `id`, even though your request received `422` without an `id`. If you then resend the same `externalOrderId`, the new order has another `id` (the one in the `200`/`201` response): a late `order.cancelled` of the refused attempt, with an `id` other than the one you received, is ignored — otherwise you would cancel your live order.\n- A cancelled payment lowers the paid amount without an event: read the current amount from `GET /api/public/v1/orders/{id}`.\n- `reserved`, `available`, `nameTranslations`, `descriptionTranslations`, `reason` and the `external*` fields are **absent** from `data` when the event does not have them, instead of coming as `null`: check whether they exist, not only whether they are `null`.\n\n### Headers\n\n| Header | Value |\n|---|---|\n| `X-Posfix-Signature` | Signature, base64 |\n| `X-Posfix-Webhook-Id` | Delivery ID (= `id` in the body) |\n| `X-Posfix-Timestamp` | Time of sending, Unix seconds |\n| `X-Posfix-Event` | Event type |\n\n### Verifying the signature\n\n```\nsignedString       = \"{X-Posfix-Webhook-Id}.{X-Posfix-Timestamp}.{corpul brut}\"\nX-Posfix-Signature = base64( HMAC_SHA256(signingSecret, signedString) )\n```\n\n- The HMAC key is the **whole** secret, including the `whsec_` prefix, as UTF-8 bytes — it isn't decoded.\n- Sign the **raw body**, exactly as it arrived. JSON that has been parsed and serialized again no longer gives the same signature.\n- Compare in constant time and reject deliveries with an `X-Posfix-Timestamp` older than ~5 minutes.\n\n```js\nconst crypto = require(\"crypto\");\n// rawBody: Buffer, citit cu express.raw({ type: \"application/json\" })\nconst signed = Buffer.concat([Buffer.from(`${webhookId}.${timestamp}.`, \"utf8\"), rawBody]);\nconst expected = crypto.createHmac(\"sha256\", signingSecret).update(signed).digest(\"base64\");\nconst valid = expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));\n```\n\n### Retries\n\n- Any **2xx** response within 15 seconds counts as received.\n- `5xx`, `408`, `429`, redirects and network failures are retried after 2, 4, 8, 16, 32, 60, 60 minutes. After 8 attempts (~3 hours) the delivery is abandoned.\n- Any other `4xx` stops the delivery **immediately**, without a retry. So does a domain name that doesn't resolve or that resolves to a private address.\n- Redirects aren't followed.\n- While the “Integration API” module is inactive, events are neither sent nor kept.\n\n## Collection variables\n\n- `baseUrl` — `https://api.posfix.md` (production) or `https://dev.posfix.md` (testing).\n- `apiKey` — the key you received from PosFix.\n- `today`, `monthStart` — calculated before each request.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_anchorHeadings": [
      "Cheia",
      "Scope-uri",
      "Limite",
      "Erori",
      "Convenții",
      "Paginare",
      "Webhook-uri: cum primești evenimentele",
      "Variabilele colecției"
    ]
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-Posfix-Api-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const pad = (n) => String(n).padStart(2, '0');",
          "const fmt = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;",
          "const now = new Date();",
          "pm.collectionVariables.set('today', fmt(now));",
          "pm.collectionVariables.set('monthStart', fmt(now).slice(0, 8) + '01');"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.posfix.md",
      "description": "Production. For testing: https://dev.posfix.md"
    },
    {
      "key": "apiKey",
      "value": "",
      "description": "The rk_… key you received from PosFix together with the “Integration API” module"
    },
    {
      "key": "today",
      "value": "",
      "description": "Calculated automatically (YYYY-MM-DD)"
    },
    {
      "key": "monthStart",
      "value": "",
      "description": "Calculated automatically (first day of the month)"
    }
  ],
  "item": [
    {
      "name": "0. Check",
      "description": "Start here: confirm that the key works and which scopes it has.",
      "item": [
        {
          "name": "Ping",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/ping",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "ping"
              ]
            },
            "description": "Checks the key. Returns the key's merchant and its scopes. Start here.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `organizationId` | string (uuid) | The merchant the key belongs to. |\n| `scopes` | string[] | The key's scopes. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing or invalid. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n\n### Keep in mind\n\n- Ping requires neither a scope nor the “Integration API” module. If ping works and another request gives `403`, the key lacks that request's scope or the module isn't active."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/ping",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "ping"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"organizationId\": \"3f6c2a1e-8b4d-4c7a-9e21-5d0f7b8a9c12\",\n  \"scopes\": [\n    \"products:read\",\n    \"inventory:read\",\n    \"orders:write\",\n    \"sales:read\"\n  ]\n}"
            }
          ],
          "_anchorName": "Ping"
        }
      ],
      "_anchorName": "0. Verificare"
    },
    {
      "name": "1. Catalog and stock",
      "description": "The sales assortment, product records, stock, categories and attributes.",
      "item": [
        {
          "name": "Product list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, default 50."
                },
                {
                  "key": "search",
                  "value": "apa",
                  "description": "Words from the code or name.",
                  "disabled": true
                },
                {
                  "key": "categoryCode",
                  "value": "BAUTURI",
                  "description": "Category code; repeatable.",
                  "disabled": true
                },
                {
                  "key": "categoryId",
                  "value": "",
                  "description": "Category id; repeatable.",
                  "disabled": true
                },
                {
                  "key": "includeSubcategories",
                  "value": "true",
                  "description": "Default true.",
                  "disabled": true
                },
                {
                  "key": "attribute",
                  "value": "tara:Moldova",
                  "description": "code:value; repeatable.",
                  "disabled": true
                },
                {
                  "key": "includeInactive",
                  "value": "false",
                  "description": "Default false.",
                  "disabled": true
                },
                {
                  "key": "includeUnlisted",
                  "value": "false",
                  "description": "Default false.",
                  "disabled": true
                },
                {
                  "key": "warehouseId",
                  "value": "",
                  "description": "Stock of a single warehouse; all by default.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor from the previous page.",
                  "disabled": true
                }
              ]
            },
            "description": "**Required scope:** `products:read`\n\nThe merchant's sales assortment, page by page: the products that have a card at the till, with price, category and stock.\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `limit` | integer | 50 | Values above 200 are capped at 200. |\n| `search` | string | — | Words from the **code or name**, in any order: every word must appear. Not case- or diacritic-sensitive. Does not search the barcode. |\n| `categoryId`, `categoryCode` | uuid / string | — | Till categories; repeatable, combined with OR: `?categoryCode=APA&categoryCode=SUCURI`. |\n| `includeSubcategories` | boolean | `true` | Includes the subcategories of the requested ones. |\n| `attribute` | string | — | `cod:valoare`, repeatable. Same code = OR, different codes = AND: `?attribute=brand:Samyang&attribute=tara:Moldova`. The code is the one from “Attributes”, in lowercase. |\n| `includeInactive` | boolean | `false` | Includes inactive products and cards. |\n| `includeUnlisted` | boolean | `false` | Includes items **without** a till card (materials, internal services). Ignored when you filter by category. |\n| `warehouseId` | string (uuid) | all | Stock (`onHand`, `onHandByWarehouse`, `inStock`) is taken from this warehouse only. The id comes from “Warehouses”. It does not narrow the product list. |\n| `cursor` | string | — | The `nextCursor` value from the previous page. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[]` | array | The products; the fields are in the table below. `composition` and `contentBlocks` come empty in the list. |\n| `nextCursor` | string? | Cursor for the next page; `null` on the last one. |\n\n### The product\n\n| Field | Type | Description |\n|---|---|---|\n| `posfixId` | string (uuid) | The product's id in PosFix. Use it to request prices. |\n| `code` | string | Product code (usually 10 digits). You use it to order and to read stock. |\n| `name` | string | The name from the Nomenclature. |\n| `names` | object | The name per language; the language-map rule is in the request notes. |\n| `description` | string? | Description in Romanian. |\n| `barcode` | string | Barcode; `\"\"` if missing. |\n| `price`, `currency` | number, string | The selling price at the till (VAT included); without a till card, the default price from the Nomenclature. |\n| `vatRate` | number? | The VAT rate, %. `null` = rate not set, which is not the same as `0`. |\n| `unit` | string | The base unit (`buc`, `kg`). |\n| `isActive` | boolean | The product is active in the Nomenclature. |\n| `category`, `categoryPath[]` | object?, array | The till category (`id`, `name`, `code`, `names`) and its path from the root. |\n| `imageUrl` | string? | The **relative** URL of the image; you read it with the same key (the “Product image” request). |\n| `attributes[]` | array | `code` and `value` — the attribute values (brand, country…); `valueNames` = the value per language. |\n| `onHand[]` | array | Total stock, a single element with `warehouseId: null`: `quantity`, `reserved`, `available` (their meaning is in the request notes). With `?warehouseId=` on the list, the stock of that warehouse, with its id. |\n| `onHandByWarehouse[]` | array | Stock in each warehouse that holds the goods: `warehouseId`, `code`, `name`, `quantity`, `reserved`, `available`. A missing warehouse has 0. |\n| `inStock` | boolean | Total physical stock is above zero, even if all of it is reserved. Whether it can still be bought is shown by `onHand[].available`. |\n| `descriptions` | object | The description per language; the language-map rule is in the request notes. |\n| `composition[]` | array | Composition: `ingredient`, `percent`, `allergen`. Only in “Product by code”. |\n| `contentBlocks[]` | array | The content blocks (sanitized HTML): `labels` and `contents` per language, plus the original fields `labelRo`… `contentEn`. Only on “Product by code”. |\n| `sku` | string? | The product's external code, otherwise the till SKU. |\n| `shelfLifeDays` | integer? | Shelf life, days. |\n| `packaging[]` | array | `unit` and `factor`: “1 `unit` = `factor` base units”. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | — | An `attribute` filter not in the `cod:valoare` form, a value of the wrong type (`tree=1`, invalid GUID). |\n| 400 | `VALIDATION` | “cursor must be a valid product id.” |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The language maps (`names`, `descriptions`, `valueNames`, `labels`, `contents`) are keyed by language code: `{ \"ro\": …, \"ru\": … }`. The languages come from the platform's list and can grow at any time — read the keys you receive, do not assume exactly `ro`/`ru`/`en`. A language without a translation is absent from the map: use the base text (`name`, `description`).\n- Stock has three figures: `quantity` is the goods in the warehouse; `reserved` is the part promised to confirmed orders that have not left yet; `available` = `quantity − reserved` is what can still be sold. Show `available` in your shop.\n- `available` is absent when there is no available quantity to publish (dishes with a recipe, service positions) or when it is unknown; an absent `available` does not mean sold out. Do not calculate it from `quantity` and do not replace it with `0`. A product without stock — and, with `?warehouseId=`, without stock in the requested warehouse — has `quantity`, `reserved` and `available` 0. An absent `reserved` means the value is unknown.\n- The order is by id, not by name. Go through the pages until `nextCursor: null`.\n- The list is cached for **45 seconds**, so the stock in it may lag behind. For up-to-date stock, use “Product stock”.\n- A product without a till card appears only with `includeUnlisted=true`.\n- Lists are sent by repeating the parameter (`?a=x&a=y`), not comma-separated. A boolean is written `true`/`false`, not `1`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    },
                    {
                      "key": "search",
                      "value": "apa",
                      "description": "Cuvinte din cod sau denumire.",
                      "disabled": true
                    },
                    {
                      "key": "categoryCode",
                      "value": "BAUTURI",
                      "description": "Codul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "categoryId",
                      "value": "",
                      "description": "Id-ul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeSubcategories",
                      "value": "true",
                      "description": "Implicit true.",
                      "disabled": true
                    },
                    {
                      "key": "attribute",
                      "value": "tara:Moldova",
                      "description": "cod:valoare; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeInactive",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "includeUnlisted",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "warehouseId",
                      "value": "",
                      "description": "Stocul unui singur depozit; implicit toate.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n      \"code\": \"0000000123\",\n      \"name\": \"Apă minerală Borjomi 0,5 L\",\n      \"description\": \"Apă minerală naturală carbogazoasă.\",\n      \"barcode\": \"4860019001339\",\n      \"price\": 18.5,\n      \"currency\": \"MDL\",\n      \"vatRate\": 20,\n      \"unit\": \"buc\",\n      \"isActive\": true,\n      \"category\": {\n        \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n        \"name\": \"Apă minerală\",\n        \"code\": \"APA\",\n        \"names\": {\n          \"ro\": \"Apă minerală\",\n          \"ru\": \"Минеральная вода\"\n        }\n      },\n      \"imageUrl\": \"/api/public/v1/products/0000000123/image\",\n      \"attributes\": [\n        {\n          \"code\": \"tara\",\n          \"value\": \"Georgia\",\n          \"valueNames\": {\n            \"ro\": \"Georgia\",\n            \"ru\": \"Грузия\"\n          }\n        },\n        {\n          \"code\": \"brand\",\n          \"value\": \"Borjomi\",\n          \"valueNames\": {\n            \"ro\": \"Borjomi\"\n          }\n        }\n      ],\n      \"composition\": [],\n      \"onHand\": [\n        {\n          \"warehouseId\": null,\n          \"quantity\": 144,\n          \"reserved\": 6,\n          \"available\": 138\n        }\n      ],\n      \"onHandByWarehouse\": [\n        {\n          \"warehouseId\": \"e4d3c2b1-0001-4f5e-8d7c-6b5a49382701\",\n          \"code\": \"DEP-1\",\n          \"name\": \"Depozit central\",\n          \"quantity\": 120,\n          \"reserved\": 6,\n          \"available\": 114\n        },\n        {\n          \"warehouseId\": \"e4d3c2b1-0002-4f5e-8d7c-6b5a49382702\",\n          \"code\": \"MAG-2\",\n          \"name\": \"Magazin Botanica\",\n          \"quantity\": 24,\n          \"reserved\": 0,\n          \"available\": 24\n        }\n      ],\n      \"names\": {\n        \"ro\": \"Apă minerală Borjomi 0,5 L\",\n        \"ru\": \"Минеральная вода Borjomi 0,5 л\"\n      },\n      \"descriptions\": {\n        \"ro\": \"Apă minerală naturală carbogazoasă.\",\n        \"ru\": \"Натуральная газированная минеральная вода.\"\n      },\n      \"contentBlocks\": [],\n      \"categoryPath\": [\n        {\n          \"id\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n          \"name\": \"Băuturi\",\n          \"code\": \"BAUTURI\",\n          \"names\": {\n            \"ro\": \"Băuturi\",\n            \"ru\": \"Напитки\"\n          }\n        },\n        {\n          \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n          \"name\": \"Apă minerală\",\n          \"code\": \"APA\",\n          \"names\": {\n            \"ro\": \"Apă minerală\",\n            \"ru\": \"Минеральная вода\"\n          }\n        }\n      ],\n      \"sku\": \"BRJ-05\",\n      \"shelfLifeDays\": 730,\n      \"packaging\": [\n        {\n          \"unit\": \"bax\",\n          \"factor\": 12\n        }\n      ],\n      \"inStock\": true\n    }\n  ],\n  \"nextCursor\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\"\n}"
            },
            {
              "name": "400 — invalid attribute filter",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    },
                    {
                      "key": "search",
                      "value": "apa",
                      "description": "Cuvinte din cod sau denumire.",
                      "disabled": true
                    },
                    {
                      "key": "categoryCode",
                      "value": "BAUTURI",
                      "description": "Codul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "categoryId",
                      "value": "",
                      "description": "Id-ul categoriei; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeSubcategories",
                      "value": "true",
                      "description": "Implicit true.",
                      "disabled": true
                    },
                    {
                      "key": "attribute",
                      "value": "tara:Moldova",
                      "description": "cod:valoare; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "includeInactive",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "includeUnlisted",
                      "value": "false",
                      "description": "Implicit false.",
                      "disabled": true
                    },
                    {
                      "key": "warehouseId",
                      "value": "",
                      "description": "Stocul unui singur depozit; implicit toate.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/problem+json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"attribute\": [\n      \"Filtrul de atribut „brand” trebuie să aibă forma cod:valoare (de ex. brand:Samyang).\"\n    ]\n  },\n  \"errorCodes\": {\n    \"attribute\": [\n      {\n        \"code\": \"INTEGRATIONS_ATTRIBUTE_FILTER_FORMAT\",\n        \"params\": {\n          \"filter\": \"brand\"\n        }\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Listă produse"
        },
        {
          "name": "Product by code",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products",
                ":code"
              ],
              "variable": [
                {
                  "key": "code",
                  "value": "0000000123",
                  "description": "Product code"
                }
              ]
            },
            "description": "**Required scope:** `products:read`\n\nA product's full record: everything the list gives, plus composition and content blocks per language. Also finds inactive products and those without a till card.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `code` | string | The exact product code (with leading zeros). |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `posfixId` | string (uuid) | The product's id in PosFix. Use it to request prices. |\n| `code` | string | Product code (usually 10 digits). You use it to order and to read stock. |\n| `name` | string | The name from the Nomenclature. |\n| `names` | object | The name per language; the language-map rule is in the request notes. |\n| `description` | string? | Description in Romanian. |\n| `barcode` | string | Barcode; `\"\"` if missing. |\n| `price`, `currency` | number, string | The selling price at the till (VAT included); without a till card, the default price from the Nomenclature. |\n| `vatRate` | number? | The VAT rate, %. `null` = rate not set, which is not the same as `0`. |\n| `unit` | string | The base unit (`buc`, `kg`). |\n| `isActive` | boolean | The product is active in the Nomenclature. |\n| `category`, `categoryPath[]` | object?, array | The till category (`id`, `name`, `code`, `names`) and its path from the root. |\n| `imageUrl` | string? | The **relative** URL of the image; you read it with the same key (the “Product image” request). |\n| `attributes[]` | array | `code` and `value` — the attribute values (brand, country…); `valueNames` = the value per language. |\n| `onHand[]` | array | Total stock, a single element with `warehouseId: null`: `quantity`, `reserved`, `available` (their meaning is in the request notes). With `?warehouseId=` on the list, the stock of that warehouse, with its id. |\n| `onHandByWarehouse[]` | array | Stock in each warehouse that holds the goods: `warehouseId`, `code`, `name`, `quantity`, `reserved`, `available`. A missing warehouse has 0. |\n| `inStock` | boolean | Total physical stock is above zero, even if all of it is reserved. Whether it can still be bought is shown by `onHand[].available`. |\n| `descriptions` | object | The description per language; the language-map rule is in the request notes. |\n| `composition[]` | array | Composition: `ingredient`, `percent`, `allergen`. Only in “Product by code”. |\n| `contentBlocks[]` | array | The content blocks (sanitized HTML): `labels` and `contents` per language, plus the original fields `labelRo`… `contentEn`. Only on “Product by code”. |\n| `sku` | string? | The product's external code, otherwise the till SKU. |\n| `shelfLifeDays` | integer? | Shelf life, days. |\n| `packaging[]` | array | `unit` and `factor`: “1 `unit` = `factor` base units”. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The code does not exist. Empty body. |\n| 400 | `INTEGRATIONS_PRODUCT_CODE_REQUIRED` | „Codul produsului (productCode) este obligatoriu.” (The product code (productCode) is required.) |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The language maps (`names`, `descriptions`, `valueNames`, `labels`, `contents`) are keyed by language code: `{ \"ro\": …, \"ru\": … }`. The languages come from the platform's list and can grow at any time — read the keys you receive, do not assume exactly `ro`/`ru`/`en`. A language without a translation is absent from the map: use the base text (`name`, `description`).\n- Stock has three figures: `quantity` is the goods in the warehouse; `reserved` is the part promised to confirmed orders that have not left yet; `available` = `quantity − reserved` is what can still be sold. Show `available` in your shop.\n- `available` is absent when there is no available quantity to publish (dishes with a recipe, service positions) or when it is unknown; an absent `available` does not mean sold out. Do not calculate it from `quantity` and do not replace it with `0`. A product without stock — and, with `?warehouseId=`, without stock in the requested warehouse — has `quantity`, `reserved` and `available` 0. An absent `reserved` means the value is unknown.\n- Content blocks come only for active products and only for active definitions (see “Content blocks”).\n- The block's original fields (`labelRu`, `contentEn`…) stay for existing integrations and will never carry a language added later; use `labels` and `contents`.\n- The response is cached for 45 seconds."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    ":code"
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "0000000123",
                      "description": "Codul produsului"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n  \"code\": \"0000000123\",\n  \"name\": \"Apă minerală Borjomi 0,5 L\",\n  \"description\": \"Apă minerală naturală carbogazoasă.\",\n  \"barcode\": \"4860019001339\",\n  \"price\": 18.5,\n  \"currency\": \"MDL\",\n  \"vatRate\": 20,\n  \"unit\": \"buc\",\n  \"isActive\": true,\n  \"category\": {\n    \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n    \"name\": \"Apă minerală\",\n    \"code\": \"APA\",\n    \"names\": {\n      \"ro\": \"Apă minerală\",\n      \"ru\": \"Минеральная вода\"\n    }\n  },\n  \"imageUrl\": \"/api/public/v1/products/0000000123/image\",\n  \"attributes\": [\n    {\n      \"code\": \"tara\",\n      \"value\": \"Georgia\",\n      \"valueNames\": {\n        \"ro\": \"Georgia\",\n        \"ru\": \"Грузия\"\n      }\n    },\n    {\n      \"code\": \"brand\",\n      \"value\": \"Borjomi\",\n      \"valueNames\": {\n        \"ro\": \"Borjomi\"\n      }\n    }\n  ],\n  \"composition\": [\n    {\n      \"ingredient\": \"Apă minerală naturală\",\n      \"percent\": null,\n      \"allergen\": false\n    },\n    {\n      \"ingredient\": \"Dioxid de carbon\",\n      \"percent\": null,\n      \"allergen\": false\n    }\n  ],\n  \"onHand\": [\n    {\n      \"warehouseId\": null,\n      \"quantity\": 144,\n      \"reserved\": 6,\n      \"available\": 138\n    }\n  ],\n  \"onHandByWarehouse\": [\n    {\n      \"warehouseId\": \"e4d3c2b1-0001-4f5e-8d7c-6b5a49382701\",\n      \"code\": \"DEP-1\",\n      \"name\": \"Depozit central\",\n      \"quantity\": 120,\n      \"reserved\": 6,\n      \"available\": 114\n    },\n    {\n      \"warehouseId\": \"e4d3c2b1-0002-4f5e-8d7c-6b5a49382702\",\n      \"code\": \"MAG-2\",\n      \"name\": \"Magazin Botanica\",\n      \"quantity\": 24,\n      \"reserved\": 0,\n      \"available\": 24\n    }\n  ],\n  \"names\": {\n    \"ro\": \"Apă minerală Borjomi 0,5 L\",\n    \"ru\": \"Минеральная вода Borjomi 0,5 л\"\n  },\n  \"descriptions\": {\n    \"ro\": \"Apă minerală naturală carbogazoasă.\",\n    \"ru\": \"Натуральная газированная минеральная вода.\"\n  },\n  \"contentBlocks\": [\n    {\n      \"key\": \"pastrare\",\n      \"labelRo\": \"Condiții de păstrare\",\n      \"labelRu\": \"Условия хранения\",\n      \"labelEn\": null,\n      \"contentRo\": \"<p>A se păstra la loc uscat, ferit de soare.</p>\",\n      \"contentRu\": null,\n      \"contentEn\": null,\n      \"sortOrder\": 1,\n      \"labels\": {\n        \"ro\": \"Condiții de păstrare\",\n        \"ru\": \"Условия хранения\"\n      },\n      \"contents\": {\n        \"ro\": \"<p>A se păstra la loc uscat, ferit de soare.</p>\"\n      }\n    }\n  ],\n  \"categoryPath\": [\n    {\n      \"id\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n      \"name\": \"Băuturi\",\n      \"code\": \"BAUTURI\",\n      \"names\": {\n        \"ro\": \"Băuturi\",\n        \"ru\": \"Напитки\"\n      }\n    },\n    {\n      \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n      \"name\": \"Apă minerală\",\n      \"code\": \"APA\",\n      \"names\": {\n        \"ro\": \"Apă minerală\",\n        \"ru\": \"Минеральная вода\"\n      }\n    }\n  ],\n  \"sku\": \"BRJ-05\",\n  \"shelfLifeDays\": 730,\n  \"packaging\": [\n    {\n      \"unit\": \"bax\",\n      \"factor\": 12\n    }\n  ],\n  \"inStock\": true\n}"
            }
          ],
          "_anchorName": "Produs după cod"
        },
        {
          "name": "Product image",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products/:code/image",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products",
                ":code",
                "image"
              ],
              "variable": [
                {
                  "key": "code",
                  "value": "0000000123",
                  "description": "Product code"
                }
              ]
            },
            "description": "**Required scope:** `products:read`\n\nThe product image from the nomenclature, as a file. Usually a JPEG thumbnail of at most 320×320.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `code` | string | Product code. |\n\n### Response\n\n**200** with the image bytes. `Content-Type` is the image type (`image/jpeg` for the thumbnail); `Cache-Control: private, max-age=300`.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The product does not exist, has no image, or the image is unavailable. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The request requires the key, so `imageUrl` won't work directly in an `<img src>` in your customer's browser. Download the image on your server and serve it yourself."
          },
          "response": [
            {
              "name": "404 — product has no image",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/:code/image",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    ":code",
                    "image"
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "0000000123",
                      "description": "Codul produsului"
                    }
                  ]
                }
              },
              "status": "Not Found",
              "code": 404,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            }
          ],
          "_anchorName": "Imaginea produsului"
        },
        {
          "name": "Product stock",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/inventory?productCode=0000000123",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "inventory"
              ],
              "query": [
                {
                  "key": "productCode",
                  "value": "0000000123",
                  "description": "Product code, required."
                },
                {
                  "key": "warehouseId",
                  "value": "",
                  "description": "One warehouse; without it, the total.",
                  "disabled": true
                }
              ]
            },
            "description": "**Required scope:** `inventory:read`\n\nThe current stock of a product, in one warehouse or across all of them. Bypasses the cache.\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `productCode` | string | — | Required. The exact product code. |\n| `warehouseId` | string (uuid) | all | The warehouse. An unknown warehouse gives quantity 0, not an error. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `posfixId`, `code`, `name` | string | The product. |\n| `onHand[]` | array | A single element: `warehouseId` (the requested one or `null` = total), `quantity`, `reserved` and `available`, in the base unit. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_PRODUCT_CODE_REQUIRED` | „Codul produsului (productCode) este obligatoriu.” (The product code (productCode) is required.) |\n| 404 | — | The code does not exist. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Stock has three figures: `quantity` is the goods in the warehouse; `reserved` is the part promised to confirmed orders that have not left yet; `available` = `quantity − reserved` is what can still be sold. Show `available` in your shop.\n- `available` is absent when there is no available quantity to publish (dishes with a recipe, service positions) or when it is unknown; an absent `available` does not mean sold out. Do not calculate it from `quantity` and do not replace it with `0`. A product without stock — and, with `?warehouseId=`, without stock in the requested warehouse — has `quantity`, `reserved` and `available` 0. An absent `reserved` means the value is unknown.\n- The quantity can be **negative**: goods were sold before they had been received.\n- `available` is also negative when more was reserved than is in the warehouse."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/inventory?productCode=0000000123",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "inventory"
                  ],
                  "query": [
                    {
                      "key": "productCode",
                      "value": "0000000123",
                      "description": "Codul produsului, obligatoriu."
                    },
                    {
                      "key": "warehouseId",
                      "value": "",
                      "description": "Un depozit; fără el, totalul.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n  \"code\": \"0000000123\",\n  \"name\": \"Apă minerală Borjomi 0,5 L\",\n  \"onHand\": [\n    {\n      \"warehouseId\": null,\n      \"quantity\": 144,\n      \"reserved\": 6,\n      \"available\": 138\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Stoc produs"
        },
        {
          "name": "Warehouses",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/warehouses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "warehouses"
              ]
            },
            "description": "**Required scope:** `inventory:read`\n\nThe merchant's active warehouses, the default one first. The id goes into `warehouseId` in “Product list” and “Product stock”.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].id` | string (uuid) | Warehouse id. |\n| `items[].code` | string | Warehouse code. |\n| `items[].name` | string | Warehouse name. |\n| `items[].isDefault` | boolean | The merchant's default warehouse. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The list is cached for 45 seconds.\n- A deactivated warehouse is missing from the list, but its stock stays in `onHand` and `onHandByWarehouse` until the goods are moved."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/warehouses",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "warehouses"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"e4d3c2b1-0001-4f5e-8d7c-6b5a49382701\",\n      \"code\": \"DEP-1\",\n      \"name\": \"Depozit central\",\n      \"isDefault\": true\n    },\n    {\n      \"id\": \"e4d3c2b1-0002-4f5e-8d7c-6b5a49382702\",\n      \"code\": \"MAG-2\",\n      \"name\": \"Magazin Botanica\",\n      \"isDefault\": false\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Depozite"
        },
        {
          "name": "Attributes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/attributes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "attributes"
              ]
            },
            "description": "**Required scope:** `products:read`\n\nProduct attributes (brand, country, flavor…) with the values in use. Build your shop's filters from them; the code goes into `attribute=cod:valoare` in “Product list”.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].code` | string | Attribute code, in lowercase. |\n| `items[].name` | string | The name given by the merchant. |\n| `items[].names` | object | The name per language (a language map, as on the product). |\n| `items[].dataType` | string | `string`, `number`, `boolean`, `date` or `enum`. |\n| `items[].values[]` | string[] | The predefined values and those used on products, without duplicates. |\n| `items[].valueNames[]` | array | Every value from `values`, in the same order: `value` and `names` (the value per language). |\n| `items[].sortOrder` | integer | Display order. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- A value used only on inactive products appears in the list, but filtering by it can return zero products.\n- The `attribute=cod:valoare` filter takes the value from `values`, never a translation from `valueNames`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/attributes",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "attributes"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"code\": \"brand\",\n      \"name\": \"Marcă\",\n      \"dataType\": \"string\",\n      \"values\": [\n        \"Borjomi\",\n        \"Franzeluța\",\n        \"Nongshim\"\n      ],\n      \"sortOrder\": 1\n    },\n    {\n      \"code\": \"tara\",\n      \"name\": \"Țara de origine\",\n      \"dataType\": \"enum\",\n      \"values\": [\n        \"Georgia\",\n        \"Moldova\"\n      ],\n      \"sortOrder\": 2,\n      \"names\": {\n        \"ro\": \"Țara de origine\",\n        \"ru\": \"Страна происхождения\"\n      },\n      \"valueNames\": [\n        {\n          \"value\": \"Georgia\",\n          \"names\": {\n            \"ro\": \"Georgia\",\n            \"ru\": \"Грузия\"\n          }\n        },\n        {\n          \"value\": \"Moldova\",\n          \"names\": {\n            \"ro\": \"Moldova\",\n            \"ru\": \"Молдова\"\n          }\n        }\n      ]\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Atribute"
        },
        {
          "name": "Categories",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/categories?tree=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "categories"
              ],
              "query": [
                {
                  "key": "tree",
                  "value": "true",
                  "description": "true = tree; flat list by default."
                }
              ]
            },
            "description": "**Required scope:** `products:read`\n\nActive till categories (the ones products are grouped by at the point of sale). Accounting categories from the nomenclature do not appear here.\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `tree` | boolean | `false` | `true` = the root categories, with `children` filled in; otherwise all categories in a single list, with `children: null`. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].id`, `name`, `code` | string | The category; the code may be empty. |\n| `items[].names` | object | The name per language (a language map, as on the product). |\n| `items[].parentId` | string (uuid)? | Parent category. |\n| `items[].sortOrder` | integer | Display order. |\n| `items[].children` | array? | Subcategories, only with `tree=true`. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | — | `tree` is something other than `true`/`false`. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- In the flat list, `parentId` may point to an inactive category that is missing from the list: treat that category as a root."
          },
          "response": [
            {
              "name": "200 — tree",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/categories?tree=true",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "categories"
                  ],
                  "query": [
                    {
                      "key": "tree",
                      "value": "true",
                      "description": "true = arbore; implicit listă plată."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n      \"name\": \"Băuturi\",\n      \"code\": \"BAUTURI\",\n      \"parentId\": null,\n      \"sortOrder\": 1,\n      \"names\": {\n        \"ro\": \"Băuturi\",\n        \"ru\": \"Напитки\"\n      },\n      \"children\": [\n        {\n          \"id\": \"a1b2c3d4-0002-4a5b-8c6d-7e8f9a0b1c02\",\n          \"name\": \"Apă minerală\",\n          \"code\": \"APA\",\n          \"parentId\": \"a1b2c3d4-0001-4a5b-8c6d-7e8f9a0b1c01\",\n          \"sortOrder\": 1,\n          \"names\": {\n            \"ro\": \"Apă minerală\",\n            \"ru\": \"Минеральная вода\"\n          },\n          \"children\": []\n        }\n      ]\n    },\n    {\n      \"id\": \"a1b2c3d4-0004-4a5b-8c6d-7e8f9a0b1c04\",\n      \"name\": \"Panificație\",\n      \"code\": \"PANIFICATIE\",\n      \"parentId\": null,\n      \"sortOrder\": 2,\n      \"names\": {\n        \"ro\": \"Panificație\"\n      },\n      \"children\": []\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Categorii"
        },
        {
          "name": "Content blocks",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/content-blocks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "content-blocks"
              ]
            },
            "description": "**Required scope:** `products:read`\n\nThe merchant's content block definitions (ingredients, storage…). They form the slots in the product page template; each product's content comes with “Product by code”.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].key` | string | The key; the same as in the product's `contentBlocks[].key`. |\n| `items[].labels` | object | The label per language (a language map, as on the product). |\n| `items[].labelRo` | string | Romanian label. |\n| `items[].labelRu`, `labelEn` | string? | The Russian and English labels — original fields; a language added later appears only in `labels`. |\n| `items[].sortOrder` | integer | Display order. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/content-blocks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "content-blocks"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"key\": \"ingrediente\",\n      \"labelRo\": \"Ingrediente\",\n      \"labelRu\": \"Состав\",\n      \"labelEn\": null,\n      \"sortOrder\": 1,\n      \"labels\": {\n        \"ro\": \"Ingrediente\",\n        \"ru\": \"Состав\"\n      }\n    },\n    {\n      \"key\": \"pastrare\",\n      \"labelRo\": \"Condiții de păstrare\",\n      \"labelRu\": \"Условия хранения\",\n      \"labelEn\": \"Storage\",\n      \"sortOrder\": 2,\n      \"labels\": {\n        \"ro\": \"Condiții de păstrare\",\n        \"ru\": \"Условия хранения\",\n        \"en\": \"Storage\"\n      }\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Blocuri de conținut"
        }
      ],
      "_anchorName": "1. Catalog și stoc"
    },
    {
      "name": "2. Prices",
      "description": "Price lists and the effective price of products on a list.",
      "item": [
        {
          "name": "Price lists",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/price-lists",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "price-lists"
              ]
            },
            "description": "**Required scope:** `products:read`\n\nThe merchant's price lists, with the default list first. Their id goes into “Product prices”.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].id`, `code`, `name` | string | The price list. |\n| `items[].currencyCode` | string | The price list's currency. |\n| `items[].isDefault` | boolean | The merchant's default price list. |\n| `items[].isActive` | boolean | The price list is active. Inactive ones are returned too. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- A price list with conditions (audience, volume) or outside its validity period appears here, but “Product prices” ignores it and gives the default price.\n- Shop lists do not appear here: their price applies only in the chosen shops, not through the public API."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/price-lists",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "price-lists"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a01\",\n      \"code\": \"PL-RETAIL\",\n      \"name\": \"Preț cu amănuntul\",\n      \"currencyCode\": \"MDL\",\n      \"isDefault\": true,\n      \"isActive\": true\n    },\n    {\n      \"id\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\",\n      \"code\": \"ANGRO\",\n      \"name\": \"Angro HoReCa\",\n      \"currencyCode\": \"MDL\",\n      \"isDefault\": false,\n      \"isActive\": true\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Liste de prețuri"
        },
        {
          "name": "Product prices",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/products/prices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "products",
                "prices"
              ]
            },
            "description": "**Required scope:** `products:read`\n\nThe effective price of selected products on a price list — for example, wholesale prices for a HoReCa customer.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `productIds[]` | string (uuid)[] | yes | The products' `posfixId` values, 1–1000. Empty → `400` „Lista de produse (productIds) este obligatorie.” (the product list is required); over 1000 → `400` „Cel mult 1.000 produse pe un apel.” (at most 1,000 products per call) |\n| `priceListId` | string (uuid) | no | The price list. Absent → the merchant's default price list. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].posfixId` | string (uuid) | The product. |\n| `items[].price` | number | The effective price. |\n| `items[].currency` | string | The product's currency. |\n\n### How the price is chosen\n\n1. The product's explicit price in the list, if there is one and it is valid today.\n2. For a discount-type list: the default price minus the list's percentage, rounded to 2 decimals.\n3. Otherwise, the product's default price from the nomenclature.\n\nThe price list is ignored (all products get the default price) if it is inactive, belongs to another merchant, has audience or volume conditions, is a shop list, or is outside its validity period. You get no error in these cases.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_PRICE_PRODUCTS_REQUIRED`, `INTEGRATIONS_PRICE_PRODUCTS_TOO_MANY` | The list is missing or has more than 1000 ids (simple format); an id is not a GUID (binding format). |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Unknown products are **omitted** from the response, without an error; repeated ids give a single row. Match rows by `posfixId`, not by order.\n- Prices aren't converted: a price list in another currency applies only to products in its currency.\n- The customer's price list (`defaultPriceListId` from “Customers”) isn't applied automatically: send it yourself in `priceListId`.\n- The body is read only with `Content-Type: application/json`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"productIds\": [\n    \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n    \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\"\n  ],\n  \"priceListId\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/prices",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    "prices"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"productIds\": [\n    \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n    \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\"\n  ],\n  \"priceListId\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"posfixId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n      \"price\": 15.9,\n      \"currency\": \"MDL\"\n    },\n    {\n      \"posfixId\": \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\",\n      \"price\": 7.11,\n      \"currency\": \"MDL\"\n    }\n  ]\n}"
            },
            {
              "name": "400 — empty list",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/products/prices",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "products",
                    "prices"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"productIds\": [\n    \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n    \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\"\n  ],\n  \"priceListId\": \"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"Validation Failed\",\n  \"status\": 400,\n  \"detail\": \"Lista de produse (productIds) este obligatorie.\",\n  \"errorCode\": \"INTEGRATIONS_PRICE_PRODUCTS_REQUIRED\"\n}"
            }
          ],
          "_anchorName": "Prețuri pe produse"
        }
      ],
      "_anchorName": "2. Prețuri"
    },
    {
      "name": "3. Customers",
      "description": "The merchant's customers. A customer is identified by fiscal code.",
      "item": [
        {
          "name": "Create customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/customers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "customers"
              ]
            },
            "description": "**Required scope:** `customers:write`\n\nCreates the customer by fiscal code. If a partner with the same code already exists, it returns it **unchanged** — the request does not update anything.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `name` | string | yes | The name. Empty → `400` “Numele clientului este obligatoriu.” (customer name is required). |\n| `fiscalCode` | string | yes | IDNO or IDNP, exactly 13 digits. Otherwise → `400` “Codul fiscal (IDNO/IDNP, 13 cifre) este obligatoriu pentru un client distinct.” |\n| `email` | string | no | Not validated. |\n| `phone` | string | no | Saved as received. |\n| `address` | string | no | Saved as both the legal and the actual address. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `posfixId` | string (uuid) | The partner's id in PosFix. On orders it appears as `partnerId`. |\n| `code` | string | Partner code, 10 digits (`0000000318`). |\n| `name` | string | The name, normalized: “Societate cu Răspundere Limitată „FLOAREA”” becomes `SRL FLOAREA`. |\n| `fiscalCode` | string? | IDNO or IDNP. |\n| `email`, `phone` | string? | Contacts. |\n| `address` | string? | Actual address; if missing, the legal one. |\n| `isActive` | boolean | The partner is active. |\n| `defaultPriceListId` | string (uuid)? | The customer's price list, if they have one. A shop's list is not shown: the field is empty. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_CUSTOMER_NAME_REQUIRED`, `INTEGRATIONS_CUSTOMER_FISCAL_CODE_REQUIRED` | Empty name or invalid fiscal code (simple format). |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The response is `200` both on creation and when the customer already existed. No field tells you which of the two it was.\n- A partner that was only a supplier also becomes a customer.\n- A code starting with `2` (IDNP) creates an individual; any other code creates a legal entity.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — customer created or found",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"b4e7c1a2-9d3f-4e61-8a0b-2f5c7d9e1a34\",\n  \"code\": \"0000000318\",\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\",\n  \"isActive\": true,\n  \"defaultPriceListId\": null\n}"
            },
            {
              "name": "400 — invalid fiscal code",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"Validation Failed\",\n  \"status\": 400,\n  \"detail\": \"Codul fiscal (IDNO/IDNP, 13 cifre) este obligatoriu pentru un client distinct.\",\n  \"errorCode\": \"INTEGRATIONS_CUSTOMER_FISCAL_CODE_REQUIRED\"\n}"
            }
          ],
          "_anchorName": "Creează client"
        },
        {
          "name": "Customer by id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/customers/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "customers",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "The customer's posfixId"
                }
              ]
            },
            "description": "**Required scope:** `customers:read`\n\nA merchant's customer, by `posfixId`.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Customer ID. Anything other than a GUID → `400`. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `posfixId` | string (uuid) | The partner's id in PosFix. On orders it appears as `partnerId`. |\n| `code` | string | Partner code, 10 digits (`0000000318`). |\n| `name` | string | The name, normalized: “Societate cu Răspundere Limitată „FLOAREA”” becomes `SRL FLOAREA`. |\n| `fiscalCode` | string? | IDNO or IDNP. |\n| `email`, `phone` | string? | Contacts. |\n| `address` | string? | Actual address; if missing, the legal one. |\n| `isActive` | boolean | The partner is active. |\n| `defaultPriceListId` | string (uuid)? | The customer's price list, if they have one. A shop's list is not shown: the field is empty. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | Does not exist, is deleted, or is a supplier only. Empty body. |\n| 400 | — | `id` is not a GUID (binding format). |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "posfixId-ul clientului"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"posfixId\": \"b4e7c1a2-9d3f-4e61-8a0b-2f5c7d9e1a34\",\n  \"code\": \"0000000318\",\n  \"name\": \"SRL Floarea Soarelui\",\n  \"fiscalCode\": \"1012600034567\",\n  \"email\": \"contabil@floarea.md\",\n  \"phone\": \"+37322123456\",\n  \"address\": \"mun. Chișinău, str. Ismail 45\",\n  \"isActive\": true,\n  \"defaultPriceListId\": null\n}"
            }
          ],
          "_anchorName": "Client după id"
        },
        {
          "name": "Customer list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/customers?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "customers"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "floarea",
                  "description": "Searches name, code and fiscal code.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, default 50."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor from the previous page.",
                  "disabled": true
                }
              ]
            },
            "description": "**Required scope:** `customers:read`\n\nThe merchant's customers, page by page. Includes inactive customers and the collective partner “Comenzi online” (online orders).\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `search` | string | — | Words from the name, code or tax code, in any order: every word must appear. Not case- or diacritic-sensitive („faina” finds „Făină”). |\n| `limit` | integer | 50 | Values above 200 are capped at 200; 0 or negative means 50. |\n| `cursor` | string (uuid) | — | The `nextCursor` value from the previous page. Anything other than a GUID → `400`. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[]` | array | Customers, with the fields from “Customer by id”. |\n| `nextCursor` | string? | Cursor for the next page; `null` on the last page. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `VALIDATION` | Invalid cursor. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The order is by id, not by name. Go through all pages until `nextCursor: null`; don't rely on the order."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/customers?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "search",
                      "value": "floarea",
                      "description": "Caută în denumire, cod și cod fiscal.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"posfixId\": \"0a1f2b3c-4d5e-4f60-8a71-92b3c4d5e6f7\",\n      \"code\": \"0000000011\",\n      \"name\": \"Comenzi online\",\n      \"fiscalCode\": null,\n      \"email\": null,\n      \"phone\": null,\n      \"address\": null,\n      \"isActive\": true,\n      \"defaultPriceListId\": null\n    },\n    {\n      \"posfixId\": \"b4e7c1a2-9d3f-4e61-8a0b-2f5c7d9e1a34\",\n      \"code\": \"0000000318\",\n      \"name\": \"SRL Floarea Soarelui\",\n      \"fiscalCode\": \"1012600034567\",\n      \"email\": \"contabil@floarea.md\",\n      \"phone\": \"+37322123456\",\n      \"address\": \"mun. Chișinău, str. Ismail 45\",\n      \"isActive\": true,\n      \"defaultPriceListId\": null\n    }\n  ],\n  \"nextCursor\": null\n}"
            }
          ],
          "_anchorName": "Listă clienți"
        }
      ],
      "_anchorName": "3. Clienți"
    },
    {
      "name": "4. Orders",
      "description": "The orders from your store. An order does no accounting: the delivery carries the journal entry. With the buyer's loyalty card (`loyalty`), PosFix also handles the order's loyalty on its own: the bonus used, the points and cashback on completion, the bonus returned on cancellation.",
      "item": [
        {
          "name": "Create order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders"
              ]
            },
            "description": "**Required scope:** `orders:write`\n\nThe order from your shop becomes a customer order in PosFix. With a warehouse, it is confirmed on the spot and the goods are reserved; without a warehouse, it stays `Draft`. The order is **informational**: it issues no fiscal receipt, invoice or journal entry. These appear only when the merchant delivers the goods.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `externalOrderId` | string | yes | The order ID in your system, max 200. It is the **idempotency key**: the same value does not create a second order. |\n| `externalNumber` | string | no | The order number in your system (`#1001`), max 100. Only a copy: the PosFix number is allocated separately. |\n| `orderDate` | string | no | `yyyy-MM-dd`. The day is the Moldovan one; an ISO instant is taken on its Moldovan day. Missing **or unreadable** → today, Moldovan time, with no error. The year selects the numbering series. |\n| `currency` | string | no | Only `MDL` for now (and the default). Another currency is refused with `TRADE_ORDER_CURRENCY_NOT_SUPPORTED`. |\n| `customer.verified` | boolean | no | Default `false`. `true` = the shop has verified its customer: a logged-in account, with the phone or IDNO confirmed. Verified → the order goes to the existing customer, with their prices. Unverified → the order does not attach to a company by IDNO and does not get the customer's price; at most it attaches to the retail customer found by phone, with the public price. |\n| `customer.idno` | string | no | 13-digit IDNO/IDNP, max 20 characters. Verified → the order goes to the company with this code, created if needed. Unverified → the IDNO does not link the order to the company; it stays on the order, with the name, phone and email, as the declared buyer, which the merchant sees and verifies. |\n| `customer.phone` | string | no | The buyer's phone, max 50. A Moldovan number (`+373 69 123 456`, `069123456`) takes the order to the customer with this phone, created if needed: verified → the existing customer, with their prices; unverified → only the retail customer (without a fiscal code), with the public price. Without a Moldovan phone and without a verified IDNO, the order goes to the collective partner “Comenzi online”. |\n| `customer.name`, `.email`, `.address` | string | no | Max 500 / 200 / 500. Used when a new partner is created (from a verified IDNO or from the phone). For an unverified buyer, the name and email also stay on the order, as the declared buyer. The customer's address is not the delivery address: for delivery use `delivery` or `deliveryAddress`. |\n| `warehouseId` | string (uuid) | no | The warehouse the goods are reserved from. Missing → the key's default warehouse, if it has one. With a warehouse, the order is confirmed according to the key's stock setting (below). |\n| `delivery.method` | string | no* | `pickup` (pickup), `own_courier` (the merchant's courier) or `nova_post`. *Required when you send `delivery`. Any other value → `400`. |\n| `delivery.recipientName`, `.recipientPhone` | string | no | The recipient, max 200 / 50. For `nova_post` they are required, and the phone must be Moldovan. |\n| `delivery.novaPostDivisionId`, `.novaPostSettlementId`, `.novaPostDivisionLabel` | number, number, string | no | The Nova Post point (their IDs, > 0) and its name, max 300. With a point, the address below is not used. |\n| `delivery.city`, `.street`, `.building`, `.flat`, `.postCode` | string | no | The address, max 100 / 200 / 20 / 20 / 10. For `nova_post` without a point, all are required except `flat`. |\n| `deliveryAddress`, `deliveryContact` | string | no | The address and contact as free text, for older integrations. With `delivery`, PosFix composes the text itself from its fields. |\n| `paymentMethod` | string | no | `online`, `on_pickup` (on pickup), `cash_on_delivery` (cash on delivery) or `bank_transfer`. Any other value → `400`. |\n| `loyalty.cardNumber` | string | no* | The buyer's loyalty card, 13 digits (you get it from “Enrol a member”). The order is linked to the member, and on completion the member gets the points or cashback on the delivered goods. A member already linked to a customer in PosFix brings the order to that customer, whatever phone is sent (a verified IDNO wins: the order stays on the company); without `customer.phone`, the member's phone is used. *Required when you send `loyalty`. Needs the `loyalty:earn` scope. |\n| `loyalty.bonusAmount` | number | no | The bonus in lei used on the order: greater than or equal to zero, at most two decimals, at most `maxRedeemable` from “How much bonus can be used” (with `orderAmount` = the order total with VAT). It is deducted from the line prices, so the customer pays the total minus the bonus. Also needs the `loyalty:redeem` scope. |\n| `lines[]` | array | yes | At least one line. |\n| `lines[].productCode` | string | yes | The product **code** from the catalog (`code`, 10 digits), not the SKU. Unknown code → `400`. |\n| `lines[].quantity` | number | yes | > 0. |\n| `lines[].unitPrice` | number | no | The unit price **excluding VAT**; VAT is added at the product's rate. Missing → the till price, brought to excluding VAT. How it is checked depends on the key's price setting (below); with the PosFix price it is simpler to omit it. For a verified customer, the price judged is their price (the list on their card or contract file): a shop that shows the public catalog price may be refused for these customers. |\n| `lines[].discountPct` | number | no | Line discount, 0–100, with at most 2 decimals (otherwise `400` `COMMON_PRECISION` on the field; it is not rounded). On keys with the PosFix price, a discount other than 0 → `400`. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `orderId` | string (uuid) | The order ID in PosFix. “Order by id” gives you its number. |\n| `idempotentReplay` | boolean | `true` if `externalOrderId` already existed. |\n| `status` | string | `created` or `replayed`. It is the result of the request, not the order status. |\n| `orderStatus` | string? | The order status: `Draft` or `Confirmed` (on a repeat, the current status). |\n| `paymentDueAt` | string? | The payment term, ISO 8601 UTC, if the key has a term, the order is confirmed and payment is not on pickup or on delivery; otherwise `null`. |\n\n### Key settings\n\nPosFix sets them on the key at the merchant's request; they are not sent in the request.\n\n| Setting | What it does |\n|---|---|\n| Line price | **PosFix price** (the default for new keys): PosFix sets the price, so `unitPrice` can be omitted. If sent, it passes when it is the till price excluding VAT, rounded to the ban or to 4 decimals, or when `unitPrice × (1 + cota/100)`, rounded to the ban, is exactly the till price; otherwise the order is refused (`422`) and you get the current prices. The saved price is the PosFix one anyway: for a verified customer, their price (the list on their card or on their contract file), otherwise the public price. **Integrator's price** (keys issued before these settings): the sent price stays as is. In both, a line without `unitPrice` gets the till price, brought to excluding VAT. With the PosFix price, a line discount (`discountPct`) is refused: discounts come from the merchant's price lists. |\n| Insufficient stock | **Refuse the order** (the default for new keys): the order is confirmed only if all the stock is available; otherwise `422` and the order does not remain. **Accept the order** (keys issued before these settings): it is confirmed and reserves as much as it can. |\n| Default warehouse | The warehouse for orders that do not send `warehouseId`. Without any warehouse, the order stays `Draft`, with no reservation. |\n| Payment term | Off or 1–10,080 minutes from receipt. A confirmed order not paid at all by the term is cancelled automatically (webhook `order.cancelled`); a partially paid one stays with the merchant. The term does not apply to payment on pickup or on delivery (`on_pickup`, `cash_on_delivery`): an order that cannot be paid in advance is not cancelled for non-payment. |\n\n### Loyalty on the order (`loyalty`)\n\nThis is how you work when orders go through PosFix: the platform does everything loyalty-related on its own, you only send the card and the bonus. The comparison with the option where the store calls the loyalty API directly is under “8. Loyalty”.\n\n| When | What PosFix does |\n|---|---|\n| On creation | Checks the card, links the order to the member and, with `bonusAmount`, holds the bonus under the programme rules (the same as at the till) and deducts it from the line prices. The held bonus can no longer be spent elsewhere, but it is not yet taken from the card. A refused hold refuses the order. |\n| Order fully delivered or closed | Takes from the card the bonus on the delivered goods, and returns the rest (on the undelivered goods) to the card. Grants the points or cashback on the delivered value, after discounts. Only once, however many events arrive. |\n| Order cancelled | Returns the whole bonus to the card. If part had been delivered, settles as on closing: bonus and accrual only on what was delivered. |\n| Order delivered, then sent back (parcel refused, delivery cancelled by the merchant) | Puts back the bonus taken and holds it again on the order; takes back the points or cashback. On the next completion it settles on what was delivered then. |\n| Cancelled order brought back to draft | The order loses the bonus (it went back to the card on cancellation) and returns to the full price; the card stays, so the accrual happens on completion. |\n| Member enrolled through the API, not linked to a customer | Linked to the order's customer on the first order with a card (not to the collective partner “Comenzi online”). From then on, their orders go to the same customer, even if the phone changes. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `VALIDATION` | A required field is missing, wrong or too long; see `errors`. An unknown value in `delivery.method` or `paymentMethod`: „Valoarea „card” nu e acceptată. Valori posibile: online, on_pickup, cash_on_delivery, bank_transfer.” (The value ‘card’ is not accepted. Possible values: online, on_pickup, cash_on_delivery, bank_transfer.) |\n| 400 | `INTEGRATIONS_ORDER_PRODUCT_UNKNOWN`, `INTEGRATIONS_ORDER_VAT_UNRESOLVED` | „Produs necunoscut: codul „X” nu există în catalogul comerciantului.” (Unknown product: code ‘X’ does not exist in the merchant's catalog.) or „Cota TVA a produsului „X” e nerezolvată, iar comanda nu se înregistrează cu 0%. Verificați cota produsului în catalog.” (The VAT rate of product ‘X’ is unresolved, and the order is not recorded at 0%. Check the product's VAT rate in the catalog.) (simple format; the product code is in `params.code`). |\n| 400 | `TRADE_ORDER_DELIVERY_RECIPIENT_REQUIRED`, `TRADE_ORDER_DELIVERY_DESTINATION_REQUIRED`, `TRADE_ORDER_DELIVERY_PHONE_INVALID` | Nova Post delivery without a recipient or without a destination, or the recipient's phone is not a Moldovan number. |\n| 400 | `INTEGRATIONS_ORDER_DISCOUNT_NOT_ALLOWED` | PosFix price: a line has `discountPct`. „Linia cu produsul „X” are reducere, dar cheia folosește prețul de la casă: reducerile vin din listele de prețuri ale comerciantului. Trimiteți linia fără discountPct.” (The line with product ‘X’ has a discount, but the key uses the till price: discounts come from the merchant's price lists. Send the line without discountPct.) |\n| 409 | `INTEGRATIONS_ORDER_UNRESOLVED` | An earlier request with the same key was interrupted before PosFix kept the key on the order, so the order may already exist. Do not resend automatically: the merchant checks; if they do not have it, send the order with a different `externalOrderId`. |\n| 422 | `INTEGRATIONS_ORDER_PRICE_CHANGED` | PosFix price: a sent price does not give the till price. The order was not created; the current prices are in `priceMismatches` (`productCode`, `sentUnitPrice`, `currentUnitPrice`, all excluding VAT). |\n| 422 | `INSUFFICIENT_STOCK` | Refuse the order on insufficient stock: „Stoc insuficient pentru „X” în „Y”: sunt necesare 5, disponibile 2, lipsesc 3. Înregistrați întâi intrarea mărfii sau micșorați cantitatea.” (Not enough stock of ‘X’ in ‘Y’: 5 needed, 2 available, 3 short. Record the goods receipt first or reduce the quantity.) The order was not created; you may still receive the `order.created` and `order.cancelled` (with `reason: stock_refused`) webhooks for it — the API response remains the truth. |\n| 400 | `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, `INTEGRATIONS_LOYALTY_AMOUNT_INVALID` | The card does not have 13 digits or the bonus is not an amount in lei (negative, more than two decimals). |\n| 400 | `TRADE_ORDER_LOYALTY_CARD_UNKNOWN` | “Loyalty card 2000000012345 does not belong to this company. Check the number or enrol the customer in the programme.” The order was not created. |\n| 400 | `TRADE_ORDER_LOYALTY_BONUS_EXCEEDS_TOTAL` | “The bonus of 300.00 lei is larger than the order, which is 243.38 lei. Send a bonus no larger than the order.” |\n| 403 | — | The order has `loyalty`, but the key lacks `loyalty:earn` (or `loyalty:redeem`, with a bonus), or the merchant does not have the “Loyalty Programs” module active. Empty body. |\n| 422 | `LOYALTY_BONUS_HOLD_EXCEEDS_MAX`, `LOYALTY_QUOTE_NOTHING_AVAILABLE` and the other quote reasons | The requested bonus cannot be used on the order (above the programme cap, insufficient balance, card without a wallet). “At most 120.50 MDL can be used on this receipt.” The order was not created: resend with the same `externalOrderId` and a smaller bonus, or without a bonus. |\n| 422 | `TRADE_ORDER_PARTNER_INACTIVE` | The partner the order would go to is deactivated in PosFix: usually the company found by the IDNO of a verified customer (`customer.verified: true`). The order was not created; the merchant reactivates the partner, then you resend with the same `externalOrderId`. It does not occur with the phone: the deactivated partner is skipped and the order goes to a new retail customer. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The response is `200`, not `201`, both on creation and on a repeat.\n- `replayed` can come even on your first request: if an internal attempt reached PosFix but its response was lost, the retry gets the order already created. Treat `created` and `replayed` the same: the order exists, and `orderId` is its ID.\n- On a repeat, the body is **not compared**: you get the existing order even if you changed the lines. A new order needs a new `externalOrderId`.\n- On a repeat, `orderStatus` and `paymentDueAt` are the current ones of the order. If the merchant has deleted the order in the meantime, the same request creates a new one.\n- Repeating is safe: if a request failed on the way (timeout, `503`), resend it with the same `externalOrderId` and no second order is created. The exception is keys left unresolved by a request interrupted before PosFix kept the key on the order: they get `409` `INTEGRATIONS_ORDER_UNRESOLVED`, and the merchant first checks whether they already have the order; if not, the order is sent with a different `externalOrderId`.\n- After a price or stock refusal the order does not exist: resend it with the same `externalOrderId`, with the prices from `priceMismatches` or once the stock is replenished.\n- The price excluding VAT from the till price: `price / (1 + vatRate/100)` (`7,90 / 1,08 = 7,3148`). With the PosFix price it passes at 4 decimals (`7,3148`), rounded to the ban (`7,31`), or as any price that, with VAT and rounded to the ban, gives the till price. Rounding to the ban is not accepted when the price excluding VAT rounded to the ban would be 0.\n- Line: net = quantity × price × (1 − discount/100); VAT = net × the product's rate; total = net + VAT. Net and VAT are rounded to 2 decimals, away from zero (2.345 → 2.35).\n- A new order triggers the `order.created` webhook, and its confirmation `order.confirmed`.\n- With `loyalty`, do not call “Use bonus on an order”, “Accrue on an order” or “Cancel an order (return)” from the loyalty API for the same order: PosFix does them, and doing them again would credit the member twice.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — order created and confirmed",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"orderId\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"idempotentReplay\": false,\n  \"status\": \"created\",\n  \"orderStatus\": \"Confirmed\",\n  \"paymentDueAt\": \"2026-10-16T10:15:00Z\"\n}"
            },
            {
              "name": "400 — loyalty card of another company",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"TRADE_ORDER_LOYALTY_CARD_UNKNOWN\",\n  \"status\": 400,\n  \"detail\": \"Cardul de fidelitate 2000000012345 nu e al acestei firme. Verificați numărul sau înscrieți clientul în program.\",\n  \"errorCode\": \"TRADE_ORDER_LOYALTY_CARD_UNKNOWN\"\n}"
            },
            {
              "name": "422 — bonus above the programme cap",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\",\n  \"status\": 422,\n  \"detail\": \"Pe bonul acesta se pot folosi cel mult 120,50 MDL.\",\n  \"errorCode\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\"\n}"
            },
            {
              "name": "200 — repeat with the same externalOrderId",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"orderId\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"idempotentReplay\": true,\n  \"status\": \"replayed\",\n  \"orderStatus\": \"Confirmed\",\n  \"paymentDueAt\": \"2026-10-16T10:15:00Z\"\n}"
            },
            {
              "name": "422 — the price differs from the till price",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"INTEGRATIONS_ORDER_PRICE_CHANGED\",\n  \"status\": 422,\n  \"detail\": \"Prețul unor linii diferă de prețul de la casă, așa că comanda nu s-a înregistrat. Prețurile curente, fără TVA, sunt în priceMismatches: retrimiteți comanda cu ele.\",\n  \"errorCode\": \"INTEGRATIONS_ORDER_PRICE_CHANGED\",\n  \"priceMismatches\": [\n    {\n      \"productCode\": \"0000000124\",\n      \"sentUnitPrice\": 6.9444,\n      \"currentUnitPrice\": 7.3148\n    }\n  ]\n}"
            },
            {
              "name": "422 — insufficient stock",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"INSUFFICIENT_STOCK\",\n  \"status\": 422,\n  \"detail\": \"Stoc insuficient pentru „Apă minerală Borjomi 0,5 L” în „Depozit central”: sunt necesare 12, disponibile 5, lipsesc 7. Înregistrați întâi intrarea mărfii sau micșorați cantitatea.\",\n  \"errorCode\": \"INSUFFICIENT_STOCK\",\n  \"params\": {\n    \"product\": \"Apă minerală Borjomi 0,5 L\",\n    \"warehouse\": \"Depozit central\",\n    \"requested\": \"12\",\n    \"available\": \"5\",\n    \"missing\": \"7\"\n  }\n}"
            },
            {
              "name": "400 — unknown product",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"Validation Failed\",\n  \"status\": 400,\n  \"detail\": \"Produs necunoscut: codul „0000000999” nu există în catalogul comerciantului.\",\n  \"errorCode\": \"INTEGRATIONS_ORDER_PRODUCT_UNKNOWN\",\n  \"params\": {\n    \"code\": \"0000000999\"\n  }\n}"
            },
            {
              "name": "400 — validation",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"externalOrderId\": \"shop-100245\",\n  \"orderDate\": \"{{today}}\",\n  \"currency\": \"MDL\",\n  \"customer\": {\n    \"name\": \"Ana Rusu\",\n    \"email\": \"ana.rusu@example.md\",\n    \"phone\": \"+373 69 123 456\"\n  },\n  \"delivery\": {\n    \"method\": \"own_courier\",\n    \"recipientName\": \"Ana Rusu\",\n    \"recipientPhone\": \"+373 69 123 456\",\n    \"city\": \"mun. Chișinău\",\n    \"street\": \"bd. Dacia\",\n    \"building\": \"27\",\n    \"flat\": \"4\",\n    \"postCode\": \"MD-2005\"\n  },\n  \"paymentMethod\": \"online\",\n  \"lines\": [\n    {\n      \"productCode\": \"0000000123\",\n      \"quantity\": 12,\n      \"unitPrice\": 15.42\n    },\n    {\n      \"productCode\": \"0000000124\",\n      \"quantity\": 3,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"VALIDATION\",\n  \"status\": 400,\n  \"detail\": \"Câmpul este obligatoriu.\",\n  \"errorCode\": \"VALIDATION\",\n  \"errors\": {\n    \"externalOrderId\": [\n      \"Câmpul este obligatoriu.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"externalOrderId\": [\n      {\n        \"code\": \"COMMON_REQUIRED\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Creează comandă"
        },
        {
          "name": "Order by id",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "orderId from “Create order”"
                }
              ]
            },
            "description": "**Required scope:** `orders:read`\n\nA customer order, with its lines, payments and the sales documents generated from it. Returns any order of the merchant, including those created in the Control Panel or at the till.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Order ID. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `id`, `number` | string | Order ID and number (10 digits). |\n| `status` | string | The state; see the table below. |\n| `partnerId` | string (uuid) | The customer. It can be the collective partner “Comenzi online” (online orders). |\n| `orderDate` | string | `yyyy-MM-dd`. |\n| `expectedShipDate` | string? | Planned delivery date, set by the merchant. |\n| `currencyCode` | string | Order currency. |\n| `subTotal`, `vatTotal`, `total` | number | Excl. VAT, VAT, incl. VAT — in the order currency. |\n| `reservedQtyTotal`, `shippedQtyTotal` | number | Quantities reserved and shipped, for the whole order. |\n| `invoicedSum` | number | The amount of posted sales from the order, in lei. |\n| `paidSum` | number | How much has been paid (sales, advances, the payment invoice), in lei. |\n| `deliveryAddress`, `deliveryContact`, `trackingNumber` | string? | Delivery. |\n| `paymentDueAt` | string? | The payment term, ISO 8601 UTC; `null` = no term. At the term, a confirmed order not paid at all is cancelled. |\n| `paymentMethod` | string? | `online`, `on_pickup`, `cash_on_delivery` or `bank_transfer`, as at creation; `null` = not stated. |\n| `loyalty` | object? | `{cardNumber, bonusAmount}`: the order's loyalty card and the bonus in lei now deducted from the line prices (0 = card only). `null` = order without a card. |\n| `cancellationReason` | string? | Why it was cancelled without the merchant: `payment_deadline` (not paid at all by the deadline) or `external_cancelled` (you cancelled it, with “Cancel order”); `null` = cancelled by a person or not cancelled. Orders refused on creation (stock, failed confirmation) are deleted, so they do not appear here; you only see them in the `order.cancelled` webhook. |\n| `description`, `notes` | string? | Filled in by the merchant in the Control Panel. |\n| `lines[]` | array | `lineNumber`, `productId` (`posfixId` from the catalog), `productName`, `unitOfMeasure`, `quantity`, `reservedQty`, `shippedQty`, `unitPrice` (excluding VAT), `discountPct`, `vatRatePct`, `lineTotal` (including VAT). |\n| `generatedSales[]` | array | Sales documents created from the order: `id`, `number`, `status` (`Draft`/`Posted`), `date`, `total`. |\n\n### Order statuses\n\n| Status | What it means |\n|---|---|\n| `Draft` | Received, unconfirmed, no reservation. An API order stays like this without a warehouse or when its confirmation did not succeed. |\n| `Confirmed` | Confirmed; the goods are reserved. An API order with a warehouse (its own or the key's default) is confirmed at creation. |\n| `Fulfilling` | Partially delivered. |\n| `Fulfilled` | Fully delivered: by invoice, courier delivery batch or pickup at the till. |\n| `Closed` | Closed manually; the rest of the reservation is released. |\n| `Cancelled` | Cancelled by the merchant or, at the key's payment term, automatically. The merchant can reopen it. |\n\nThe status is changed by the merchant and the delivery flows; the API cannot change it.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The order does not exist or has been deleted. Empty body. |\n| 400 | — | `id` is not a GUID. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- For pickup at the till or courier delivery, the order can reach `Fulfilled` with nothing in `generatedSales`: the accounting entry is carried by the Z report or the delivery batch."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId din „Creează comandă”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"number\": \"0000000042\",\n  \"status\": \"Draft\",\n  \"partnerId\": \"0a1f2b3c-4d5e-4f60-8a71-92b3c4d5e6f7\",\n  \"orderDate\": \"2026-09-25\",\n  \"expectedShipDate\": null,\n  \"currencyCode\": \"MDL\",\n  \"subTotal\": 204.79,\n  \"vatTotal\": 38.59,\n  \"total\": 243.38,\n  \"reservedQtyTotal\": 0,\n  \"shippedQtyTotal\": 0,\n  \"invoicedSum\": 0,\n  \"paidSum\": 0,\n  \"description\": null,\n  \"notes\": null,\n  \"deliveryAddress\": \"mun. Chișinău, bd. Dacia 27, ap. 4\",\n  \"deliveryContact\": \"Ana Rusu, +373 69 123 456\",\n  \"trackingNumber\": null,\n  \"paymentDueAt\": null,\n  \"paymentMethod\": \"cash_on_delivery\",\n  \"cancellationReason\": null,\n  \"lines\": [\n    {\n      \"lineNumber\": 1,\n      \"productId\": \"0b8f5e2a-6c1d-4e7f-9a3b-2d4c6e8f1a01\",\n      \"productName\": \"Apă minerală Borjomi 0,5 L\",\n      \"unitOfMeasure\": \"buc\",\n      \"quantity\": 12,\n      \"reservedQty\": 0,\n      \"shippedQty\": 0,\n      \"unitPrice\": 15.42,\n      \"discountPct\": 0,\n      \"vatRatePct\": 20,\n      \"lineTotal\": 222.05\n    },\n    {\n      \"lineNumber\": 2,\n      \"productId\": \"1c9a6f3b-7d2e-4f80-8b4c-3e5d7f9a2b02\",\n      \"productName\": \"Pâine Franzeluța albă feliată 400 g\",\n      \"unitOfMeasure\": \"buc\",\n      \"quantity\": 3,\n      \"reservedQty\": 0,\n      \"shippedQty\": 0,\n      \"unitPrice\": 7.3148,\n      \"discountPct\": 10,\n      \"vatRatePct\": 8,\n      \"lineTotal\": 21.33\n    }\n  ],\n  \"generatedSales\": []\n}"
            }
          ],
          "_anchorName": "Comandă după id"
        },
        {
          "name": "Order list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders?from={{monthStart}}&to={{today}}&page=1&pageSize=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "Draft",
                  "description": "Draft · Confirmed · Fulfilling · Fulfilled · Closed · Cancelled",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "{{monthStart}}",
                  "description": "yyyy-MM-dd, inclusive."
                },
                {
                  "key": "to",
                  "value": "{{today}}",
                  "description": "yyyy-MM-dd, inclusive."
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Starting at 1."
                },
                {
                  "key": "pageSize",
                  "value": "20",
                  "description": "1–200, default 20."
                }
              ]
            },
            "description": "**Required scope:** `orders:read`\n\nThe merchant's orders, newest first (by date, then by number).\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `status` | string | all | A status from the table under “Order by id”. Any other value → `400`. |\n| `from` / `to` | string | — | Order date, inclusive. Send `yyyy-MM-dd`: the server also accepts other formats and may misread them (`01.09.2026` becomes January 9). Text that is not a date → `400`. |\n| `page` | integer | 1 | Page number, starting at 1. |\n| `pageSize` | integer | 20 | 1–200. **Above 200 it falls back to 20**, not to 200. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[]` | array | `id`, `number`, `status`, `orderDate`, `expectedShipDate`, `total`, `currencyCode`, `paidSum` (MDL), `shippedQtyTotal`, `deliveryAddress`, `deliveryContact`, `trackingNumber`, `paymentDueAt`, `paymentMethod`, `cancellationReason`. |\n| `total` | integer | How many orders match the filter, across all pages. |\n| `page`, `pageSize` | integer | The values applied. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `VALIDATION` | Invalid status or date. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders?from={{monthStart}}&to={{today}}&page=1&pageSize=20",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders"
                  ],
                  "query": [
                    {
                      "key": "status",
                      "value": "Draft",
                      "description": "Draft · Confirmed · Fulfilling · Fulfilled · Closed · Cancelled",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, inclusiv."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, inclusiv."
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "20",
                      "description": "1–200, implicit 20."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n      \"number\": \"0000000042\",\n      \"status\": \"Draft\",\n      \"orderDate\": \"2026-09-25\",\n      \"expectedShipDate\": null,\n      \"total\": 243.38,\n      \"currencyCode\": \"MDL\",\n      \"paidSum\": 0,\n      \"shippedQtyTotal\": 0,\n      \"deliveryAddress\": \"mun. Chișinău, bd. Dacia 27, ap. 4\",\n      \"deliveryContact\": \"Ana Rusu, +373 69 123 456\",\n      \"trackingNumber\": null,\n      \"paymentDueAt\": null,\n      \"paymentMethod\": \"cash_on_delivery\",\n      \"cancellationReason\": null\n    }\n  ],\n  \"total\": 1,\n  \"page\": 1,\n  \"pageSize\": 20\n}"
            }
          ],
          "_anchorName": "Listă comenzi"
        },
        {
          "name": "Update delivery",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders/:id/delivery",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders",
                ":id",
                "delivery"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "orderId"
                }
              ]
            },
            "description": "**Required scope:** `orders:write`\n\nSets the tracking number and, if needed, the delivery address or contact, after you have shipped.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Order ID. |\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `trackingNumber` | string | no | The parcel tracking number. |\n| `deliveryAddress` | string | no | Delivery address. |\n| `deliveryContact` | string | no | Contact person. |\n\n### Response\n\n**204** with no body.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The order does not exist or has been deleted. Empty body. |\n| 400 | — | `id` is not a GUID. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Only fields that are sent and non-empty are written. A field **cannot be cleared** through the API.\n- Works in any order status and doesn't send a webhook.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"trackingNumber\": \"RB123456789MD\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "204 — updated",
              "originalRequest": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id/delivery",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id",
                    "delivery"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId"
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"trackingNumber\": \"RB123456789MD\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "No Content",
              "code": 204,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            }
          ],
          "_anchorName": "Actualizează livrarea"
        },
        {
          "name": "Cancel order",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/orders/:id/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "orders",
                ":id",
                "cancel"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "orderId from “Create order”"
                }
              ]
            },
            "description": "**Required scope:** `orders:write`\n\nCancels an order sent through the API while the goods have not left: it is `Draft` or `Confirmed` and nothing has been delivered or picked up. It is the same cancellation as the button in the merchant panel: the stock reservation is released, courier lots not yet handed over are withdrawn, and the loyalty bonus held on the order goes back to the card. Use it when the customer gives up or the online payment failed.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Order ID. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | The order. |\n| `status` | string | `Cancelled`. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The order does not exist, is deleted or did not come through the API (the merchant created it in the panel or at the till). Empty body. |\n| 400 | — | `id` is not a GUID. |\n| 422 | `TRADE_ORDER_API_CANCEL_GOODS_LEFT` | “Order 0000000042 can no longer be cancelled through the API: the goods have started to leave (part is delivered or picked up, or the order is closed). The company handles the return.” |\n| 422 | `TRADE_ORDER_CANCEL_HANDED_TO_CARRIER` | The parcel is already handed to the courier; the merchant requests the return. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Repeating on an order already cancelled answers `200` with `status: Cancelled`, without an error: you can resend it after a timeout.\n- Cancelling triggers the `order.cancelled` webhook, with `reason: external_cancelled`.\n- The merchant can bring the order back to draft from the panel; it then comes back without the bonus (the bonus went back to the card on cancellation).\n- A delivered order, even partly, can no longer be cancelled through the API: the merchant handles the return."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id/cancel",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id",
                    "cancel"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId din „Creează comandă”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"3c9a1f52-7d3e-4b8a-a0c4-6e2b91f0d7a8\",\n  \"status\": \"Cancelled\"\n}"
            },
            {
              "name": "422 — the goods have left",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/orders/:id/cancel",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "orders",
                    ":id",
                    "cancel"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "orderId din „Creează comandă”"
                    }
                  ]
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"TRADE_ORDER_API_CANCEL_GOODS_LEFT\",\n  \"status\": 422,\n  \"detail\": \"Comanda 0000000042 nu se mai poate anula din API: marfa a început să plece (o parte e livrată, ridicată sau comanda e închisă). Returul îl face firma.\",\n  \"errorCode\": \"TRADE_ORDER_API_CANCEL_GOODS_LEFT\"\n}"
            }
          ],
          "_anchorName": "Anulează comanda"
        }
      ],
      "_anchorName": "4. Comenzi"
    },
    {
      "name": "5. Sales",
      "description": "The merchant's sales: the Z reports from the till, day by day, and all sales by day and channel.",
      "item": [
        {
          "name": "Cash registers",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/terminals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "terminals"
              ]
            },
            "description": "**Required scope:** `sales:read`\n\nThe merchant's cash registers. Link your tills by `id` — it is stable. A key limited to certain tills sees only those.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].id` | string (uuid) | Till ID. |\n| `items[].name` | string | The name; may be empty. |\n| `items[].code` | string | Till code (`0000-00124-QWERT`); may be empty. |\n| `items[].model` | string? | Model. |\n| `items[].factoryNumber` | string? | Factory serial number. |\n| `items[].fiscalRegistrationNumber` | string? | Registration number with SFS. |\n| `items[].address` | string? | Installation address. |\n| `items[].warehouseId` | string (uuid)? | The till's warehouse. |\n| `items[].isFiscalRegistered` | boolean | The till is registered with SFS. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The list also includes deactivated tills (but not deleted ones). It has no pagination."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/terminals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "terminals"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\",\n      \"name\": \"Casa 1 — Magazin Centru\",\n      \"code\": \"0000-00124-QWERT\",\n      \"model\": \"POSfix\",\n      \"factoryNumber\": \"000000124QWERT\",\n      \"fiscalRegistrationNumber\": \"J403001234\",\n      \"address\": \"mun. Chișinău, str. București 45\",\n      \"warehouseId\": \"0d9e2a8b-1f3c-4b6e-8a7d-2c5f9e1b3a46\",\n      \"isFiscalRegistered\": true\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Case de marcat"
        },
        {
          "name": "Z reports",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/z-reports?from={{monthStart}}&to={{today}}&page=1&pageSize=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "z-reports"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{monthStart}}",
                  "description": "yyyy-MM-dd, required."
                },
                {
                  "key": "to",
                  "value": "{{today}}",
                  "description": "yyyy-MM-dd, required, ≥ from."
                },
                {
                  "key": "terminalId",
                  "value": "",
                  "description": "Till id; repeatable.",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Starting at 1."
                },
                {
                  "key": "pageSize",
                  "value": "50",
                  "description": "1–200, default 50."
                }
              ]
            },
            "description": "**Required scope:** `sales:read`\n\nThe Z reports closed in a period, in closing order. This is where importing sales into your accounting starts: one Z = one day of sales on one till.\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `from` / `to` | string | — | Required, `yyyy-MM-dd`, inclusive; `to` ≥ `from`. Filters by **closing time**, in UTC days. |\n| `terminalId` | string (uuid) | all | Repeatable: `?terminalId=a&terminalId=b`. |\n| `page` | integer | 1 | ≥ 1. |\n| `pageSize` | integer | 50 | 1–200. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[]` | array | The Z reports; the fields are in the table below. |\n| `totalCount` | integer | How many reports match the filter. |\n| `page`, `pageSize` | integer | The values applied. |\n| `totalPages` | integer | The number of pages; 0 when there is nothing. |\n| `hasNextPage`, `hasPreviousPage` | boolean | Pagination. |\n| `summary`, `filterableColumns`, `ignoredFilters` | null | Always `null`. |\n\n### The Z report\n\n| Field | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Z report ID. Use it as the idempotency key in your system. |\n| `terminalId` | string (uuid) | The till. |\n| `number` | integer | The Z number on the till. |\n| `reportDateTime` | string | Closing time, ISO 8601 UTC. |\n| `tradingDay` | string | The trading day, `yyyy-MM-dd`: the day of the first sale in the Z report. Date your accounting documents by it. |\n| `warehouseId` | string (uuid)? | The warehouse. |\n| `cashierName` | string? | Cashier. |\n| `totalSales`, `totalReturns` | number | Sales and returns, lei, VAT included. |\n| `totalDiscounts` | number | Discounts, lei. |\n| `netRevenue` | number | `totalSales − totalReturns`. Net of returns, **not** of VAT. |\n| `totalVat` | number | The day's VAT, lei. |\n| `cashAmount`, `cardAmount`, `otherAmount` | number | Amounts collected: cash, card, all other payment methods. |\n| `receiptsCount`, `returnsCount` | integer | Receipts and returns. |\n| `sealedAt` | string? | When the Z report was sealed, ISO 8601 UTC. |\n| `fiscalSeal` | string? | SHA-256 hash (64 hex characters) of the Z report. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_API_DATE_REQUIRED`, `INTEGRATIONS_API_DATE_INVALID`, `COMMON_DATE_ON_OR_AFTER`, `COMMON_BETWEEN` | „Parametrul este obligatoriu: o dată în formatul yyyy-MM-dd.” (The parameter is required: a date in yyyy-MM-dd format.), „Data trebuie să fie cel mai devreme 01.09.2026.” (The date must be 01.09.2026 or later.), „Valoarea trebuie să fie între 1 și 200.” (The value must be between 1 and 200.) — in `errors` and `errorCodes`, per field (binding format). |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Moldova is at UTC+2/+3: a Z closed at 01:30 local time falls on the previous day when filtering.\n- A key restricted to certain tills sees only their reports. A filter on a till the key isn't allowed returns an empty page, not `403`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports?from={{monthStart}}&to={{today}}&page=1&pageSize=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, ≥ from."
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"8f1c2b7a-3d4e-4f5a-9b6c-7d8e9f0a1b2c\",\n      \"terminalId\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\",\n      \"number\": 67,\n      \"reportDateTime\": \"2026-09-24T17:47:31Z\",\n      \"tradingDay\": \"2026-09-24\",\n      \"warehouseId\": \"0d9e2a8b-1f3c-4b6e-8a7d-2c5f9e1b3a46\",\n      \"cashierName\": \"Ana Rusu\",\n      \"totalSales\": 282,\n      \"totalReturns\": 89.9,\n      \"totalDiscounts\": 0,\n      \"netRevenue\": 192.1,\n      \"totalVat\": 22.56,\n      \"cashAmount\": 102.2,\n      \"cardAmount\": 89.9,\n      \"otherAmount\": 0,\n      \"receiptsCount\": 4,\n      \"returnsCount\": 1,\n      \"sealedAt\": \"2026-09-24T17:47:33Z\",\n      \"fiscalSeal\": \"3f9a0c1e5b7d2a4f6e8c0b1d3f5a7c9e1b3d5f7a9c0e2b4d6f8a0c2e4b6d8f0a\"\n    }\n  ],\n  \"totalCount\": 1,\n  \"page\": 1,\n  \"pageSize\": 50,\n  \"summary\": null,\n  \"filterableColumns\": null,\n  \"ignoredFilters\": null,\n  \"totalPages\": 1,\n  \"hasNextPage\": false,\n  \"hasPreviousPage\": false\n}"
            },
            {
              "name": "400 — missing period",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports?from={{monthStart}}&to={{today}}&page=1&pageSize=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, ≥ from."
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/problem+json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"from\": [\n      \"Parametrul este obligatoriu: o dată în formatul yyyy-MM-dd.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"from\": [\n      {\n        \"code\": \"INTEGRATIONS_API_DATE_REQUIRED\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Rapoarte Z"
        },
        {
          "name": "Z report",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/z-reports/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "z-reports",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "id from “Z reports”"
                }
              ]
            },
            "description": "**Required scope:** `sales:read`\n\nA Z report with its sales aggregated by product, the payment methods and the VAT by rate. It's all you need to post the day to your accounts.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Z report ID. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `report` | object | The Z report, with the fields from “Z reports”. |\n| `lines[]` | array | The day's sales, aggregated by product × VAT rate × direction (sale/return). The same product can appear on several lines; a return has a negative quantity and total. |\n| `lines[].productCode`, `productName` | string | The code and name; may be empty. |\n| `lines[].productId` | string (uuid)? | The product from the Nomenclature; `null` if the till item isn't linked to the Nomenclature. |\n| `lines[].posProductId` | string (uuid)? | The till's product card. |\n| `lines[].unit` | string? | The unit on the receipt (`buc`, `kg`). |\n| `lines[].quantity`, `lines[].total` | number | The quantity and the total **including VAT**, lei. |\n| `lines[].vatCode` | string | 20% → `A`, 8% → `B`, otherwise the letter on the receipt (`C`, `D`, `_` = no VAT). |\n| `lines[].vatAmount`, `lines[].vatPercent` | number | The VAT actually collected and the rate. Don't recalculate VAT from the letter. |\n| `lines[].discountAmount` | number | The discount given on the receipts for this line, already subtracted from `total`. `total` + `discountAmount` = the value before the discount. |\n| `tenders[]` | array | Takings by payment method: `code`, `amount`, `isCash`. Payments are settled from here. |\n| `vat` | object | `totalA`…`totalD` (incl. VAT) and `vatA`…`vatD` by rate; `total` = the sum of the lines. The letter `_` and unknown letters go into `D`. |\n| `cardVoidsTotal` | number | Card payments voided before the bank's settlement: the bank settles less than `tenders` shows. |\n| `cardRefundsTotal` | number | Card refunds after settlement; they belong to a later period. |\n\n### Codes in `tenders[].code`\n\n`cash` · `card` · `voucher` · `check` · `ticket` · `mealTicket` · `subscription` · `transfer` · `credit` · `leasing` · `advance` · `deposit` · `pledge` · `compensation` · `otherCredit`. `other` appears only in old reports without a breakdown. `isCash` is `true` only for `cash`.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The Z report does not exist, belongs to another merchant, or its till is not allowed for the key. Empty body. |\n| 400 | — | `id` is not a GUID (binding format). |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Rapoarte Z”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"report\": {\n    \"id\": \"8f1c2b7a-3d4e-4f5a-9b6c-7d8e9f0a1b2c\",\n    \"terminalId\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\",\n    \"number\": 67,\n    \"reportDateTime\": \"2026-09-24T17:47:31Z\",\n    \"tradingDay\": \"2026-09-24\",\n    \"warehouseId\": \"0d9e2a8b-1f3c-4b6e-8a7d-2c5f9e1b3a46\",\n    \"cashierName\": \"Ana Rusu\",\n    \"totalSales\": 282,\n    \"totalReturns\": 89.9,\n    \"totalDiscounts\": 0,\n    \"netRevenue\": 192.1,\n    \"totalVat\": 22.56,\n    \"cashAmount\": 102.2,\n    \"cardAmount\": 89.9,\n    \"otherAmount\": 0,\n    \"receiptsCount\": 4,\n    \"returnsCount\": 1,\n    \"sealedAt\": \"2026-09-24T17:47:33Z\",\n    \"fiscalSeal\": \"3f9a0c1e5b7d2a4f6e8c0b1d3f5a7c9e1b3d5f7a9c0e2b4d6f8a0c2e4b6d8f0a\"\n  },\n  \"lines\": [\n    {\n      \"productCode\": \"4840167001234\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000001\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000001\",\n      \"productName\": \"Lapte Fabrikant 2,5% 1L\",\n      \"unit\": \"buc\",\n      \"quantity\": 3,\n      \"total\": 59.7,\n      \"vatCode\": \"B\",\n      \"vatAmount\": 4.42,\n      \"vatPercent\": 8,\n      \"discountAmount\": 0\n    },\n    {\n      \"productCode\": \"4840001000057\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000002\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000002\",\n      \"productName\": \"Pâine albă 500 g\",\n      \"unit\": \"buc\",\n      \"quantity\": 5,\n      \"total\": 42.5,\n      \"vatCode\": \"B\",\n      \"vatAmount\": 3.15,\n      \"vatPercent\": 8,\n      \"discountAmount\": 0\n    },\n    {\n      \"productCode\": \"8714599101230\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000003\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000003\",\n      \"productName\": \"Cafea Jacobs Monarch 250 g\",\n      \"unit\": \"buc\",\n      \"quantity\": 2,\n      \"total\": 179.8,\n      \"vatCode\": \"A\",\n      \"vatAmount\": 29.97,\n      \"vatPercent\": 20,\n      \"discountAmount\": 0\n    },\n    {\n      \"productCode\": \"8714599101230\",\n      \"productId\": \"a1b2c3d4-0000-4000-8000-000000000003\",\n      \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000003\",\n      \"productName\": \"Cafea Jacobs Monarch 250 g\",\n      \"unit\": \"buc\",\n      \"quantity\": -1,\n      \"total\": -89.9,\n      \"vatCode\": \"A\",\n      \"vatAmount\": -14.98,\n      \"vatPercent\": 20,\n      \"discountAmount\": 0\n    }\n  ],\n  \"tenders\": [\n    {\n      \"code\": \"cash\",\n      \"amount\": 102.2,\n      \"isCash\": true\n    },\n    {\n      \"code\": \"card\",\n      \"amount\": 89.9,\n      \"isCash\": false\n    }\n  ],\n  \"vat\": {\n    \"totalA\": 89.9,\n    \"totalB\": 102.2,\n    \"totalC\": 0,\n    \"totalD\": 0,\n    \"vatA\": 14.99,\n    \"vatB\": 7.57,\n    \"vatC\": 0,\n    \"vatD\": 0,\n    \"total\": 192.1\n  },\n  \"cardVoidsTotal\": 0,\n  \"cardRefundsTotal\": 0\n}"
            }
          ],
          "_anchorName": "Raport Z"
        },
        {
          "name": "Receipts of a Z report",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/z-reports/:id/receipts?page=1&pageSize=100",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "z-reports",
                ":id",
                "receipts"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "Starting at 1."
                },
                {
                  "key": "pageSize",
                  "value": "100",
                  "description": "1–500, default 100."
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "id from “Z reports”"
                }
              ]
            },
            "description": "**Required scope:** `sales:read`\n\nThe raw receipts of a Z report, each with its lines and payments, in the order issued. For accounting, “Z report” is enough; the receipts are for reconciliation and for analysis by customer or by hour.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Z report ID. |\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `page` | integer | 1 | ≥ 1. |\n| `pageSize` | integer | 100 | 1–500. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[]` | array | The receipts. |\n| `items[].id`, `transactionNumber` | string, integer | Transaction ID and number. |\n| `items[].receiptNumber` | integer? | Receipt number. |\n| `items[].fiscalReceiptNumber`, `sfsFiscalCode`, `sfsReceiptUrl` | string? | Fiscal data; the SFS verification URL. |\n| `items[].dateTime` | string | ISO 8601 UTC. |\n| `items[].cashierName` | string? | The cashier. |\n| `items[].total`, `vat`, `discount`, `change` | number | Total including VAT, the VAT, the discount, the change given. **Negative on a return.** |\n| `items[].currency` | string | Currency (`MDL`). |\n| `items[].isRefund`, `isFiscal` | boolean | Return; fiscal receipt. |\n| `items[].lines[]` | array | `productCode`, `productId`, `posProductId`, `productName`, `unit`, `quantity` (negative on a return), `unitPrice`, `total`, `discountAmount`, `vatCode` (the letter on the receipt), `vatAmount`, `vatPercent`. |\n| `items[].payments[]` | array | `code` (the ACPS code on the receipt: `\"1\"` cash, `\"2\"` card…), `name`, `amount` (for cash: the amount the customer handed over), `cardMask`, `reference` (RRN), `voidedAmount`, `refundedAmount`, `reversedAt`. |\n| `totalCount`, `page`, `pageSize` | integer | Pagination. |\n| `terminalId` | string (uuid) | The Z report's till. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The Z report does not exist, belongs to another merchant, or its till is not allowed for the key. Empty body. |\n| 400 | — | `id` is not a GUID, or `page`/`pageSize` is out of range. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The payment codes here are the ACPS codes on the receipt, a **different vocabulary** from `tenders[].code` in “Z report”."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/z-reports/:id/receipts?page=1&pageSize=100",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "z-reports",
                    ":id",
                    "receipts"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "100",
                      "description": "1–500, implicit 100."
                    }
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Rapoarte Z”"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"c7d8e9f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f\",\n      \"transactionNumber\": 1051,\n      \"receiptNumber\": 41,\n      \"fiscalReceiptNumber\": null,\n      \"dateTime\": \"2026-09-24T07:12:05Z\",\n      \"cashierName\": \"Ana Rusu\",\n      \"total\": 76.7,\n      \"vat\": 5.68,\n      \"discount\": 0,\n      \"change\": 23.3,\n      \"currency\": \"MDL\",\n      \"isRefund\": false,\n      \"isFiscal\": true,\n      \"sfsFiscalCode\": \"7F3A9C21\",\n      \"sfsReceiptUrl\": \"https://mev.sfs.md/receipt-verifier/7F3A9C21\",\n      \"lines\": [\n        {\n          \"productCode\": \"4840167001234\",\n          \"productId\": \"a1b2c3d4-0000-4000-8000-000000000001\",\n          \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000001\",\n          \"productName\": \"Lapte Fabrikant 2,5% 1L\",\n          \"unit\": \"buc\",\n          \"quantity\": 3,\n          \"unitPrice\": 19.9,\n          \"total\": 59.7,\n          \"discountAmount\": 0,\n          \"vatCode\": \"B\",\n          \"vatAmount\": 4.42,\n          \"vatPercent\": 8\n        },\n        {\n          \"productCode\": \"4840001000057\",\n          \"productId\": \"a1b2c3d4-0000-4000-8000-000000000002\",\n          \"posProductId\": \"b1c2d3e4-0000-4000-8000-000000000002\",\n          \"productName\": \"Pâine albă 500 g\",\n          \"unit\": \"buc\",\n          \"quantity\": 2,\n          \"unitPrice\": 8.5,\n          \"total\": 17,\n          \"discountAmount\": 0,\n          \"vatCode\": \"B\",\n          \"vatAmount\": 1.26,\n          \"vatPercent\": 8\n        }\n      ],\n      \"payments\": [\n        {\n          \"code\": \"1\",\n          \"name\": \"NUMERAR\",\n          \"amount\": 100,\n          \"cardMask\": null,\n          \"reference\": null,\n          \"voidedAmount\": 0,\n          \"refundedAmount\": 0,\n          \"reversedAt\": null\n        }\n      ]\n    }\n  ],\n  \"totalCount\": 1,\n  \"page\": 1,\n  \"pageSize\": 100,\n  \"terminalId\": \"5b2d6f3e-8c1a-4f7e-9a2b-3c4d5e6f7a81\"\n}"
            }
          ],
          "_anchorName": "Bonurile unui Z"
        },
        {
          "name": "Sales by day and channel",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "sales",
                "daily"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{monthStart}}",
                  "description": "yyyy-MM-dd, required."
                },
                {
                  "key": "to",
                  "value": "{{today}}",
                  "description": "yyyy-MM-dd, required, at most 366 days after from."
                },
                {
                  "key": "channelId",
                  "value": "",
                  "description": "Sales channel.",
                  "disabled": true
                },
                {
                  "key": "terminalId",
                  "value": "",
                  "description": "Till id; repeatable.",
                  "disabled": true
                }
              ]
            },
            "description": "**Required scope:** `sales:read`\n\nAll of the merchant's sales, added up by day and sales channel: till receipts, invoices, returns and retail reports. These are the same figures the merchant sees on the “Sales” screen and that the `sale.*` webhooks send.\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `from` / `to` | string | — | Required, `yyyy-MM-dd`, inclusive; `to` ≥ `from`, at most 366 days. Filters by the **day of the sale**, Moldova time. |\n| `channelId` | string (uuid) | all | A single sales channel. |\n| `terminalId` | string (uuid) | all | Repeatable. With tills chosen, only their sales count — invoices have no till, so they are left out. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[]` | array | One row per day and channel, in day order. |\n| `items[].date` | string | The day, `yyyy-MM-dd`. |\n| `items[].channelId`, `channelName` | string? | The sales channel; `null` when the document has no channel. |\n| `items[].channelType` | string | `Store`, `DirectSales`, `Online`, `SocialNetwork`, `Phone`, `Marketplace`, `Vending` or `Other`. |\n| `items[].documents` | integer | How many documents (receipts, invoices, returns) the row includes. |\n| `items[].netAmount` | number | Sales without VAT, MDL, returns subtracted. |\n| `items[].vatAmount` | number | The VAT, MDL. |\n| `items[].totalAmount` | number | `netAmount + vatAmount`. |\n| `currency` | string | Always `MDL`. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_API_DATE_REQUIRED`, `INTEGRATIONS_API_DATE_INVALID`, `COMMON_DATE_ON_OR_AFTER`, `INTEGRATIONS_API_PERIOD_TOO_LONG` | “The period can be at most 366 days long. Request the report in parts.” — in `errors` and `errorCodes`, per field (binding format). |\n| 422 | `REPORTS_SALES_LEDGER_DISABLED` | “The combined sales ledger is not enabled in this environment yet. Sales remain in the register and accounting reports.” |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The report has no pagination: a year has at most 366 days × the merchant's number of channels.\n- Sales from the last few minutes may be missing: the ledger is filled from events, usually within seconds.\n- A key limited to certain tills sees only their sales. A filter on a forbidden till returns an empty list, not `403`."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "sales",
                    "daily"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, cel mult 366 de zile după from."
                    },
                    {
                      "key": "channelId",
                      "value": "",
                      "description": "Canalul de vânzare.",
                      "disabled": true
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"date\": \"2026-09-24\",\n      \"channelId\": \"3c1e9a7b-5d2f-4b8e-9a1c-7e5d3b2f1a90\",\n      \"channelName\": \"Magazin\",\n      \"channelType\": \"Store\",\n      \"documents\": 41,\n      \"netAmount\": 9634.26,\n      \"vatAmount\": 1926.85,\n      \"totalAmount\": 11561.11\n    }\n  ],\n  \"currency\": \"MDL\"\n}"
            },
            {
              "name": "400 — period too long",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "sales",
                    "daily"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, cel mult 366 de zile după from."
                    },
                    {
                      "key": "channelId",
                      "value": "",
                      "description": "Canalul de vânzare.",
                      "disabled": true
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/problem+json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"to\": [\n      \"Perioada poate avea cel mult 366 de zile. Cereți raportul pe bucăți.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"to\": [\n      {\n        \"code\": \"INTEGRATIONS_API_PERIOD_TOO_LONG\",\n        \"params\": {\n          \"days\": 366\n        }\n      }\n    ]\n  }\n}"
            },
            {
              "name": "422 — ledger not enabled",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/sales/daily?from={{monthStart}}&to={{today}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "sales",
                    "daily"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "{{monthStart}}",
                      "description": "yyyy-MM-dd, obligatoriu."
                    },
                    {
                      "key": "to",
                      "value": "{{today}}",
                      "description": "yyyy-MM-dd, obligatoriu, cel mult 366 de zile după from."
                    },
                    {
                      "key": "channelId",
                      "value": "",
                      "description": "Canalul de vânzare.",
                      "disabled": true
                    },
                    {
                      "key": "terminalId",
                      "value": "",
                      "description": "Id de casă; repetabil.",
                      "disabled": true
                    }
                  ]
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"REPORTS_SALES_LEDGER_DISABLED\",\n  \"status\": 422,\n  \"detail\": \"Registrul comun de vânzări nu e pornit încă pe acest mediu. Vânzările rămân în rapoartele de casă și de contabilitate.\",\n  \"errorCode\": \"REPORTS_SALES_LEDGER_DISABLED\"\n}"
            }
          ],
          "_anchorName": "Vânzări pe zile și canale"
        }
      ],
      "_anchorName": "5. Vânzări"
    },
    {
      "name": "6. Webhooks",
      "description": "The events sent to your address: products, stock, orders, sales, recognised invoices, loyalty. The delivery contract is in the collection description.",
      "item": [
        {
          "name": "Create webhook",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "webhooks"
              ]
            },
            "description": "**Required scope:** `webhooks:manage`\n\nSubscribes one of your URLs to events. The response contains the signing secret — the **only time** you see it.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `url` | string | yes | An `https` URL, max 2048 characters. A private IP address is rejected. |\n| `eventTypes[]` | string[] | yes | At least one event from the list in the collection description. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Subscription ID. |\n| `url` | string | The URL. |\n| `eventTypes[]` | string[] | The events, in lowercase, without duplicates. |\n| `signingSecret` | string | `whsec_…` — the key you verify the signature with. It can't be read again and can't be changed: for a new secret, delete the subscription and create another one. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `VALIDATION` | „Adresa webhook-ului trebuie să folosească https.” (The webhook URL must use https.), „Alegeți cel puțin un tip de eveniment.” (Choose at least one event type.), „Tip de eveniment necunoscut. Tipurile permise: ….” (Unknown event type. Allowed types: ….) — the code of each one is in `errorCodes`, per field. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The response is `200`, not `201`.\n- The same URL registered twice receives each event twice.\n- The domain name is checked on every delivery, not at registration: a domain that resolves to a private address is accepted here, but deliveries fail.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"e3b5c1d2-7a8f-4c6e-9d0b-1a2b3c4d5e6f\",\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ],\n  \"signingSecret\": \"whsec_Q2x4bE9mY2J3aVZ5dGNWa0Q2ZUx0ZlQ5c2h1Tk1jUgA\"\n}"
            },
            {
              "name": "400 — http URL",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://erp.magazin.md/hooks/posfix\",\n  \"eventTypes\": [\n    \"order.created\",\n    \"order.paid\",\n    \"inventory.updated\"\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"VALIDATION\",\n  \"status\": 400,\n  \"detail\": \"Adresa webhook-ului trebuie să folosească https.\",\n  \"errorCode\": \"VALIDATION\",\n  \"errors\": {\n    \"url\": [\n      \"Adresa webhook-ului trebuie să folosească https.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"url\": [\n      {\n        \"code\": \"INTEGRATIONS_WEBHOOK_URL_HTTPS\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            }
          ],
          "_anchorName": "Creează webhook"
        },
        {
          "name": "Webhook list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "webhooks"
              ]
            },
            "description": "**Required scope:** `webhooks:manage`\n\nActive subscriptions, newest first. The secret is not included.\n\n### Response 200 — list\n\n| Field | Type | Description |\n|---|---|---|\n| `[].id` | string (uuid) | Subscription ID. |\n| `[].url` | string | URL. |\n| `[].eventTypes[]` | string[] | Events. |\n| `[].isActive` | boolean | Always `true`: a subscription is stopped only by deleting it. |\n| `[].createdAt` | string | ISO 8601 UTC. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The response is a plain JSON list, without pagination."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"e3b5c1d2-7a8f-4c6e-9d0b-1a2b3c4d5e6f\",\n    \"url\": \"https://erp.magazin.md/hooks/posfix\",\n    \"eventTypes\": [\n      \"order.created\",\n      \"order.paid\",\n      \"inventory.updated\"\n    ],\n    \"isActive\": true,\n    \"createdAt\": \"2026-09-25T08:02:11.482913Z\"\n  }\n]"
            }
          ],
          "_anchorName": "Listă webhook-uri"
        },
        {
          "name": "Delete webhook",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/webhooks/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "webhooks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "id from “Create webhook”"
                }
              ]
            },
            "description": "**Required scope:** `webhooks:manage`\n\nStops a subscription. Queued deliveries are no longer sent.\n\n### Path parameters\n\n| Parameter | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Subscription ID. |\n\n### Response\n\n**204** with no body.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | `INTEGRATIONS_RESOURCE_NOT_FOUND` | The id is unknown, belongs to another merchant, or is **already deleted** (simple format). |\n| 400 | — | `id` is not a GUID. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Deleting the same id a second time gives `404`, not `204`."
          },
          "response": [
            {
              "name": "204 — deleted",
              "originalRequest": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Creează webhook”"
                    }
                  ]
                }
              },
              "status": "No Content",
              "code": 204,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            },
            {
              "name": "404 — unknown or already deleted",
              "originalRequest": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/webhooks/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "webhooks",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "",
                      "description": "id din „Creează webhook”"
                    }
                  ]
                }
              },
              "status": "Not Found",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/404\",\n  \"title\": \"Not Found\",\n  \"status\": 404,\n  \"detail\": \"Resursa cerută nu există sau nu aparține acestui comerciant.\",\n  \"errorCode\": \"INTEGRATIONS_RESOURCE_NOT_FOUND\"\n}"
            }
          ],
          "_anchorName": "Șterge webhook"
        }
      ],
      "_anchorName": "6. Webhook-uri"
    },
    {
      "name": "7. Invoice recognition",
      "description": "PDF or photographed invoices → structured data, matched to your nomenclature. Steps: (1) upload the nomenclature (once, then only the changes); (2) send the file and get `202` with an id; (3) wait for the `docai.recognition.completed` webhook or poll by id every 10 seconds; (4) read the invoice and import it; (5) confirm with `ack`. The service is paid: each new file is deducted from the merchant's package.",
      "item": [
        {
          "name": "Nomenclature — upload / update",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "catalog",
                "items"
              ]
            },
            "description": "**Required scope:** `docai:catalog`\n\nThe Nomenclature of **your software** (1C, ERP), against which the lines of recognized invoices are matched. Your code (`externalId`) is the key: sending it again updates the item instead of duplicating it. Send it in batches of at most 1000 items. Nothing is deleted — send an item taken out of use with `isActive: false`.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `items[]` | array | yes | 1–1000 items; each code only once per request. |\n| `items[].externalId` | string | yes | Your code for the item, at most 100 characters. |\n| `items[].name` | string | yes, on active ones | At most 500 characters. |\n| `items[].unit` | string | no | Your unit of measure, at most 50; it is not converted. |\n| `items[].barcodes` | string[] | no | Barcodes. Only those with 8–14 digits are kept; the rest are counted in `ignoredBarcodes`. |\n| `items[].isActive` | boolean | no | Default `true`. `false` = retired (no longer offered). |\n| `items[].nomenclatureType` | string | no | Your nomenclature type (e.g. Dairy), at most 200. Without it, the item has no type. It comes back under the same name in the list, on `match` and in the export. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `created`, `updated`, `unchanged`, `deactivated` | integer | What happened to the items you sent. |\n| `ignoredBarcodes` | integer | Barcodes left out (not 8–14 digits). |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `DOCAI_CATALOG_API_TOO_MANY_ITEMS`, `DOCAI_CATALOG_API_DUPLICATE_CODE`, field validation | „O cerere poate avea cel mult 1.000 de poziții, iar aceasta are 1.200. Trimiteți nomenclatorul în mai multe cereri.” (A request can have at most 1,000 items, and this one has 1,200. Send the Nomenclature in several requests.) |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Once your software maintains the Nomenclature through the API, uploading it from Excel in the Control Panel is refused — otherwise the next sync would overwrite what a person uploaded.\n- Items created by people in PosFix have `DX-…` codes (they reach you through the list, `match` and the export). A request that hits the code of such an item does not overwrite it — it is counted under `unchanged`. Do not use the `DX-` prefix for your own codes.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"externalId\": \"LAC-0012\",\n      \"name\": \"Lapte Lactis 2,5% 1l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [\n        \"4840811000181\"\n      ],\n      \"nomenclatureType\": \"Lactate\"\n    },\n    {\n      \"externalId\": \"LAC-0099\",\n      \"isActive\": false\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "catalog",
                    "items"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"items\": [\n    {\n      \"externalId\": \"LAC-0012\",\n      \"name\": \"Lapte Lactis 2,5% 1l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [\n        \"4840811000181\"\n      ],\n      \"nomenclatureType\": \"Lactate\"\n    },\n    {\n      \"externalId\": \"LAC-0099\",\n      \"isActive\": false\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"created\": 1,\n  \"updated\": 0,\n  \"unchanged\": 0,\n  \"deactivated\": 1,\n  \"ignoredBarcodes\": 0\n}"
            }
          ],
          "_anchorName": "Nomenclator — încărcare / actualizare"
        },
        {
          "name": "Nomenclature — list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items?includeInactive=false&page=1&pageSize=100",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "catalog",
                "items"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "description": "Text from the code or name; a full barcode is matched exactly.",
                  "disabled": true
                },
                {
                  "key": "includeInactive",
                  "value": "false",
                  "description": "Withdrawn items too."
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Starting at 1."
                },
                {
                  "key": "pageSize",
                  "value": "100",
                  "description": "1–200, default 100."
                }
              ]
            },
            "description": "**Required scope:** `docai:catalog`\n\nWhat PosFix received from your Nomenclature, in alphabetical order — to check the sync. The list also includes items created by people in PosFix (`DX-…` codes), so your software can take them over.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].externalId`, `name`, `unit`, `barcodes` |  | As you sent them (or as the person created them, for `DX-…`). |\n| `items[].nomenclatureType` | string? | The nomenclature type; `null` when it has none. |\n| `items[].isActive` | boolean | The item is offered during matching. |\n| `items[].usageCount` | integer | On how many lines people chose it during review. |\n| `items[].updatedAt` | string | Last change, ISO 8601 UTC. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items?includeInactive=false&page=1&pageSize=100",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "catalog",
                    "items"
                  ],
                  "query": [
                    {
                      "key": "search",
                      "value": "",
                      "description": "Text din cod sau denumire; un cod de bare întreg se caută exact.",
                      "disabled": true
                    },
                    {
                      "key": "includeInactive",
                      "value": "false",
                      "description": "Și pozițiile retrase."
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "100",
                      "description": "1–200, implicit 100."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"externalId\": \"DX-000001\",\n      \"name\": \"Chefir Lactis 2,5% 0,5l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [],\n      \"isActive\": true,\n      \"usageCount\": 1,\n      \"updatedAt\": \"2026-11-02T09:12:40Z\",\n      \"nomenclatureType\": \"Lactate\"\n    },\n    {\n      \"externalId\": \"LAC-0012\",\n      \"name\": \"Lapte Lactis 2,5% 1l\",\n      \"unit\": \"buc\",\n      \"barcodes\": [\n        \"4840811000181\"\n      ],\n      \"isActive\": true,\n      \"usageCount\": 7,\n      \"updatedAt\": \"2026-09-27T16:02:11Z\",\n      \"nomenclatureType\": \"Lactate\"\n    }\n  ],\n  \"totalCount\": 2,\n  \"page\": 1,\n  \"pageSize\": 50,\n  \"totalPages\": 1,\n  \"hasNextPage\": false,\n  \"hasPreviousPage\": false,\n  \"summary\": null,\n  \"filterableColumns\": null,\n  \"ignoredFilters\": null\n}"
            }
          ],
          "_anchorName": "Nomenclator — listă"
        },
        {
          "name": "Nomenclature — retire",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items/:externalId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "catalog",
                "items",
                ":externalId"
              ],
              "variable": [
                {
                  "key": "externalId",
                  "value": "LAC-0099",
                  "description": "The item code in your system (URL-encoded)."
                }
              ]
            },
            "description": "**Required scope:** `docai:catalog`\n\nRetires an item (same as `isActive: false`). It does not delete it: invoices already matched to it stay linked to its code. Returns the item.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The code is not in the nomenclature you sent. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/catalog/items/:externalId",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "catalog",
                    "items",
                    ":externalId"
                  ],
                  "variable": [
                    {
                      "key": "externalId",
                      "value": "LAC-0099",
                      "description": "Codul poziției la tine (URL-encoded)."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"externalId\": \"LAC-0099\",\n  \"name\": \"Chefir 1l\",\n  \"unit\": \"buc\",\n  \"barcodes\": [],\n  \"isActive\": false,\n  \"usageCount\": 0,\n  \"updatedAt\": \"2026-09-28T08:00:00Z\",\n  \"nomenclatureType\": null\n}"
            }
          ],
          "_anchorName": "Nomenclator — retragere"
        },
        {
          "name": "Send an invoice for recognition",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions"
              ]
            },
            "description": "**Required scope:** `docai:recognize`\n\nPuts the file in the recognition queue and responds immediately, **without** waiting for it to be read (reading usually takes 20–90 seconds). You receive the result as a `docai.recognition.completed` / `docai.recognition.failed` webhook, or read it by id.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `file` | file (multipart) | yes | PDF up to 12 MB; photo up to 20 MB. The type is read from the content (PDF, JPEG, PNG, GIF, WebP); the part header only counts when the content is not recognised. |\n| `documentKind` | string | no | `supplier_invoice` (an invoice received from a supplier, default) or `sales_invoice`. |\n| `externalRef` | string | no | Your key (e.g. the record number in your system). A second submission with the same key returns the existing recognition, at no extra cost. |\n\n### Response\n\n**202** — the file has been queued. **200** — the recognition already existed: the same `externalRef`, the same file still in progress (`existing: true`), or a file already recognized, copied at no cost (`duplicate: true`, `status: completed`).\n\n| Field | Type | Description |\n|---|---|---|\n| `id` | string (uuid) | Recognition ID. |\n| `status` | string | `queued` \\| `processing` \\| `completed` \\| `failed`. |\n| `externalRef` | string? | Your key. |\n| `existing` | boolean | Nothing new was queued. |\n| `duplicate` | boolean | The result was copied from the same file, already recognized. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `DOCAI_FILE_REQUIRED`, `DOCAI_FILE_TYPE_NOT_SUPPORTED`, `DOCAI_FILE_TOO_LARGE`, `DOCAI_PDF_TOO_LARGE`, `DOCAI_DOCUMENT_KIND_UNKNOWN` | „Tipul de fișier „application/zip” nu este acceptat. Se acceptă JPEG, PNG, WebP, GIF și PDF.” (File type ‘application/zip’ is not accepted. JPEG, PNG, WebP, GIF and PDF are accepted.) |\n| 402 | `DOCAI_FREE_TRIAL_USED`, `DOCAI_MONTHLY_LIMIT_REACHED`, `DOCAI_BATCH_EXCEEDS_MONTHLY_LIMIT`, `DOCAI_LIMIT_DENIED` | The recognitions have run out: without a contract, the 3 free ones; with a contract, its monthly cap (a contract without a cap never refuses). Plain format, with `title: \"Payment Required\"`. |\n| 422 | `DOCAI_EXTERNAL_REF_REUSED` | “The external key ‘INV-2026-000812’ has already been used for another document. Use a new key for each document.” |\n| 422 | `DOCAI_EXTERNAL_REF_FILE_IN_FLIGHT` | The same file is being read right now under another recognition. Resend it with the same key after that one finishes: you get a free copy under your key. |\n| 422 | `DOCAI_DISABLED`, `DOCAI_API_KEY_MISSING` | Recognition is disabled on the platform. Retry later. |\n| 503 | `SERVICE_UNAVAILABLE` | Too many simultaneous submissions, or the service does not respond. Resend with the same `externalRef` — you are not charged twice. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Each **new** file uses one recognition: the first 3 are free, then only as many as the merchant's contract with PosFix provides (no cap in the contract = unlimited). The same file sent again is not paid for again, but without `externalRef` it becomes a new recognition (a copy), with its own id and webhook — always send a key.\n- We **do not retry** the submission automatically: on a timeout or `503`, resend it yourself, with the same `externalRef`.\n- At most two simultaneous uploads per key; the rest wait their turn. Don't send dozens of files in parallel — the queue reads them two at a time anyway.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": [],
                  "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                },
                {
                  "key": "documentKind",
                  "type": "text",
                  "value": "supplier_invoice",
                  "description": "supplier_invoice (implicit) | sales_invoice"
                },
                {
                  "key": "externalRef",
                  "type": "text",
                  "value": "INV-2026-000812",
                  "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                }
              ]
            }
          },
          "response": [
            {
              "name": "202",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "Accepted",
              "code": 202,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"queued\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"existing\": false,\n  \"duplicate\": false\n}"
            },
            {
              "name": "200 already exists",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"completed\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"existing\": true,\n  \"duplicate\": false\n}"
            },
            {
              "name": "402",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "Payment Required",
              "code": 402,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/402\",\n  \"title\": \"Payment Required\",\n  \"status\": 402,\n  \"detail\": \"Ați atins limita lunară din contract: 100 din 100 recunoașteri. Pentru mai multe, contactați PosFix ca să mărească limita din contract.\",\n  \"errorCode\": \"DOCAI_MONTHLY_LIMIT_REACHED\",\n  \"params\": {\n    \"used\": 100,\n    \"limit\": 100\n  }\n}"
            },
            {
              "name": "400 file type",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ]
                },
                "body": {
                  "mode": "formdata",
                  "formdata": [
                    {
                      "key": "file",
                      "type": "file",
                      "src": [],
                      "description": "PDF (≤ 12 MB) sau fotografie JPEG/PNG/WebP/GIF (≤ 20 MB)."
                    },
                    {
                      "key": "documentKind",
                      "type": "text",
                      "value": "supplier_invoice",
                      "description": "supplier_invoice (implicit) | sales_invoice"
                    },
                    {
                      "key": "externalRef",
                      "type": "text",
                      "value": "INV-2026-000812",
                      "description": "Cheia ta de idempotență, ≤ 100 de caractere."
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/400\",\n  \"title\": \"DOCAI_FILE_TYPE_NOT_SUPPORTED\",\n  \"status\": 400,\n  \"detail\": \"Tipul de fișier „application/zip” nu este acceptat. Se acceptă JPEG, PNG, WebP, GIF și PDF.\",\n  \"errorCode\": \"DOCAI_FILE_TYPE_NOT_SUPPORTED\"\n}"
            }
          ],
          "_anchorName": "Trimite o factură la recunoaștere"
        },
        {
          "name": "Recognitions — list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions?status=completed&acknowledged=false&page=1&pageSize=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "completed",
                  "description": "queued | processing | completed | failed"
                },
                {
                  "key": "acknowledged",
                  "value": "false",
                  "description": "false = only those not yet picked up"
                },
                {
                  "key": "source",
                  "value": "",
                  "description": "api | control_panel",
                  "disabled": true
                },
                {
                  "key": "reviewed",
                  "value": "",
                  "description": "true = only those reviewed by a person in the Control Panel",
                  "disabled": true
                },
                {
                  "key": "createdFrom",
                  "value": "",
                  "description": "ISO 8601, inclusive",
                  "disabled": true
                },
                {
                  "key": "createdTo",
                  "value": "",
                  "description": "ISO 8601, exclusive",
                  "disabled": true
                },
                {
                  "key": "externalRef",
                  "value": "",
                  "description": "Your key",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Starting at 1."
                },
                {
                  "key": "pageSize",
                  "value": "50",
                  "description": "1–200, default 50."
                }
              ]
            },
            "description": "**Required scope:** `docai:read`\n\nThe merchant's recognitions, **from any source** — both those you sent and those made by people in the Control Panel — oldest first. The usual pickup: `status=completed&acknowledged=false`, the details of each one by id, then `POST …/ack` once you have imported it.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].id`, `status`, `source`, `externalRef`, `fileName` |  | As in the details. |\n| `items[].reviewed` | boolean | A person reviewed it in the Control Panel. |\n| `items[].acknowledgedAt` | string? | When you acknowledged the pickup. |\n| `items[].supplierName`, `supplierIdno`, `documentSeries`, `documentNumber`, `issueDate`, `total`, `currency`, `lineCount` |  | Invoice summary, for display; the import data is in the details. |\n| `totalCount`, `page`, `pageSize`, `totalPages`, `hasNextPage` |  | Pagination. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `COMMON_INVALID_REQUEST_FIELD`, `COMMON_BETWEEN` | Unknown filter value, page out of range. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- As an inbox (`acknowledged=false`), **always read page 1**: after each `ack` the list gets shorter, and a recognition finished later can have an older creation date than the ones already read. With `page++` you would skip invoices."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions?status=completed&acknowledged=false&page=1&pageSize=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions"
                  ],
                  "query": [
                    {
                      "key": "status",
                      "value": "completed",
                      "description": "queued | processing | completed | failed"
                    },
                    {
                      "key": "acknowledged",
                      "value": "false",
                      "description": "false = doar cele încă nepreluate"
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "api | control_panel",
                      "disabled": true
                    },
                    {
                      "key": "reviewed",
                      "value": "",
                      "description": "true = doar cele verificate de un om în panou",
                      "disabled": true
                    },
                    {
                      "key": "createdFrom",
                      "value": "",
                      "description": "ISO 8601, inclusiv",
                      "disabled": true
                    },
                    {
                      "key": "createdTo",
                      "value": "",
                      "description": "ISO 8601, exclusiv",
                      "disabled": true
                    },
                    {
                      "key": "externalRef",
                      "value": "",
                      "description": "Cheia ta",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "De la 1."
                    },
                    {
                      "key": "pageSize",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n      \"status\": \"completed\",\n      \"source\": \"api\",\n      \"externalRef\": \"INV-2026-000812\",\n      \"fileName\": \"lactis-1791127.pdf\",\n      \"documentKind\": \"supplier_invoice\",\n      \"createdAt\": \"2026-09-28T09:14:02Z\",\n      \"completedAt\": \"2026-09-28T09:14:41Z\",\n      \"reviewed\": false,\n      \"acknowledgedAt\": null,\n      \"supplierName\": \"Lactis SRL\",\n      \"supplierIdno\": \"1003600012345\",\n      \"documentSeries\": \"AAY\",\n      \"documentNumber\": \"1791127\",\n      \"issueDate\": \"2026-09-21\",\n      \"total\": 410.31,\n      \"currency\": \"MDL\",\n      \"lineCount\": 1\n    }\n  ],\n  \"totalCount\": 1,\n  \"page\": 1,\n  \"pageSize\": 50,\n  \"totalPages\": 1,\n  \"hasNextPage\": false,\n  \"hasPreviousPage\": false,\n  \"summary\": null,\n  \"filterableColumns\": null,\n  \"ignoredFilters\": null\n}"
            }
          ],
          "_anchorName": "Recunoașteri — listă"
        },
        {
          "name": "Recognition — details",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                  "description": "The recognition id."
                }
              ]
            },
            "description": "**Required scope:** `docai:read`\n\nThe status and, once ready, the invoice as read. If a person reviewed it in the Control Panel (`reviewed: true`), `invoice` is their version, and on the lines `match` is the item they chose. Otherwise each line comes with `suggestions` — the matches proposed from your Nomenclature, most confident first.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `status` | string | `queued` \\| `processing` \\| `completed` \\| `failed`. |\n| `error` | object? | On `failed`: `code` (stable) and `message` (text, in Romanian). |\n| `confidence` | number? | 0..1, from arithmetic checks (lines, totals, IDNO), not from the model. Below 0.8 it is worth a look. |\n| `duplicateOf` | string? | ID of the recognition the result was copied from. |\n| `invoice` | object? | Only on `completed`: header, `supplier`/`buyer`/`carrier`, `lines[]`, `totals`. |\n| `invoice.lines[].match` | object? | `{type, id, name, unit, nomenclatureType}`; `type` = `external` (`id` = your code, or `DX-…` for an item created in PosFix) or `platform`. |\n| `invoice.lines[].suggestions[]` | array | `{externalId, name, unit, score, method, unitMismatch}`; `method`: `learned` (already confirmed for the same supplier), `barcode`, `name`. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | Not found in the merchant's data. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- A `failed` recognition with `DOCAI_RESPONSE_TRUNCATED` is an invoice too long for the current budget; don't import anything from it.\n- Fields the model couldn't read come as `null` — don't replace them with zero."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                      "description": "Id-ul recunoașterii."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"completed\",\n  \"source\": \"api\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"fileName\": \"lactis-1791127.pdf\",\n  \"documentKind\": \"supplier_invoice\",\n  \"createdAt\": \"2026-09-28T09:14:02Z\",\n  \"completedAt\": \"2026-09-28T09:14:41Z\",\n  \"reviewed\": false,\n  \"reviewedAt\": null,\n  \"acknowledgedAt\": null,\n  \"confidence\": 0.96,\n  \"duplicateOf\": null,\n  \"error\": null,\n  \"invoice\": {\n    \"documentType\": \"Factură fiscală\",\n    \"series\": \"AAY\",\n    \"number\": \"1791127\",\n    \"issueDate\": \"2026-09-21\",\n    \"deliveryDate\": \"2026-09-21\",\n    \"currency\": \"MDL\",\n    \"waybillNumber\": null,\n    \"supplier\": {\n      \"name\": \"Lactis SRL\",\n      \"idno\": \"1003600012345\",\n      \"vatCode\": \"0401234\",\n      \"address\": \"mun. Chișinău, str. Uzinelor 1\",\n      \"iban\": \"MD24AG000000022512345678\",\n      \"bank\": \"Moldova Agroindbank\",\n      \"bankCode\": \"AGRNMD2X\"\n    },\n    \"buyer\": {\n      \"name\": \"Aroma Corner SRL\",\n      \"idno\": \"1015600098765\",\n      \"vatCode\": null,\n      \"address\": null,\n      \"iban\": null,\n      \"bank\": null,\n      \"bankCode\": null\n    },\n    \"carrier\": null,\n    \"lines\": [\n      {\n        \"index\": 0,\n        \"barcode\": \"4840811000181\",\n        \"tariffCode\": null,\n        \"name\": \"Lapte 2,5% 1L\",\n        \"unit\": \"buc\",\n        \"quantity\": 24,\n        \"unitPriceExclVat\": 15.83,\n        \"amountExclVat\": 379.92,\n        \"vatRate\": 8,\n        \"vatAmount\": 30.39,\n        \"amountInclVat\": 410.31,\n        \"match\": null,\n        \"suggestions\": [\n          {\n            \"externalId\": \"LAC-0012\",\n            \"name\": \"Lapte Lactis 2,5% 1l\",\n            \"unit\": \"buc\",\n            \"score\": 100,\n            \"method\": \"barcode\",\n            \"unitMismatch\": false\n          },\n          {\n            \"externalId\": \"LAC-0013\",\n            \"name\": \"Lapte Lactis 3,2% 1l\",\n            \"unit\": \"buc\",\n            \"score\": 67,\n            \"method\": \"name\",\n            \"unitMismatch\": false\n          }\n        ]\n      }\n    ],\n    \"totals\": {\n      \"amountExclVat\": 379.92,\n      \"vatAmount\": 30.39,\n      \"total\": 410.31\n    },\n    \"notes\": null\n  }\n}"
            }
          ],
          "_anchorName": "Recunoaștere — detaliu"
        },
        {
          "name": "Recognition — acknowledge pickup",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id/ack",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "docai",
                "recognitions",
                ":id",
                "ack"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                  "description": "The recognition id."
                }
              ]
            },
            "description": "**Required scope:** `docai:read`\n\nMarks the invoice as picked up by your system: the `acknowledged=false` list no longer shows it. Idempotent — a second confirmation keeps the date of the first. The confirmation belongs to the merchant, not to the key: two systems that pick up the same invoices must coordinate with each other.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 422 | `DOCAI_ACK_NOT_READY` | „Recunoașterea nu s-a terminat încă. Confirmați preluarea după ce ajunge la „completed” sau „failed”.” (Recognition has not finished yet. Confirm the pickup after it reaches ‘completed’ or ‘failed’.) |\n| 409 | `CONCURRENCY_CONFLICT` | The invoice was modified at the same moment (review in the panel). Repeat the request. |\n| 404 | — | Not found in the merchant's data. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 403 | — | The key lacks the required scope, or the “Integration API” module is not active. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/docai/recognitions/:id/ack",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "docai",
                    "recognitions",
                    ":id",
                    "ack"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46",
                      "description": "Id-ul recunoașterii."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6a1f3c9e-2b4d-4e8f-a1c3-5d7e9f0b2a46\",\n  \"status\": \"completed\",\n  \"source\": \"api\",\n  \"externalRef\": \"INV-2026-000812\",\n  \"fileName\": \"lactis-1791127.pdf\",\n  \"documentKind\": \"supplier_invoice\",\n  \"createdAt\": \"2026-09-28T09:14:02Z\",\n  \"completedAt\": \"2026-09-28T09:14:41Z\",\n  \"reviewed\": false,\n  \"acknowledgedAt\": \"2026-09-28T09:20:00Z\",\n  \"supplierName\": \"Lactis SRL\",\n  \"supplierIdno\": \"1003600012345\",\n  \"documentSeries\": \"AAY\",\n  \"documentNumber\": \"1791127\",\n  \"issueDate\": \"2026-09-21\",\n  \"total\": 410.31,\n  \"currency\": \"MDL\",\n  \"lineCount\": 1\n}"
            }
          ],
          "_anchorName": "Recunoaștere — confirmă preluarea"
        }
      ],
      "_anchorName": "7. Recunoaștere facturi"
    },
    {
      "name": "8. Loyalty",
      "description": "The merchant's loyalty program, from your system: you enroll customers, read their balance, accrue points or bonus on paid orders, let them use their bonus in lei on orders and take it back on returns. It needs the `loyalty:*` scopes **and** the “Loyalty Programs” module active for the merchant. The member is identified by the card number (13 digits).\n\nEach program has a type (`mechanic`): **points** (`points`), **stamps** (`stamps`) or **bonus in lei** (`money`). The merchant sets its rules in the Control Panel; you send only the amount paid, and the platform works out what the member gets, the same as at the till. Types and statuses are explained under “Programmes”.\n\n### Two ways to work with loyalty from an online store\n\nPick one of them per order, not both: otherwise the member is credited twice.\n\n| | **A. The store keeps its orders** | **B. Orders go through PosFix** |\n|---|---|---|\n| Where the order lives | In your system; PosFix does not see it. | In PosFix, like any customer order (“4. Orders”). |\n| What you call | The requests in this folder: quote, bonus use, accrual, cancellation. | Only “Create order” with `loyalty: {cardNumber, bonusAmount}` and, when needed, “Cancel order”. |\n| Bonus used | Taken from the card immediately, on “Use bonus on an order”. | Held on creation and taken when the order is completed, only on the goods delivered. |\n| Points and cashback | You grant them, after payment (“Accrue on an order”). | PosFix grants them on completion, on the delivered value. |\n| Payment failed, customer gives up | You cancel the order without an amount (“Cancel an order (return)”). | “Cancel order” from “4. Orders”: the bonus comes back on its own. |\n| The return | You cancel the returned part, with an amount. | The merchant handles it, in the panel. |\n| The customer in PosFix | The member stays only in loyalty. | The member is linked to the order's customer; the next orders go to the same customer, even if the phone changes. |\n| Scopes | `loyalty:read`, `loyalty:redeem`, `loyalty:earn`. | `orders:write`, `loyalty:earn` and, with a bonus, `loyalty:redeem`. |\n\nIn both, the customer is identified by the card number, which you get from “Enrol a member” and keep next to your customer record. The phone only serves the first match.\n\n### Option A, step by step\n\nFor a store that keeps its own orders and payments and calls the requests below directly:\n\n1. **The customer identifies themselves.** If they have no card, enrol them (“Enrol a member”) and keep `cardNumber` next to your customer record. If they have one, find them by card (“Member by card”) or by phone (“Members — list”, with `search`).\n2. **The cart.** Show how much bonus they can use (“How much bonus can be used”, with the order amount). This request deducts nothing.\n3. **Placing the order.** If the customer uses bonus, deduct it (“Use bonus on an order”, with the `orderId` of your order). The bonus leaves the card immediately, for every channel: it can no longer be spent at the till.\n4. **The payment succeeded.** Accrue the order (“Accrue on an order”) on the amount paid in money, that is without the part paid with bonus.\n5. **The payment failed or the customer gives up.** Cancel the order without an amount (“Cancel an order (return)”): the whole bonus comes back. A cancelled order can no longer use bonus; a new payment attempt is a new order, with a different `orderId`.\n6. **The return.** Cancel the returned part, with `amount`: what the member earned on it is taken back and the bonus share used on it comes back.\n\nRequests that change the balance are idempotent: on a timeout or a `503`, resend them with the same key (`orderId` or `reversalId`) and the operation happens only once. On a `409`, resend the same way: another request on the same card went through at the same moment, and the new response takes it into account.",
      "item": [
        {
          "name": "Programmes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/programs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "programs"
              ]
            },
            "description": "**Required scope:** `loyalty:read`\n\nThe merchant's loyalty programs. This is where you get the `programId` for enrollment. The rules (points per leu, bonus percentage, tiers, rewards) are set by the merchant in the Control Panel and are not in the response: you send the amount paid, the platform does the calculation, and the accrual response tells you what the member received.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].id` | string (uuid) | The programme id. |\n| `items[].name` | string | The name. |\n| `items[].mechanic` | string | The program type — what the member collects: `points` (points), `stamps` (stamps) or `money` (bonus in lei). What each one means: the first table below. |\n| `items[].status` | string | The program status: `active`, `draft`, `paused` or `closed`. What you can do in each: the second table below. |\n| `items[].currency` | string | The program currency, `MDL`: of the amounts sent on accrual and of the bonus in lei. |\n| `items[].acceptsEnrollment` | boolean | `true` as long as the program is not `closed` — you can also enroll into a `draft` or `paused` program. |\n| `items[].hasBonus` | boolean | `true` = members have a wallet in lei (the member's `bonus` field). Always `true` for `money`; for `points` — when the merchant has also added a bonus in lei, and the member then collects points **and** lei. |\n\n### Program type (`mechanic`)\n\n| Value | What the member collects | What an order brings |\n|---|---|---|\n| `points` | Points, exchanged for rewards at the till. | Amount paid × the program's points per leu × the tier multiplier, rounded down. At one point per 10 lei, the 749.90 lei order brings 74 points. |\n| `stamps` | Stamps, exchanged for a reward (“the 10th coffee free”). | The same number of stamps on every order, whatever the amount — usually one. |\n| `money` | Bonus in lei, in a wallet; spent at payment. | A percentage of the amount paid (cashback), rounded down to the ban. At 5%, the 749.90 lei order brings 37.49 lei. |\n\n### Program status (`status`)\n\n| Value | Meaning | Enrollment | Accrual |\n|---|---|---|---|\n| `active` | The program is running. | yes | yes, by the rules |\n| `draft` | Created, not yet launched by the merchant. | yes | answers `201`, but with 0 points, 0 stamps, 0 lei; only the purchase is counted |\n| `paused` | Stopped by the merchant (“Deactivate” in the Control Panel); can be restarted. | yes | same as `draft` |\n| `closed` | Closed for good. | no, `422` | no, `422`; reversing an old accrual still works |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- An order sent while the program is `draft` or `paused` stays without points after the restart too: sending it again is a repeat (`duplicate: true`), not a new accrual."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/programs",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "programs"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n      \"name\": \"Aroma Club\",\n      \"mechanic\": \"points\",\n      \"status\": \"active\",\n      \"currency\": \"MDL\",\n      \"acceptsEnrollment\": true,\n      \"hasBonus\": false\n    }\n  ]\n}"
            }
          ],
          "_anchorName": "Programe"
        },
        {
          "name": "Enrol a member",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members"
              ]
            },
            "description": "**Required scope:** `loyalty:write`\n\nEnrols a customer in the programme and returns the member, with the card number and the links to save it on the phone. The phone is the key: if it is already a member of the programme, you get the existing member, unchanged.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `programId` | string (uuid) | yes | A programme from “Programmes”. |\n| `firstName`, `lastName` | string | yes | At most 100 characters each. |\n| `phone` | string | yes | 8–15 digits, with or without a leading `+`, no spaces: `+37369123456`. It is stored and returned as digits only (`37369123456`). It cannot be changed after enrollment. |\n| `email` | string | no | At most 200 characters. |\n\n### Response\n\n**201** — new member (`created: true`). **200** — the phone was already in the programme (`created: false`).\n\n| Field | Type | Description |\n|---|---|---|\n| `created` | boolean | `false` = the member already existed. |\n| `member` | object | The member, as in “Member by card”. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `LOYALTY_MEMBER_PHONE_FORMAT`, field validation | “Enter the phone in international format (+37369123456) or national format, 8–15 digits.” |\n| 422 | `LOYALTY_PROGRAM_NOT_FOUND` | “The loyalty program was not found. It may have been deleted; reload the page.” — `programId` is not one of the merchant's programmes. |\n| 422 | `LOYALTY_PROGRAM_CLOSED` | “The loyalty program \"Aroma Club\" is closed — no new members can be enrolled.” |\n| 422 | `DUPLICATE_MEMBER_EMAIL` | “A member with this email address is already enrolled in the program.” |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- A new member triggers the `loyalty.member.enrolled` webhook — so does one enrolled at the till or in the Control Panel.\n- If the program gives a welcome bonus, a `loyalty.balance.changed` is sent too, and it can arrive before `loyalty.member.enrolled`: the delivery order is not guaranteed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"created\": true,\n  \"member\": {\n    \"cardNumber\": \"2000000012345\",\n    \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n    \"programName\": \"Aroma Club\",\n    \"firstName\": \"Ana\",\n    \"lastName\": \"Rusu\",\n    \"phone\": \"37369123456\",\n    \"email\": \"ana.rusu@example.md\",\n    \"tier\": null,\n    \"balance\": {\n      \"unit\": \"points\",\n      \"value\": 0,\n      \"lifetime\": 0\n    },\n    \"bonus\": null,\n    \"totalSpent\": 0,\n    \"purchaseCount\": 0,\n    \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n    \"lastActivityAt\": null,\n    \"wallet\": {\n      \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n      \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n    }\n  }\n}"
            },
            {
              "name": "200 existing",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"created\": false,\n  \"member\": {\n    \"cardNumber\": \"2000000012345\",\n    \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n    \"programName\": \"Aroma Club\",\n    \"firstName\": \"Ana\",\n    \"lastName\": \"Rusu\",\n    \"phone\": \"37369123456\",\n    \"email\": \"ana.rusu@example.md\",\n    \"tier\": \"Argint\",\n    \"balance\": {\n      \"unit\": \"points\",\n      \"value\": 120,\n      \"lifetime\": 340\n    },\n    \"bonus\": null,\n    \"totalSpent\": 2450.5,\n    \"purchaseCount\": 7,\n    \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n    \"lastActivityAt\": \"2026-10-27T15:42:10Z\",\n    \"wallet\": {\n      \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n      \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n    }\n  }\n}"
            },
            {
              "name": "400 phone",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"phone\": [\n      \"Telefonul trebuie scris în format internațional (+37369123456) sau național, din 8–15 cifre.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"phone\": [\n      {\n        \"code\": \"LOYALTY_MEMBER_PHONE_FORMAT\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            },
            {
              "name": "422 programme closed",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"+37369123456\",\n  \"email\": \"ana.rusu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_PROGRAM_CLOSED\",\n  \"status\": 422,\n  \"detail\": \"Programul de fidelitate „Aroma Club” este închis — nu se mai pot înscrie membri noi.\",\n  \"errorCode\": \"LOYALTY_PROGRAM_CLOSED\"\n}"
            }
          ],
          "_anchorName": "Înscrie un membru"
        },
        {
          "name": "Member by card",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "The card number, 13 digits."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:read`\n\nThe member with the current balance, the bonus in lei and the links to save the card on the phone.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `cardNumber` | string | The virtual card number, 13 digits — the member's identifier in the API. |\n| `programId`, `programName` | string | The program the member is enrolled in: its id from “Programmes” and its name. |\n| `firstName`, `lastName`, `phone`, `email` |  | The member's details; `email` can be `null`. |\n| `tier` | string? | The member's tier (for example “Silver”). It goes up on its own, by the points collected since enrollment (`balance.lifetime`), and can bring more points per leu or a discount at the till, as the merchant has set it. `null` when the program has no tiers or the member has not reached the first one. |\n| `balance.unit` | string | What the balance counts, the same value as the program's `mechanic`: `points` (points), `stamps` (stamps) or `money` (lei). |\n| `balance.value` | number | The current balance: what the member has to spend. For `money` it is the amount in the wallet, in lei. It is never negative: on a return only what is still on the card is taken back, and what the member had already spent is not claimed. |\n| `balance.lifetime` | number | How much the member has collected since enrollment. It does not go down when they spend. For `points` and `stamps` it goes down when an accrual is taken back on a return; for `money` it is all the bonus ever received and does not go down on a return either. |\n| `bonus` | object? | The wallet in lei, separate from the balance — only when the program has a bonus in lei (`hasBonus: true`), otherwise `null`. For a `money` program it has the same figures as `balance`. |\n| `bonus.amount` | number | Lei in the wallet. It is never negative: bonus taken back on a return that had already been spent is not claimed. |\n| `bonus.available` | number | What the member can spend right now: `amount` minus the sum a till has reserved on a receipt in progress and minus the bonus just earned that is still locked for a few hours (if the program locks it). |\n| `bonus.currency` | string | The wallet currency, `MDL`. |\n| `bonus.nextExpiresAt` | string? | When the nearest bonus received expires (the bonus has a validity period, set in the program). `null` = nothing expires. |\n| `totalSpent`, `purchaseCount` | number, integer | How much they bought with the card — at the till and through the orders accrued via the API, after reversals. |\n| `enrolledAt`, `lastActivityAt` | string | ISO 8601 UTC; `lastActivityAt` can be `null`. |\n| `wallet` | object? | `{googleSaveUrl, applePassUrl}` — the links to save the card in the phone. Only on enrollment and in “Member by card”; in the list and after an update it is `null`. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT` | “The card number has exactly 13 digits, without spaces.” |\n| 404 | — | The card does not belong to the merchant. Empty body. |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"programName\": \"Aroma Club\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu\",\n  \"phone\": \"37369123456\",\n  \"email\": \"ana.rusu@example.md\",\n  \"tier\": \"Argint\",\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 120,\n    \"lifetime\": 340\n  },\n  \"bonus\": null,\n  \"totalSpent\": 2450.5,\n  \"purchaseCount\": 7,\n  \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n  \"lastActivityAt\": \"2026-10-27T15:42:10Z\",\n  \"wallet\": {\n    \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n    \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n  }\n}"
            },
            {
              "name": "400 card",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"cardNumber\": [\n      \"Numărul cardului are exact 13 cifre, fără spații.\"\n    ]\n  },\n  \"errorCodes\": {\n    \"cardNumber\": [\n      {\n        \"code\": \"INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            },
            {
              "name": "404",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "Not Found",
              "code": 404,
              "_postman_previewlanguage": "text",
              "header": [],
              "body": ""
            }
          ],
          "_anchorName": "Membru după card"
        },
        {
          "name": "Members — list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members"
              ],
              "query": [
                {
                  "key": "programId",
                  "value": "",
                  "description": "Only the members of one programme; empty = all.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Phone, name or card number.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor from the previous page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, default 50."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:read`\n\nThe merchant's members, paginated with a cursor.\n\n### Query parameters\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `programId` | string (uuid) | all | Programme filter. |\n| `search` | string | — | Phone, name or card number; at most 100 characters. |\n| `cursor` | string | — | The `nextCursor` value from the previous page. |\n| `limit` | integer | 50 | 1–200. |\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[]` | array | The members, with the same fields as in “Member by card”; `wallet` is `null`. |\n| `nextCursor` | string? | Cursor for the next page; `null` on the last page. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `COMMON_BETWEEN`, `COMMON_MAX_LENGTH` | “The value must be between 1 and 200.” |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Go through the pages until `nextCursor: null`; the cursor is not built by hand."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members"
                  ],
                  "query": [
                    {
                      "key": "programId",
                      "value": "",
                      "description": "Doar membrii unui program; gol = toate.",
                      "disabled": true
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "Telefon, nume sau număr de card.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"cardNumber\": \"2000000012345\",\n      \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n      \"programName\": \"Aroma Club\",\n      \"firstName\": \"Ana\",\n      \"lastName\": \"Rusu\",\n      \"phone\": \"37369123456\",\n      \"email\": \"ana.rusu@example.md\",\n      \"tier\": \"Argint\",\n      \"balance\": {\n        \"unit\": \"points\",\n        \"value\": 120,\n        \"lifetime\": 340\n      },\n      \"bonus\": null,\n      \"totalSpent\": 2450.5,\n      \"purchaseCount\": 7,\n      \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n      \"lastActivityAt\": \"2026-10-27T15:42:10Z\"\n    }\n  ],\n  \"nextCursor\": null\n}"
            }
          ],
          "_anchorName": "Membri — listă"
        },
        {
          "name": "Update a member",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "The card number, 13 digits."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:write`\n\nChanges the member's name or email. Send only the fields to change; the missing ones stay as they are. The phone does not change.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `firstName`, `lastName` | string | no | If you send them, they cannot be empty; at most 100 characters. |\n| `email` | string | no | At most 200 characters. `\"\"` clears the email. |\n\n### Response\n\n**200** — the member after the change, as in “Member by card”, with `wallet: null`.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, field validation | Wrong card, empty name, invalid email. |\n| 404 | — | The card does not belong to the merchant. Empty body. |\n| 422 | `DUPLICATE_MEMBER_EMAIL` | “A member with this email address is already enrolled in the program.” |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lastName\": \"Rusu-Ciobanu\",\n  \"email\": \"ana.ciobanu@example.md\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"lastName\": \"Rusu-Ciobanu\",\n  \"email\": \"ana.ciobanu@example.md\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"programId\": \"6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f\",\n  \"programName\": \"Aroma Club\",\n  \"firstName\": \"Ana\",\n  \"lastName\": \"Rusu-Ciobanu\",\n  \"phone\": \"37369123456\",\n  \"email\": \"ana.ciobanu@example.md\",\n  \"tier\": \"Argint\",\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 120,\n    \"lifetime\": 340\n  },\n  \"bonus\": null,\n  \"totalSpent\": 2450.5,\n  \"purchaseCount\": 7,\n  \"enrolledAt\": \"2026-09-01T08:00:00Z\",\n  \"lastActivityAt\": \"2026-10-27T15:42:10Z\",\n  \"wallet\": {\n    \"googleSaveUrl\": \"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiJ9…\",\n    \"applePassUrl\": \"https://cp.posfix.md/loyalty-api/api/v1/loyalty/public/members/3e5f9a1c-…/apple-pass\"\n  }\n}"
            }
          ],
          "_anchorName": "Modifică un membru"
        },
        {
          "name": "Member history",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/transactions?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "transactions"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "nextCursor from the previous page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "1–200, default 50."
                }
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "The card number, 13 digits."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:read`\n\nThe movements on the card, newest first: earnings, spending, reversals, expiries, bonus in lei.\n\n### Response 200\n\n| Field | Type | Description |\n|---|---|---|\n| `items[].id` | string | The movement id. |\n| `items[].occurredAt` | string | ISO 8601 UTC. |\n| `items[].kind` | string | What happened; the values are in the table below. Those with the `bonus_` prefix touch the wallet in lei; the others are the purchases, returns and the points or stamps. |\n| `items[].source` | string | `pos` (receipt at the till), `api` (order via the API), `panel` (a person, in the Control Panel), `system` (welcome, expiry). |\n| `items[].points`, `stamps`, `bonus` | number? | What the row moved, with sign; `null` for what it does not touch. Lei are only on the `bonus_` rows: on a `money` program the `earn` or `reversal` row has all three `null` and carries only the amount, and the bonus comes on a separate row. |\n| `items[].purchaseAmount` | number? | The purchase amount (for `earn`) or the return amount (for `reversal`). |\n| `items[].orderId` | string? | Your order, on movements made via the API. |\n| `items[].receiptRef` | string? | The fiscal receipt code, on movements from the till. |\n| `nextCursor` | string? | Cursor for the next page; `null` on the last page. |\n\n### Movement kind (`kind`)\n\n| Value | Meaning |\n|---|---|\n| `earn` | A purchase (receipt at the till or order through the API), with the points or stamps received for it. For `money` only the amount; the bonus is the `bonus_grant` row. |\n| `reversal` | A return, with the points or stamps taken back. For `money` only the amount; the bonus taken back is the `bonus_revoke` row. |\n| `redeem` | Points or stamps exchanged for a reward. |\n| `adjustment` | A correction made by a person in the Control Panel (plus or minus). |\n| `expiry` | Points expired. |\n| `bonus_grant` | Bonus in lei received: cashback on a purchase, welcome bonus, campaign. |\n| `bonus_redeem` | Bonus spent at payment. |\n| `bonus_expiry` | Bonus expired. |\n| `bonus_restore` | Bonus returned to the wallet: a receipt paid with bonus was refunded. |\n| `bonus_revoke` | Bonus taken back: on a return, or corrected by a person in the Control Panel. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 404 | — | The card does not belong to the merchant. Empty body. |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |"
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/transactions?limit=50",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "transactions"
                  ],
                  "query": [
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "nextCursor din pagina precedentă.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200, implicit 50."
                    }
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"items\": [\n    {\n      \"id\": \"a71f0c2e-5b3d-4e8a-9c1f-2d4e6f8a0b1c\",\n      \"occurredAt\": \"2026-10-27T15:42:10Z\",\n      \"kind\": \"earn\",\n      \"source\": \"api\",\n      \"points\": 74,\n      \"stamps\": null,\n      \"bonus\": null,\n      \"purchaseAmount\": 749.9,\n      \"orderId\": \"WEB-10482\",\n      \"receiptRef\": null\n    },\n    {\n      \"id\": \"c93e1a4b-7d5f-4a2c-8e0b-1f3a5c7e9d2b\",\n      \"occurredAt\": \"2026-10-20T11:05:33Z\",\n      \"kind\": \"earn\",\n      \"source\": \"pos\",\n      \"points\": 23,\n      \"stamps\": null,\n      \"bonus\": null,\n      \"purchaseAmount\": 231,\n      \"orderId\": null,\n      \"receiptRef\": \"J403001234-0067-0012\"\n    }\n  ],\n  \"nextCursor\": null\n}"
            }
          ],
          "_anchorName": "Istoricul membrului"
        },
        {
          "name": "How much bonus can be used",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/bonus-quote?orderAmount=749.90&otherDiscount=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "bonus-quote"
              ],
              "query": [
                {
                  "key": "orderAmount",
                  "value": "749.90",
                  "description": "The order amount before the bonus, in lei."
                },
                {
                  "key": "otherDiscount",
                  "value": "0",
                  "description": "The discount already given on the order from other sources (coupon, promotion); absent = 0."
                }
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "The card number, 13 digits."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:read`\n\nHow much bonus in lei the member can use on an order, by the program's rules — the same as at the till: the cap per order (percentage or amount), the minimum order amount, stacking with other discounts. Nothing is deducted; show the customer in the cart how much they can use.\n\n### Response\n\n| Field | Type | Description |\n|---|---|---|\n| `cardNumber` | string | The member. |\n| `orderAmount` | number | The order amount the quote was calculated on. |\n| `available` | number | What can be spent on the card right now. |\n| `maxRedeemable` | number | How much can be used on this order: what is available, capped by the program's rules. 0 = nothing. |\n| `currency` | string? | The wallet currency, `MDL`. |\n| `refusal` | object? | Why the bonus cannot be used: `{code, message}`. `null` when `maxRedeemable` is above zero. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, field validation | “The amount must be greater than zero, with at most two decimals (for example 249.90).” |\n| 404 | — | The card does not belong to the merchant. Empty body. |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- The quote does not hold the money: between the quote and using it the customer may spend the bonus at the till. The final amount is checked by “Use bonus on an order”."
          },
          "response": [
            {
              "name": "200",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/bonus-quote?orderAmount=749.90&otherDiscount=0",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "bonus-quote"
                  ],
                  "query": [
                    {
                      "key": "orderAmount",
                      "value": "749.90",
                      "description": "Suma comenzii înainte de bonus, în lei."
                    },
                    {
                      "key": "otherDiscount",
                      "value": "0",
                      "description": "Reducerea deja dată pe comandă din alte surse (cupon, promoție); lipsă = 0."
                    }
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderAmount\": 749.9,\n  \"available\": 150,\n  \"maxRedeemable\": 149.98,\n  \"currency\": \"MDL\",\n  \"refusal\": null\n}"
            },
            {
              "name": "200 no bonus",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/bonus-quote?orderAmount=749.90&otherDiscount=0",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "bonus-quote"
                  ],
                  "query": [
                    {
                      "key": "orderAmount",
                      "value": "749.90",
                      "description": "Suma comenzii înainte de bonus, în lei."
                    },
                    {
                      "key": "otherDiscount",
                      "value": "0",
                      "description": "Reducerea deja dată pe comandă din alte surse (cupon, promoție); lipsă = 0."
                    }
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderAmount\": 749.9,\n  \"available\": 0,\n  \"maxRedeemable\": 0,\n  \"currency\": \"MDL\",\n  \"refusal\": {\n    \"code\": \"LOYALTY_QUOTE_NOTHING_AVAILABLE\",\n    \"message\": \"Clientul nu are bonus disponibil acum.\"\n  }\n}"
            }
          ],
          "_anchorName": "Cât bonus se poate folosi"
        },
        {
          "name": "Use bonus on an order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "redemptions"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "The card number, 13 digits."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:redeem`\n\nDeducts bonus in lei from the member's card for an order: the customer pays `orderAmount` minus `amount`. The same rules as at the till (cap, minimum, stacking), and the money is deducted at once, for all channels together — the same bonus cannot be spent both at the till and on the site. Send it when the customer places the order.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `orderId` | string | yes | Your order, at most 100 characters — the idempotency key. |\n| `amount` | number | yes | The bonus used, in lei: greater than zero, at most two decimals, at most `maxRedeemable` from the quote. |\n| `orderAmount` | number | yes | The order amount before the bonus, in lei. |\n| `otherDiscount` | number | no | The discount already given from other sources; absent = 0. It matters when the program does not stack the bonus with other discounts. |\n\n### Response\n\n**201** — bonus used now. **200** — the order had already used its bonus (`duplicate: true`), nothing moved.\n\n| Field | Type | Description |\n|---|---|---|\n| `cardNumber`, `orderId` | string | The member and the order. |\n| `duplicate` | boolean | `true` = the request had already been made; nothing moved now. |\n| `amount` | number | The bonus deducted from the card, in lei. |\n| `orderAmount` | number | The order amount before the bonus. |\n| `balance`, `bonusBalance` | object, object? | The member's balance after the operation, as for the member. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, field validation | “The amount must be greater than zero, with at most two decimals (for example 249.90).” |\n| 404 | — | The card does not belong to the merchant. Empty body. |\n| 409 | `CONCURRENCY_CONFLICT` | The same card spent bonus at the same moment on another channel, or the same order was sent twice at once. Resend the request: it sees the new balance and gets the right answer for the order. |\n| 422 | `LOYALTY_BONUS_HOLD_EXCEEDS_MAX` | “At most 149.98 MDL can be used on this receipt.” |\n| 422 | `LOYALTY_QUOTE_NOTHING_AVAILABLE` and the other quote reasons | “The customer has no bonus available right now.” |\n| 422 | `LOYALTY_ORDER_REDEMPTION_AMOUNT_MISMATCH` | “Order WEB-10482 has already used 100.00 of bonus. A different amount cannot be used on the same order — cancel the order and use the bonus on a new one.” |\n| 422 | `LOYALTY_ORDER_REDEEMED_ON_OTHER_CARD` | “Order WEB-10482 has already used the bonus of another card. An order's bonus comes from a single card.” |\n| 422 | `LOYALTY_ORDER_CANCELLED_NO_REDEMPTION` | “Order WEB-10482 was cancelled, so no bonus can be used on it any more. Send a new payment attempt as a new order with a different number.” |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- On a timeout or `503`, resend the request **with the same** `orderId` and the same amount: the bonus is deducted only once.\n- If the payment fails or the customer gives up, cancel the order with “Cancel an order (return)” without `amount`: the whole bonus returns to the card. The bonus does not come back on its own.\n- A cancelled order can no longer use bonus. Send a new payment attempt as a new order, with a different `orderId`.\n- After payment, accrue the order (“Accrue on an order”) on the amount paid in money, that is `orderAmount` minus the bonus.\n- This request is for option A (the order stays with you). If you send the order to PosFix, put the bonus in `loyalty.bonusAmount` on “Create order” and do not call this request.\n- Using bonus triggers the `loyalty.balance.changed` webhook.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "redemptions"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"duplicate\": false,\n  \"amount\": 100,\n  \"orderAmount\": 749.9,\n  \"balance\": {\n    \"unit\": \"money\",\n    \"value\": 50,\n    \"lifetime\": 350\n  },\n  \"bonusBalance\": {\n    \"amount\": 50,\n    \"available\": 50,\n    \"currency\": \"MDL\",\n    \"nextExpiresAt\": \"2027-01-31T00:00:00Z\"\n  }\n}"
            },
            {
              "name": "200 repeated",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "redemptions"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"duplicate\": true,\n  \"amount\": 100,\n  \"orderAmount\": 749.9,\n  \"balance\": {\n    \"unit\": \"money\",\n    \"value\": 50,\n    \"lifetime\": 350\n  },\n  \"bonusBalance\": {\n    \"amount\": 50,\n    \"available\": 50,\n    \"currency\": \"MDL\",\n    \"nextExpiresAt\": \"2027-01-31T00:00:00Z\"\n  }\n}"
            },
            {
              "name": "422 over the cap",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/redemptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "redemptions"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 100,\n  \"orderAmount\": 749.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\",\n  \"status\": 422,\n  \"detail\": \"Pe bonul acesta se pot folosi cel mult 149,98 MDL.\",\n  \"errorCode\": \"LOYALTY_BONUS_HOLD_EXCEEDS_MAX\"\n}"
            }
          ],
          "_anchorName": "Folosește bonus pe o comandă"
        },
        {
          "name": "Accrue on an order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "members",
                ":cardNumber",
                "accruals"
              ],
              "variable": [
                {
                  "key": "cardNumber",
                  "value": "2000000012345",
                  "description": "The card number, 13 digits."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:earn`\n\nAwards the member what they are due for an order paid in your system (online shop, app), under the programme's rules — the same as at the till. Send it once the order is paid.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `orderId` | string | yes | Your order, at most 100 characters — the idempotency key. |\n| `amount` | number | yes | The amount paid, after discounts, in lei: greater than zero, at most two decimals. |\n| `occurredAt` | string | no | The moment of the purchase, ISO 8601; missing = now. |\n\n### Response\n\n**201** — new accrual. **200** — the order had already been accrued (`duplicate: true`); nothing moved.\n\n| Field | Type | Description |\n|---|---|---|\n| `cardNumber`, `orderId` | string | The member and the order. |\n| `reversalId` | string? | Only on a reversal: the return reference. |\n| `duplicate` | boolean | `true` = the operation had already been made with the same key; nothing moved now. |\n| `amount` | number | The amount the operation was calculated on: paid on an accrual, returned on a reversal. |\n| `points`, `stamps`, `bonus` | number | What the member received for the order (negative on reversal). Only the program's unit is non-zero: `points` for points, `stamps` for stamps, `bonus` (lei) for bonus in lei; for a points program that also has a wallet — `points` and `bonus`. All 0 when the program is `draft` or `paused`. |\n| `remainingAmount` | number | How much of the order is left unreversed after the operation. |\n| `bonusRestored` | number | Only on a reversal: the bonus used on the order (through “Use bonus on an order”) put back on the card now, in lei. 0 on an accrual. |\n| `balance`, `bonusBalance` | object, object? | The member's balance after the operation, like `balance` and `bonus` on the member; `bonusBalance` is `null` when the program has no bonus in lei. |\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, `INTEGRATIONS_LOYALTY_CARD_NUMBER_FORMAT`, field validation | “The amount must be greater than zero, with at most two decimals (for example 249.90).” |\n| 404 | — | The card does not belong to the merchant. Empty body. |\n| 422 | `LOYALTY_ORDER_ACCRUAL_AMOUNT_MISMATCH` | “Order WEB-10482 was already credited for 749.90. A different amount cannot be credited for the same order — to correct it, reverse the order (fully or partially).” |\n| 422 | `LOYALTY_ORDER_ACCRUED_TO_OTHER_CARD` | “Order WEB-10482 has already earned points on another card. An order is credited once, to a single member.” |\n| 422 | `LOYALTY_PROGRAM_CLOSED_ACCRUAL` | “The loyalty program \"Aroma Club\" is closed — no more points are credited.” |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- On a timeout or `503`, resend the request **with the same** `orderId`: it is accrued only once.\n- An order is accrued only once, to a single card, for a single amount. A correction is made by a reversal (full or partial), not by a second accrual.\n- The accrual triggers the `loyalty.balance.changed` webhook.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"reversalId\": null,\n  \"duplicate\": false,\n  \"amount\": 749.9,\n  \"points\": 74,\n  \"stamps\": 0,\n  \"bonus\": 0,\n  \"remainingAmount\": 749.9,\n  \"bonusRestored\": 0,\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 194,\n    \"lifetime\": 414\n  },\n  \"bonusBalance\": null\n}"
            },
            {
              "name": "200 repeated",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"reversalId\": null,\n  \"duplicate\": true,\n  \"amount\": 749.9,\n  \"points\": 74,\n  \"stamps\": 0,\n  \"bonus\": 0,\n  \"remainingAmount\": 749.9,\n  \"bonusRestored\": 0,\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 194,\n    \"lifetime\": 414\n  },\n  \"bonusBalance\": null\n}"
            },
            {
              "name": "400 amount",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"statusCode\": 400,\n  \"message\": \"One or more errors occurred!\",\n  \"errors\": {\n    \"amount\": [\n      \"Suma trebuie să fie mai mare decât zero, cu cel mult două zecimale (de exemplu 249.90).\"\n    ]\n  },\n  \"errorCodes\": {\n    \"amount\": [\n      {\n        \"code\": \"INTEGRATIONS_LOYALTY_AMOUNT_INVALID\",\n        \"params\": {}\n      }\n    ]\n  }\n}"
            },
            {
              "name": "422 different amount",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/members/:cardNumber/accruals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "members",
                    ":cardNumber",
                    "accruals"
                  ],
                  "variable": [
                    {
                      "key": "cardNumber",
                      "value": "2000000012345",
                      "description": "Numărul cardului, 13 cifre."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"orderId\": \"WEB-10482\",\n  \"amount\": 749.9,\n  \"occurredAt\": \"2026-10-27T15:40:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_ORDER_ACCRUAL_AMOUNT_MISMATCH\",\n  \"status\": 422,\n  \"detail\": \"Comanda WEB-10482 a fost deja acordată pentru 749,90. Pe aceeași comandă nu se poate acorda altă sumă — pentru o corecție anulați comanda (integral sau parțial).\",\n  \"errorCode\": \"LOYALTY_ORDER_ACCRUAL_AMOUNT_MISMATCH\"\n}"
            }
          ],
          "_anchorName": "Acordă pe o comandă"
        },
        {
          "name": "Cancel an order (return)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/public/v1/loyalty/accruals/:orderId/reversals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "public",
                "v1",
                "loyalty",
                "accruals",
                ":orderId",
                "reversals"
              ],
              "variable": [
                {
                  "key": "orderId",
                  "value": "WEB-10482",
                  "description": "The accrued order (URL-encoded)."
                }
              ]
            },
            "description": "**Required scope:** `loyalty:earn`\n\nTakes back from an order's accrual the returned part, proportionally, and puts back on the card the share of bonus used on the order. Without `amount` everything left of the order is cancelled. An order that only used bonus (the payment failed before the accrual) is cancelled without `amount`: the whole bonus comes back.\n\n### Request body\n\n| Field | Type | Required | Rules |\n|---|---|---|---|\n| `reversalId` | string | yes | The return or cancellation on your side, at most 100 characters, without a colon (`:`) — the idempotency key. |\n| `amount` | number | no | The amount returned, in lei: greater than zero, at most two decimals, at most what is left unreversed. |\n\n### Response\n\n**201** — new reversal. **200** — the reversal had already been made (`duplicate: true`).\n\nThe fields are as in “Accrue on an order”: what is taken back comes negative (the program's unit), the rest 0; `bonusRestored` is the bonus put back.\n\n### Errors\n\n| HTTP | `errorCode` | When |\n|---|---|---|\n| 400 | `INTEGRATIONS_LOYALTY_AMOUNT_INVALID`, field validation | Wrong amount, missing `reversalId`. |\n| 400 | `INTEGRATIONS_LOYALTY_REFERENCE_COLON` | “The return reference cannot contain a colon (“:”). Use letters, digits, a hyphen or a slash.” |\n| 404 | — | The order was neither accrued nor used bonus at this merchant. Empty body. |\n| 422 | `LOYALTY_ORDER_REVERSAL_FULL_ONLY_WITHOUT_ACCRUAL` | “Order WEB-10482 received no accrual, it only used bonus: cancelling it puts the whole bonus back, so it is sent without an amount.” |\n| 422 | `LOYALTY_REVERSAL_EXCEEDS_REMAINING` | “Order WEB-10482 has 500.00 left that is not reversed; 600.00 cannot be reversed.” |\n| 422 | `LOYALTY_REVERSAL_AMOUNT_MISMATCH` | “Reversal RET-2231 was already made for 249.90. Use a new reversal reference for a different amount.” |\n| 403 | — | The key lacks the required scope, or the merchant does not have the “Integration API” module or the “Loyalty Programs” module active. Empty body. |\n| 401 | — | The key is missing, wrong, revoked or expired. Empty body. |\n| 429 | `RATE_LIMITED` | Too many requests; see “Limits” in the collection description. |\n| 503 | `SERVICE_UNAVAILABLE` | An internal service does not respond. Retry in a few seconds. |\n\n### Keep in mind\n\n- Resend with the same `reversalId` on a timeout: it is reversed only once. A new return needs a new `reversalId`.\n- The balance never goes below zero: only what is still on the card is taken back. What the member has already spent is not claimed — neither now nor from later accruals.\n- The bonus used on the order comes back in the same share as the amount returned out of what was paid in money: order 749.90, bonus 100, paid 649.90 — a return of 324.95 puts back 50.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reversalId\": \"RET-2231\",\n  \"amount\": 249.9\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/accruals/:orderId/reversals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "accruals",
                    ":orderId",
                    "reversals"
                  ],
                  "variable": [
                    {
                      "key": "orderId",
                      "value": "WEB-10482",
                      "description": "Comanda acordată (URL-encoded)."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"reversalId\": \"RET-2231\",\n  \"amount\": 249.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"cardNumber\": \"2000000012345\",\n  \"orderId\": \"WEB-10482\",\n  \"reversalId\": \"RET-2231\",\n  \"duplicate\": false,\n  \"amount\": 249.9,\n  \"points\": -24,\n  \"stamps\": 0,\n  \"bonus\": 0,\n  \"remainingAmount\": 500,\n  \"bonusRestored\": 0,\n  \"balance\": {\n    \"unit\": \"points\",\n    \"value\": 170,\n    \"lifetime\": 414\n  },\n  \"bonusBalance\": null\n}"
            },
            {
              "name": "422 over the remainder",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/public/v1/loyalty/accruals/:orderId/reversals",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "public",
                    "v1",
                    "loyalty",
                    "accruals",
                    ":orderId",
                    "reversals"
                  ],
                  "variable": [
                    {
                      "key": "orderId",
                      "value": "WEB-10482",
                      "description": "Comanda acordată (URL-encoded)."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"reversalId\": \"RET-2231\",\n  \"amount\": 249.9\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"type\": \"https://httpstatuses.io/422\",\n  \"title\": \"LOYALTY_REVERSAL_EXCEEDS_REMAINING\",\n  \"status\": 422,\n  \"detail\": \"Din comanda WEB-10482 au rămas neanulați 500,00; nu se pot anula 600,00.\",\n  \"errorCode\": \"LOYALTY_REVERSAL_EXCEEDS_REMAINING\"\n}"
            }
          ],
          "_anchorName": "Anulează o comandă (retur)"
        }
      ],
      "_anchorName": "8. Fidelitate"
    }
  ]
}
