REST API
Inventory

Inventory Management

All routes under /api/v1/inventory require authentication.

Categories

GET /inventory/categories

List all categories for the current organisation.

Auth: Required

Response (200):

{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "name": "Beverages",
      "description": "Drinks and beverages",
      "sort_order": 0,
      "is_active": true,
      "product_count": 15,
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}

POST /inventory/categories

Create a new category.

Auth: Required

Request Body:

{
  "name": "Beverages",
  "description": "Drinks and beverages"
}

Response: Category object.


PATCH /inventory/categories/:id

Update a category.

Auth: Required

Request Body (all optional):

{
  "name": "Soft Drinks",
  "description": "Non-alcoholic beverages",
  "sort_order": 1,
  "is_active": true
}

Response: Category object.


DELETE /inventory/categories/:id

Delete a category.

Auth: Required

Response (204): No Content


Products

GET /inventory/products

List products with filtering and pagination.

Auth: Required

Query Parameters:

ParameterTypeDescription
searchstringSearch by name/SKU
category_iduuidFilter by category
statusstringactive or inactive
low_stockbooleanOnly low-stock products
pageintPage number (default: 1)
page_sizeintItems per page (default: 20)

Response: Paginated product list.


POST /inventory/products

Create a new product.

Auth: Required

Request Body:

{
  "name": "Pepsi 500ml",
  "category_id": "uuid",
  "sku": "BEV-001",
  "barcode": "1234567890123",
  "cost_price": 800,
  "selling_price": 1500,
  "stock": 100,
  "reorder_point": 20,
  "image": "https://...",
  "tax_rate": 18.0,
  "description": "500ml bottle of Pepsi",
  "is_service": false,
  "is_active": true
}

Response: Product object.


GET /inventory/products/:id

Get a single product by ID.

Auth: Required

Response: Product object with full details.


PATCH /inventory/products/:id

Update a product. All fields optional.

Auth: Required

Request Body: Same as POST /inventory/products but all fields are optional.

Response: Product object.


DELETE /inventory/products/:id

Delete a product.

Auth: Required

Response (204): No Content


GET /inventory/products/barcode/:code

Look up a product by barcode.

Auth: Required

Response: Product object.


POST /inventory/products/:id/adjust

Manually adjust stock level.

Auth: Required

Request Body:

{
  "qty_change": -5,
  "reason": "damaged",
  "note": "5 units damaged during delivery"
}

Response: Updated Product object.


GET /inventory/products/:id/adjustments

View stock adjustment audit trail.

Auth: Required

Query: limit (default: 50, max: 200)

Response: Array of StockAdjustment objects:

{
  "id": "uuid",
  "product_id": "uuid",
  "qty_change": -5,
  "reason": "damaged",
  "note": "5 units damaged during delivery",
  "adjusted_by": "user-uuid",
  "created_at": "2024-01-15T10:30:00Z"
}

GET /inventory/low-stock

List products at or below reorder point.

Auth: Required

Response: Array of Product objects with is_low_stock: true.


Product Input Schema

{
  "name": "string (required)",
  "category_id": "uuid (optional)",
  "sku": "string (optional, unique within org)",
  "barcode": "string (optional, unique within org)",
  "cost_price": 0.0,
  "selling_price": 0.0,
  "stock": 0,
  "reorder_point": 5,
  "image": "string (URL, optional)",
  "tax_rate": 18.0,
  "description": "string (optional)",
  "is_service": false,
  "is_active": true
}