Receiving & stock

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 token14 endpoints

Get warehouse summary#

GET/warehouse/dashboard

Requires scope warehouse_stock:read

Everything held, claimable, forwarded recently, and wanting attention.

Returns

HTTP 200
  • sealed_box_countinteger

    Defaults to 0

  • sealed_unit_countinteger

    Defaults to 0

  • loose_unit_countinteger

    Defaults to 0

  • total_unit_countinteger

    Defaults to 0

  • inbound_claimable_boxesinteger

    Defaults to 0

  • inbound_expected_ordersinteger

    Orders a brand has named this warehouse as destination for, but no factory has dispatched yet — advance visibility, not something to claim. Kept apart from inbound_claimable_boxes so the two are never summed under one "unclaimed deliveries" label again (they mean different things).

    Defaults to 0

  • forwarded_last_7_daysinteger

    Defaults to 0

  • expiring_soon_countinteger

    Defaults to 0

  • expired_countinteger

    Defaults to 0

  • aging_countinteger

    Defaults to 0

  • open_stock_check_idstring | null
GET /warehouse/dashboard
curl
curl http://62.169.23.239:8001/api/v1/warehouse/dashboard \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "sealed_box_count": 24,
  "sealed_unit_count": 24,
  "loose_unit_count": 24,
  "total_unit_count": 24,
  "inbound_claimable_boxes": 24,
  "inbound_expected_orders": 1,
  "forwarded_last_7_days": 1,
  "expiring_soon_count": 24,
  "expired_count": 24,
  "aging_count": 24,
  "open_stock_check_id": "294c6e5c-1e81-5f39-ada2-fa00177041a9"
}

List inbound deliveries#

GET/warehouse/inbound

Requires scope warehouse_stock:read

Delivered boxes nobody has claimed yet.

Nothing is assigned to a particular warehouse — as with a shop, the boxes are simply sitting in brand custody, and whoever physically receives them scans them in.

Returns

HTTP 200
  • itemsarray of objects

    The objects on this page.

    +Show 17 child attributes
    • box_idstringRequired

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

    • order_idstringRequired

      Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

    • order_display_idstring

      Order number formatted for display (#PO-YYYY-XXXX).

      Defaults to

    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • brand_namestring

      Defaults to —

    • statusstringRequired

      Current state of the object. See the allowed values.

      One ofsealedunpackeddispatchedforwardedsoldremovedexpected
    • unit_countinteger

      Defaults to 0

    • linesarray of objects

      Line items, one per variant.

      +Show 4 child attributes
      • colorstring | null

        Colour name, exactly as on the product's variants.

      • sizestring | null

        Size name, exactly as on the product's variants.

      • batch_skustring | null

        SKU printed on the label for this variant in this batch.

      • quantityinteger

        Number of units.

        Defaults to 0

    • expiry_datedate | null
    • receipt_conditionstring | null

      Condition recorded when the box was received.

      One ofgooddamagedpartial
    • received_attimestamp | null
    • unpacked_attimestamp | null
    • forwarded_attimestamp | null
    • forwarded_to_idstring | null
    • locationstring | null

      Where it sits in the building, e.g. "Aisle 4, Bay B".

    • transit_issuesarray of objects

      Inbound only: what the carrier reported on the way here.

      +Show 6 child attributes
      • sourcestringRequired
        One ofdamage_reportdispatch_alert
      • damage_typestring | null
      • severitystring | null
      • notestring | null
      • photo_urlstring | null
      • reported_attimestamp | null
  • totalinteger

    Total count.

    Defaults to 0

GET /warehouse/inbound
curl
curl http://62.169.23.239:8001/api/v1/warehouse/inbound \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "items": [
    {
      "box_id": "BOX-F4E1A9-0003",
      "order_id": "ord_8c2f4e1a9b7d3c60",
      "order_display_id": "#PO-2026-7F3A",
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "brand_name": "Northwind Apparel",
      "status": "sealed",
      "unit_count": 24,
      "lines": [
        {
          "color": "White",
          "size": "M",
          "batch_sku": "TT-100-WHT-M",
          "quantity": 1
        }
      ],
      "expiry_date": "2026-11-20",
      "receipt_condition": "good",
      "received_at": null,
      "unpacked_at": null,
      "forwarded_at": null,
      "forwarded_to_id": "a03218a3-a2d6-5112-8c31-778970bde949",
      "location": "Aisle 4, Bay B",
      "transit_issues": [
        {
          "source": "web_shop",
          "damage_type": "crushed",
          "severity": "high",
          "note": "Corner of box dented",
          "photo_url": "https://files.skannit.example/photo.png",
          "reported_at": null
        }
      ]
    }
  ],
  "total": 1
}

