Direct sales

Called with warehouse account keys. A key only sees its own account's data. Every example uses the same sample brand, order and item, so you can follow one item from call to call.

Base URL http://62.169.23.239:8001/api/v1Auth Bearer token3 endpoints

Create a sale#

POST/warehouse/sales

Requires scope warehouse_sales:write

Record a sale of sealed boxes, loose units, or both on one receipt.

A box line moves the whole carton: the box flips to 'sold' and no per-unit passport changes hands, because the sale does not open it. A line carrying a unit_token is a serialised loose unit, so it transfers real ownership — it requires buyer_email (an existing consumer account), writes the consumer_items row, and queues the custody/activate/certificate legs on chain. A line with just sku and quantity claims that many anonymous loose units, oldest first.

Sale, stock and passports share one transaction — none of them commits without the others.

409 if a box is not sealed and held here, if a referenced unit is not loose in stock, or if there are fewer loose units than a quantity line asks for. 422 if the warehouse has no country set (VAT cannot be resolved).

Request body

  • source_terminalstring | null
  • destinationstring | null
  • line_itemsarray of objectsRequired

    What was sold.

    +Show 8 child attributes
    • line_kindstring

      unit = one loose item; box = a whole sealed box.

      One ofboxunit

      Defaults to unit

    • box_idstring | null

      Id printed on the box label. Scanning the box resolves to this id.

    • unit_tokenstring | null

      The item's own token. It is what the item's QR code carries, and the id every item event uses.

    • product_namestringRequired
    • variant_labelstring | null
    • skustring | null

      Stock-keeping unit of the variant.

    • unit_price_eurnumberRequired

      Price of one unit in euro, including VAT.

    • quantityinteger

      Number of units.

      Defaults to 1

  • discount_kindstring | null
    One ofpercentamount
  • discount_valuenumber | null

    0-100 when discount_kind is 'percent', a euro amount when 'amount'. Applied to the subtotal before VAT.

  • buyer_emailstring | null

    Buyer's account email. Required when any line carries a unit_token or sells a whole box — ownership of every unit sold transfers to this account, so it cannot be sold into no account (a passport orphan). Only anonymous quantity lines (sku + quantity, no unit_token) may omit it.

Returns

HTTP 201
  • sale_idstringRequired
  • source_terminalstring | null
  • destinationstring | null
  • processed_attimestampRequired
  • statusstringRequired

    Current state of the object. See the allowed values.

    One ofcompletedprocessingcancelled
  • line_itemsarray of objects

    What was sold.

    +Show 11 child attributes
    • line_kindstring

      unit = one loose item; box = a whole sealed box.

      One ofboxunit

      Defaults to unit

    • box_idstring | null

      Id printed on the box label. Scanning the box resolves to this id.

    • unit_tokenstring | null

      The item's own token. It is what the item's QR code carries, and the id every item event uses.

    • product_namestringRequired
    • variant_labelstring | null
    • skustring | null

      Stock-keeping unit of the variant.

    • unit_price_eurnumber

      Price of one unit in euro, including VAT.

      Defaults to 0

    • quantityinteger

      Number of units.

      Defaults to 1

    • subtotal_eurnumber

      Defaults to 0

    • unit_countinteger

      Physical units this line moved. For a box line this is the box's unit_count, which is why it can exceed quantity (1 box, 120 units).

      Defaults to 0

    • consumer_item_idinteger | null
  • box_countinteger

    Defaults to 0

  • unit_countinteger

    Defaults to 0

  • subtotal_eurnumber

    Defaults to 0

  • discount_kindstring | null
    One ofpercentamount
  • discount_valuenumber | null
  • discount_eurnumber

    Defaults to 0

  • vat_rate_pctnumber

    Defaults to 0

  • vat_countrystring | null
  • vat_eurnumber

    Defaults to 0

  • total_eurnumber

    Total paid in euro, including VAT.

    Defaults to 0

  • buyer_consumer_idstring | null
  • storeobject | null

    Warehouse identity for the receipt, from retail_profiles — a Warehouse account shares that table with a shop (middleware.auth STOCK_HOLDING_ROLES); there is no separate warehouse_profiles table.

    +Show 5 child attributes
    • store_namestring | null
    • citystring | null
    • countrystring | null
    • glnstring | null
    • emailstring | null
  • buyerobject | null
    +Show 3 child attributes
    • consumer_idstring | null
    • namestring | null
    • emailstring | null
