Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

Authentication

Firmly accepts two distinct authentication patterns. Pick the one that matches where your code runs.

Pattern Where your code runs What you send Endpoint reference
Browser session Client-side (browser, mobile app, embedded widget) App ID → bootstrap → use returned JWT Browser Session
Server-to-server Backend (your servers, agent backend, integration tier) Long-lived S2S secret + device identifier Server-to-Server

Your App ID is the durable destination credential Firmly issues you. Depending on where your code runs, it either bootstraps a short-lived JWT (browser-session pattern) or is paired with an S2S secret and device identifier (server-to-server pattern). Both result in a per-request x-firmly-authorization header — only the value differs.

​​ The two patterns

​​ Browser session (most common)

The browser-session pattern is what client-side applications use: one bootstrap call returns a JWT, which you then send in x-firmly-authorization on every subsequent request. The Sandbox Setup walks the actual request and response.

Token lifetime is ~1 hour (expires_in: 3600). To renew, re-post to the same endpoint with the prior (expired) token in the body — this returns a new token with the same device_id as long as the session state is still retained (~7 days from last activity). There’s no separate server-enforced renewal window.


# Bootstrap
curl -X POST https://api.firmly.work/api/v1/browser-session \
-H "x-firmly-app-id: YOUR_APP_ID"

The bootstrap returns the token and session identity:


{ "access_token": "eyJhbGc...", "device_id": "dev_01H2X...", "device_created": true, "expires_in": 3600 }

Send access_token as x-firmly-authorization on every subsequent call:


curl -X GET https://api.firmly.work/api/v2/domains/staging.luma.gift/cart \
-H "x-firmly-authorization: <access_token from above>"

See Browser Session reference for the full field list, renewal pattern, and error cases.

​​ Server-to-server

The S2S pattern is for backend systems that act on behalf of devices/users. The flow:

Firmly issues the S2S secret to you alongside your App ID. There’s no bootstrap call — you put that long-lived secret in x-firmly-authorization on every request, plus a device_id you assign per user/session so Firmly can isolate carts and orders correctly.


curl -X POST https://api.firmly.work/api/v1/discovery/search \
-H "x-firmly-authorization: YOUR_S2S_SECRET" \
-H "x-firmly-device-id: user-12345" \
-H "Content-Type: application/json" \
-d '{"query": "shoes"}'

The x-firmly-device-id value is your own user/session identifier — typically your own user ID or a session UUID. It must be ≤ 256 chars and contain only a-z, A-Z, 0-9, -, _. See Server-to-Server reference for the full spec.

​​ Which to pick

Question Browser Session S2S
Does your code run client-side? ✅ ❌
Can the credential safely appear in client code? App ID (intentional) No — the S2S secret must stay server-side
Do you want to manage device identity yourself? No (Firmly assigns) Yes (you assign device_id)
Is your code a backend service acting for many users? Not the fit ✅ Use S2S
Is your code an embedded widget or browser app? ✅ Use Browser Session Not the fit

Most agentic deployments use browser session because the agent is acting client-side or as a per-user session. Backend integrations and batch jobs use S2S.

​​ What’s in the JWT

The POST /browser-session call returns a JSON body with these bootstrap response fields:

Field Meaning
access_token The JWT itself
device_id Unique session identifier — store it so you can renew the token later
device_created Boolean — was this device new in this call?
expires_in Seconds until expiry (typically 3600)
expires Unix timestamp of expiry

The access_token is a standard JWT — not encrypted, just signed. Decode its payload to see the claims inside:

  • appid — your App ID
  • deviceid — same as the device_id above
  • sub — partner / destination name bound to your App ID
  • env — environment (uat, prod, etc.)
  • iss — identity (the issuer)
  • iat / exp — issued at / expires at (Unix timestamps)

​​ Idempotency

Pass an Idempotency-Key header — a UUID your code generates — on mutation requests, and reuse the same key when retrying the same attempt.


curl -X POST .../complete-order \
-H "x-firmly-authorization: $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{...}'

​​ Common auth errors

All errors use the standard REST envelope: { "code": <status>, "error": "<ErrorName>", "description": "..." }.

Status Error Fix
400 MissingAuthHeader Add the x-firmly-authorization header
400 InvalidToken Token is malformed (not a valid JWT structure) — re-bootstrap
401 InvalidJWTToken JWT signature failed verification, or the token is expired — bootstrap a new session or renew

See Errors & Conventions for the full catalog.

​​ Endpoint references