Receive boxes#

POST/warehouse/deliveries/{order_id}/receive

Requires scope warehouse_stock:write

Take scanned boxes into warehouse custody, sealed.

The boxes are stored unopened — one row per box, not per unit — and their contents are known from the manifest. Pass expected_units to have the server confirm what arrived against what the manifest says; a mismatch is reported in discrepancies rather than rejecting goods already on the dock.

Parameters

  • order_idstringpathRequired

    Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

Request body

  • box_idsarray of strings

    Ids of the boxes this call acts on.

  • expiry_datedate | null
  • conditionstring | null
    One ofgooddamagedpartial
  • notesstring | null
  • expected_unitsinteger | null
  • locationstring | null

    Where the received boxes are put away, e.g. "Aisle 4, Bay B".

Returns

HTTP 200
  • order_idstringRequired

    Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

  • received_boxesinteger

    Defaults to 0

  • already_received_boxesinteger

    Defaults to 0

  • received_unitsinteger

    Defaults to 0

  • boxesarray of objects
    +Show 17 child attributes
    • box_idstringRequired

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

    • order_idstringRequired

      Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

    • order_display_idstring

      Order number formatted for display (#PO-YYYY-XXXX).

      Defaults to

    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • brand_namestring

      Defaults to —

    • statusstringRequired

      Current state of the object. See the allowed values.

      One ofsealedunpackeddispatchedforwardedsoldremovedexpected
    • unit_countinteger

      Defaults to 0

    • linesarray of objects

      Line items, one per variant.

      +Show 4 child attributes
      • colorstring | null

        Colour name, exactly as on the product's variants.

      • sizestring | null

        Size name, exactly as on the product's variants.

      • batch_skustring | null

        SKU printed on the label for this variant in this batch.

      • quantityinteger

        Number of units.

        Defaults to 0

    • expiry_datedate | null
    • receipt_conditionstring | null

      Condition recorded when the box was received.

      One ofgooddamagedpartial
    • received_attimestamp | null
    • unpacked_attimestamp | null
    • forwarded_attimestamp | null
    • forwarded_to_idstring | null
    • locationstring | null

      Where it sits in the building, e.g. "Aisle 4, Bay B".

    • transit_issuesarray of objects

      Inbound only: what the carrier reported on the way here.

      +Show 6 child attributes
      • sourcestringRequired
        One ofdamage_reportdispatch_alert
      • damage_typestring | null
      • severitystring | null
      • notestring | null
      • photo_urlstring | null
      • reported_attimestamp | null
  • discrepanciesarray of objects

    Non-empty when the count the worker expected differs from the manifest. Receiving still succeeds — the discrepancy is recorded for the manager, because refusing the goods that are physically on the dock helps nobody.

    +Show 4 child attributes
    • box_idstringRequired

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

    • expected_unitsinteger

      Defaults to 0

    • manifest_unitsinteger

      Defaults to 0

    • detailstringRequired
  • custody_tx_queuedinteger

    True when the hand-over was queued for on-chain recording.

    Defaults to 0

POST /warehouse/deliveries/{order_id}/receive
curl
curl -X POST http://62.169.23.239:8001/api/v1/warehouse/deliveries/ord_8c2f4e1a9b7d3c60/receive \
  -H "Authorization: Bearer $SKANNIT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "box_ids": [
      "BOX-F4E1A9-0003"
    ],
    "condition": "good",
    "expected_units": 48,
    "location": "Aisle 4, Bay B"
  }'
