Inventory API¶
Enterprise feature
The Inventory API is available on Enterprise plans. Compare plans to find the right fit for your team.
The Inventory API lets you manage parts and supplies, track stock across warehouses and service vehicles, adjust quantities, transfer stock between locations, and review transaction history. Use it to integrate your inventory data with ERP systems, procurement platforms, or custom dashboards.
Endpoints summary¶
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /api/v1/inventory | inventory:read | List items |
GET | /api/v1/inventory?id={id} | inventory:read | Get a single item |
POST | /api/v1/inventory | inventory:write | Create an item |
PUT | /api/v1/inventory?id={id} | inventory:write | Update an item (full) |
PATCH | /api/v1/inventory?id={id} | inventory:write | Update an item (partial) |
GET | /api/v1/inventory?id={id}&sub=stock | inventory:read | Get item stock |
GET | /api/v1/inventory?id={id}&sub=transactions | inventory:read | Get item transactions |
POST | /api/v1/inventory?id={id}&sub=adjust | inventory:write | Adjust stock |
POST | /api/v1/inventory?sub=transfer | inventory:transfer | Transfer stock |
GET | /api/v1/inventory?sub=locations | inventory:read | List locations |
GET | /api/v1/inventory?sub=locations&location_id={id} | inventory:read | Get a single location |
GET | /api/v1/inventory?sub=low-stock | inventory:read | Low stock alerts |
Scopes¶
Your API key must include the appropriate scope for each endpoint.
| Scope | Access |
|---|---|
inventory:read | List items, get item details, view stock, transactions, locations, and low-stock alerts |
inventory:write | Create items, update items (PUT/PATCH), and adjust stock. Implies inventory:read access. |
inventory:transfer | Transfer stock between locations. Does not imply read or write access. |
Tip
Most integrations need inventory:read and inventory:write. Add inventory:transfer only if your integration moves stock between locations.
Item object¶
Every item returned by the API contains these fields:
| Field | Type | Description |
|---|---|---|
id | integer | Unique item identifier |
part_number | string | SKU or part number (unique within your company) |
part_name | string | Display name |
description | string | Free-text description, or null |
category | string | Category name, or null |
serial_number | string | Serial number, or null |
quantity_on_hand | integer | Total quantity across all locations |
min_stock_level | integer | Low-stock alert threshold |
reorder_quantity | integer | Suggested reorder amount |
cost_per_unit | float | Cost price, or null |
price_per_unit | float | Sell price, or null |
supplier_id | integer | Linked supplier ID, or null |
created_at | string | Creation timestamp (UTC) |
updated_at | string | Last update timestamp (UTC) |
Stock entry object¶
Stock entries represent inventory levels at a specific location.
| Field | Type | Description |
|---|---|---|
location_id | integer | Location identifier |
location_name | string | Location display name, or null |
location_type | string | warehouse, vehicle, technician, job_site, other, or null |
quantity | integer | Total quantity at this location |
reserved_quantity | integer | Quantity reserved for jobs |
available | integer | Available quantity (quantity - reserved_quantity) |
last_updated | string | Last stock change timestamp |
Transaction object¶
Transactions record every stock movement for a full audit trail.
| Field | Type | Description |
|---|---|---|
id | integer | Transaction identifier |
transaction_type | string | One of: Add, Remove, Transfer_out, Transfer_in, Adjust, Reserve, Release, Job_complete |
quantity | integer | Signed quantity change (positive = increase, negative = decrease) |
location_id | integer | Primary location, or null |
location_name | string | Primary location name, or null |
related_location_id | integer | Secondary location (for transfers), or null |
related_location_name | string | Secondary location name, or null |
job_id | integer | Associated job ID, or null |
user_name | string | User who performed the action, "API" for API-initiated changes, or null |
notes | string | Transaction notes or reason, or null |
created_at | string | Transaction timestamp |
Location object¶
Locations represent warehouses, service vehicles, and other places where you store inventory.
| Field | Type | Description |
|---|---|---|
id | integer | Location identifier |
location_name | string | Display name |
location_type | string | warehouse, vehicle, technician, job_site, or other |
address | string | Street address, or null |
city | string | City, or null |
state | string | State or province, or null |
zip | string | ZIP or postal code, or null |
is_active | boolean | Whether the location is active |
created_at | string | Creation timestamp |
updated_at | string | Last update timestamp |
List items¶
GET /api/v1/inventory
Scope: inventory:read
Returns a paginated list of inventory items.
Query parameters¶
| Parameter | Type | Description |
|---|---|---|
category | string | Filter by exact category name |
search | string | Search by part number or part name |
low_stock | boolean | If true, return only items at or below their minimum stock level |
page | integer | Page number (default: 1) |
per_page | integer | Results per page (default: 50, max: 100) |
Example request¶
curl -X GET "https://fsmnavigator.com/api/v1/inventory?category=Filters&per_page=25" \
-H "X-API-Key: YOUR_API_KEY"
Example response¶
{
"data": [
{
"id": 1,
"part_number": "FLT-001",
"part_name": "HVAC Air Filter 20x25x1",
"description": "Standard residential air filter",
"category": "Filters",
"serial_number": null,
"quantity_on_hand": 150,
"min_stock_level": 20,
"reorder_quantity": 50,
"cost_per_unit": 8.50,
"price_per_unit": 15.99,
"supplier_id": null,
"created_at": "2026-02-24T10:00:00Z",
"updated_at": "2026-02-24T10:00:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 25,
"total": 14,
"total_pages": 1
},
"request_id": "a1b2c3d4e5f6"
}
Get a single item¶
GET /api/v1/inventory?id={id}
Scope: inventory:read
Retrieves full details for one item, including stock levels broken down by location.
Example request¶
Example response¶
{
"data": {
"item": {
"id": 1,
"part_number": "FLT-001",
"part_name": "HVAC Air Filter 20x25x1",
"description": "Standard residential air filter",
"category": "Filters",
"serial_number": null,
"quantity_on_hand": 150,
"min_stock_level": 20,
"reorder_quantity": 50,
"cost_per_unit": 8.50,
"price_per_unit": 15.99,
"supplier_id": null,
"created_at": "2026-02-24T10:00:00Z",
"updated_at": "2026-02-24T10:00:00Z"
},
"stock": [
{
"location_id": 1,
"location_name": "Main Warehouse",
"location_type": "warehouse",
"quantity": 100,
"reserved_quantity": 0,
"available": 100,
"last_updated": "2026-02-24T12:00:00Z"
},
{
"location_id": 3,
"location_name": "Van #1 — Alex Johnson",
"location_type": "vehicle",
"quantity": 50,
"reserved_quantity": 0,
"available": 50,
"last_updated": "2026-02-24T12:00:00Z"
}
]
},
"request_id": "a1b2c3d4e5f6"
}
Create an item¶
POST /api/v1/inventory
Scope: inventory:write
Creates a new inventory item. Returns 201 Created on success.
Request body¶
| Field | Type | Required | Description |
|---|---|---|---|
part_number | string | Yes | Unique SKU or part number within your company |
part_name | string | Yes | Display name |
description | string | No | Free-text description |
category | string | No | Category name |
serial_number | string | No | Serial number |
quantity_on_hand | integer | No | Initial total quantity (default: 0) |
min_stock_level | integer | No | Low-stock alert threshold (default: 0) |
reorder_quantity | integer | No | Suggested reorder amount (default: 0) |
cost_per_unit | float | No | Cost price |
price_per_unit | float | No | Sell price |
supplier_id | integer | No | Associated supplier ID |
Example request¶
curl -X POST "https://fsmnavigator.com/api/v1/inventory" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"part_number": "CAP-002",
"part_name": "Run Capacitor 35/5 MFD",
"description": "Dual run capacitor for HVAC systems",
"category": "Capacitors",
"min_stock_level": 10,
"reorder_quantity": 25,
"cost_per_unit": 12.50,
"price_per_unit": 24.99
}'
Example response 201 Created¶
{
"success": true,
"data": {
"id": 15,
"part_number": "CAP-002",
"part_name": "Run Capacitor 35/5 MFD",
"description": "Dual run capacitor for HVAC systems",
"category": "Capacitors",
"serial_number": null,
"quantity_on_hand": 0,
"min_stock_level": 10,
"reorder_quantity": 25,
"cost_per_unit": 12.50,
"price_per_unit": 24.99,
"supplier_id": null,
"created_at": "2026-02-24T14:30:00Z",
"updated_at": "2026-02-24T14:30:00Z"
},
"item_id": 15,
"request_id": "a1b2c3d4e5f6"
}
Error responses¶
| HTTP code | Error | Description |
|---|---|---|
400 | missing_part_number | part_number was not provided or is empty |
400 | missing_part_name | part_name was not provided or is empty |
400 | duplicate_part_number | Another item already uses this part number |
Update an item (full)¶
PUT /api/v1/inventory?id={id}
Scope: inventory:write
Replaces all fields on an existing item. Fields not included in the request body are reset to their defaults (null or 0).
Stock is not updated by PUT
The quantity_on_hand field is read-only on item endpoints. Use Adjust stock or Transfer stock to change stock levels.
Request body¶
| Field | Type | Required | Description |
|---|---|---|---|
part_number | string | Yes | Unique SKU (must not conflict with other items) |
part_name | string | Yes | Display name |
description | string | No | Defaults to null if omitted |
category | string | No | Defaults to null if omitted |
serial_number | string | No | Defaults to null if omitted |
min_stock_level | integer | No | Defaults to 0 if omitted |
reorder_quantity | integer | No | Defaults to 0 if omitted |
cost_per_unit | float | No | Defaults to null if omitted |
price_per_unit | float | No | Defaults to null if omitted |
supplier_id | integer | No | Defaults to null if omitted |
Example request¶
curl -X PUT "https://fsmnavigator.com/api/v1/inventory?id=15" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"part_number": "CAP-002-V2",
"part_name": "Run Capacitor 35/5 MFD (Updated)",
"description": "Dual run capacitor v2",
"category": "Capacitors",
"min_stock_level": 15,
"reorder_quantity": 30,
"cost_per_unit": 13.00,
"price_per_unit": 25.99
}'
Example response¶
{
"success": true,
"data": {
"id": 15,
"part_number": "CAP-002-V2",
"part_name": "Run Capacitor 35/5 MFD (Updated)",
"description": "Dual run capacitor v2",
"category": "Capacitors",
"serial_number": null,
"quantity_on_hand": 0,
"min_stock_level": 15,
"reorder_quantity": 30,
"cost_per_unit": 13.00,
"price_per_unit": 25.99,
"supplier_id": null,
"created_at": "2026-02-24T14:30:00Z",
"updated_at": "2026-02-24T14:35:00Z"
},
"request_id": "a1b2c3d4e5f6"
}
Error responses¶
| HTTP code | Error | Description |
|---|---|---|
400 | missing_part_number | part_number was not provided or is empty |
400 | missing_part_name | part_name was not provided or is empty |
400 | duplicate_part_number | Another item already uses this part number |
404 | not_found | Item ID does not exist |
Update an item (partial)¶
PATCH /api/v1/inventory?id={id}
Scope: inventory:write
Updates only the fields you include in the request body. All other fields remain unchanged.
Request body¶
Include any combination of these fields:
| Field | Type | Description |
|---|---|---|
part_number | string | Cannot be empty; duplicate check applies |
part_name | string | Cannot be empty |
description | string | Set to null to clear |
category | string | Set to null to clear |
serial_number | string | Set to null to clear |
min_stock_level | integer | Low-stock alert threshold |
reorder_quantity | integer | Suggested reorder amount |
cost_per_unit | float | Set to null to clear |
price_per_unit | float | Set to null to clear |
supplier_id | integer | Set to null to clear |
Example request¶
curl -X PATCH "https://fsmnavigator.com/api/v1/inventory?id=15" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"min_stock_level": 25,
"price_per_unit": 27.99
}'
Example response¶
{
"success": true,
"data": {
"id": 15,
"part_number": "CAP-002-V2",
"part_name": "Run Capacitor 35/5 MFD (Updated)",
"description": "Dual run capacitor v2",
"category": "Capacitors",
"serial_number": null,
"quantity_on_hand": 0,
"min_stock_level": 25,
"reorder_quantity": 30,
"cost_per_unit": 13.00,
"price_per_unit": 27.99,
"supplier_id": null,
"created_at": "2026-02-24T14:30:00Z",
"updated_at": "2026-02-24T14:40:00Z"
},
"request_id": "a1b2c3d4e5f6"
}
Tip
Use PATCH when you want to update one or two fields without resending the entire item. Use PUT when you want to replace all fields at once.
Error responses¶
| HTTP code | Error | Description |
|---|---|---|
400 | invalid_part_number | part_number was provided but is empty |
400 | invalid_part_name | part_name was provided but is empty |
400 | duplicate_part_number | Another item already uses this part number |
400 | no_fields | No recognized fields in the request body |
404 | not_found | Item ID does not exist |
Get item stock¶
GET /api/v1/inventory?id={id}&sub=stock
Scope: inventory:read
Returns stock levels for a specific item, broken down by location.
Example request¶
curl -X GET "https://fsmnavigator.com/api/v1/inventory?id=1&sub=stock" \
-H "X-API-Key: YOUR_API_KEY"
Example response¶
{
"data": [
{
"location_id": 1,
"location_name": "Main Warehouse",
"location_type": "warehouse",
"quantity": 100,
"reserved_quantity": 5,
"available": 95,
"last_updated": "2026-02-24T12:00:00Z"
},
{
"location_id": 3,
"location_name": "Van #1 — Alex Johnson",
"location_type": "vehicle",
"quantity": 50,
"reserved_quantity": 0,
"available": 50,
"last_updated": "2026-02-24T12:00:00Z"
}
],
"item_id": 1,
"request_id": "a1b2c3d4e5f6"
}
Get item transactions¶
GET /api/v1/inventory?id={id}&sub=transactions
Scope: inventory:read
Returns a paginated transaction history for a specific item.
Query parameters¶
| Parameter | Type | Description |
|---|---|---|
type | string | Filter by transaction type: Add, Remove, Transfer_out, Transfer_in, Adjust, Reserve, Release, Job_complete |
page | integer | Page number (default: 1) |
per_page | integer | Results per page (default: 50, max: 100) |
Example request¶
curl -X GET "https://fsmnavigator.com/api/v1/inventory?id=1&sub=transactions&type=Adjust" \
-H "X-API-Key: YOUR_API_KEY"
Example response¶
{
"data": [
{
"id": 42,
"transaction_type": "Adjust",
"quantity": -5,
"location_id": 1,
"location_name": "Main Warehouse",
"related_location_id": null,
"related_location_name": null,
"job_id": null,
"user_name": "API",
"notes": "Damaged units removed",
"created_at": "2026-02-24T14:00:00Z"
}
],
"item_id": 1,
"pagination": {
"page": 1,
"per_page": 50,
"total": 1,
"total_pages": 1
},
"request_id": "a1b2c3d4e5f6"
}
Transaction types¶
| Type | Description |
|---|---|
Add | Stock added to a location |
Remove | Stock removed from a location |
Transfer_out | Stock sent to another location |
Transfer_in | Stock received from another location |
Adjust | Manual stock adjustment (add or remove) |
Reserve | Stock reserved for an upcoming job |
Release | Reserved stock released back to available |
Job_complete | Stock consumed when a job is completed |
Adjust stock¶
POST /api/v1/inventory?id={id}&sub=adjust
Scope: inventory:write
Adds or removes stock for an item at a specific location. Every adjustment is recorded in the transaction history for a complete audit trail.
Request body¶
| Field | Type | Required | Description |
|---|---|---|---|
location_id | integer | Yes | Location to adjust stock at |
quantity | integer | Yes | Signed adjustment — positive to add, negative to remove. Cannot be 0. |
reason | string | Yes | Explanation for the audit trail |
Example request — add stock¶
curl -X POST "https://fsmnavigator.com/api/v1/inventory?id=1&sub=adjust" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": 1,
"quantity": 25,
"reason": "Shipment received from supplier"
}'
Example request — remove stock¶
curl -X POST "https://fsmnavigator.com/api/v1/inventory?id=1&sub=adjust" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": 1,
"quantity": -5,
"reason": "Damaged units removed from inventory"
}'
Example response¶
{
"success": true,
"data": {
"new_quantity": 120
},
"message": "Stock adjusted",
"request_id": "a1b2c3d4e5f6"
}
The new_quantity value reflects the updated quantity at the specified location after the adjustment.
Error responses¶
| HTTP code | Error | Description |
|---|---|---|
400 | missing_location_id | location_id was not provided |
400 | missing_quantity | quantity was not provided or is not numeric |
400 | missing_reason | reason was not provided |
400 | invalid_quantity | quantity is 0 |
400 | location_not_found | Location does not exist in your company |
400 | insufficient_stock | A negative adjustment would reduce stock below zero |
404 | not_found | Item ID does not exist |
Transfer stock¶
POST /api/v1/inventory?sub=transfer
Scope: inventory:transfer
Moves stock from one location to another. The transfer creates two transaction records — a Transfer_out at the source location and a Transfer_in at the destination.
Request body¶
| Field | Type | Required | Description |
|---|---|---|---|
item_id | integer | Yes | Inventory item to transfer |
from_location_id | integer | Yes | Source location |
to_location_id | integer | Yes | Destination location |
quantity | integer | Yes | Number of units to transfer (must be positive) |
notes | string | No | Optional notes for the audit trail |
Example request¶
curl -X POST "https://fsmnavigator.com/api/v1/inventory?sub=transfer" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"item_id": 1,
"from_location_id": 1,
"to_location_id": 3,
"quantity": 10,
"notes": "Restocking service van for Monday jobs"
}'
Example response¶
{
"success": true,
"data": {
"from_location": {
"location_id": 1,
"location_name": "Main Warehouse",
"new_quantity": 90
},
"to_location": {
"location_id": 3,
"location_name": "Van #1 — Alex Johnson",
"new_quantity": 60
}
},
"message": "Stock transferred",
"request_id": "a1b2c3d4e5f6"
}
Reserved stock
Transfers check available stock (total minus reserved). If 100 units are at a location but 20 are reserved for jobs, you can transfer up to 80 units.
Error responses¶
| HTTP code | Error | Description |
|---|---|---|
400 | missing_item_id | item_id was not provided |
400 | missing_from_location_id | from_location_id was not provided |
400 | missing_to_location_id | to_location_id was not provided |
400 | invalid_quantity | quantity is not a positive integer |
400 | same_location | Source and destination are the same location |
400 | from_location_not_found | Source location does not exist |
400 | to_location_not_found | Destination location does not exist |
400 | no_stock | No stock record exists at the source location |
400 | insufficient_stock | Available stock is less than the requested transfer amount |
404 | item_not_found | Item ID does not exist |
List locations¶
GET /api/v1/inventory?sub=locations
Scope: inventory:read
Returns a paginated list of inventory locations (warehouses, vehicles, and other storage points).
Query parameters¶
| Parameter | Type | Description |
|---|---|---|
type | string | Filter by type: warehouse, vehicle, technician, job_site, other |
active_only | boolean | Set to false to include inactive locations (default: true) |
page | integer | Page number (default: 1) |
per_page | integer | Results per page (default: 50, max: 100) |
Example request¶
curl -X GET "https://fsmnavigator.com/api/v1/inventory?sub=locations&type=warehouse" \
-H "X-API-Key: YOUR_API_KEY"
Example response¶
{
"data": [
{
"id": 1,
"location_name": "Main Warehouse",
"location_type": "warehouse",
"address": "123 Industrial Blvd",
"city": "Tampa",
"state": "FL",
"zip": "33601",
"is_active": true,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-02-20T10:00:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 50,
"total": 3,
"total_pages": 1
},
"request_id": "a1b2c3d4e5f6"
}
Get a single location¶
GET /api/v1/inventory?sub=locations&location_id={id}
Scope: inventory:read
Retrieves details for one location, including a summary of all items stocked there.
Example request¶
curl -X GET "https://fsmnavigator.com/api/v1/inventory?sub=locations&location_id=1" \
-H "X-API-Key: YOUR_API_KEY"
Example response¶
{
"data": {
"location": {
"id": 1,
"location_name": "Main Warehouse",
"location_type": "warehouse",
"address": "123 Industrial Blvd",
"city": "Tampa",
"state": "FL",
"zip": "33601",
"is_active": true,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-02-20T10:00:00Z"
},
"stock_summary": [
{
"part_name": "HVAC Air Filter 20x25x1",
"part_number": "FLT-001",
"quantity": 100,
"reserved_quantity": 5,
"available": 95
},
{
"part_name": "Run Capacitor 35/5 MFD",
"part_number": "CAP-002",
"quantity": 30,
"reserved_quantity": 0,
"available": 30
}
]
},
"request_id": "a1b2c3d4e5f6"
}
Low stock alerts¶
GET /api/v1/inventory?sub=low-stock
Scope: inventory:read
Returns items that are at or below their minimum stock level. Results are sorted with the most critical items first. Only items with a configured min_stock_level greater than 0 are included.
Query parameters¶
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
per_page | integer | Results per page (default: 50, max: 100) |
Example request¶
curl -X GET "https://fsmnavigator.com/api/v1/inventory?sub=low-stock" \
-H "X-API-Key: YOUR_API_KEY"
Example response¶
{
"data": [
{
"id": 8,
"part_number": "THM-001",
"part_name": "Thermostat Wire 18/5",
"description": null,
"category": "Wire & Cable",
"serial_number": null,
"quantity_on_hand": 3,
"min_stock_level": 10,
"reorder_quantity": 20,
"cost_per_unit": 0.45,
"price_per_unit": 0.89,
"supplier_id": null,
"created_at": "2026-02-24T10:00:00Z",
"updated_at": "2026-02-24T09:00:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 50,
"total": 3,
"total_pages": 1
},
"request_id": "a1b2c3d4e5f6"
}
Automate reordering
Poll the low-stock endpoint on a schedule to detect items that need reordering. Combine it with the reorder_quantity field to know how much to order.
Error responses¶
See Error codes for the full reference. These errors can occur on any Inventory API endpoint:
| HTTP code | Error | Description |
|---|---|---|
400 | invalid_json | Request body contains malformed JSON |
400 | validation_failed | Missing required fields or invalid values |
401 | unauthorized | Missing or invalid API key |
403 | forbidden | IP address is not whitelisted for this API key |
403 | feature_not_available | Enterprise plan with Inventory feature required |
403 | insufficient_scope | API key lacks the required scope for this endpoint |
404 | not_found | Requested resource does not exist |
405 | method_not_allowed | HTTP method not supported (only GET, POST, PUT, and PATCH are available) |
429 | rate_limit_exceeded | Rate limit exceeded — check the Retry-After header |
500 | internal_error | Unexpected server error |
Include the request ID in support tickets
Every response includes a request_id field. Include this value when contacting support so we can quickly trace the issue.
Related guides¶
- Authentication — API key setup and management
- Scopes & Permissions — available scopes and what they allow
- Rate Limits — request throttling details
- Error Codes — full error reference
- Inventory management guide — managing inventory from the web dashboard