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

Browser Session

POST https://api.firmly.work/api/v1/browser-session

​​ Overview


POST https://api.firmly.work/api/v1/browser-session

Browser Session is the authentication endpoint that serves as the starting point for all Cart API integrations. It exchanges your App ID for a JWT access token that authenticates subsequent API calls.

Send the request without a body to start a new device session, or include an existing access_token (in the body or fsession_v2 cookie) to renew an existing session while preserving the same device_id.

​​ Authentication

  • x-firmly-app-id (string, required) — Your App ID, provided by Firmly. This is the only credential this endpoint requires — it does not use the x-firmly-authorization header.

​​ Request Body

The body is optional. Send it only to renew an existing token; omit it to start a new device session.

  • access_token (string) — An existing access token (active or expired) to renew. When provided, the response returns a new token bound to the same device_id. When omitted, a fresh device session is created.

​​ Response

  • device_created (boolean, required) — true when a brand-new device was created for this session; false when an existing token was renewed.

  • access_token (string, required) — The JWT to pass in the x-firmly-authorization header on all subsequent API calls.

  • device_id (string, required) — Unique identifier for this device session.

  • expires_in (number, required) — Seconds until the token expires. Defaults to 3600 (1 hour); a different lifetime may be configured for your App ID.

  • expires (number, required) — Unix timestamp (seconds) when the token expires.

​​ Code Examples


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

const response = await fetch('https://api.firmly.work/api/v1/browser-session', {
method: 'POST',
headers: {
'x-firmly-app-id': 'YOUR_APPLICATION_ID'
}
});
const { access_token, expires_in } = await response.json();
console.log(`Token expires in ${expires_in} seconds`);

import requests
response = requests.post(
'https://api.firmly.work/api/v1/browser-session',
headers={'x-firmly-app-id': 'YOUR_APPLICATION_ID'}
)
data = response.json()
access_token = data['access_token']
expires_in = data['expires_in']
print(f"Token expires in {expires_in} seconds")

​​ Response Example


{
"device_created": true,
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXZpY2VfaWQiOiI4MmIxMDUyMi01NDgzLTQ3MTktYjU5OS02ZDc4YjEyODI3ZjAiLCJhcHBfaWQiOiJhcHAtMTIzNDU2IiwiZXhwIjoxNjg4OTk4NTcyLCJpYXQiOjE2ODg5OTQ5NzJ9.signature",
"expires_in": 3600,
"expires": 1688998572,
"device_id": "82b10522-5483-4719-b599-6d78b12827f0"
}

​​ Token Renewal

To preserve the same device_id — and therefore the cart and session state attached to it — pass the previous access_token in the request body or in the fsession_v2 cookie. The server verifies the token’s signature but does not enforce an expiry window, so even long-expired tokens can be renewed as long as the signature is still valid.


curl -X POST https://api.firmly.work/api/v1/browser-session \
-H "x-firmly-app-id: YOUR_APPLICATION_ID" \
-H "Content-Type: application/json" \
-d '{"access_token": "EXPIRED_TOKEN"}'

​​ Using the Access Token

After obtaining the access token, include it in the x-firmly-authorization header on all subsequent API requests:


curl -X GET https://api.firmly.work/api/v2/domains/staging.luma.gift/cart \
-H "x-firmly-authorization: YOUR_ACCESS_TOKEN"

​​ Implementation Example


class FirmlyAuth {
constructor(appId) {
this.appId = appId;
this.token = null;
this.expiresAt = null;
}
async getToken() {
// Return existing token if still valid
if (this.token && Date.now() < this.expiresAt) {
return this.token;
}
// Get new token
const response = await fetch('https://api.firmly.work/api/v1/browser-session', {
method: 'POST',
headers: { 'x-firmly-app-id': this.appId }
});
const data = await response.json();
this.token = data.access_token;
this.expiresAt = data.expires * 1000; // Convert to milliseconds
return this.token;
}
}

​​ Next Steps

After authentication, you can:

​​ Error Responses

Errors return a JSON body with code, error, and description. Program against the error value — descriptions are human-readable and may change.

400 — ErrorMissingAppId

The x-firmly-app-id header was not sent.


{ "code": 400, "error": "ErrorMissingAppId", "description": "x-firmly-app-id header is missing or invalid." }
400 — ErrorInvalidAppId

The App ID is not recognized or exceeds the maximum length (512 characters).


{ "code": 400, "error": "ErrorInvalidAppId", "description": "App ID is invalid." }
400 — InvalidToken

An access_token was supplied (in the body or fsession_v2 cookie) but it is not a valid JWT structure (malformed). Returned only during token-renewal calls.


{ "code": 400, "error": "InvalidToken", "description": "Jwt token is invalid." }
401 — InvalidJWTToken

An access_token was supplied but its signature could not be verified, or required claims are missing. Returned only during token-renewal calls.


{ "code": 401, "error": "InvalidJWTToken", "description": "The JWT token is invalid." }