Base URL
http://localhost:5000
Inventory Management
List all inventory items with filters and pagination.
Query Parameters
product_id(optional) - Filter by productstatus(optional) - Filter by status: active, inactive, archivedlow_stock(optional) - Set to "true" to show only items where quantity <= reorder_pointq(optional) - Search by product name or SKUpage,per_page(optional; default 1 / 20)
Example:
GET /inventory?low_stock=true&page=1&per_page=20
200 OK
{
"inventory": [
{
"id": 1,
"product_id": 1,
"product": {"id": 1, "name": "Potatoes", "sku": "POT-001"},
"quantity": "50.000",
"unit_id": 3,
"unit": {"id": 3, "name": "Kilogram", "symbol": "kg"},
"reorder_point": "20.000",
"status": "active",
"created_at": "2025-01-15T10:00:00",
"updated_at": "2025-01-15T10:00:00"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"pages": 1,
"has_next": false,
"has_prev": false
}
}
Get inventory details for a specific product.
200 OK
404 Not Found - Product or inventory not found
Initialize inventory tracking for a product. Can only be done if inventory doesn't already exist.
Request Body (JSON)
unit_id(required) - Unit ID for measuring quantityquantity(optional) - Initial quantity (default: 0)reorder_point(optional) - Minimum stock level before reordering
Example:
POST /inventory/1/initialize
{
"unit_id": 3,
"quantity": 0,
"reorder_point": 20
}
201 Created
400 - Missing required fields or unit not found
404 - Product not found
409 - Inventory already initialized
Update inventory details (quantity, reorder_point, status). Note: Quantity is usually updated automatically by purchase/sale operations.
Request Body (JSON)
quantity(optional) - Update quantity manuallyreorder_point(optional) - Update reorder pointstatus(optional) - Update status: active, inactive, archived
Example:
PATCH /inventory/1
{
"quantity": 60,
"reorder_point": 25
}
200 OK
404 - Product or inventory not found
Purchase Management
Record a stock purchase. Creates a PurchaseItem record and automatically increases inventory quantity.
Request Body (JSON)
product_id(required) - Product IDsupplier_id(required) - Supplier ID who provided the stockquantity(required) - Quantity purchasedunit_cost(required) - Cost per unit at time of purchase
Example:
POST /inventory/purchases
{
"product_id": 1,
"supplier_id": 5,
"quantity": 40,
"unit_cost": 35
}
201 Created
{
"purchase_item": {
"id": 1,
"product_id": 1,
"supplier_id": 5,
"supplier": {
"id": 5,
"name": "Fresh Farm Supplies",
"business_name": "Fresh Farm Co.",
"email": "contact@freshfarm.com",
"phone_number": "+254700000000"
},
"quantity": "40.000",
"unit_cost": "35.000",
"created_at": "2025-01-15T10:30:00"
},
"inventory": {
"id": 1,
"product_id": 1,
"quantity": "90.000",
...
}
}
400 - Missing required fields, invalid values, or inventory not initialized
404 - Product or supplier not found
List all purchase history with filters.
Query Parameters
product_id(optional) - Filter by productsupplier_id(optional) - Filter by supplierdate_from(optional) - Filter purchases from datedate_to(optional) - Filter purchases to datepage,per_page(optional; default 1 / 20)
Example:
GET /inventory/purchases?supplier_id=5&date_from=2025-01-01&page=1
200 OK
Returns paginated list of purchases with supplier details included.
Get purchase history for a specific product with totals (total quantity, total cost, average cost).
Query Parameters
page,per_page(optional; default 1 / 20)
200 OK
{
"product": {"id": 1, "name": "Potatoes", ...},
"purchases": [...],
"totals": {
"total_quantity": "60.000",
"total_cost": "1950.000",
"average_cost": "32.500"
},
"pagination": {...}
}
404 - Product not found
Sale Management
Record a sale. Creates a SaleItem record with price snapshot and automatically decreases inventory quantity. Validates sufficient stock availability.
Request Body (JSON)
product_id(required) - Product IDquantity(required) - Quantity soldunit_price(required) - Price per unit at time of sale (snapshot)
Example:
POST /inventory/sales
{
"product_id": 1,
"quantity": 10,
"unit_price": 60
}
201 Created
{
"sale_item": {
"id": 1,
"product_id": 1,
"product": {"id": 1, "name": "Potatoes", ...},
"quantity": "10.000",
"unit_price": "60.000",
"created_at": "2025-01-15T11:00:00"
},
"inventory": {
"id": 1,
"quantity": "80.000",
...
}
}
400 - Missing required fields, invalid values, insufficient stock, or inventory not initialized
404 - Product not found
List all sale history with filters.
Query Parameters
product_id(optional) - Filter by productdate_from(optional) - Filter sales from datedate_to(optional) - Filter sales to datepage,per_page(optional; default 1 / 20)
200 OK
Get sale history for a specific product with totals (total quantity, total revenue, average price).
200 OK
{
"product": {"id": 1, "name": "Potatoes", ...},
"sales": [...],
"totals": {
"total_quantity": "15.000",
"total_revenue": "900.000",
"average_price": "60.000"
},
"pagination": {...}
}
404 - Product not found
Price Management
effective_from and effective_to dates.
Set a new price for a product. Creates a new price rule and optionally closes the previous active price by setting its effective_to date.
Request Body (JSON)
amount(required) - Price amountprice_type(optional) - Type: retail, promo, wholesale (default: retail)effective_from(optional) - When this price becomes active (ISO format, default: now)close_old_price(optional) - Set previous active price's effective_to (default: true)
Example:
POST /products/1/prices
{
"amount": 60,
"price_type": "retail",
"close_old_price": true
}
201 Created
{
"price": {
"id": 2,
"product_id": 1,
"amount": "60.000",
"price_type": "retail",
"effective_from": "2025-01-15T12:00:00",
"effective_to": null,
"created_at": "2025-01-15T12:00:00",
"updated_at": "2025-01-15T12:00:00"
}
}
400 - Missing amount, invalid price_type, or invalid date format
404 - Product not found
List price history for a product. By default shows only active prices.
Query Parameters
include_inactive(optional) - Set to "true" to include inactive/expired prices (default: false)page,per_page(optional; default 1 / 20)
Example:
GET /products/1/prices?include_inactive=true
200 OK
404 - Product not found
Get the currently active price for a product. This is the price that should be displayed to customers.
200 OK
{
"price": {
"id": 2,
"product_id": 1,
"amount": "60.000",
"price_type": "retail",
"effective_from": "2025-01-15T12:00:00",
"effective_to": null,
...
}
}
404 - Product not found or no active price
unit_price field to create an immutable snapshot.
Typical Workflow
1. Add Product
Create product via POST /products (no inventory or price yet)
2. Initialize Inventory
POST /inventory/{product_id}/initialize with unit_id and optional initial quantity
3. Record First Purchase
POST /inventory/purchases with product_id, supplier_id, quantity, unit_cost
This automatically increases inventory quantity
4. Set Selling Price
POST /products/{product_id}/prices with amount and price_type
5. Customer Views Product
GET /products/{product_id} for product details
GET /products/{product_id}/prices/active for current price
GET /inventory/{product_id} for stock availability
6. Record Sale at Checkout
POST /inventory/sales with product_id, quantity, and unit_price (snapshot from step 5)
This automatically decreases inventory quantity
7. Future Operations
- More stock arrives: Record another purchase (step 3) - price unchanged, inventory increases
- Price change: Create new price rule (step 4) - old price preserved in history
- More sales: Record sales (step 6) - uses current active price