Tutorial 01

Quickstart: your first request

Generate an API key, verify it, and read your first page of inventory, in about five minutes.

beginner ·5 min read ·curlPython ·Updated 3 Sep 2026

Everything in this tutorial runs against the live API at https://api.stockpilot.dev. You need a Stockpilot account on the Growth tier or higher, because API access is gated to those plans.

Get a credential pair

Stockpilot authenticates with a static client ID and secret, not OAuth. In the Stockpilot app, open Settings → API and generate a pair. You get two values:

X-CLIENT-ID:     7c1f4ab93e5d2680
X-CLIENT-SECRET: e04b9d7a15c3f8621b0e94d7c5a3f8be

The secret is shown once. Store it the way you would store a database password.

Both headers go on every request. There is no token exchange, no refresh, and no Authorization header. Sending a bearer token instead will get you a 401.

Export them so the rest of this page is copy-pasteable:

export SP_CLIENT_ID="7c1f4ab93e5d2680"
export SP_CLIENT_SECRET="e04b9d7a15c3f8621b0e94d7c5a3f8be"

Verify the credentials

Before writing any integration code, confirm the pair works. /auth/who-is is the cheapest call in the API and tells you which organization you are acting as.

GET /auth/who-is
curl -s https://api.stockpilot.dev/auth/who-is \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" \
  -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET"

A working pair returns your organization:

{
  "organization": "Acme Commerce BV",
  "organization_id": 4812,
  "plan": "growth"
}

If you get a 401, the pair is wrong or incomplete. If you get a 403, the credentials are valid but the plan does not include API access.

Read the rate-limit headers

Every response carries the current state of your budget. Ask for the headers on that same call:

curl -sI https://api.stockpilot.dev/auth/who-is \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" \
  -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET" | grep -i ratelimit
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 299
X-RateLimit-Reset: 47
X-RateLimit-Policy: read-single

These tell you which bucket the call fell into and how much of that budget is left. Pace your integration against them. See Rate limits and idempotency for the four buckets and what lands in each.

Read your first page of inventory

Now something real. List endpoints paginate with page and page_size:

GET /inventory
curl -s "https://api.stockpilot.dev/inventory?page_size=5" \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" \
  -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET"
{
  "count": 1284,
  "next": true,
  "previous": false,
  "current_page": 1,
  "total_pages": 13,
  "results": [
    {
      "id": 34897,
      "product_id": 456,
      "item_name": "Vichy Homme Structure Force 50 ml",
      "sku": "201156",
      "barcode": "3337875647212",
      "bin_location": ["A1-001-01", "B2-003-05"],
      "threshold": "5u",
      "stock_threshold": 5,
      "quantity": 150,
      "reserved_quantity": 3,
      "incoming_quantity": 50,
      "backorder_amount": 0,
      "is_active": true
    }
  ]
}

Three of those fields carry more than they look:

  • quantity is the physical count, including units already allocated to open orders.
  • reserved_quantity is how many of those are spoken for.
  • bin_location is an array, because one item can sit in several bins.

Note there is no single “sellable” number here. What Stockpilot offers to your sales channels is computed across warehouses and arrives on the inventory.stock_changed webhook as offered_stock. The inventory model explains the difference and when each one is the right number.

next and previous are booleans here, not URLs. Page with current_page and total_pages, and note page_size caps at 100 on this endpoint. API conventions covers the two envelopes.

Prefer one paginated list call over many single lookups; they come out of different rate-limit buckets.

Sort and filter it

/inventory takes a sort, prefixed with - for descending, plus filters that combine:

curl -s "https://api.stockpilot.dev/inventory?supplier_id=12&is_active=true&sort=-quantity" \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" \
  -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET"

Sort on sku, item_name, barcode, quantity, base_price, stock_threshold, created_at or updated_at. Anything else is a 400.

Filter by supplier_id, brand_id, category_id, is_active, created_at and updated_after. An unknown supplier_id, brand_id or category_id is a 404 naming what was not found, rather than an empty page, so a typo does not read as “no results”.

Fetch a single item

When you already know an identifier, use the single-item endpoint. It accepts exactly one of id, sku or barcode. Sending two is an error.

GET /inventory/get
curl -s "https://api.stockpilot.dev/inventory/get?sku=201156" \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" \
  -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET"

The same thing in Python

A minimal client you can build on:

import os
import httpx

client = httpx.Client(
    base_url="https://api.stockpilot.dev",
    headers={
        "X-CLIENT-ID": os.environ["SP_CLIENT_ID"],
        "X-CLIENT-SECRET": os.environ["SP_CLIENT_SECRET"],
    },
    timeout=30.0,
)

who = client.get("/auth/who-is").json()
print("authenticated as", who["organization"])

page = client.get("/inventory", params={"page_size": 5}).json()
for item in page["results"]:
    print(f"{item['sku']:<12} {item['item_name'][:34]:<34} {item['quantity']:>5}")

Errors come back as JSON with a detail or error key, so a real client should check the status before parsing:

resp = client.get("/inventory/get", params={"sku": "DOES-NOT-EXIST"})
if resp.is_error:
    raise RuntimeError(resp.json().get("detail", resp.text))

Where to go next

You now have a working credential pair and a client that can read. The three surfaces worth bookmarking:

  • API reference covers every operation, rendered for humans.
  • llms.txt is the same reference, compact, for the model in your editor.
  • openapi.json is the raw schema, for generating a typed client.

The last two are regenerated from the schema on every request, so they cannot drift from the API.

A Stockpilot engineer talking through an integration with a developer

Stuck on something

Ask a person, not a search box

The API surface is wide, and some of it only makes sense once someone explains why it works that way. If a payload is not doing what you expect, or you are weighing two approaches, say so and we will look at it with you.