Skip to main content

Inventory

Base path: /api/inventory

Products

GET /inventory/products

Get all products with category and supplier data.

Auth: Logged in

Query parameters:

ParameterTypeDescription
searchstringFilter by product name
supplier_idintegerFilter by supplier
pageintegerPage number (activates pagination)
limitintegerItems per page (default 50)

Response: Array of product objects with category_name, category_color, supplier_name.

POST /inventory/products

Create a new product. An article_number is auto-generated using the ART_PRODUCT sequence. If no vat_rate_id is given, the default VAT rate is applied automatically.

Auth: Admin

FieldTypeRequiredDescription
namestringYesProduct name
skustringYesStock-keeping unit code
pricenumberYesSale price (>= 0)
purchase_pricenumberYesPurchase price (required for cost calculations)
quantity_on_handintegerYesCurrent stock level
low_stock_thresholdintegerNoAlert threshold (default 5)
category_idintegerNoProduct category ID
supplier_idintegerNoSupplier ID
vat_rate_idintegerNoVAT rate ID (defaults to the system default VAT rate)
ean_codestringNoEAN barcode

PUT /inventory/products/:id

Update a product.

Auth: Admin

All fields are optional. When purchase_orders_enabled is false in settings, you may update quantity_on_hand directly here.

DELETE /inventory/products/:id

Delete a product.

Auth: Admin


Product Categories

GET /inventory/categories

Get all product categories ordered by position.

POST /inventory/categories

Create a new category.

Auth: Admin

FieldTypeRequiredDefault
namestringYes
colorstring (hex)No#6e56cf

PUT /inventory/categories/:id

Update a category (name, colour, position).

Auth: Admin

DELETE /inventory/categories/:id

Delete a category.

Auth: Admin


Stock Transactions

GET /inventory/transactions

Get stock mutations (incoming deliveries, sales, manual adjustments, internal consumption).

Auth: Logged in

Query parameters: product_id, from (date), to (date)

Each transaction has a delta field (positive = stock in, negative = stock out) and a reason string.


Suppliers

Base path: /api/suppliers

GET /suppliers

List all suppliers ordered by name.

Auth: Logged in

Query parameters: active=true to filter active suppliers only; page, limit for pagination.

POST /suppliers

Create a new supplier. An auto-generated creditor_number is assigned using the CRED sequence.

Auth: Admin

FieldTypeRequired
namestringYes
contact_personstringNo
emailstringNo
phonestringNo
addressstringNo
notesstringNo
websitestringNo

GET /suppliers/:id

Get a single supplier with their 20 most recent purchase orders.

PUT /suppliers/:id

Update a supplier.

Auth: Admin

Additional field: active (boolean) to deactivate a supplier.

DELETE /suppliers/:id

Delete a supplier.

Auth: Admin


Purchase Orders

Base path: /api/suppliers (purchase order endpoints are under the suppliers route)

GET /suppliers/orders

List all purchase orders with supplier name and item counts.

Auth: Logged in

Query parameters: status to filter by status; page, limit for pagination.

GET /suppliers/orders/:id

Get a single purchase order with all its line items.

Auth: Logged in

POST /suppliers/orders

Create a new purchase order. An order_number is auto-generated using the IO sequence. New orders have status created.

Auth: Admin

FieldTypeRequiredDescription
supplier_idintegerYesSupplier ID
staff_idintegerNoAssigned staff member (defaults to current user)
notesstringNoOrder notes
order_datedateNoOrder date (defaults to today)
expected_datedateNoExpected delivery date

Response (201): Order object with empty items array.

PUT /suppliers/orders/:id/items

Bulk-set the line items on a purchase order. Replaces all existing items. Only allowed when status is created or ordered.

Auth: Admin

{
"items": [
{ "product_id": 5, "quantity": 10, "unit_cost": 4.50 },
{ "product_id": 8, "quantity": 2, "unit_cost": 12.00 }
]
}

PUT /suppliers/orders/:id

Update a purchase order's status or metadata.

Auth: Admin

FieldTypeDescription
statusstringcreated, ordered, received, cancelled
notesstringOrder notes
order_datedateOrder date
expected_datedateExpected delivery date
received_datedateActual receipt date
packing_slip_numberstringPacking slip reference number

When status transitions to received, stock is automatically incremented for each line item and inventory_transactions rows are created. received_date is set to today if not provided.

POST /suppliers/orders/:id/packing-slip

Upload a packing slip file (PDF, image) for a received order. Max 10MB. The file is saved with a structured name: {order_number}_{creditor_number}_{received_date}.

Auth: Admin

Content-Type: multipart/form-data

FieldTypeRequired
filefileYes

Response: { "ok": true, "filename": "IO2601_CRED001_20260325.pdf" }

GET /suppliers/orders/:id/packing-slip

Download the packing slip file for an order.

Auth: Logged in

GET /suppliers/packing-slips/zip

Download all packing slips for a given year as a ZIP archive.

Auth: Admin

Query parameters: year (defaults to current year)

DELETE /suppliers/orders/:id

Delete a purchase order. Works for all statuses.

Auth: Admin

Behaviour by status:

Order statusStock effect
createdNo stock change
orderedNo stock change
receivedStock is decremented for all received items; reversing inventory_transactions rows are created; packing slip file is removed
cancelledNo stock change

Response: { "ok": true }

Purchase Order Statuses

StatusDescription
createdOrder created, items being added
orderedOrder sent to supplier
receivedGoods received; stock updated
cancelledOrder cancelled

Auto-numbering

Sequence prefixEntityExample
IOPurchase ordersIO2601, IO2602
CREDSupplier creditor numbersCRED001, CRED002
ART_PRODUCTProduct article numbersART_PRODUCT001

Sequences reset yearly (last two digits of the year are part of the number).