Session Transfer
POST https://api.firmly.work/api/v2/domains/{domain}/session/transfer
Transfers an existing merchant-side session (the buyer’s cart on the merchant’s own store) into the Firmly cart for the current device. The merchant adapter resolves the supplied handle (and any cookies) into the merchant cart, Firmly imports its contents, and the response is the cart in V2 form.
Authentication
x-firmly-authorization(string, required) — Device access token from Browser Session- Server-to-server (S2S) auth is not accepted — this is a device-scoped session route. It requires a device JWT from Browser Session; the S2S secret is rejected. See Server-to-Server Authentication.
Path Parameters
domain(string, required) — The merchant’s domain (e.g.staging.luma.gift).
Request Body
-
handle(string, required) — Merchant session identifier. Always sent as a JSON string. Two forms are accepted: - Plain string — the merchant’s session id directly (e.g."merchant-session-12345"). - JSON-stringified envelope — a JSON object stringified before being placed inhandle, used when a merchant integration needs to pass additional metadata. Fields the envelope may carry: | Field | Type | Meaning | | ————– | —— | ——- | |handle| string | The merchant session id. | |token| string | Security token shared with the merchant. Must be ≥ 36 characters. | |merchantData| object | Arbitrary metadata the merchant supplies to Firmly for the transfer. | |version| string | Script / integration version identifier. | Pre-stringify the envelope before sending — the wire value ofhandlemust be a string, not a JSON object. -
cookies(string[], required) — List of cookies the merchant adapter needs to validate / hydrate the session. Always send this field as an array, even when you have no cookies — pass an empty array ([]) in that case. Although it is technically optional in the request schema, omittingcookiesentirely causes the request to fail, because the server folds it into the session fingerprint before validation runs.
Response
Returns the imported cart in the V2 shape — same schema as Get Cart.
Examples
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/session/transfer \-H "x-firmly-authorization: YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"handle": "external-session-12345","cookies": ["session_cookie_value"]}'
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/session/transfer \-H "x-firmly-authorization: YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"handle": "{\"handle\":\"external-session-12345\",\"token\":\"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0\",\"version\":\"2.0\",\"merchantData\":{\"source\":\"mobile-app\"}}","cookies": ["session_cookie_value"]}'
// Plain handleconst response = await fetch('https://api.firmly.work/api/v2/domains/staging.luma.gift/session/transfer',{method: 'POST',headers: {'x-firmly-authorization': 'YOUR_TOKEN','Content-Type': 'application/json'},body: JSON.stringify({handle: 'external-session-12345',cookies: ['session_cookie_value']})});// Stringified envelopeconst envelope = {handle: 'external-session-12345',token: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0',version: '2.0',merchantData: { source: 'mobile-app' }};const responseWithMeta = await fetch('https://api.firmly.work/api/v2/domains/staging.luma.gift/session/transfer',{method: 'POST',headers: {'x-firmly-authorization': 'YOUR_TOKEN','Content-Type': 'application/json'},body: JSON.stringify({handle: JSON.stringify(envelope),cookies: ['session_cookie_value']})});const cart = await responseWithMeta.json();
import jsonimport requests# Plain handleresponse = requests.post('https://api.firmly.work/api/v2/domains/staging.luma.gift/session/transfer',headers={'x-firmly-authorization': 'YOUR_TOKEN','Content-Type': 'application/json'},json={'handle': 'external-session-12345','cookies': ['session_cookie_value']})# Stringified envelopeenvelope = {'handle': 'external-session-12345','token': 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0','version': '2.0','merchantData': {'source': 'mobile-app'}}response_with_meta = requests.post('https://api.firmly.work/api/v2/domains/staging.luma.gift/session/transfer',headers={'x-firmly-authorization': 'YOUR_TOKEN','Content-Type': 'application/json'},json={'handle': json.dumps(envelope),'cookies': ['session_cookie_value']})cart = response_with_meta.json()
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 — MissingAuthHeader
The x-firmly-authorization header is missing or empty.
{ "code": 400, "error": "MissingAuthHeader", "description": "x-firmly-authorization header is missing or invalid." }
400 — InvalidToken
The authorization token is not a valid JWT structure.
{ "code": 400, "error": "InvalidToken", "description": "Jwt token is invalid." }
401 — InvalidJWTToken
The device JWT signature does not verify, or required claims are missing.
{ "code": 401, "error": "InvalidJWTToken", "description": "Jwt token is invalid." }
404 — PartnerNotFound
The appid claim on the device JWT does not map to a known partner / tenant.
{ "code": 404, "error": "PartnerNotFound", "description": "Partner not found." }
404 — DomainNotFound
The {domain} path parameter does not match any merchant configured with Firmly, or the merchant has been disabled.
{ "code": 404, "error": "DomainNotFound", "description": "This domain was not found in firmly servers." }
409 — CannotTransferSession
The supplied handle could not be turned into a usable merchant session. Common causes: invalid handle, expired or revoked session on the merchant side, or a token shorter than the required 36 characters when an envelope is sent.
{ "code": 409, "error": "CannotTransferSession", "description": "Required information to perform session transfer is not available." }
409 — NotEnoughStockError
The merchant cart was retrieved, but one or more line items have insufficient stock to import.
{ "code": 409, "error": "NotEnoughStockError", "description": "Not enough stock." }
412 — NoLineItemError
The transferred cart is empty (the merchant returned a cart with no line items).
{ "code": 412, "error": "NoLineItemError", "description": "No line item." }
412 — OperationNotSupported
The merchant adapter does not implement session transfer.
{ "code": 412, "error": "OperationNotSupported", "description": "This operation is not supported for this store." }
400 — InvalidInputBody
Request body fails schema validation. handle must be a string; cookies must be an array of strings when present.
{ "code": 400, "error": "InvalidInputBody", "description": "The body is not processable" }