FurniOpsDocs

Test mode

Build against a sandbox catalogue with test keys, a seeded sample catalogue and sample events, without touching live data.

Every organisation has two catalogues: its live catalogue and a sandbox. A test key (fo_test_… or fo_rk_test_…) reaches only the sandbox; a live key only the live catalogue. The API, the change feed, the stream and webhooks behave the same in both: only the data differs.

Live modeTest mode
Keysfo_live_…, fo_rk_live_…fo_test_…, fo_rk_test_…
CatalogueYour live catalogue, and the network catalogues your licences coverYour sandbox catalogue only
Objects and eventslivemode: truelivemode: false
Webhook endpointsReceive live events only; URLs must be HTTPSReceive test events only
POST /v1/test/events403 test_mode_requiredEmits a sample event
Monthly quotasCapped by your plan; usage.threshold_reached at 80% and 100%Counted and shown, never capped
Bulk exportsYour live catalogue; three open at onceYour sandbox; three open at once, apart from live
Rate budgetsYour organisation's live budgetsSeparate budgets of the same size

Nothing crosses between modes: a test key cannot read a live product, a test webhook endpoint never receives a live event, and a live product's ID answers 404 to a test key. Test usage is metered apart from live, so GET /v1/usage with a test key shows the sandbox's figures, every quota is null, and testing never uses up a live quota.

Choosing the mode with a signed-in token

An API key's mode is fixed by its prefix. An OAuth access token (yours from furniops login, the dashboard's, or an organisation's machine client's) has no mode of its own: it acts in live mode, or in test mode when the request says so with the FurniOps-Mode header:

curl https://api.furniops.com/v1/products \
  -H "Authorization: Bearer $FURNIOPS_ACCESS_TOKEN" \
  -H "FurniOps-Mode: test"
CredentialWithout the headerFurniOps-Mode: testFurniOps-Mode: live
Access tokenLiveTestLive
Test keyTestTest400 invalid_request
Live keyLive400 invalid_requestLive

With the SDK, set mode on the client or on one call:

test-mode.ts
// An OAuth access token acts in live mode unless asked: here every call acts in test mode…
const sandbox = new FurniOps({ apiKey: accessToken, mode: "test" });
const sample = await sandbox.products.list({ limit: 10 });
// …and this one call in live mode.
const live = await sandbox.products.list({ limit: 10 }, { mode: "live" });
console.log(sample.data.length, live.data.length);

A key named with the other mode is refused rather than ignored, so a request never acts in a mode you did not mean. Any value but live or test is refused too. The SDK's mode option and the CLI's --mode flag (or FURNIOPS_MODE) send the header for you.

The sample catalogue

Shortly after an organisation is created, FurniOps seeds its sandbox with a sample catalogue so there is something to read on day one:

  • a sample source (kind: "platform_feed", a Shopify-style storefront feed) with no schedule, so it never runs again on its own;
  • 23 products in 7 families, imported through the real pipeline (fetch, normalise, matching, master records), so every product.created, variant.created and offer.created event is a real one with livemode: false;
  • one deliberate near-duplicate (the same product with a misspelt title), so matching has a review pair to show you.

The data is synthetic: no images, no real vendor's content. It is seeded once; deleting the sample source does not bring it back. Start a run of the sample source to watch a run end to end:

furniops sources run src_…    # or POST /v1/sources/{id}/runs with a test key

Sample events

POST /v1/test/events emits a sample event of any public type in your sandbox, as if the change had happened: it reaches the test webhook endpoints subscribed to that type, the sandbox's change feed and the stream. Its objects are samples that do not exist, so do not read them back.

curl https://api.furniops.com/v1/test/events \
  -H "Authorization: Bearer $FURNIOPS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"type": "offer.changed"}'

It answers 202 with the event it emitted. With the SDK:

// Test mode only: a live key answers 403 test_mode_required.
const event = await furniops.test.triggerEvent({ type: "offer.changed" });
console.log(event.id, event.type, event.livemode); // livemode: false

Or with the CLI, which also explains a test_mode_required answer:

furniops trigger offer.changed

The key needs the webhooks:write scope. Every public event type can be triggered:

TypeSent when
source.degradedA connection to a vendor's data
source.recoveredA connection to a vendor's data
source.blockedA connection to a vendor's data
product.createdA master product's change: what moved, and to what
product.updatedA master product's change: what moved, and to what
product.discontinuedA master product's change: what moved, and to what
product.publishedA master product's change: what moved, and to what
product.withdrawnA master product's change: what moved, and to what
product.mergedA master product merged into another: it now redirects to `merged_into`
product.unmergedA merged master product is its own again: every field, and the survivor it left
variant.createdA master variant's change: what moved, and to what
variant.updatedA master variant's change: what moved, and to what
variant.mergedA master variant merged into another: it now redirects to `merged_into`
variant.unmergedA merged master variant is its own again: every field, and the survivor it left
offer.createdA source's offer changed: price, stock or availability
offer.changedA source's offer changed: price, stock or availability
catalogue.verifiedWhether a vendor catalogue is verified by its brand
catalogue.unverifiedWhether a vendor catalogue is verified by its brand

Testing webhooks on your machine

A webhook endpoint's URL must resolve to a public address: FurniOps refuses localhost and private ranges, in both modes. To receive events on your laptop, use furniops listen: it holds the event stream for you and POSTs each event to a local URL, signed exactly as real deliveries are. No tunnel is needed.

furniops login --with-api-key           # paste a fo_test_ key
furniops listen --forward-to localhost:3000/webhooks
furniops trigger offer.changed           # in another terminal

Going live

When your integration works in test mode:

  1. Create a live key, and store it where your test key was.
  2. Create your webhook endpoints again in live mode: endpoints, their secrets and their deliveries belong to one mode. Live URLs must be HTTPS.
  3. Start your change feed sync from scratch: cursors from the sandbox mean nothing in live mode.

On this page