POST /warehouse/sales
curl
curl -X POST http://62.169.23.239:8001/api/v1/warehouse/sales \
  -H "Authorization: Bearer $SKANNIT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "buyer_email": "customer@example.com",
    "source_terminal": "wh-desk-1",
    "line_items": [
      {
        "line_kind": "box",
        "box_id": "BOX-F4E1A9-0003",
        "product_name": "Trail Tee",
        "unit_price_eur": 22.0
      },
      {
        "line_kind": "unit",
        "unit_token": "Qm7xK2pL9vR4tY8wZ1nB3c",
        "product_name": "Trail Tee",
        "variant_label": "White / M",
        "quantity": 1,
        "unit_price_eur": 29.0
      }
    ]
  }'
201Response
json
{
  "sale_id": "beb26697-234b-56da-9552-60c999feed1f",
  "source_terminal": "till-1",
  "destination": "in_store",
  "processed_at": "2026-10-09T09:30:00Z",
  "status": "completed",
  "line_items": [
    {
      "line_kind": "box",
      "box_id": "BOX-F4E1A9-0003",
      "unit_token": "Qm7xK2pL9vR4tY8wZ1nB3c",
      "product_name": "Trail Tee",
      "variant_label": "White / M",
      "sku": "TT-100-WHT-M",
      "unit_price_eur": 29,
      "quantity": 1,
      "subtotal_eur": 29,
      "unit_count": 24,
      "consumer_item_id": "7bf9462f-3324-593d-9012-c800e102b014"
    }
  ],
  "box_count": 24,
  "unit_count": 24,
  "subtotal_eur": 29,
  "discount_kind": "percent",
  "discount_value": 24,
  "discount_eur": 0,
  "vat_rate_pct": 21,
  "vat_country": "NL",
  "vat_eur": 5.03,
  "total_eur": 29,
  "buyer_consumer_id": "b3a40984-b53d-50bf-b3de-f096ab2c40a8",
  "store": {
    "store_name": "Northwind Amsterdam",
    "city": "Rotterdam",
    "country": "NL",
    "gln": "8712345000004",
    "email": "ops@northwind.example"
  },
  "buyer": {
    "consumer_id": "f4811030-eecf-579d-a105-e02ee601e044",
    "name": "Northwind Apparel",
    "email": "ops@northwind.example"
  }
}

List sales#

GET/warehouse/sales

Requires scope warehouse_sales:read

Recent sales, newest first, with box and unit counts per sale.

Returns

HTTP 200
  • itemsarray of objects

    The objects on this page.

    +Show 8 child attributes
    • sale_idstringRequired
    • source_terminalstring | null
    • destinationstring | null
    • processed_attimestampRequired
    • statusstringRequired

      Current state of the object. See the allowed values.

      One ofcompletedprocessingcancelled
    • box_countinteger

      Defaults to 0

    • unit_countinteger

      Defaults to 0

    • total_eurnumber

      Total paid in euro, including VAT.

      Defaults to 0

  • totalinteger

    Total count.

    Defaults to 0

GET /warehouse/sales
curl
curl http://62.169.23.239:8001/api/v1/warehouse/sales \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "items": [
    {
      "sale_id": "beb26697-234b-56da-9552-60c999feed1f",
      "source_terminal": "till-1",
      "destination": "in_store",
      "processed_at": "2026-10-09T09:30:00Z",
      "status": "completed",
      "box_count": 24,
      "unit_count": 24,
      "total_eur": 29
    }
  ],
  "total": 1
}

Retrieve a sale#

GET/warehouse/sales/{sale_id}

Requires scope warehouse_sales:read

Single sale detail — line items, VAT, totals, buyer and receipt header.