200Response
json
{
  "order_id": "ord_8c2f4e1a9b7d3c60",
  "received_boxes": 24,
  "already_received_boxes": 24,
  "received_units": 24,
  "boxes": [
    {
      "box_id": "BOX-F4E1A9-0003",
      "order_id": "ord_8c2f4e1a9b7d3c60",
      "order_display_id": "#PO-2026-7F3A",
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "brand_name": "Northwind Apparel",
      "status": "sealed",
      "unit_count": 24,
      "lines": [
        {
          "color": "White",
          "size": "M",
          "batch_sku": "TT-100-WHT-M",
          "quantity": 1
        }
      ],
      "expiry_date": "2026-11-20",
      "receipt_condition": "good",
      "received_at": null,
      "unpacked_at": null,
      "forwarded_at": null,
      "forwarded_to_id": "a03218a3-a2d6-5112-8c31-778970bde949",
      "location": "Aisle 4, Bay B",
      "transit_issues": [
        {
          "source": "web_shop",
          "damage_type": "crushed",
          "severity": "high",
          "note": "Corner of box dented",
          "photo_url": "https://files.skannit.example/photo.png",
          "reported_at": null
        }
      ]
    }
  ],
  "discrepancies": [
    {
      "box_id": "BOX-F4E1A9-0003",
      "expected_units": 24,
      "manifest_units": 24,
      "detail": "Box BOX-F4E1A9-0003 manifest says 48 units"
    }
  ],
  "custody_tx_queued": 1
}

Refuse boxes#

POST/warehouse/deliveries/{order_id}/reject

Requires scope warehouse_stock:write

Refuse delivered boxes instead of accepting them — e.g. the carrier reported damage (see transit_issues on GET /inbound) or the goods are wrong. Nothing moves: the boxes stay in the brand's custody, the refusal and reason are recorded, the brand is notified and its systems receive a custody.exception webhook. To accept only some boxes, receive those and reject the rest.

Parameters

  • order_idstringpathRequired

    Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

Request body

  • box_idsarray of strings

    Ids of the boxes this call acts on.

  • reasonstringRequired

Returns

HTTP 200
  • order_idstringRequired

    Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

  • rejected_box_idsarray of strings
  • already_rejected_box_idsarray of strings
  • reasonstringRequired
POST /warehouse/deliveries/{order_id}/reject
curl
curl -X POST http://62.169.23.239:8001/api/v1/warehouse/deliveries/ord_8c2f4e1a9b7d3c60/reject \
  -H "Authorization: Bearer $SKANNIT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "box_ids": [
      "BOX-F4E1A9-0004"
    ],
    "reason": "Box crushed, seal broken"
  }'
200Response
json
{
  "order_id": "ord_8c2f4e1a9b7d3c60",
  "rejected_box_ids": [
    "e3a18da1-ad26-5640-bf3d-714fec94bc30"
  ],
  "already_rejected_box_ids": [
    "e12f686f-8b49-536b-ad7e-81b0555c90dd"
  ],
  "reason": "Customer changed their mind"
}

List boxes#

GET/warehouse/boxes

Requires scope warehouse_stock:read

Boxes held by this warehouse, with what each one contains.

Parameters

  • statusstringquery

    Current state of the object. See the allowed values.

Returns

