API reference
Every FurniOps API operation, generated from the public OpenAPI document, with the conventions they share.
The FurniOps API is a JSON API over HTTPS. Each operation has its own page in this section, generated from the same OpenAPI document the SDK and the server are built from, so its parameters, bodies, responses and error codes are exactly what the server enforces.
Base URL
https://api.furniops.comEvery path starts with /v1. HTTPS only.
Authentication
Send an API key, or an OAuth access token, as a bearer token on every request:
curl https://api.furniops.com/v1/products \
-H "Authorization: Bearer $FURNIOPS_API_KEY"A test key (fo_test_…, fo_rk_test_…) reaches your sandbox, a live key your live data; an access
token is live unless the request sends FurniOps-Mode: test. Each operation lists the scopes it
needs; see the scope table and Authentication.
Conventions
| Topic | Rule |
|---|---|
| Bodies | JSON, sent with Content-Type: application/json. Unknown fields in a request body are refused. |
| IDs | Prefixed and opaque: prod_…, var_…, ofr_…, src_…, run_…, evt_…. A malformed ID or one with the wrong prefix answers 404. |
| Money | An integer in minor units with a lowercase ISO 4217 currency: 94900 and usd is $949.00. |
| Times | RFC 3339 in UTC: 2026-10-04T10:00:00Z. |
| Absent fields | Left out, not sent as null, unless the schema says null has a meaning. |
| Versions | FurniOps-Version (the latest is 2026-10-04) pins the response shape; see API versions. |
| Retries | POST takes an Idempotency-Key; PATCH of sources and webhook endpoints takes If-Match. |
| Lists | Cursor pagination: limit, starting_after, ending_before. |
| Errors | Problem details with a stable code. |
| Limits | Rate budgets by class, stated in RateLimit headers. |
Headers you send
| Header | When |
|---|---|
Authorization: Bearer <key> | Always. |
FurniOps-Mode | Optional: live or test. Lets an OAuth access token act in test mode; a key's mode is its own. |
FurniOps-Version | Optional: the API version to answer in. |
Idempotency-Key | On every POST but POST /v1/sources/{id}/test. |
If-Match | On PATCH /v1/sources/{id} and PATCH /v1/webhook-endpoints/{id} (required). |
If-None-Match | Optional on a GET: 304 when unchanged. |
Content-Type: application/json | With a body. |
Headers you get
| Header | On |
|---|---|
Request-Id | Every response. Quote it to support. |
FurniOps-Version | Every successful response: the version the body is shaped for. |
ETag | Every successful JSON GET. |
RateLimit, RateLimit-Policy | Every budgeted response. |
Retry-After | 429 rate_limited, 429 quota_exceeded, 503 overloaded and 409 idempotency_request_in_progress. |
Location | 202 from POST /v1/exports: the new export's URL. |
Idempotent-Replayed: true | A replayed POST. |
Operations
The sidebar groups operations by resource. Each page shows the request, every response with its schema, the error codes it can answer, a curl example and the matching SDK call. The interactive "try it" console is turned off: send real requests from your own terminal, with your own key.
The OpenAPI document itself is the source of truth for code generators: it ships in
@furniops/contracts as openapi/public.json.
The Workbench
The dashboard's developer drawer. Send real API requests with your session in test or live mode, copy them as curl, keep this tab's history, and watch webhook health, without creating a key.
Get a stored image GET
FurniOps' stored copy of an image a product or variant names (its `media[].id`; `media[].stored_url` points here): the copy's size and a download link signed for 15 minutes, so a reader can keep its own copy without fetching from the vendor. Readable when the organisation can see a product or variant that names the image, else 404. Until the copy is stored, `status` is `pending` or `failed` and `download_url` is null.