Skip to content

The curated commands ​

Hand-written commands for the entities most people touch, plus the core commands that have nothing to do with the API. Everything else lives in discovery.md.

Run --help on any of them. It tells you the endpoint, the permission it needs, and a sample body. The notes below are the things --help will not tell you.

Orders ​

An order is the demand the platform fulfills, addressed by its platform id (the one list, search and get print — orders have no tenantFacilityId-style shorthand).

sh
fft order list --consumer-id C-4711
fft order get 7f3ce2a4-1f00-4c6a-9a2b-5d1f0b7a1c33
fft order search --status OPEN --sort orderDate:desc
fft order search --since 2026-07-01 --until 2026-07-15
fft order create --example > order.json
fft order create --file order.json
fft order update 7f3ce2a4-1f00-4c6a-9a2b-5d1f0b7a1c33 --file order.json
fft order cancel 7f3ce2a4-1f00-4c6a-9a2b-5d1f0b7a1c33 --reason-id out-of-stock
fft order unlock 7f3ce2a4-1f00-4c6a-9a2b-5d1f0b7a1c33
  • list is the plain GET: it returns a stripped order (id, status, orderDate, line count, version) and filters only on --tenant-order-id and --consumer-id. For status, a date range or a sort, use search.
  • search is a BETA endpoint guarded by ADMIN_MODULES_READ, not ORDER_READ — a token that lists orders fine may be refused here. Give it a narrow --since/--until; it pages far faster when the date range is bounded.
  • Neither list nor search filters by facility. An order routes to a facility through its pickjobs, so filter pickjobs by facility instead.
  • update is a PATCH, but orderLineItems is a full replacement: send them all or the ones you omit are deleted. Read the order, edit, send it back.
  • cancel --force sends FORCE_CANCEL, which only works if the tenant permits it; it takes no --reason-id. Cancelling cannot be undone, so fft asks first (--yes answers).

Facilities ​

A facility is addressed by its id.

sh
fft facility list --status ACTIVE
fft facility get berlin-warehouse
fft facility create --example > facility.json
fft facility create --file facility.json
fft facility patch berlin-warehouse --name "Berlin Warehouse"
fft facility delete berlin-warehouse
fft facility coordinates set berlin-warehouse --lat 52.52 --lon 13.405
fft facility search --file query.json
  • patch changes named fields; update replaces the whole object from a file. Prefer patch — it cannot silently drop a field you did not know about.
  • delete is destructive and irreversible. Ask first, every time.

Connections ​

A connection is an edge of the fulfillment graph: an outbound lane from one facility to a SUPPLIER, to another MANAGED_FACILITY, or to the CUSTOMER. The router can only source along an edge that exists, so this is the first place to look when an order routes somewhere surprising — or refuses to route at all.

A connection belongs to the facility it leaves, so every command needs --facility as well as the connection's own id:

sh
fft connection list --facility berlin-warehouse
fft connection list --facility berlin-warehouse --target frankfurt-warehouse
fft connection get 3f9c1e77-2b4a-4f0e-9d61-8a2c5b7e4d10 --facility berlin-warehouse
fft connection create --facility berlin-warehouse --example --type SUPPLIER
fft connection delete 3f9c1e77-2b4a-4f0e-9d61-8a2c5b7e4d10 --facility berlin-warehouse
  • update is a PUT and there is no patch. The connection becomes what the file says and loses anything the file omits — so unlike a facility, you cannot reach for the safer verb. Read the whole thing, edit it, send it back. fft supplies the version.
  • The connection the API returns carries its type only inside target; the body it accepts wants one at the top level too. fft fills that in, so fft connection get … -o json piped back into --file works. Do not hand-edit it in.
  • --example prints a body per type: --type SUPPLIER, MANAGED_FACILITY or CUSTOMER. A CUSTOMER target names no facility, and is not missing one.
  • delete removes a routing edge. Orders stop being sourced along it immediately, and if it was the only way to reach a target, that target becomes unreachable. Ask first.

Sourcing ​

fft sourcing simulate changes nothing. It is a POST, and it is a read: it runs the router against a hypothetical order and hands back the ways it could be fulfilled. No order is created, no stock is reserved, nothing moves. It works on a read-only project, and fft knows it does — so do not refuse to run it on the grounds that POST means write.

