Guide
API conventions
Pagination, dates, identifiers, location and threshold formats, error shapes and async operations. The rules that apply across every endpoint.
These rules hold across the whole API. Learning them once saves reading them 74 times in the reference.
Pagination
List endpoints take page and page_size query parameters.
curl -s "https://api.stockpilot.dev/inventory?page=2&page_size=500" \
-H "X-CLIENT-ID: $SP_CLIENT_ID" \
-H "X-CLIENT-SECRET: $SP_CLIENT_SECRET"
page starts at 1. page_size defaults to 100 and caps at 1000.
{
"count": 1284,
"page": 2,
"page_size": 500,
"results": [ ]
}
Walk pages until a page returns fewer results than you asked for:
def all_pages(client, path, page_size=1000, **params):
page = 1
while True:
data = client.get(path, params={**params, "page": page, "page_size": page_size}).json()
yield from data["results"]
if len(data["results"]) < page_size:
return
page += 1
GET /returns paginates differently. Its next and previous fields are booleans, not URLs, and you page with current_page and total_pages. Its status filters are lowercase (requested, partly_accepted, accepted) even though the statuses themselves are returned uppercase, and its page_size caps at 100. See sales channels and returns.
Identifying a product
Endpoints that look up a single product accept exactly one of id, sku or barcode.
curl -s "https://api.stockpilot.dev/inventory/get?sku=TSHIRT-BLK-M" # good
curl -s "https://api.stockpilot.dev/inventory/get?id=90211" # good
curl -s "https://api.stockpilot.dev/inventory/get?sku=X&id=90211" # error
Passing none or more than one is a client error, not a silent preference for whichever came first.
Dates and times
Dates are ISO YYYY-MM-DD. Timestamps are ISO 8601 with a timezone offset.
2026-09-03
2026-09-03T09:14:22.481Z
Analytics endpoints take from and to as plain dates. Webhook envelopes carry event_triggered_at as a full timestamp.
Locations
Warehouse locations are hierarchical bins, written as aisle, rack, shelf:
A1-001-01
The format is a string, not a structured object. Match on it literally rather than parsing it into parts unless you know how a specific warehouse is laid out.
Thresholds
A reorder threshold is either a number of units or a number of weeks of cover:
| Value | Meaning |
|---|---|
5u | Five units |
33w | Thirty three weeks of stock cover, based on measured demand |
The weeks form is what makes a fast selling SKU trigger long before a slow one at the same unit count.
Order statuses
open, pending, on-hold, completed, cancelled
Filter list calls with ?status=open. Note that the Stockpilot CLI’s status command reports on open orders specifically.
Errors
Every error returns JSON with a detail or an error key, alongside the HTTP status.
{ "detail": "Product not found for sku 'DOES-NOT-EXIST'" }
A robust client checks the status before parsing:
resp = client.get("/inventory/get", params={"sku": sku})
if resp.is_error:
raise StockpilotError(resp.status_code, resp.json().get("detail", resp.text))
Asynchronous operations
Three things in the API are too slow to answer inline, and all follow the same start then poll shape:
| Start | Poll | Returns |
|---|---|---|
POST /shipping/request-label | POST /shipping/retrieve-label | Label PDF and tracking metadata |
POST /purchase-orders/recommendations | GET /purchase-orders/recommendations/status/{task_id} | Recommended order lines |
POST /sales-channels/sync-listings | Channel state via GET /sales-channels | Sync outcome |
The start call returns a handle (entity_id or task_id) rather than a result. Back off between polls: all three sit in the heavy rate limit bucket at 20 requests per minute.
What is not there
Worth knowing so you do not go looking:
- No
Idempotency-Keyheader. Which calls are safe to retry is covered in rate limits and idempotency. - No cursor pagination. Offset paging only.
- No field selection or sparse responses. Endpoints return their full shape.
- No bulk write endpoints. Loop, and pace yourself against the
writebucket.
