The Inventory provides a comprehensive REST API for all inventory management operations. This guide covers authentication, common workflows, and endpoint reference.
Login to get JWT tokens:
curl -X POST http://localhost:8000/api/v1/auth/login/ \
-H "Content-Type: application/json" \
-d '{
"username": "your_username",
"password": "your_password"
}'
Response:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"user": {
"id": 1,
"username": "your_username",
"email": "user@example.com",
"is_superuser": false
}
}
Python example:
import requests
url = "http://localhost:8000/api/v1/auth/login/"
payload = {
"username": "your_username",
"password": "your_password"
}
response = requests.post(url, json=payload)
data = response.json()
access_token = data["access"]
print(access_token)
JavaScript example:
(async () => {
const response = await fetch("http://localhost:8000/api/v1/auth/login/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: "your_username",
password: "your_password"
})
});
const data = await response.json();
const accessToken = data.access;
console.log(accessToken);
})();
Use the access token in the Authorization header:
curl http://localhost:8000/api/v1/products/ \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..."
Python example:
import requests
url = "http://localhost:8000/api/v1/products/"
headers = {
"Authorization": "Bearer your_access_token"
}
response = requests.get(url, headers=headers)
products = response.json()
print(products)
JavaScript example:
(async () => {
const response = await fetch("http://localhost:8000/api/v1/products/", {
method: "GET",
headers: {
"Authorization": "Bearer your_access_token"
}
});
const products = await response.json();
console.log(products);
})();
Interactive API documentation:
JWT (JSON Web Tokens) is the recommended authentication method for API clients.
POST /api/v1/auth/login/
Content-Type: application/json
{
"username": "your_username",
"password": "your_password"
}
Response:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"user": {
"id": 1,
"username": "your_username",
"email": "user@example.com",
"is_superuser": false,
"memberships": [
{
"id": 1,
"tenant": {
"id": 1,
"name": "Default Tenant"
},
"role": "owner"
}
]
}
}
Include the access token in the Authorization header:
Authorization: Bearer <access_token>
Access tokens expire after 30 minutes. Use the refresh token to get a new access token:
POST /api/v1/auth/refresh/
Content-Type: application/json
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}
Response:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}
For scripts and integrations, use token authentication:
Authorization: Token <api_token>
For browser-based requests, session cookies are automatically used.
POST /api/v1/products/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"sku": "PROD-001",
"name": "Widget A",
"description": "A useful widget",
"category": 1,
"unit_of_measure": "pcs",
"unit_cost": 10.50,
"reorder_point": 50,
"is_active": true
}
POST /api/v1/stock-locations/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"name": "Warehouse A - Shelf 1",
"description": "Main warehouse, shelf 1",
"is_active": true
}
POST /api/v1/stock-movements/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"product": 1,
"from_location": null,
"to_location": 1,
"quantity": 100,
"unit_cost": 10.50,
"movement_type": "receive",
"reference": "PO-2026-001",
"notes": "Initial stock receipt"
}
GET /api/v1/stock-records/?product=1&location=1
Authorization: Bearer <access_token>
Response:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"product": {
"id": 1,
"sku": "PROD-001",
"name": "Widget A"
},
"location": {
"id": 1,
"name": "Warehouse A - Shelf 1"
},
"quantity": 100,
"is_low_stock": false
}
]
}
POST /api/v1/purchase-orders/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"supplier": 1,
"order_number": "PO-2026-001",
"order_date": "2026-05-16",
"expected_delivery_date": "2026-05-23",
"status": "draft",
"lines": [
{
"product": 1,
"quantity": 100,
"unit_price": 10.50
}
]
}
POST /api/v1/sales-orders/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"customer": 1,
"order_number": "SO-2026-001",
"order_date": "2026-05-16",
"status": "draft",
"lines": [
{
"product": 1,
"quantity": 50
}
]
}
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/auth/login/ |
User login (get JWT tokens) |
POST |
/api/v1/auth/refresh/ |
Refresh access token |
GET |
/api/v1/auth/me/ |
Get current user profile |
POST |
/api/v1/auth/change-password/ |
Change password |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/products/ |
List products |
POST |
/api/v1/products/ |
Create product |
GET |
/api/v1/products/{id}/ |
Get product details |
PUT |
/api/v1/products/{id}/ |
Update product |
PATCH |
/api/v1/products/{id}/ |
Partial update |
DELETE |
/api/v1/products/{id}/ |
Delete product |
GET |
/api/v1/categories/ |
List categories |
POST |
/api/v1/categories/ |
Create category |
GET |
/api/v1/stock-locations/ |
List stock locations |
POST |
/api/v1/stock-locations/ |
Create stock location |
GET |
/api/v1/stock-records/ |
List stock records |
GET |
/api/v1/stock-movements/ |
List stock movements |
POST |
/api/v1/stock-movements/ |
Create stock movement |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/suppliers/ |
List suppliers |
POST |
/api/v1/suppliers/ |
Create supplier |
GET |
/api/v1/purchase-orders/ |
List purchase orders |
POST |
/api/v1/purchase-orders/ |
Create purchase order |
GET |
/api/v1/purchase-orders/{id}/ |
Get purchase order |
POST |
/api/v1/purchase-orders/{id}/confirm/ |
Confirm purchase order |
POST |
/api/v1/purchase-orders/{id}/cancel/ |
Cancel purchase order |
GET |
/api/v1/goods-received-notes/ |
List GRNs |
POST |
/api/v1/goods-received-notes/ |
Create GRN |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/customers/ |
List customers |
POST |
/api/v1/customers/ |
Create customer |
GET |
/api/v1/sales-orders/ |
List sales orders |
POST |
/api/v1/sales-orders/ |
Create sales order |
GET |
/api/v1/sales-orders/{id}/ |
Get sales order |
POST |
/api/v1/sales-orders/{id}/confirm/ |
Confirm sales order |
POST |
/api/v1/sales-orders/{id}/cancel/ |
Cancel sales order |
GET |
/api/v1/dispatches/ |
List dispatches |
POST |
/api/v1/dispatches/ |
Create dispatch |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/reports/stock-valuation/ |
Stock valuation report |
GET |
/api/v1/reports/movements/ |
Movement history report |
GET |
/api/v1/reports/low-stock/ |
Low stock report |
GET |
/api/v1/reports/overstock/ |
Overstock report |
GET |
/api/v1/reports/purchases/ |
Purchase summary report |
GET |
/api/v1/reports/sales/ |
Sales summary report |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/tenants/current/ |
Get current tenant |
GET |
/api/v1/tenants/memberships/ |
List user memberships |
All errors return a JSON response with status code and error details:
{
"detail": "Error message",
"code": "error_code"
}
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request data |
401 |
unauthorized |
Missing or invalid authentication |
403 |
permission_denied |
User doesn’t have permission |
404 |
not_found |
Resource not found |
409 |
conflict |
Resource conflict (e.g., duplicate SKU) |
500 |
server_error |
Server error |
curl -X POST http://localhost:8000/api/v1/products/ \
-H "Content-Type: application/json" \
-d '{"sku": "PROD-001"}'
Response (400):
{
"name": ["This field is required."],
"category": ["This field is required."]
}
Customer and Supplier phone fields validate phone number formats at the API level. Valid phone numbers may include an optional country code, spaces, hyphens, and parentheses.
Examples of accepted formats:
9876543210
+919876543210
+91 98765 43210
98765-43210
(123) 456-7890
Invalid phone values return a 400 validation error:
{
"phone": ["Enter a valid phone number."]
}
401 Unauthorized
{
"detail": "Authentication credentials were not provided."
}
404 Not Found
{
"detail": "Product not found."
}
400 Bad Request
{
"detail": "Invalid request data."
}
All list endpoints support pagination:
GET /api/v1/products/?page=1&page_size=25
Python example:
import requests
url = "http://localhost:8000/api/v1/products/"
headers = {
"Authorization": "Bearer your_access_token"
}
params = {
"page": 1,
"page_size": 25
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
print(data)
JavaScript example:
(async () => {
const url = new URL("http://localhost:8000/api/v1/products/");
url.searchParams.set("page", "1");
url.searchParams.set("page_size", "25");
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": "Bearer your_access_token"
}
});
const data = await response.json();
console.log(data);
})();
Response:
{
"count": 100,
"next": "http://localhost:8000/api/v1/products/?page=2",
"previous": null,
"results": [...]
}
Filter results using query parameters:
# Filter by category
GET /api/v1/products/?category=1
# Filter by status
GET /api/v1/products/?is_active=true
# Filter by date range
GET /api/v1/stock-movements/?created_at__gte=2026-01-01&created_at__lte=2026-12-31
Python example:
import requests
url = "http://localhost:8000/api/v1/products/"
headers = {
"Authorization": "Bearer your_access_token"
}
params = {
"category": 1,
"is_active": "true"
}
response = requests.get(url, headers=headers, params=params)
products = response.json()
print(products)
JavaScript example:
(async () => {
const url = new URL("http://localhost:8000/api/v1/products/");
url.searchParams.set("category", "1");
url.searchParams.set("is_active", "true");
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": "Bearer your_access_token"
}
});
const products = await response.json();
console.log(products);
})();
Search across multiple fields:
GET /api/v1/products/?search=widget
Sort results:
# Ascending
GET /api/v1/products/?ordering=name
# Descending
GET /api/v1/products/?ordering=-created_at
Currently, there is no rate limiting. For production deployments, consider implementing rate limiting based on your needs.