TypeScript SDK
Install @furniops/sdk, create a client, and configure retries, timeouts, versions and credentials.
@furniops/sdk is the official TypeScript client for the FurniOps API.
- Typed from the spec. Every operation in the API reference is a method, with its parameters, body and result typed from the same OpenAPI document. An operation added to the API is a method in the next release, with no hand-written code in between.
- Zero dependencies. It needs only
fetchand Web Crypto: Node 20 or later, Deno, Bun, Cloudflare Workers and other runtimes that have them. ESM only. - Safe by default. Retries with backoff, an idempotency key on every
POSTreused across its retries, typed errors, auto-pagination, a reconnecting change stream and webhook verification.
Install
The SDK is published on the npm registry under the MIT licence. It is ESM only and needs Node 20
or later (or another runtime with fetch and Web Crypto).
pnpm add @furniops/sdk
# or
npm install @furniops/sdk
# or
yarn add @furniops/sdkThe version documented here is 0.27.2. Releases follow semantic versioning; pin an exact version
if you want to upgrade deliberately.
Create a client
Create one client and share it: it holds no connection state, and every call is independent.
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"] ?? "",
});Keep the key in an environment variable or your secret manager, never in source. A test key
(fo_test_…) reaches your sandbox, a live key your live data.
Options
import { FurniOps } from "@furniops/sdk";
const furniops = new FurniOps({
// An API key, an OAuth access token, or a function answering one (asked once per call, so a
// short-lived token can be refreshed between calls).
apiKey: async () => process.env["FURNIOPS_API_KEY"] ?? "",
// Defaults to https://api.furniops.com.
baseUrl: "https://api.furniops.com",
// Sent as FurniOps-Version on every call. Leave it out to use the organisation's pinned version.
apiVersion: "2026-10-04",
// Retries after the first attempt (429, 5xx, timeouts, network failures). Default 2.
maxRetries: 3,
// Milliseconds per attempt, the response body included. Default 60 000.
timeout: 30_000,
// Off by default: when on, each request carries the previous one's Request-Id and duration.
telemetry: false,
});| Option | Default | Meaning |
|---|---|---|
apiKey | (required) | An API key, an OAuth access token, or a function answering one. A function is called once per call and once per stream connection, so a short-lived token can be refreshed between them. |
baseUrl | https://api.furniops.com | Where requests go. |
apiVersion | your organisation's pin | Sent as FurniOps-Version on every call. See API versions. |
mode | none: the credential's | "live" or "test", sent as FurniOps-Mode. Lets an OAuth access token act in test mode; an API key's mode is fixed, and naming its other mode is refused. See Test mode. |
maxRetries | 2 | Retries after the first attempt, on 429, 5xx, timeouts and network failures. |
timeout | 60000 | Milliseconds per attempt, the response body included. |
fetch | the global fetch | Another implementation: a proxy agent, or a test double. |
telemetry | false | When on, each request carries a FurniOps-Client-Telemetry header with the previous request's Request-Id and duration, and nothing else. |
Per-call options
Every method takes an options object last. It overrides the client's settings for that call:
const controller = new AbortController();
const product = await furniops.products.get("prod_034XqBaL2mRLTZPXyRbaUc", {
apiVersion: "2026-10-01", // this call only
timeout: 5_000,
maxRetries: 0,
signal: controller.signal,
headers: { "X-Correlation-Id": "order-1042" },
});| Option | Meaning |
|---|---|
apiVersion, mode, timeout, maxRetries | As on the client, for this call. |
signal | An AbortSignal that cancels the call, its retries and its waits. An aborted call is never retried. |
headers | Extra request headers, sent as given. |
idempotencyKey | The Idempotency-Key for a POST, reused on every retry. A fresh UUID when absent. See Idempotent requests. |
ifMatch | If-Match for the operations that require it (sources.update, webhookEndpoints.update). See ETags. |
The response behind a result
Every result carries the response it came from as lastResponse, not enumerable, so it never
shows up when you serialise the result:
const meta = product.lastResponse;
console.log(
meta.status,
meta.requestId,
meta.headers.get("etag"),
meta.headers.get("furniops-version"),
);| Field | Meaning |
|---|---|
status | The HTTP status. |
headers | The response headers (a Headers): ETag, RateLimit, FurniOps-Version and the rest. |
requestId | The Request-Id, for support. |
idempotencyKey | The key the call was sent with: yours, or the generated one. |