HTTP 200
  • itemsarray of objects

    The objects on this page.

    +Show 17 child attributes
    • box_idstringRequired

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

    • order_idstringRequired

      Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

    • order_display_idstring

      Order number formatted for display (#PO-YYYY-XXXX).

      Defaults to

    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • brand_namestring

      Defaults to —

    • statusstringRequired

      Current state of the object. See the allowed values.

      One ofsealedunpackeddispatchedforwardedsoldremovedexpected
    • unit_countinteger

      Defaults to 0

    • linesarray of objects

      Line items, one per variant.

      +Show 4 child attributes
      • colorstring | null

        Colour name, exactly as on the product's variants.

      • sizestring | null

        Size name, exactly as on the product's variants.

      • batch_skustring | null

        SKU printed on the label for this variant in this batch.

      • quantityinteger

        Number of units.

        Defaults to 0

    • expiry_datedate | null
    • receipt_conditionstring | null

      Condition recorded when the box was received.

      One ofgooddamagedpartial
    • received_attimestamp | null
    • unpacked_attimestamp | null
    • forwarded_attimestamp | null
    • forwarded_to_idstring | null
    • locationstring | null

      Where it sits in the building, e.g. "Aisle 4, Bay B".

    • transit_issuesarray of objects

      Inbound only: what the carrier reported on the way here.

      +Show 6 child attributes
      • sourcestringRequired
        One ofdamage_reportdispatch_alert
      • damage_typestring | null
      • severitystring | null
      • notestring | null
      • photo_urlstring | null
      • reported_attimestamp | null
  • totalinteger

    Total count.

    Defaults to 0

GET /warehouse/boxes
curl
curl http://62.169.23.239:8001/api/v1/warehouse/boxes \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "items": [
    {
      "box_id": "BOX-F4E1A9-0003",
      "order_id": "ord_8c2f4e1a9b7d3c60",
      "order_display_id": "#PO-2026-7F3A",
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "brand_name": "Northwind Apparel",
      "status": "sealed",
      "unit_count": 24,
      "lines": [
        {
          "color": "White",
          "size": "M",
          "batch_sku": "TT-100-WHT-M",
          "quantity": 1
        }
      ],
      "expiry_date": "2026-11-20",
      "receipt_condition": "good",
      "received_at": null,
      "unpacked_at": null,
      "forwarded_at": null,
      "forwarded_to_id": "a03218a3-a2d6-5112-8c31-778970bde949",
      "location": "Aisle 4, Bay B",
      "transit_issues": [
        {
          "source": "web_shop",
          "damage_type": "crushed",
          "severity": "high",
          "note": "Corner of box dented",
          "photo_url": "https://files.skannit.example/photo.png",
          "reported_at": null
        }
      ]
    }
  ],
  "total": 1
}

Set a box location#

PATCH/warehouse/boxes/{box_id}

Requires scope warehouse_stock:write

Record where a box sits in the building, e.g. "Aisle 4, Bay B" (send null to clear). Units unpacked from it later inherit the location.

Parameters

  • box_idstringpathRequired

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

Request body

  • locationstring | null

    Free-text place in your warehouse, for example "Aisle 4, Bay B".

Returns

HTTP 200
  • box_idstringRequired

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

  • order_idstringRequired

    Skannit's id for the production order. Use it in every /production-orders/{order_id} and QR call.

  • order_display_idstring

    Order number formatted for display (#PO-YYYY-XXXX).

    Defaults to

  • product_idstringRequired

    Skannit's id for the product.

  • product_namestring

    Defaults to —

  • brand_namestring

    Defaults to —

  • statusstringRequired

    Current state of the object. See the allowed values.

    One ofsealedunpackeddispatchedforwardedsoldremovedexpected
  • unit_countinteger

    Defaults to 0

  • linesarray of objects

    Line items, one per variant.

    +Show 4 child attributes
    • colorstring | null

      Colour name, exactly as on the product's variants.

    • sizestring | null

      Size name, exactly as on the product's variants.

    • batch_skustring | null

      SKU printed on the label for this variant in this batch.

    • quantityinteger

      Number of units.

      Defaults to 0

  • expiry_datedate | null
  • receipt_conditionstring | null

    Condition recorded when the box was received.

    One ofgooddamagedpartial
  • received_attimestamp | null
  • unpacked_attimestamp | null
  • forwarded_attimestamp | null
  • forwarded_to_idstring | null
  • locationstring | null

    Where it sits in the building, e.g. "Aisle 4, Bay B".

  • transit_issuesarray of objects

    Inbound only: what the carrier reported on the way here.

    +Show 6 child attributes
    • sourcestringRequired
      One ofdamage_reportdispatch_alert
    • damage_typestring | null
    • severitystring | null
    • notestring | null
    • photo_urlstring | null
    • reported_attimestamp | null
PATCH /warehouse/boxes/{box_id}
curl
curl -X PATCH http://62.169.23.239:8001/api/v1/warehouse/boxes/BOX-F4E1A9-0003 \
  -H "Authorization: Bearer $SKANNIT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "location": "Aisle 4, Bay B"
  }'