Parameters

  • sale_idstringpathRequired

Returns

HTTP 200
  • sale_idstringRequired
  • source_terminalstring | null
  • destinationstring | null
  • processed_attimestampRequired
  • statusstringRequired

    Current state of the object. See the allowed values.

    One ofcompletedprocessingcancelled
  • line_itemsarray of objects

    What was sold.

    +Show 11 child attributes
    • line_kindstring

      unit = one loose item; box = a whole sealed box.

      One ofboxunit

      Defaults to unit

    • box_idstring | null

      Id printed on the box label. Scanning the box resolves to this id.

    • unit_tokenstring | null

      The item's own token. It is what the item's QR code carries, and the id every item event uses.

    • product_namestringRequired
    • variant_labelstring | null
    • skustring | null

      Stock-keeping unit of the variant.

    • unit_price_eurnumber

      Price of one unit in euro, including VAT.

      Defaults to 0

    • quantityinteger

      Number of units.

      Defaults to 1

    • subtotal_eurnumber

      Defaults to 0

    • unit_countinteger

      Physical units this line moved. For a box line this is the box's unit_count, which is why it can exceed quantity (1 box, 120 units).

      Defaults to 0

    • consumer_item_idinteger | null
  • box_countinteger

    Defaults to 0

  • unit_countinteger

    Defaults to 0

  • subtotal_eurnumber

    Defaults to 0

  • discount_kindstring | null
    One ofpercentamount
  • discount_valuenumber | null
  • discount_eurnumber

    Defaults to 0

  • vat_rate_pctnumber

    Defaults to 0

  • vat_countrystring | null
  • vat_eurnumber

    Defaults to 0

  • total_eurnumber

    Total paid in euro, including VAT.

    Defaults to 0

  • buyer_consumer_idstring | null
  • storeobject | null

    Warehouse identity for the receipt, from retail_profiles — a Warehouse account shares that table with a shop (middleware.auth STOCK_HOLDING_ROLES); there is no separate warehouse_profiles table.

    +Show 5 child attributes
    • store_namestring | null
    • citystring | null
    • countrystring | null
    • glnstring | null
    • emailstring | null
  • buyerobject | null
    +Show 3 child attributes
    • consumer_idstring | null
    • namestring | null
    • emailstring | null
GET /warehouse/sales/{sale_id}
curl
curl http://62.169.23.239:8001/api/v1/warehouse/sales/beb26697-234b-56da-9552-60c999feed1f \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "sale_id": "beb26697-234b-56da-9552-60c999feed1f",
  "source_terminal": "till-1",
  "destination": "in_store",
  "processed_at": "2026-10-09T09:30:00Z",
  "status": "completed",
  "line_items": [
    {
      "line_kind": "box",
      "box_id": "BOX-F4E1A9-0003",
      "unit_token": "Qm7xK2pL9vR4tY8wZ1nB3c",
      "product_name": "Trail Tee",
      "variant_label": "White / M",
      "sku": "TT-100-WHT-M",
      "unit_price_eur": 29,
      "quantity": 1,
      "subtotal_eur": 29,
      "unit_count": 24,
      "consumer_item_id": "7bf9462f-3324-593d-9012-c800e102b014"
    }
  ],
  "box_count": 24,
  "unit_count": 24,
  "subtotal_eur": 29,
  "discount_kind": "percent",
  "discount_value": 24,
  "discount_eur": 0,
  "vat_rate_pct": 21,
  "vat_country": "NL",
  "vat_eur": 5.03,
  "total_eur": 29,
  "buyer_consumer_id": "b3a40984-b53d-50bf-b3de-f096ab2c40a8",
  "store": {
    "store_name": "Northwind Amsterdam",
    "city": "Rotterdam",
    "country": "NL",
    "gln": "8712345000004",
    "email": "ops@northwind.example"
  },
  "buyer": {
    "consumer_id": "f4811030-eecf-579d-a105-e02ee601e044",
    "name": "Northwind Apparel",
    "email": "ops@northwind.example"
  }
}