sh
fft sourcing simulate --example > order.json
fft sourcing simulate --file order.json --results 3
fft sourcing get 284f32cc-b106-487d-b633-f90d93d8c251
  • An empty answer is not "no matches". It means the router could not fulfil the order at all, from anywhere. Say that, rather than reporting that the search came back empty.
  • The penalty is a penalty: lower is better. The table is sorted so the router's favourite is first. Never present it as a score.
  • UNSOURCED is the partial-failure column. An option can look healthy and still have quietly dropped items. If it is not zero, that option will not fulfil the whole order.
  • --results defaults to 3 because the API's default is 1 — one option is an answer, not a set of options.
  • Every transfer carries a facilityConnectionRef. That is the join to fft connection get, and it is how you answer "why there?" — see the recipe in recipes.md.

Routing ​

A routing strategy is the decision tree the engine walks to source an order: a root node with fences (which facilities are eligible) and ratings (which is preferred), and conditions that branch to further nodes. Only one strategy is ever live — inUse — and fft routing is where you inspect, edit and switch it.

sh
fft routing strategy list
fft routing strategy get 3f9c1e77-2b4a-4f0e-9d61-8a2c5b7e4d10 -o json
fft routing strategy create --example > strategy.json
fft routing strategy activate 3f9c1e77-2b4a-4f0e-9d61-8a2c5b7e4d10
fft routing strategy evaluate 3f9c1e77-2b4a-4f0e-9d61-8a2c5b7e4d10 --file order.json
  • activate makes one strategy live, taking over from whichever was inUse. It is versioned in the body, so a stale version is exit 7 — re-read and retry. --if-version skips the read for a clean 409. fft routing strategy actions <id> --file is the escape hatch for the other four actions (COPY, REPLACE_NODE, REPLACE_CONDITION, REPLACE_GLOBAL_CONFIGURATION) — REPLACE_NODE/REPLACE_CONDITION edit one part of the tree by id, the safer alternative to a whole-strategy PUT.
  • evaluate is a read. It dry-runs the strategy against an order (the same body shape fft order create takes) and reports the path the engine would walk. It reserves nothing and is safe under --read-only. This answers "why would this strategy route this order there?"; fft sourcing simulate answers the same question for the live one. fft routing strategy evaluate-node <id> <nodeId> evaluates a single node instead of the whole tree.
  • update is a PUT, there is no patch. Read the whole strategy, edit it, send it back; fft supplies the version. There is no delete — the API does not offer one.
  • fft routing decision-logs is the audit trail of real routing runs. Filter by exact id: --order, --routing-plan, --process, --tenant-order-id, --sourcing-option(s).
  • fft routing category manages the node config category labels (a name and a colour) that a strategy's nodes are grouped under. Full CRUD, including delete.

Listings ​

A listing has no id of its own. It is addressed by the facility it is in and the article it is for, so every command that addresses a single listing needs --facility (the bulk upsert is the exception: it reads the facility from the file):

sh
fft listing list --facility berlin-warehouse
fft listing get --facility berlin-warehouse ARTICLE-123
fft listing patch --facility berlin-warehouse ARTICLE-123 --status ACTIVE
fft listing delete --facility berlin-warehouse ARTICLE-123
  • fft listing set --facility <id> --file listings.json is a PUT, and all or nothing: one bad listing rejects the whole file. But it is not a replacement of the catalog — listings the file does not mention are left alone. You do not have to send a catalog back to keep it.
  • fft listing upsert --file listings.json is the bulk path, and the one you almost always want. It is chunked, and it reports per item — so it can exit 8 with some items written, where set would have written none.
  • fft listing purge --facility <id> deletes every listing in a facility. There is no narrower way to say "I meant that".

Stock ​

Stock has an id of its own, but you will nearly always arrive at it through the facility and the article.

sh
fft stock list --facility berlin-warehouse --tenant-article-id ARTICLE-123
fft stock get 7f3ce2a4-1f00-4c6a-9a2b-5d1f0b7a1c33
fft stock summary --facility berlin-warehouse
fft stock upsert --file stocks.json
fft stock actions --file action.json
  • stock summary answers "how much of this do we have" without paging every stock record. Reach for it before you reach for list.
  • stock actions is how stock is moved and changed (pick, book in, correct) rather than overwritten. If the user is describing a real-world event, this is usually the endpoint.
  • stock upsert is bulk and chunked; exit 8 means some items landed.

Templates ​

A saved request body with parameters. render prints the finished body on stdout, so it composes with any command that takes --file:

