@furniops/sdk
v0.30.0
Published
The FurniOps TypeScript SDK: typed resources from the public OpenAPI spec on a zero-dependency fetch core with retries, idempotency keys, typed errors, auto-pagination and webhook verification
Maintainers
Readme
@furniops/sdk
The official TypeScript client for the FurniOps API.
- Typed from the spec. Every operation in the API reference is a method, typed from the same OpenAPI document.
- Zero dependencies. Needs only
fetchand Web Crypto: Node 20+, Deno, Bun, Cloudflare Workers. 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
npm install @furniops/sdkUse
import { FurniOps } from "@furniops/sdk";
const furniops = new FurniOps({ apiKey: process.env.FURNIOPS_API_KEY });
for await (const product of furniops.products.list({ updated_since: "2026-10-01T00:00:00Z" })) {
console.log(product.id, product.status, product.updated_at);
}A machine client (OAuth client credentials) gets short-lived tokens, cached and refreshed early:
import { FurniOps, clientCredentials } from "@furniops/sdk";
const furniops = new FurniOps({
apiKey: clientCredentials({
tokenUrl: "https://auth.furniops.com/oauth/token",
clientId: process.env.FURNIOPS_CLIENT_ID,
clientSecret: process.env.FURNIOPS_CLIENT_SECRET,
}),
});Verify a webhook from its raw body and headers:
const event = await furniops.webhooks.verify(rawBody, request.headers, process.env.FURNIOPS_WEBHOOK_SECRET);An endpoint in batch delivery also receives change batches; verify with verifyDelivery and
tell them apart with isChangeBatch:
const delivery = await furniops.webhooks.verifyDelivery(rawBody, request.headers, secret);
for (const event of isChangeBatch(delivery) ? delivery.events : [delivery]) apply(event);Keep a connected vendor catalogue in step: connections.sync reads a snapshot of the catalogue
(a products export), then the change feed from the connection's baseline, so nothing is missed
or read twice. Store each cursor with what its events changed and pass it back to resume; an event
whose version is at or below the product's already applied is safe to drop:
await furniops.connections.sync("con_…", {
cursor: await store.cursor(), // absent on the first run: start with a snapshot
onReset: () => store.clear(), // a snapshot is about to replace what was synced
onSnapshot: (products) => store.upsert(products),
onEvents: (events, cursor) => store.apply(events, cursor),
signal, // follows the feed until aborted; `once: true` stops when caught up
});A cursor older than the feed's 30-day retention is refused; the sync then starts over from a snapshot by itself.
Keep keys in environment variables or a secret manager, never in source. A test key (fo_test_…)
reaches your sandbox, a live key your live data.
Documentation
Guides, the API reference and every SDK option: https://docs.furniops.com/sdk.
Licence
MIT © Softup LLC