200Response
json
{
  "box_id": "BOX-F4E1A9-0003",
  "order_id": "ord_8c2f4e1a9b7d3c60",
  "order_display_id": "#PO-2026-7F3A",
  "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
  "product_name": "Trail Tee",
  "brand_name": "Northwind Apparel",
  "status": "sealed",
  "unit_count": 24,
  "lines": [
    {
      "color": "White",
      "size": "M",
      "batch_sku": "TT-100-WHT-M",
      "quantity": 1
    }
  ],
  "expiry_date": "2026-11-20",
  "receipt_condition": "good",
  "received_at": null,
  "unpacked_at": null,
  "forwarded_at": null,
  "forwarded_to_id": "a03218a3-a2d6-5112-8c31-778970bde949",
  "location": "Aisle 4, Bay B",
  "transit_issues": [
    {
      "source": "web_shop",
      "damage_type": "crushed",
      "severity": "high",
      "note": "Corner of box dented",
      "photo_url": "https://files.skannit.example/photo.png",
      "reported_at": null
    }
  ]
}

Unpack a box#

POST/warehouse/boxes/{box_id}/unpack

Requires scope warehouse_stock:write

Open a box into individually trackable units.

Optional — a warehouse that only ships whole boxes never needs this. Afterwards the units are ordinary inventory rows: sellable one by one, or packed into a new box (POST /boxes/pack) for a shop. Nothing changes hands, so no custody event is recorded.

Parameters

  • box_idstringpathRequired

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

Returns

HTTP 200
  • box_idstringRequired

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

  • units_createdinteger

    Defaults to 0

  • already_unpacked_unitsinteger

    Defaults to 0

  • statusstringRequired

    Current state of the object. See the allowed values.

    One ofsealedunpackeddispatchedforwardedsoldremovedexpected
POST /warehouse/boxes/{box_id}/unpack
curl
curl -X POST http://62.169.23.239:8001/api/v1/warehouse/boxes/BOX-F4E1A9-0003/unpack \
  -H "Authorization: Bearer $SKANNIT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
200Response
json
{
  "box_id": "BOX-F4E1A9-0003",
  "units_created": 24,
  "already_unpacked_units": 24,
  "status": "sealed"
}

Pack loose units into a box#

POST/warehouse/boxes/pack

Requires scope warehouse_stock:write

Seal N loose units of one SKU into a brand-new box.

The reverse of unpack — for an order smaller than any box already sealed, most often exactly one unit. The resulting box is dispatchable the same way any other sealed box is (see POST /warehouse/transport/dispatches).

Request body

  • skustringRequired

    Stock-keeping unit of the variant.

  • colorstring | null

    Colour name, exactly as on the product's variants.

  • sizestring | null

    Size name, exactly as on the product's variants.

  • quantityintegerRequired

    Number of units.

  • notesstring | null

Returns

HTTP 200
  • box_idstringRequired

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

  • packed_unitsinteger

    Defaults to 0

POST /warehouse/boxes/pack
curl
curl -X POST http://62.169.23.239:8001/api/v1/warehouse/boxes/pack \
  -H "Authorization: Bearer $SKANNIT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "TT-100-WHT-M",
    "color": "White",
    "size": "M",
    "quantity": 1,
    "notes": "Received at dock 3"
  }'
200Response
json
{
  "box_id": "BOX-F4E1A9-0003",
  "packed_units": 24
}

Get a box QR label#

GET/warehouse/boxes/{box_id}/qr.svg

Requires scope warehouse_stock:read

QR label for a box this warehouse holds. Print it on the box so the transporter can scan it at pickup (it resolves via GET /scan/box/{box_id}).

Parameters

  • box_idstringpathRequired

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

Returns

HTTP 200

No body.

GET /warehouse/boxes/{box_id}/qr.svg
curl
curl http://62.169.23.239:8001/api/v1/warehouse/boxes/BOX-F4E1A9-0003/qr.svg \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
// 200 — no body

Get inventory#

GET/warehouse/inventory

Requires scope warehouse_stock:read

Stock at both granularities.

Sealed and loose are reported separately on purpose: a sealed box can be shipped whole but cannot be picked from, so summing them into one availability number would overstate what is actually sellable.

Returns

