StockroomAPIOpen the app
Guide

Authentication and scopes

One credential, sent one way. A key belongs to one store, carries a fixed set of scopes, and is named in the history of everything it changes.

Sending the key

Authorization: Bearer srk2_your_key_here

That is the only accepted form. There is no query parameter, no basic auth, no cookie, and no signed-request scheme. The API lives on its own hostname, api.stockroom.app, with no session and no CSRF: a key is the whole of the credential.

The key identifies the store too, so there is no store id in the URL. One key, one store - a key can never read or write another store's data, and a request for an id that belongs to a different store is a 404, never a 403.

Scopes

The merchant picks scopes when creating the key. Ask for the narrowest set that does your job: the store sees the list on the key, and a warehouse scanner that can also archive suppliers is a question you will have to answer.

ScopeLets the key
readRead everything the API exposes: purchase orders, suppliers, adjustments, history and the catalog mirror. Always present - every key can read, and asking for a write scope adds it automatically.
write:purchase_ordersCreate and edit purchase orders, send them, mark them ordered or on the way, record and void receipts, record payments, close, reopen, cancel what is left, archive, duplicate, delete a draft.
write:suppliersCreate, edit, archive and merge suppliers, and write the per-supplier catalog.
write:inventoryCreate, edit, apply and cancel stock adjustments.
write:manufacturingReserved for builds and recipes, which are not in the API yet. A key can hold it today so that nothing has to change when they land.
webhooksList, create, edit and delete webhook subscriptions, send tests, and redeliver.

A call outside the key's scopes is refused before anything happens, with the scope it needed in a header:

HTTP/1.1 403 Forbidden
X-Required-Scope: write:purchase_orders

{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key does not have the `write:purchase_orders` scope."
  }
}

Scopes cannot be edited after a key exists. To widen or narrow what an integration can do, the merchant creates a new key and revokes the old one - which is also the audit trail of when that happened.

Expiry and revocation

A key expires never, in 90 days or in 1 year, chosen at creation. GET /v2/whoamireturns key.expires_at, so a long-running integration can warn someone before the date rather than stopping on it. An expired or revoked key gets401 unauthorized - the same answer as a key that never existed, on purpose.

Attribution: your key is named in the history

Every write is recorded against the key that made it. A purchase order's history showsvia API - ERP sync, using the key's name, and so does an adjustment's. The merchant can tell your integration's receive from a person's, and from another integration's.

That is the practical reason to name keys after systems rather than people, and to use one key per integration. It also means a key's name is merchant-visible text - Warehouse scanner, not test key 3.

Every refusal, in one place

StatusCodeWhat happened
401unauthorizedNo key, a malformed header, or a key that is wrong, revoked or expired.
402feature_not_enabledThe store does not have API access. Nothing you can fix in code - the merchant asks for it under Settings, then API.
403insufficient_scopeThe key is good and lacks the scope this route needs. X-Required-Scope names it.
404not_foundNo such record in this store. Includes a record that exists in another store.
429rate_limitedOver a rate limit. Retry-After says when to come back.

Conventions covers the rest of the error envelope, including validation failures and the status-machine refusals a write can get.