FurniOpsDocs

Quickstart

Get an API key, make your first request with curl, then do the same with the TypeScript SDK.

By the end of this page you will have listed products from your catalogue and read one with its variants and offers, first with curl, then with @furniops/sdk.

Get an API key

API keys belong to an organisation. A member who may manage keys creates one in the dashboard, under Developers › API keys; the key is shown once, so store it in your secret manager straight away. API keys covers the wizard, scopes and rotation.

PrefixKindReaches
fo_test_…Secret, test modeYour sandbox catalogue only
fo_live_…Secret, live modeYour live data
fo_rk_test_…, fo_rk_live_…RestrictedOnly the permissions chosen for it, in that mode

Start with a test key: it can only see and change your sandbox, which is seeded with sample products for exactly this.

Keys are long (the prefix, 43 random characters and a 6-character checksum) and are only ever stored by FurniOps as a hash. Keep them on your servers; never ship one in a browser or mobile app.

export FURNIOPS_API_KEY="fo_test_…"   # paste yours

Signing in from a terminal instead

The CLI can sign you in with your FurniOps account in the browser (furniops login), with no key to copy. Those sessions act in live mode; for test mode, save a test key with furniops login --with-api-key.

Make your first request

Every request sends the key as a bearer token. Sending FurniOps-Version pins the response shape to a dated API version; without it, you get the version your organisation was pinned to on its first request.

curl "https://api.furniops.com/v1/products?limit=2" \
  -H "Authorization: Bearer $FURNIOPS_API_KEY" \
  -H "FurniOps-Version: 2026-10-04"

The answer is a page of products, newest first:

{
  "object": "list",
  "data": [{ "object": "product", "id": "prod_034XqBaL2mRLTZPXyRbaUc", "…": "…" }],
  "has_more": true,
  "next_cursor": "eyJ2IjoxLCJpZCI6InByb2RfMDAwIn0"
}

Pass next_cursor back as starting_after for the next page (Pagination). The response headers matter too:

HeaderWhat it tells you
Request-IdThis request's ID. Quote it to support.
FurniOps-VersionThe API version the body is shaped for.
ETagA hash of the exact body; send it back as If-None-Match to get an empty 304 when nothing changed.
RateLimit, RateLimit-PolicyHow much of your rate budget is left.

Read one product

A product carries its variants, and each variant its offers: one source's price, stock and availability at one location.

curl https://api.furniops.com/v1/products/prod_034XqBaL2mRLTZPXyRbaUc \
  -H "Authorization: Bearer $FURNIOPS_API_KEY"

Prices are integers in minor units of currency (94900 is $949.00). Fields your licences do not cover are left out, never sent empty.

Do the same with the TypeScript SDK

The SDK is typed from the same OpenAPI document as this reference and has no runtime dependencies: it needs only fetch and Web Crypto (Node 20 or later, and other runtimes that have them). Install it from npm (or see Install for npm and yarn):

pnpm add @furniops/sdk

Create a client once and reuse it:

client.ts
import { FurniOps } from "@furniops/sdk";

export const furniops = new FurniOps({
  // fo_test_… reaches your sandbox, fo_live_… your live data.
  apiKey: process.env["FURNIOPS_API_KEY"] ?? "",
});

List products and read the first:

quickstart.ts
// The first page of products (20 by default, newest first).
const page = await furniops.products.list({ limit: 5 });
for (const product of page.data) {
  console.log(product.id, product.data.title, `v${product.version}`);
}
console.log("more?", page.has_more, "request id:", page.lastResponse.requestId);
quickstart.ts
const first = page.data[0];
if (first !== undefined) {
  const product = await furniops.products.get(first.id);
  for (const variant of product.variants) {
    for (const offer of variant.offers) {
      // Money is in minor units of `currency`: 94900 is $949.00.
      console.log(variant.id, offer.source, offer.currency, offer.price, offer.availability);
    }
  }
}

Every method returns the typed body, with the response's status, headers and Request-Id on lastResponse. List methods can also be iterated with for await to walk every page.

Next steps

On this page