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

Set Consents

PUT https://api.firmly.work/api/v2/domains/{domain}/cart/consents

​​ Overview

Updates the consent preferences for a buyer’s cart session. Grants or revokes consent for the IDs returned by Get Consents. Each grant captures a server-side signature for compliance.

​​ Authentication

  • x-firmly-authorization (string, required) — Device access token from Browser Session

​​ Path Parameters

  • domain (string, required) — Domain of the merchant website (e.g., staging.luma.gift)

​​ Request Body

  • consents (object[], required) — Array of consent updates to apply. Each entry references a consent returned by Get Consents.
    Consent update properties
    • id (string, required) — Unique identifier of the consent to update — must match an id returned by Get Consents.
    • revoke (boolean, default false) — Set to true to revoke a previously granted consent. Omit or set to false to grant.

​​ Response

Returns an updated array of all consent objects with the same structure as the Get Consents response, reflecting the changes made.

​​ Code Examples


curl -X PUT https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"consents": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
]
}'

const response = await fetch('https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents', {
method: 'PUT',
headers: {
'x-firmly-authorization': 'YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
consents: [
{
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
}
]
})
});
const updatedConsents = await response.json();

import requests
response = requests.put(
'https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents',
headers={
'x-firmly-authorization': 'YOUR_TOKEN',
'Content-Type': 'application/json'
},
json={
'consents': [
{
'id': 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
}
]
}
)
updated_consents = response.json()

$data = [
'consents' => [
[
'id' => 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
]
]
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'x-firmly-authorization: YOUR_TOKEN',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$updatedConsents = json_decode($response, true);
curl_close($ch);

curl -X PUT https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"consents": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"revoke": true
}
]
}'

const response = await fetch('https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents', {
method: 'PUT',
headers: {
'x-firmly-authorization': 'YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
consents: [
{
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
revoke: true
}
]
})
});

​​ Update Multiple Consents


curl -X PUT https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"consents": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
},
{
"id": "8c9e3f12-4567-8901-2345-678901234567",
"revoke": true
}
]
}'

const response = await fetch('https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/consents', {
method: 'PUT',
headers: {
'x-firmly-authorization': 'YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
consents: [
{ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' }, // Grant
{ id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8' }, // Grant
{ id: '8c9e3f12-4567-8901-2345-678901234567', revoke: true } // Revoke
]
})
});

​​ Response Example


[
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"ui_slot": "UNDER_EMAIL_INPUT",
"text": "I would like to receive marketing emails about special offers and new products.",
"type": "MARKETING",
"explicit": true,
"required": false,
"revokable": true,
"signed": true
},
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"ui_slot": "ABOVE_PLACE_ORDER_BUTTON",
"text": "I agree to the Terms of Service and Privacy Policy.",
"html": "I agree to the <a href='/terms'>Terms of Service</a> and <a href='/privacy'>Privacy Policy</a>.",
"type": "TERMS_AND_CONDITIONS",
"explicit": true,
"required": true,
"revokable": false,
"signed": true
}
]
  • Consents with explicit: true require an opt-in action from the buyer; sending the id (without revoke) records that opt-in.
  • Required consents must be signed before placing an order — place-order rejects the request if any required && explicit consent is unsigned.
  • Only consents with revokable: true can be revoked. Sending revoke: true for a non-revokable consent fails the entire request with 400 BadRequest (the description identifies the offending IDs).

​​ 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." }
400 — InvalidAPIToken

The API token (server-to-server secret) is invalid or has been revoked.


{ "code": 400, "error": "InvalidAPIToken", "description": "API token is invalid." }
400 — BadRequest

All of these conditions share the same error (BadRequest) and code (400), so the description is the only way to tell them apart. Unlike consent-object descriptions (which are merchant-authored display text and may change), these BadRequest descriptions are Firmly-emitted and stable enough to branch on. Prefer matching on a stable substring (e.g. invalid, non revokable, no consents) rather than the whole string:

  • No consents available — the session has no consents to sign. Call Get Consents first.

    { "code": 400, "error": "BadRequest", "description": "There are no consents to sign" }
  • Unknown consent IDs — one or more ids in the request body do not match any consent from Get Consents. Description lists the offending IDs.

    { "code": 400, "error": "BadRequest", "description": "The consents <id1>,<id2> are invalid." }
  • Attempt to revoke a non-revokable consent — at least one consent has revokable: false. Description lists the offending IDs.

    { "code": 400, "error": "BadRequest", "description": "The consents <id> are non revokable" }
  • Server-to-server auth headers missing or malformed (different code path, same wire error).

    { "code": 400, "error": "BadRequest", "description": "Bad request." }
401 — Unauthorized

The authorization token was rejected.


{ "code": 401, "error": "Unauthorized", "description": "Unauthorized." }
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." }
412 — OperationNotSupported

The merchant’s platform does not support this operation.


{ "code": 412, "error": "OperationNotSupported", "description": "This operation is not supported for this store." }
400 — InvalidInputBody

Request body fails schema validation. Each entry under consents must have a string id; revoke is optional and boolean.


{ "code": 400, "error": "InvalidInputBody", "description": "The body is not processable" }