Stackroom

API reference

Every endpoint, with the parameters that matter

Assets, people and workspace members over plain REST. Small on purpose: these are the objects worth automating, and each one behaves the same way the console does.

What can the Stackroom API do?

It reads and writes the register: list, fetch, create and update assets; the same for the people equipment is issued to; and list, invite and re-role workspace members. Everything is under https://api.stackroom.io/api/v1, authenticated with a scoped key, and documented by a public OpenAPI specification.

Endpoints

The whole surface

Ping

One call to prove a key works, before you write anything against it.

GET/pingVerify an API keyread

Returns the workspace the key belongs to and the scopes it carries. A 401 here means the key is wrong, revoked, or the plan no longer includes API access.

Assets

The register itself — every item, its identity, status, value and custom fields.

GET/assetsList assetsread
page
integer
1-based. Always send it — see the note below.
pageSize
integer
1–200. Defaults to 50.
status
string
active, inactive, maintenance, retired, lost or stolen.
search
string
Matches name, asset tag, serial, brand and model.

Without `page`, this returns a bare array of up to 10,000 assets — kept for callers written before paging existed. Send `page` and you get the paginated envelope instead. New integrations should always send it.

POST/assetsCreate an assetwrite

Writes go through the same domain services as the console, so plan limits, validation and custom-field rules apply identically.

GET/assets/{id}Fetch one assetread
PATCH/assets/{id}Update an assetwrite

Partial: send only the fields you are changing.

People

The humans equipment is issued to. Distinct from workspace members, who are logins.

GET/peopleList peopleread
search
string
Matches name and email.
POST/peopleCreate a personwrite

A person can exist without a login — most people equipment is issued to never need one.

GET/people/{id}Fetch one personread
PATCH/people/{id}Update a personwrite

Workspace members

Logins and their roles — what an HR or identity system needs to drive joiners and leavers.

GET/membersList workspace membersread
POST/membersInvite a memberwrite

Sends the invitation email. The seat is only consumed when it is accepted.

PATCH/members/{userId}Change a member's rolewrite

Examples

Reading and writing the register

List assets
curl -s -G "https://api.stackroom.io/api/v1/assets" \
  -H "Authorization: Bearer $STACKROOM_KEY" \
  -d page=1 -d pageSize=50 -d status=active
Response
{
  "data": [
    {
      "id": "cl9x...",
      "name": "MacBook Pro 14\"",
      "sku": "IT-0421",
      "status": "active",
      "serial_number": "C02XL0ABCDEF",
      "category": { "name": "Laptops" },
      "assigned_to": { "id": "cl7a...", "name": "Priya N." },
      "updated_at": "2026-09-21T10:14:02.000Z"
    }
  ],
  "page": 1,
  "pageSize": 50,
  "total": 1284
}
Create an asset
curl -s -X POST "https://api.stackroom.io/api/v1/assets" \
  -H "Authorization: Bearer $STACKROOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dell U2720Q monitor",
    "sku": "IT-0932",
    "status": "active",
    "brand": "Dell",
    "model": "U2720Q",
    "serial_number": "CN0ABC123"
  }'
Page through everything
# Walk the whole register without holding it in memory.
page=1
while :; do
  body=$(curl -s -G "https://api.stackroom.io/api/v1/assets" \
    -H "Authorization: Bearer $STACKROOM_KEY" \
    -d page=$page -d pageSize=200)
  echo "$body" | jq -c '.data[]'
  total=$(echo "$body" | jq '.total')
  [ $((page * 200)) -ge "$total" ] && break
  page=$((page + 1))
done
Node — with the 429 handled
const KEY = process.env.STACKROOM_KEY;

async function listAssets(page = 1) {
  const url = new URL("https://api.stackroom.io/api/v1/assets");
  url.searchParams.set("page", String(page));
  url.searchParams.set("pageSize", "200");

  const res = await fetch(url, { headers: { authorization: `Bearer ${KEY}` } });
  if (res.status === 429) {
    // Retry-After is in seconds, and it is not a suggestion.
    const wait = Number(res.headers.get("retry-after") ?? 5);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return listAssets(page);
  }
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json();
}

Conventions

Things that apply everywhere

Ids are workspace-scoped

An id from another workspace is a 404, not a 403 — the API never confirms that another tenant's record exists.

PATCH is partial

Send only the fields you are changing. Omitted fields keep their value; nulls clear one.

Timestamps are ISO 8601 UTC

Always with a Z. No local times, no epoch seconds except in webhook signatures.

Writes obey plan limits

The API shares the console's domain services, so caps and validation behave identically.

Errors share one shape

{ statusCode, error, message } — with a message written for whoever is debugging.

The spec is the contract

The interactive reference is generated from the OpenAPI document the service publishes.

Interactive referenceOpenAPI specification (JSON)

FAQ

Reference questions

Is there an OpenAPI specification?

Yes, and it is public — you can read it before you sign up. The interactive reference is generated from the same document, so it cannot drift from the running service.

How does pagination work?

Send page and pageSize (1–200, default 50) and you get { data, page, pageSize, total }. Omitting page returns a bare array of up to 10,000 assets, kept only for integrations written before paging existed. Always send page.

Are there client libraries?

Not yet. The API is plain REST over JSON with bearer auth, so the built-in HTTP client of any language covers it — the examples here are curl, and fetch in Node.

Does the API respect plan limits?

Yes. Writes go through the same domain services as the console, so asset caps, validation and custom-field rules apply identically. An import that would breach your plan fails on the API exactly as it would in the UI.

Can I use the API on the free plan?

No. API access starts on Pro. The specification stays public regardless, so you can evaluate the shape of the integration before paying for anything.

How do I avoid creating duplicates?

Match on your own identifier before you create: search by serial or asset tag, and PATCH when you get a hit. The API does not deduplicate for you, because only you know which field is your key.

Push events to your stack

The API is half of it. Webhooks tell you the moment something is issued, returned or falls due.