FurniOpsDocs

Authentication

The credentials FurniOps accepts, which one to use for what, how the mode of a request is decided, and what every credential can never do.

Every request to https://api.furniops.com carries one bearer credential:

Authorization: Bearer <credential>

There are two families of credential. Pick by who is acting:

You are buildingUseLivesPage
Your own server, script or sync jobAn API key (fo_live_…, fo_rk_live_…)Until you revoke it, or its expiryAPI keys
An AI agent that runs unattendedA restricted API key made for an AI agentUntil you revoke it, or its expiryAPI keys
An app your organisation's members sign in toAn OAuth app (web, single-page or native) and the authorization code flow with PKCEAccess token 10 minutes, refreshedOAuth apps
A server that calls the API as your organisation, without a key to storeAn OAuth app of the machine kind, and client credentialsAccess token 10 minutesOAuth apps
A terminal sessionfurniops login (the device flow)Refreshed while you use itCLI
An MCP client (Claude, Cursor, ChatGPT…)FurniOps sign-in from the client, or an agent keyAccess token 10 minutesMCP server

Keys are the simplest and the most common. OAuth tokens are for acting as a person, within that person's role, or for a server that should hold a client secret it can rotate rather than a key.

What every credential shares

  • One organisation. A key belongs to the organisation that made it; an OAuth token is bound to one organisation when it is issued. No request is ever ambiguous about whose data it reads.
  • Permissions. Every operation in the API reference names the scopes it needs, such as products:read. A credential holds a set of them; a missing one answers 403 permission_denied. The catalogue is on API keys.
  • Never more than a person. A credential never holds a permission the member who made it (or the member it acts for) does not hold. An OAuth token's permissions are its scopes intersected with the member's role at that moment, so a role change applies to the next token.
  • People-only actions stay with people. No key and no OAuth app can manage API keys, OAuth apps, members, the organisation's settings, billing, single sign-on or the AI agents' settings. Those are done in the dashboard, by a signed-in member. A leaked credential can never mint another one or change who controls the organisation.

Live and test

Each organisation has live data and a sandbox (test mode). Which one a request acts on depends on the credential:

CredentialMode
API keyFixed by its prefix: fo_test_ and fo_rk_test_ are test, the others live.
OAuth access tokenLive, unless the request sends FurniOps-Mode: test.
curl https://api.furniops.com/v1/products \
  -H "Authorization: Bearer $FURNIOPS_ACCESS_TOKEN" \
  -H "FurniOps-Mode: test"

A key that names the other mode in FurniOps-Mode is refused with 400 invalid_request, never silently ignored, so a request cannot act in a mode you did not mean. The header is read only after the credential is accepted. The SDK sends it from its mode option and the CLI from --mode or FURNIOPS_MODE. Everything a request creates belongs to its mode; Live and test lists what is kept apart.

When a credential is refused

AnswerMeansDo
401 authentication_requiredNo credential, or one that cannot be used: malformed, unknown, revoked, expired, an expired access token, or a key used from an address its access policy does not allow. The answer is the same for every reason, so a probe learns nothing.Check the credential is whole and current. Refresh an access token. Check your server's address is in the key's policy.
403 permission_deniedThe credential is valid but lacks the scope, or the member's role does not allow the action.Use a credential that holds the scope.
403 feature_unavailableThe organisation's plan does not include what the request needs.See Plans and features.
400 invalid_request on FurniOps-ModeA key named the other mode, or the value is not exactly live or test.Leave the header out for a key, or use a key of the mode you want.

Every request an organisation makes (refusals included, except a 401, which has no organisation) appears in its request logs with the credential that made it.

On this page