HTTP 200
  • sealedarray of objects
    +Show 6 child attributes
    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • brand_namestring

      Defaults to —

    • box_countinteger

      Defaults to 0

    • unit_countinteger

      Defaults to 0

    • locationsarray of strings
  • loosearray of objects
    +Show 9 child attributes
    • item_idstringRequired
    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • brand_namestring

      Defaults to —

    • colorstring | null

      Colour name, exactly as on the product's variants.

    • sizestring | null

      Size name, exactly as on the product's variants.

    • batch_skustring | null

      SKU printed on the label for this variant in this batch.

    • current_qtyinteger

      Defaults to 0

    • locationsarray of strings

      Where these units sit, e.g. ["Aisle 4, Bay B"]; empty when not recorded.

  • sealed_box_countinteger

    Defaults to 0

  • sealed_unit_countinteger

    Defaults to 0

  • loose_unit_countinteger

    Defaults to 0

  • total_unit_countinteger

    Defaults to 0

  • reservedarray of objects

    Held for waiting retailer orders until a dispatch ships them. Can exceed what is on hand — the excess is still waiting on production.

    +Show 8 child attributes
    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • skustringRequired

      Stock-keeping unit of the variant.

    • colorstring | null

      Colour name, exactly as on the product's variants.

    • sizestring | null

      Size name, exactly as on the product's variants.

    • quantityinteger

      Number of units.

      Defaults to 0

    • held_from_stockinteger

      Part of `quantity` covered by stock on hand; the rest waits on production.

      Defaults to 0

    • order_countinteger

      Defaults to 0

  • reserved_unit_countinteger

    Defaults to 0

  • available_unit_countinteger

    total_unit_count minus the reserved units actually on hand.

    Defaults to 0

GET /warehouse/inventory
curl
curl http://62.169.23.239:8001/api/v1/warehouse/inventory \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "sealed": [
    {
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "brand_name": "Northwind Apparel",
      "box_count": 24,
      "unit_count": 24,
      "locations": [
        "Aisle 4, Bay B"
      ]
    }
  ],
  "loose": [
    {
      "item_id": "Qm7xK2pL9vR4tY8wZ1nB3c",
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "brand_name": "Northwind Apparel",
      "color": "White",
      "size": "M",
      "batch_sku": "TT-100-WHT-M",
      "current_qty": 24,
      "locations": [
        "Aisle 4, Bay B"
      ]
    }
  ],
  "sealed_box_count": 24,
  "sealed_unit_count": 24,
  "loose_unit_count": 24,
  "total_unit_count": 24,
  "reserved": [
    {
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "sku": "TT-100-WHT-M",
      "color": "White",
      "size": "M",
      "quantity": 1,
      "held_from_stock": 1,
      "order_count": 24
    }
  ],
  "reserved_unit_count": 24,
  "available_unit_count": 24
}

Get sellable stock#

GET/warehouse/sellable

Requires scope warehouse_stock:read

What can go on a sale right now — sealed boxes and loose units.

The two are returned as separate lists, not one merged catalogue, because they are not interchangeable: a sealed box sells whole, loose units sell by the piece. Only sealed boxes appear under boxes — once a box is unpacked its units are offered under units instead, so nothing is counted twice.

Returns

HTTP 200
  • boxesarray of objects
    +Show 10 child attributes
    • box_idstringRequired

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

    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • brand_namestring

      Defaults to —

    • unit_countinteger

      Defaults to 0

    • linesarray of objects

      Line items, one per variant.

      +Show 4 child attributes
      • colorstring | null

        Colour name, exactly as on the product's variants.

      • sizestring | null

        Size name, exactly as on the product's variants.

      • batch_skustring | null

        SKU printed on the label for this variant in this batch.

      • quantityinteger

        Number of units.

        Defaults to 0

    • expiry_datedate | null
    • received_attimestamp | null
    • unit_price_eurnumber

      Price of one unit in euro, including VAT.

      Defaults to 0

    • suggested_price_eurnumber

      unit_price_eur * unit_count — the whole-box asking price.

      Defaults to 0

  • unitsarray of objects
    +Show 9 child attributes
    • item_idstringRequired
    • product_idstringRequired

      Skannit's id for the product.

    • product_namestring

      Defaults to —

    • colorstring | null

      Colour name, exactly as on the product's variants.

    • sizestring | null

      Size name, exactly as on the product's variants.

    • batch_skustring | null

      SKU printed on the label for this variant in this batch.

    • available_qtyinteger

      Defaults to 0

    • unit_price_eurnumber

      Price of one unit in euro, including VAT.

      Defaults to 0

    • unitsarray of objects

      Individual units behind available_qty, oldest first. Capped per group so a warehouse holding thousands of one SKU does not turn the picker payload into a multi-megabyte response; available_qty remains the true count.

      +Show 2 child attributes
      • unit_tokenstringRequired

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

      • received_attimestamp | null
  • sellable_box_countinteger

    Defaults to 0

  • sellable_unit_countinteger

    Defaults to 0

