REST API
Floormode exposes its operations over HTTP at /api/v1. It is not a separate, thinner copy of the app — the API and the app run the same operations, so anything the interface can do, an integration can do, and neither can drift ahead of the other.
On this page
Section titled “On this page”- What the API is
- Authenticating
- Requests
- Responses and errors
- Limits
- Discovering the surface
- Changes
- See also
What the API is
Section titled “What the API is”Orders, allocations, picking, packing, shipments, receiving, returns, inventory movements, counts, replenishment, analytics — the same operations and the same vocabulary the app’s screens use. Each operation says whether it reads or writes.
Two properties are worth knowing before you build anything against it:
- Operation names are the API.
orders_listis the operation id in REST, the methodapp.orders.listan agent calls over the MCP server (the first_becomes a dot), and the handler name inside the app. One operation, three doors. - The tenant comes from your credentials, never from the URL. There is no shop or account parameter anywhere in the surface. A token identifies exactly one Floormode account, and a request can only ever reach that account’s data.
Authenticating
Section titled “Authenticating”Every request carries a bearer token:
GET /api/v1/orders?status=unfulfilled HTTP/1.1Host: your-app-domainAuthorization: Bearer <token>Two token types are accepted. External integrations and agents use a WorkOS OAuth access token — discovery metadata is published at /.well-known/oauth-protected-resource (RFC 9728), so a standards-aware client can find the authorization server on its own. The embedded app uses its Shopify session token. A request with neither is answered 401.
Requests
Section titled “Requests”GET and DELETE operations take their input from the query string; POST, PUT and PATCH take a JSON body.
Query values are coerced against each parameter’s declared type before validation, so ?limit=25 reaches a numeric field as the number 25, and ?includeArchived=true reaches a boolean as true. Arrays accept a repeated parameter (?ids=a&ids=b) or a JSON array.
One behaviour deserves its own paragraph, because it is deliberately stricter than most APIs: a query parameter the operation does not declare is refused, not ignored.
{ "error": "Unknown query parameter(s): stat. orders_list accepts: status, cursor.", "code": "unknown_parameter", "unknown": ["stat"], "accepted": ["status", "cursor"]}Silently dropping stat=open would return the whole unfiltered list with a 200 — a wrong answer wearing a correct answer’s clothes. The refusal names what the operation does accept, so one round trip fixes a typo.
Responses and errors
Section titled “Responses and errors”A success is always wrapped:
{ "data": { "orders": [], "cursor": null } }A failure is always {"error": "<human message>"}, plus a code whenever the refusal is one the app classified. Branch on code, never on the message text — messages are written for people and will be reworded.
code | HTTP | Means |
|---|---|---|
invalid_input | 400 | A domain precondition the operation checked |
unknown_parameter | 400 | A query parameter the operation does not declare |
unauthorized | 401 | No usable identity |
plan_required | 403 | The account’s plan does not include this |
forbidden | 403 | Authenticated, but the wrong kind of caller for this operation |
not_found | 404 | The addressed record does not exist |
conflict | 409 | A state or uniqueness conflict — the write cannot be applied as asked |
payload_too_large | 413 | The write would exceed a storage ceiling |
rate_limited | 429 | The store’s calls for this minute are used up — wait and send it again |
internal | 500 | Our bug |
An input that does not fit the operation’s schema is the one error without a code: it is answered 400 with {"error": "Invalid input", "issues": [...]}, the schema’s own list of what failed.
A 409 usually carries guidance in its message: it is how the API says “this order is already packed” or “use the component-pick operation for a kit line” rather than failing opaquely.
Limits
Section titled “Limits”Each store can make 60 calls a minute through the API. Every REST operation call that passes validation counts, and so does every app.* call inside an MCP run; the catalog (GET /api/v1), describe_api and the app’s own screens do not. A call past the limit is answered 429 with a Retry-After: 60 header and "code": "rate_limited" (over MCP: RATE_LIMITED). Nothing was run, so wait the minute and send the same call again.
Discovering the surface
Section titled “Discovering the surface”GET /api/v1 returns the machine-readable catalog — every operation with its method, path, minimum plan and input JSON Schema, straight from the running app. It needs a token but no plan, so a client can see the whole surface before its plan allows every call.
The same information, as a static document you can point tooling at:
The document is regenerated from the registry on every change, so an operation that exists in the app exists in the document, with the same description the app carries. It cannot go stale without the app’s build failing.
Changes
Section titled “Changes”The API follows the app: an operation that changes shape changes it for every door at once, and there is no compatibility layer. Recent changes, October 2026:
- MCP:
describe_apiwithout input answers an index of every method, reads apart from writes.describe_api({ namespaces: ["orders"] })declares those namespaces with their input and return types, anddescribe_api({ search: "short stock" })the methods whose name or description holds every word. The server’s instructions carry the index instead of the whole declaration. picking_create_batchno longer takesfilter, and its input is strict: a call that still sendsfilter: "singles"is answered400(Invalid input, with the schema’sissues). Cut single-item orders with a pick profile instead (the Singles template: units = 1) and pass itsprofileId. WithoutfoDocIdsthe batch takes the oldest ready orders first, and before it does it re-plans up to ten reservations that went stale (a lot that expired, a bin that is no longer allocatable).picking_create_batchholds an explicit selection to the batch size too:max, else the profile’s order count, else 20. A selection with more ready orders is answered409 selection_too_large(withmaxandready) and nothing is batched; it is never cut short.- Pick profiles (Elevate):
pick_profiles_list,_preview,_create,_update,_archive,_restoreand_reorderunder/picking/profiles.picking_queueandpicking_list_batchestakeprofileId;orders_recent_tags(GET /orders/tags) suggests the tags and priorities to write conditions with. picking_queue_exclusionsgainsstale: orders that wait for a fresh reservation.orders_work_queue’stagmatches like an automation rule (case, surrounding spaces and accent composition do not matter) and is answered409 tags_indexingfor the minute after an update that re-derives the order tags.- A shipment rate’s
amountandcurrencycan benull: inshipments_get(shipment.rates[],shipment.chosenRate,shipment.purchaseRate) and in theshipments_listrows.nullmeans a service on your own carrier contract that Sendcloud returned no price for, so the carrier’s invoice decides. A label bought on such a rate is recorded withcost: nullandcurrency: null. Code that adds upamountmust skip thenullones.
September 2026:
setup_guide_statusis nowsetup_status(GET /setup), and answers four steps instead of six.settings_setanswers{ ok: true, value }on success and{ ok: false, reason: "warehouses_not_live", detail, warehouses, capped }when the Logistified switch is refused;inventoryWriterCutoverRequiredis gone.picking_queue_exclusionsgainsunmappedand takeswarehouseIdoptionally.- The setting names
openingStockSkippedandprinterSetupSkippedno longer exist and are answered400.
See also
Section titled “See also”- REST quickstart — a token, a list, a safe change and the errors, step by step
- Connect an AI agent — the same operations through Claude, ChatGPT or another MCP client
- API explorer — the interactive reference
- Subscription plans — what the Enterprise tier includes
- Integrations — how Floormode talks to the sibling apps
- Webhooks — have Floormode call your system when something happens, and how to verify each message