sh
fft template save rush-order --file body.json --require email=order.consumer.email
fft template save rush-order --operation addOrder --file body.json
fft template list
fft template show rush-order
fft template render rush-order --set email=a@b.de | fft order create --file -
fft template remove rush-order
  • Rendering makes no request: no project, no credentials, no network, and a read-only project cannot refuse it. The command receiving the body is still gated as it was.
  • --set takes a declared parameter or a path (--set order.items.0.quantity=3). An id made only of digits needs --set-string, or it goes out as a number.
  • Saving strips a top-level version, because replaying a stale one is a guaranteed 409.
  • --operation <operationId> records which operation the body is for; an unknown id, or one that takes no body, is exit 2.
  • render --if-digest <digest> renders only the template whose DIGEST show printed, and exits 7 if the file changed since — for a script that reviews a template before it sends it.
  • show -o json names the file it read under resolved (name, scope, path): a project template hides a user one of the same name.
  • --local writes ./.fft/templates, which the repository commits. Read one before you commit it: a body captured from real work carries real ids and consumer emails.
  • Full rules in templates.md.

Paging ​

Every list and search takes the same flags:

sh
fft stock list --facility berlin-warehouse --size 100
fft stock list --facility berlin-warehouse --all --max-items 500
fft facility list --total
  • The default is one page. --all pages to the end, stopping at --max-items — which defaults to 10,000, so --all is bounded whether you say so or not. Lower it when the question is exploratory; a real tenant is a great many requests.
  • The API has two paging models and they have different page sizes. The searches behind facility, listing and stock default to 20 per page; fft connection list is a plain GET and defaults to 25. --size overrides either. Do not assume a page size — count the rows, or pass --size.
  • Hitting that limit is not an error: fft warns on stderr and exits 0 with what it got, so the JSON alone cannot tell you the answer was cut short.
  • --total prints the count to stderr, never into the JSON. Only on a single page does it ask the API to count: under --all fft prints the number it actually fetched, and — if the run was truncated — prints nothing at all, because then it cannot know the total.

Core commands ​

sh
fft project list
fft project current
fft project use staging
fft project read-only prod
fft auth whoami
fft auth status -o json
fft ping
fft version
fft skill status -o json
fft component list
fft history top -o json
fft history top --all-projects --limit 0 -o json
fft history list --limit 10 -o json
fft history list --project staging -o json
fft history list --all-projects -o json
# only when the user asks; it asks first
fft history clear
  • fft auth whoami prints the permissions the current credentials actually have. When something exits 5, this is the command that explains why.
  • fft auth status -o json says what is stored to sign in as the current project (with its email and username) — store, signIn (password, idToken or none), hasPassword, hasRefreshToken, hasIdToken — and the cached token's token state (valid, expiring, expired, unknown, none) with its expiresAt and expiresIn. It takes no flags of its own, and exits 3 when no project is configured. It sends nothing and mints nothing, so it is the cheap check before a long run; expired: true is not an error, the next command renews the token. It never prints a credential.
  • fft ping needs no credentials at all. It is the way to tell "the tenant is down" apart from "my token is wrong".
  • fft project read-only prod marks a project read-only for good, in the config file. It is the right suggestion whenever a user tells you a project is production.
  • fft skill status -o json compares each installed copy of this skill with the running fft: {fft, installs: [{scope, dir, version, status, error}]}, where status is CURRENT, OUTDATED (installed by another fft version), MODIFIED (edited since), MISSING, NOT_SKILL or UNREADABLE (with the reason in error). fft skill install updates an OUTDATED copy without --force unless the user edited a file in it; then it stops and asks. Status only reads the installed files, sends nothing, and always exits 0.
  • fft component list says which components are installed, and whether fft ships them or somebody else does — a command in --help that came from a component is somebody else's code. See components.
  • fft history top -o json lists the operations used most in the current project, each with project, operationId, command, count and lastUsed — a quick way to learn what a user normally does before suggesting a command. --all-projects counts every project, each separately; --limit (default 10, 0 for all) caps the list. It needs a current project unless --all-projects is given: exit 3 otherwise.
  • fft history list -o json prints the recent requests in the current project, newest first; --all-projects lists every project's; --limit defaults to 20, 0 for all. Like top, it needs a current project unless --all-projects is given: exit 3 otherwise. Each entry has v (the record's format version), ts, source (cli or tui), project, operationId, command, args, status (left out when no response arrived), exit and durationMs.
  • Neither holds a request body, and inline --data, header values, --set values, credential-shaped flags and flags or query parameters named like a person's data (--email, --query email=…, --search-term) appear as <redacted>; ids and other values are kept as typed. History is off in headless mode unless FFT_HISTORY=on, so in CI both usually print []; when nothing is being recorded, stderr says why.
  • fft history clear deletes the whole history. It asks first, and without a terminal it refuses with exit 2 unless --yes is given. It is the user's record: do not clear it unless they ask.
  • fft component init pricing scaffolds a new component (a command or a transport, in shell, Go, Python or Node) that installs and runs straight away. See components.

An independent open-source project — not affiliated with, endorsed by, or supported by fulfillmenttools.