FurniOpsDocs
TypeScript SDK

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 fetch and 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 POST reused 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/sdk

The 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.

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"] ?? "",
});

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

client.ts
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,
});
OptionDefaultMeaning
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.
baseUrlhttps://api.furniops.comWhere requests go.
apiVersionyour organisation's pinSent as FurniOps-Version on every call. See API versions.
modenone: 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.
maxRetries2Retries after the first attempt, on 429, 5xx, timeouts and network failures.
timeout60000Milliseconds per attempt, the response body included.
fetchthe global fetchAnother implementation: a proxy agent, or a test double.
telemetryfalseWhen 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" },
});
OptionMeaning
apiVersion, mode, timeout, maxRetriesAs on the client, for this call.
signalAn AbortSignal that cancels the call, its retries and its waits. An aborted call is never retried.
headersExtra request headers, sent as given.
idempotencyKeyThe Idempotency-Key for a POST, reused on every retry. A fresh UUID when absent. See Idempotent requests.
ifMatchIf-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"),
);
FieldMeaning
statusThe HTTP status.
headersThe response headers (a Headers): ETag, RateLimit, FurniOps-Version and the rest.
requestIdThe Request-Id, for support.
idempotencyKeyThe key the call was sent with: yours, or the generated one.

Next

On this page