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.
| Prefix | Kind | Reaches |
|---|---|---|
fo_test_… | Secret, test mode | Your sandbox catalogue only |
fo_live_… | Secret, live mode | Your live data |
fo_rk_test_…, fo_rk_live_… | Restricted | Only 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 yoursSigning 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:
| Header | What it tells you |
|---|---|
Request-Id | This request's ID. Quote it to support. |
FurniOps-Version | The API version the body is shaped for. |
ETag | A hash of the exact body; send it back as If-None-Match to get an empty 304 when nothing changed. |
RateLimit, RateLimit-Policy | How 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"{
"object": "product",
"id": "prod_034XqBaL2mRLTZPXyRbaUc",
"catalogue": "cat_034XqBaL2mRLTZPXyRbaUd",
"status": "active",
"version": 13,
"data": {
"title": "Harbor 3-Seat Sofa",
"brand": "Northwind Living",
"collection": "Harbor",
"description": "A deep-seated sofa with feather-wrapped cushions and a kiln-dried hardwood frame.",
"category_path": ["Living Room", "Sofas"],
"category": "ctg_034XqBaL2mRLTZPXyRbaUe"
},
"variants": [
{
"object": "variant",
"id": "var_034XqBaL2mRLTZPXyRbaUf",
"status": "active",
"version": 7,
"data": {
"sku": "HBR-3S-OAT",
"title": "Harbor 3-Seat Sofa, Oatmeal",
"colour": "Oatmeal"
},
"offers": [
{
"object": "offer",
"id": "ofr_034XqBaL2mRLTZPXyRbaUg",
"source": "src_034XqBaL2mRLTZPXyRbaUh",
"location": "DAL",
"currency": "usd",
"cost": 94900,
"msrp": 189900,
"quantity": 12,
"availability": "in_stock",
"lead_time_days": 3,
"version": 12,
"observed_at": "2026-10-04T10:00:00Z"
}
],
"updated_at": "2026-10-04T10:00:02Z"
}
],
"created_at": "2026-09-12T08:30:00Z",
"updated_at": "2026-10-04T11:00:04Z"
}
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/sdkCreate a client once and reuse it:
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:
// 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);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.