GET /warehouse/sellable
curl
curl http://62.169.23.239:8001/api/v1/warehouse/sellable \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "boxes": [
    {
      "box_id": "BOX-F4E1A9-0003",
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "brand_name": "Northwind Apparel",
      "unit_count": 24,
      "lines": [
        {
          "color": "White",
          "size": "M",
          "batch_sku": "TT-100-WHT-M",
          "quantity": 1
        }
      ],
      "expiry_date": "2026-11-20",
      "received_at": null,
      "unit_price_eur": 29,
      "suggested_price_eur": 29
    }
  ],
  "units": [
    {
      "item_id": "Qm7xK2pL9vR4tY8wZ1nB3c",
      "product_id": "f6abe715-90aa-5908-a0a6-12951a3a21cc",
      "product_name": "Trail Tee",
      "color": "White",
      "size": "M",
      "batch_sku": "TT-100-WHT-M",
      "available_qty": 24,
      "unit_price_eur": 29,
      "units": [
        {
          "unit_token": "Qm7xK2pL9vR4tY8wZ1nB3c",
          "received_at": null
        }
      ]
    }
  ],
  "sellable_box_count": 24,
  "sellable_unit_count": 24
}

List aging stock#

GET/warehouse/aging

Requires scope warehouse_stock:read

Stock wanting a decision — expired, expiring, or sitting too long.

Reports only; nothing here changes stock state. Acting on a row is the separate, explicit POST below, because whether to write stock off or send it to a recycler is the manager's call rather than a consequence of looking.

Parameters

  • include_healthybooleanquery

    Also list stock that needs no attention yet, so the expiry date of the whole holding is visible in one place. Those rows carry reason=null and are never counted in the three totals.

    Defaults to false

Returns

HTTP 200
  • itemsarray of objects

    The objects on this page.

    +Show 13 child attributes
    • refstringRequired
    • ref_kindstringRequired
      One ofboxunit
    • product_namestring

      Defaults to —

    • brand_namestring

      Defaults to —

    • colorstring | null

      A sealed box has no single colour/size (a mixed carton); a loose unit always does. Both None on a box is correct, not a missing-data bug.

    • sizestring | null

      Size name, exactly as on the product's variants.

    • locationstring | null

      Where to find it, e.g. "Aisle 4, Bay B" — None when never recorded.

    • reasonstring | null

      None means the row needs no attention yet — it is healthy stock, listed only so the expiry date of everything held is visible in one place. The three counters on the screen count reasons, so they ignore these rows.

      One ofexpiringexpiredaging
    • expiry_datedate | null
    • received_attimestamp | null
    • days_in_stockinteger | null
    • days_to_expiryinteger | null
    • unit_countinteger

      Defaults to 1

  • totalinteger

    Total count.

    Defaults to 0

  • expired_countinteger

    Counted server-side over the whole holding, so the cards stay correct regardless of which filter the client is showing.

    Defaults to 0

  • expiring_countinteger

    Defaults to 0

  • aging_countinteger

    Defaults to 0

GET /warehouse/aging
curl
curl http://62.169.23.239:8001/api/v1/warehouse/aging \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "items": [
    {
      "ref": "BOX-F4E1A9-0003",
      "ref_kind": "box",
      "product_name": "Trail Tee",
      "brand_name": "Northwind Apparel",
      "color": "White",
      "size": "M",
      "location": "Aisle 4, Bay B",
      "reason": "Customer changed their mind",
      "expiry_date": "2026-11-20",
      "received_at": null,
      "days_in_stock": 1,
      "days_to_expiry": 1,
      "unit_count": 1
    }
  ],
  "total": 1,
  "expired_count": 24,
  "expiring_count": 24,
  "aging_count": 24
}

