FurniOpsDocs
CLI

CLI

The furniops command line tool. Install it, sign in, use profiles, and script it with JSON output and exit codes.

furniops is the FurniOps command line tool. With it you can:

  • sign in with your FurniOps account in the browser, or save an API key;
  • forward live events to a webhook handler on your machine, signed like real deliveries, with no tunnel (furniops listen);
  • emit sample events in test mode, tail the event stream, read a product and start a source run.

Every command is listed in the command reference.

Install

Not on npm yet

Version 0.1.0 is released as the git tag release/cli/v0.1.0 of the softup-llc/furniops-packages repository; the CLI is not published to npm yet (the SDK it builds on is). This page documents 0.1.0 as built.

It needs Node.js 22.12 or later. Check what you have with furniops --version.

Sign in

There are three ways to give the CLI a credential. The first one found wins:

CredentialHowMode
FURNIOPS_API_KEYAn environment variable. Never stored.The key's mode
An API key saved to a profilefurniops login --with-api-keyThe key's mode
Your FurniOps accountfurniops login (in the browser)Live, or test with --mode test

With your account (device flow)

furniops login
Your confirmation code: WDJB-MJHT
Confirm it at https://auth.furniops.com/…
Opened your browser. Check the code matches before you confirm.
Signed in as [email protected] in org_… (profile default, live mode). Stored in the keychain.

The CLI opens your browser at a page showing a code. Check that it matches the one in your terminal, then confirm. The CLI never sees your password.

  • The session acts in live mode unless you pass --mode test (or set FURNIOPS_MODE=test), which reaches your sandbox with the same sign-in.
  • It asks for every permission a credential may hold, limited by your role. Ask for fewer with --scopes products:read,events:read.
  • The access token is refreshed automatically shortly before it expires. If the session was revoked, the next command exits with code 3 and asks you to sign in again.
  • On a machine without a browser (over SSH), use --no-browser and open the link anywhere.

With an API key

furniops login --with-api-key            # prompts, hidden; nothing is echoed
pbpaste | furniops login --with-api-key  # or pipe it in

The key is read from standard input or a hidden prompt, never from a flag, so it never lands in your shell history. A test key (fo_test_…) puts the profile in test mode, which furniops trigger needs.

In CI, set FURNIOPS_API_KEY instead: it overrides any saved credential and is never written anywhere.

Check and sign out

furniops whoami    # who, which mode, where the credential came from
furniops logout    # revokes the session and forgets the profile's credential

Where credentials are kept

The CLI keeps credentials in the operating system's secret store, and everything else in a config file.

macOSLinuxWindows and elsewhere
CredentialsThe login keychain, service furniops-cliThe Secret Service (GNOME Keyring, KWallet) through secret-tool, when it is installed and a session bus is runningcredentials.json in the config directory, mode 0600
Config~/.config/furniops/config.json$XDG_CONFIG_HOME/furniops/config.json, else ~/.config/furniops/config.json%APPDATA%\furniops\config.json
  • Secrets are handed to security and secret-tool on standard input, never on a command line that other users could see in ps.
  • The config directory is created 0700 and its files 0600, written whole through a temporary file and a rename, so a crash never leaves half a file.
  • FURNIOPS_CREDENTIAL_STORE picks the store: keychain, secret-service or file. Use file on a headless Linux machine without a session bus.
  • FURNIOPS_CONFIG_DIR moves the whole config directory.

Profiles

A profile is a named set of settings and a credential, so you can keep, say, a live account and a test key side by side:

furniops login                                       # the "default" profile
furniops login --profile sandbox --with-api-key      # a test key, in "sandbox"
furniops trigger offer.changed --profile sandbox
FURNIOPS_PROFILE=sandbox furniops events tail

A profile name is letters, digits, - and _. When you sign in with --api-url, --issuer or --audience, the profile remembers them, so later commands go to the same place.

Global options

Every command takes these. Each setting is taken from the flag, then the environment, then the profile, then the default.

FlagEnvironmentDefaultMeaning
--profile <name>FURNIOPS_PROFILEdefaultThe profile to use.
--api-url <url>FURNIOPS_API_URLhttps://api.furniops.comThe API's base URL.
--issuer <url>FURNIOPS_ISSUERhttps://auth.furniops.comThe sign-in server.
--audience <uri>FURNIOPS_AUDIENCEthe API URL's originThe API audience tokens are asked for.
--mode <mode>FURNIOPS_MODEthe credential'slive or test. A signed-in session acts in live mode unless this says test; an API key's mode is its own, and naming the other one exits 2.
--jsonoffPrint results as JSON: one object, or one per line for streams.
-v, --versionPrint the version.
-h, --helpHelp for any command: furniops listen --help.

Other environment variables: FURNIOPS_API_KEY (a credential, above), FURNIOPS_CONFIG_DIR and FURNIOPS_CREDENTIAL_STORE.

Scripting

With --json, results go to standard output as JSON, and progress messages are not printed. A failure prints one JSON object:

{ "message": "not signed in (profile default)", "code": "not_signed_in", "hint": "Run `furniops login`." }

API failures add status and request_id. Without --json, a failure is one line on standard error, such as Error: … [code, HTTP 403, request req_…], followed by a hint when there is one.

Exit codes

CodeMeaning
0Success, including --help and --version.
1Any other error.
2Usage: an unknown command, option or argument, or an invalid value.
3Authentication: not signed in, the session expired or was revoked, or the API refused the credential.
4Permission: signed in but not allowed; a missing scope, or a test-mode command with a live credential.
5Network: the API or the sign-in server could not be reached.
furniops whoami --json > /dev/null
case $? in
  0) ;;
  3) echo "sign in first: furniops login" >&2; exit 1 ;;
  5) echo "FurniOps is unreachable; try again later" >&2; exit 1 ;;
  *) exit 1 ;;
esac

On this page