Skip to content

Warehouses API

The Warehouses API provides endpoints for managing warehouses and their stock. All endpoints require admin authentication.

Endpoints

MethodPathDescription
GET/api/v1/admin/warehousesList warehouses
POST/api/v1/admin/warehousesCreate warehouse
GET/api/v1/admin/warehouses/{id}Get warehouse
PUT/api/v1/admin/warehouses/{id}Update warehouse
DELETE/api/v1/admin/warehouses/{id}Delete warehouse
GET/api/v1/admin/warehouses/{id}/stockGet stock by warehouse
PUT/api/v1/admin/warehouses/{id}/stockSet stock entries
GET/api/v1/admin/products/{productID}/stockGet stock by product

List Warehouses

bash
curl http://localhost:8080/api/v1/admin/warehouses?page=1&limit=20 \
  -H 'Authorization: Bearer <token>'

Query Parameters:

ParamTypeDefaultDescription
pageint1Page number
limitint20Items per page (max 200)
activeboolFilter by active status

Response:

json
{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000001",
      "name": "Default Warehouse",
      "code": "DEFAULT",
      "active": true,
      "allow_negative_stock": false,
      "priority": 0,
      "address_line1": "",
      "city": "",
      "country": "",
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-01T00:00:00Z"
    }
  ],
  "meta": { "total": 1, "page": 1, "limit": 20, "pages": 1 }
}

Create Warehouse

bash
curl -X POST http://localhost:8080/api/v1/admin/warehouses \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Berlin Warehouse",
    "code": "WH-BER",
    "active": true,
    "priority": 1,
    "allow_negative_stock": false,
    "address_line1": "Alexanderplatz 1",
    "city": "Berlin",
    "postal_code": "10178",
    "country": "DE"
  }'

Required fields: name, code

Response: 201 Created with the created warehouse object.

Unique Code

The code field must be unique across all warehouses. A 409 Conflict is returned if the code already exists.

Get Warehouse

bash
curl http://localhost:8080/api/v1/admin/warehouses/{id} \
  -H 'Authorization: Bearer <token>'

Update Warehouse

bash
curl -X PUT http://localhost:8080/api/v1/admin/warehouses/{id} \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Berlin Warehouse (Main)",
    "code": "WH-BER",
    "active": true,
    "priority": 0,
    "allow_negative_stock": true
  }'

Delete Warehouse

bash
curl -X DELETE http://localhost:8080/api/v1/admin/warehouses/{id} \
  -H 'Authorization: Bearer <token>'

Returns 204 No Content on success. Deleting a warehouse also removes all its stock entries (cascade).

Get Stock by Warehouse

Returns all stock entries for a specific warehouse.

bash
curl http://localhost:8080/api/v1/admin/warehouses/{id}/stock \
  -H 'Authorization: Bearer <token>'

Response:

json
{
  "data": [
    {
      "id": "...",
      "warehouse_id": "...",
      "product_id": "...",
      "variant_id": null,
      "quantity": 50,
      "warehouse_name": "Berlin Warehouse",
      "warehouse_code": "WH-BER",
      "product_sku": "PROD-001",
      "product_name": "Example Product",
      "variant_sku": "",
      "created_at": "...",
      "updated_at": "..."
    }
  ]
}

Stock entries include human-readable identifiers alongside UUIDs:

FieldTypeDescription
product_skustringSKU of the product
product_namestringName of the product
variant_skustringSKU of the variant (empty if product-level stock)

Set Stock

Upsert stock quantities for products at a warehouse. Also records adjustment movements.

bash
curl -X PUT http://localhost:8080/api/v1/admin/warehouses/{id}/stock \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      {
        "product_id": "abc-123",
        "quantity": 100,
        "reference": "Initial stock"
      },
      {
        "product_id": "abc-123",
        "variant_id": "var-456",
        "quantity": 25,
        "reference": "Variant stock"
      }
    ]
  }'

Each item in the items array:

FieldTypeRequiredDescription
product_idUUIDYesProduct ID
variant_idUUIDNoVariant ID (omit for product-level stock)
quantityintYesNew stock quantity (min 0)
referencestringNoAudit reference note

