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:     sp_live_a1b2c3d4e5f6
X-CLIENT-SECRET: sk_live_9f8e7d6c5b4a3210

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="sp_live_a1b2c3d4e5f6"
export SP_CLIENT_SECRET="sk_live_9f8e7d6c5b4a3210"

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

Limits are advisory today, since Stockpilot does not return 429, but they tell you which bucket a call falls into, and a well-behaved integration paces itself 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,
  "page": 1,
  "page_size": 5,
  "results": [
    {
      "id": 90211,
      "sku": "TSHIRT-BLK-M",
      "barcode": "8712345678906",
      "name": "Heavyweight Tee Black / M",
      "quantity": 42,
      "offered_stock": 38,
      "location": "A1-001-01",
      "threshold": "5u"
    }
  ]
}

Two fields deserve attention immediately:

  • quantity is the physical count.
  • offered_stock is what Stockpilot actually advertises to your sales channels: the sum of warehouses that feed the offer, minus buffer stock. This is the number most integrations want. The inventory model explains why they differ.

page_size defaults to 100 and caps at 1000. Prefer one paginated list call over many single lookups; they come out of different rate-limit buckets.

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=TSHIRT-BLK-M" \
  -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']:<20} {item['quantity']:>5} on hand  {item['offered_stock']:>5} offered")

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 all 74 operations, 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.