Skip to content

REST quickstart

Enterprise plan

Four steps from nothing to a working integration: get a token, read a list, make one change that is safe to send twice, and read what an error says. The REST API page has the full rules, the API explorer every operation.

  • Get a token
  • Read a list
  • Make a change safely
  • Read the errors
  • See also

Every request carries Authorization: Bearer <token>: an OAuth access token of a Floormode person whose role includes Use the API and AI agents (REST, MCP). The token can do what that person can do, and nothing more. To give an integration less than your own access, invite a person for it with a narrower role in Settings → Team.

Where to get the token is published:

Terminal window
curl https://prod.floormode.app/.well-known/oauth-protected-resource
{
"resource": "https://prod.floormode.app/mcp",
"authorization_servers": ["https://<sign-in domain>"],
"bearer_methods_supported": ["header"]
}

Point any OAuth library at that document: it finds the sign-in, registers its client, and signs the person in once in a browser (authorization code with PKCE). Ask for the resource above. The same token works on /api/v1 and on the MCP server. Access tokens are short-lived; let your library renew them with its refresh token.

Open orders that are short of stock, two at a time:

Terminal window
curl "https://prod.floormode.app/api/v1/orders/queue?allocationState=short&limit=2" \
-H "Authorization: Bearer $TOKEN"

A success is always { "data": … }. A list answers a page of rows, whether the list was cut (capped), and a cursor for the next page:

{
"data": {
"rows": [
{
"_id": "orders_01K8C4Z3W9V7RS2A6J3QPMB5EN",
"shopifyOrderId": "6253011468585",
"name": "#1038",
"allocationState": "short",
"shortSkuCount": 1,
"ageMs": 93600000
}
],
"capped": true,
"cursor": "1761036000000:orders_01K8C4Z3W9V7RS2A6J3QPMB5EN"
}
}

Each row carries more fields than shown here; the explorer lists them. For the next page, send the same request with &cursor=<cursor>, until cursor is null. Pass the cursor back as it came; never build one.

Query values are read as their declared type, so limit=2 is the number 2. A parameter the operation does not have is refused with 400 unknown_parameter, which names the ones it accepts.

Put an order on hold because its address is incomplete:

Terminal window
curl -X POST "https://prod.floormode.app/api/v1/holds/apply-for-orders" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"shopifyOrderIds": ["6253011468585"],
"reason": "address_issue",
"note": "Street number missing",
"requestKey": "7c4f0a52-2f0e-4c1e-9a41-0b8f6c2d1e33"
}'
{ "data": { "applied": 1, "skipped": [] } }

requestKey is your own key for this one change, for example a new UUID. Keep it when you send the request again after a timeout:

  • the same key with the same body answers the first answer again, and nothing happens twice;
  • the same key with a different body is refused:
{
"error": "This request key was already used for a different request. Use a new key.",
"code": "conflict",
"details": { "code": "idempotency_key_reused" }
}

Operations that take a key say so in the explorer (requestKey or idempotencyKey). For one without a key, read before you send it again: for example, after a lost answer to picking_create_batch, list the open batches with picking_list_batches first.

An error is { "error": "<sentence>", "code": "<code>" }, and details.code when Floormode names the exact reason. Branch on the codes; the sentence is written for people and may change.

A request whose input does not fit the operation’s schema (a missing field, a wrong type, limit=0) is the one error without a code: it is answered 400 with the schema’s own list of what failed.

{
"error": "Invalid input",
"issues": [
{
"origin": "number",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": ["limit"],
"message": "Too small: expected number to be >=1"
}
]
}
HTTPcodeMeans
400none, issues insteadThe input does not fit the operation’s schema
400unknown_parameterA query parameter the operation does not have
400invalid_inputThe values fit, but Floormode refused them; the sentence says why
401noneNo token, or one that is not valid for Floormode
403plan_requiredYour plan does not include the API (it names requiredPlan)
403forbiddenThe person may not do this; details.code is permission_required
404not_foundThe record does not exist
409conflictThe change cannot be applied as asked; the sentence says why
429rate_limitedThe store’s 60 calls for this minute are used up; send it again after Retry-After