List recycling facilities#

GET/warehouse/recycling-facilities

Requires scope warehouse_stock:read

Recyclers this warehouse can hand stock to, nearest first.

Everything listed here is eligible to take custody, so anything the picker offers the dispose call below will accept. Licence details are included because they belong in the choice — the stock is leaving for good.

Returns

HTTP 200
  • itemsarray of objects

    The objects on this page.

    +Show 8 child attributes
    • facility_idstringRequired
    • facility_namestring

      Defaults to —

    • facility_typestring | null
    • citystring | null
    • countrystring | null
    • registration_idstring | null

      Licence fields travel with the listing on purpose: the warehouse is being asked to hand goods over for permanent disposal, so who is licensed to receive them — and whether anyone has checked — belongs in the choice rather than behind a second call. 'unverified' is self-asserted, never accreditation.

    • licence_authoritystring | null
    • licence_statusstring | null
  • totalinteger

    Total count.

    Defaults to 0

GET /warehouse/recycling-facilities
curl
curl http://62.169.23.239:8001/api/v1/warehouse/recycling-facilities \
  -H "Authorization: Bearer $SKANNIT_TOKEN"
200Response
json
{
  "items": [
    {
      "facility_id": "be5df03e-bc08-5a20-8773-8f8f63bffeaf",
      "facility_name": "ReLoop Recycling",
      "facility_type": "textile",
      "city": "Rotterdam",
      "country": "NL",
      "registration_id": "e7367686-035b-5200-824a-93091ffaa355",
      "licence_authority": "Rijkswaterstaat",
      "licence_status": "valid"
    }
  ],
  "total": 1
}

Dispose of stock#

POST/warehouse/aging/{ref}/dispose

Requires scope warehouse_stock:write

Take aged-out stock out of inventory: a sealed box or a loose unit.

REMOVE drops it from stock and says nothing about where it went. RECYCLE hands it to a licensed recycler and records the custody handoff, so the goods have a documented exit from the market. ref is a box_id or a unit_token; ref_kind says which, as everywhere else in this module.

Disposing of a sealed box disposes of the units inside it too — they cannot outlive the carton they were sealed in.

Parameters

  • refstringpathRequired

Request body

  • ref_kindstringRequired
    One ofboxunit
  • actionstring
    One ofREMOVERECYCLE

    Defaults to REMOVE

  • reasonstring
    One ofexpireddamagedrecalledother

    Defaults to expired

  • facility_idstring | null

    RECYCLE only: pick a specific facility instead of the nearest active one.

Returns

HTTP 200
  • refstringRequired
  • ref_kindstringRequired
    One ofboxunit
  • statusstringRequired

    Current state of the object. See the allowed values.

    One ofremovedrecycled
  • units_removedinteger

    How many units left stock — 1 for a loose unit, the whole carton for a box.

    Defaults to 0

  • recycling_request_idsarray of integers

    Populated for RECYCLE so the caller can follow the stock to its certificate.

  • facility_idstring | null
  • facility_namestring | null
  • custody_recordedboolean

    Defaults to false

POST /warehouse/aging/{ref}/dispose
curl
curl -X POST http://62.169.23.239:8001/api/v1/warehouse/aging/BOX-F4E1A9-0003/dispose \
  -H "Authorization: Bearer $SKANNIT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "ref_kind": "box",
    "reason": "Customer changed their mind",
    "facility_id": "be5df03e-bc08-5a20-8773-8f8f63bffeaf"
  }'
200Response
json
{
  "ref": "BOX-F4E1A9-0003",
  "ref_kind": "box",
  "status": "removed",
  "units_removed": 24,
  "recycling_request_ids": [
    1
  ],
  "facility_id": "be5df03e-bc08-5a20-8773-8f8f63bffeaf",
  "facility_name": "ReLoop Recycling",
  "custody_recorded": false
}
NextStock checks→