Setting stock also updates the denormalized stock field on the product/variant.

Get Stock by Product

Returns stock entries across all warehouses for a specific product.

bash
curl http://localhost:8080/api/v1/admin/products/{productID}/stock \
  -H 'Authorization: Bearer <token>'

Response:

json
{
  "data": [
    {
      "id": "...",
      "warehouse_id": "...",
      "product_id": "...",
      "variant_id": null,
      "quantity": 50,
      "warehouse_name": "Berlin Warehouse",
      "warehouse_code": "WH-BER",
      "product_sku": "PROD-001",
      "product_name": "Example Product",
      "variant_sku": ""
    },
    {
      "id": "...",
      "warehouse_id": "...",
      "product_id": "...",
      "variant_id": null,
      "quantity": 30,
      "warehouse_name": "Munich Warehouse",
      "warehouse_code": "WH-MUC",
      "product_sku": "PROD-001",
      "product_name": "Example Product",
      "variant_sku": ""
    }
  ]
}

Stock Deduction (Automatic)

Stock is deducted automatically when an order is created via the Store checkout endpoint. The system:

  1. Selects warehouses by priority (ascending)
  2. Deducts stock from each warehouse until the order quantity is fulfilled
  3. Records sale movements with the order reference
  4. Updates denormalized product/variant stock

If total available stock is insufficient and no warehouse allows negative stock, the checkout is rejected with a 422 Unprocessable Entity response:

json
{
  "errors": [
    {
      "code": "insufficient_stock",
      "detail": "one or more items are out of stock",
      "field": ""
    }
  ]
}

Stock Restoration (Automatic)

When an order status transitions to cancelled or refunded, all sale movements for that order are reversed automatically. Stock is restored to the original warehouses.

Negative Stock (allow_negative_stock)

By default, warehouses enforce a stock floor of zero — customers cannot add more items to their cart than are available, and checkout is rejected if quantities drop to zero.

Setting allow_negative_stock: true on a warehouse removes this restriction for products stored there. This is useful for made-to-order or pre-order products where overselling is intentional.

How it works:

  • When StockAvailable is evaluated (on cart add, cart item update, and checkout), the system first checks whether any active warehouse carries the product with allow_negative_stock enabled.
  • If at least one such warehouse exists, the stock check is bypassed entirely and the operation is allowed regardless of current quantity.
  • During deduction, stock is decremented normally — the quantity field will go negative if demand exceeds supply.

Enabling on create:

bash
curl -X POST http://localhost:8080/api/v1/admin/warehouses \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Pre-Order Warehouse",
    "code": "WH-PREORDER",
    "active": true,
    "allow_negative_stock": true
  }'

Enabling on an existing warehouse:

bash
curl -X PUT http://localhost:8080/api/v1/admin/warehouses/{id} \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Pre-Order Warehouse",
    "code": "WH-PREORDER",
    "active": true,
    "allow_negative_stock": true
  }'

Admin Panel

The "Negativer Bestand erlaubt" checkbox in the warehouse create and edit forms maps directly to this field.

Scope

allow_negative_stock is a per-warehouse flag. A product is only exempt from the stock floor when it has a stock entry in at least one warehouse where the flag is true. If the product has no stock entry in a negative-stock warehouse, the normal limit still applies.

Cart Stock Validation

Stock is checked at two points during the cart lifecycle, in addition to checkout:

  1. POST /api/v1/store/cart/{id}/items — When a customer adds a product, the service fetches the quantity already in the cart for that product/variant and checks existing + requested <= available. This prevents bypassing the stock limit by adding the same product multiple times in small increments.

  2. PUT /api/v1/store/cart/{id}/items/{itemId} — When a customer changes the quantity of an existing cart line item, the new absolute quantity is checked against available stock.

Both operations return ErrInsufficientStock (surfaced as a 422 error to the API consumer) when the check fails, unless the product is in a warehouse with allow_negative_stock: true.

Released under the APACHE 2.0 License.