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

Add Addons

POST https://api.firmly.work/api/v2/domains/{domain}/cart/addons

POST /api/v2/domains/{domain}/cart/addons

Applies the addon selections represented in the request body as the cart’s desired final state. The endpoint replaces the cart’s selections with the supplied list — entries you omit are deselected, entries you include are selected (or updated). Pass selections: [] to clear all addons.

​​ Authentication

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

​​ Path Parameters

  • domain (string, required) — The merchant’s domain (e.g. staging.luma.gift).

​​ Request Body

  • selections (object[], required) — Desired final list of selected addons.
    Selection entry
    • addon_id (string, required) — The addon_id from one of cart.addons.offers.
    • selected_line_item_ids (string[]) — For PER_ITEM / FREE_GROUPING offers, the line items this addon applies to. Pass an empty array to disable the addon for all items while keeping it referenced.
    • selected_child_ids (string[]) — For offers with child_offers, the addon_ids of the chosen child entries.

​​ Response

Returns the full cart (same schema as Get Cart). The relevant areas after a successful call:

  • cart.addons.selections — the new list.
  • cart.addons.offers — possibly re-evaluated (e.g. mutually-exclusive offers in the same exclusive_group_id).
  • cart.addon_total, cart.total — recalculated.

​​ Examples


curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/addons \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"selections": [
{ "addon_id": "shipping_protection" }
]
}'

curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/addons \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"selections": [
{
"addon_id": "extended_warranty",
"selected_line_item_ids": ["761ff52b-8e6d-d373-fdf2-91a1a70df20c", "38d3f385-ea8d-8bb2-dcfc-759ac85af6ef"]
}
]
}'

curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/addons \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"selections": [
{
"addon_id": "extended_warranty",
"selected_line_item_ids": ["761ff52b-8e6d-d373-fdf2-91a1a70df20c"],
"selected_child_ids": ["33fd7d89-635d-5466-1a50-01519c4488a3"]
}
]
}'

curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/addons \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "selections": [] }'

​​ Response Example


{
"cart_id": "eac41edf-215f-cdf5-9bab-1253e3fc6390",
"platform_id": "example_commerce",
"shop_id": "staging.luma.gift",
"cart_status": "active",
"line_items": [
{
"line_item_id": "761ff52b-8e6d-d373-fdf2-91a1a70df20c",
"sku": "MH07-XS-Gray",
"quantity": 1,
"price": {
"currency": "USD",
"value": 54.0,
"number": 5400,
"symbol": "$"
},
"line_price": {
"currency": "USD",
"value": 54.0,
"number": 5400,
"symbol": "$"
},
"msrp": {
"currency": "USD",
"value": 64.8,
"number": 6480,
"symbol": "$"
},
"image": {
"url": "https://staging.luma.gift/mh07-xs-gray.jpg",
"alt": "MH07-XS-Gray",
"type": "default"
}
},
{
"line_item_id": "38d3f385-ea8d-8bb2-dcfc-759ac85af6ef",
"sku": "WS12-XS-Orange",
"quantity": 1,
"price": {
"currency": "USD",
"value": 22.0,
"number": 2200,
"symbol": "$"
},
"line_price": {
"currency": "USD",
"value": 22.0,
"number": 2200,
"symbol": "$"
},
"msrp": {
"currency": "USD",
"value": 26.4,
"number": 2640,
"symbol": "$"
},
"image": {
"url": "https://staging.luma.gift/ws12-xs-orange.jpg",
"alt": "WS12-XS-Orange",
"type": "default"
}
}
],
"addons": {
"offers": [
{
"addon_id": "shipping_protection",
"display": {
"name": "Shipping Protection"
},
"scope": "CART",
"coverage_mode": "PREDEFINED_GROUP",
"price": {
"currency": "USD",
"value": 12.99,
"number": 1299,
"symbol": "$"
}
},
{
"addon_id": "extended_warranty",
"display": {
"name": "Extended Warranty - 3 Year"
},
"scope": "ITEM",
"coverage_mode": "PER_ITEM",
"eligible_line_item_ids": [
"761ff52b-8e6d-d373-fdf2-91a1a70df20c",
"38d3f385-ea8d-8bb2-dcfc-759ac85af6ef"
],
"price": {
"currency": "USD",
"value": 5.4,
"number": 540,
"symbol": "$"
}
}
],
"selections": [
{
"addon_id": "shipping_protection",
"price": {
"currency": "USD",
"value": 12.99,
"number": 1299,
"symbol": "$"
}
}
]
},
"sub_total": {
"currency": "USD",
"value": 76.0,
"number": 7600,
"symbol": "$"
},
"fees": [
{
"description": "Recycle fee",
"currency": "USD",
"value": 2.00,
"number": 200,
"symbol": "$"
}
],
"fee_total": {
"currency": "USD",
"value": 2.00,
"number": 200,
"symbol": "$"
},
"addon_total": {
"currency": "USD",
"value": 12.99,
"number": 1299,
"symbol": "$"
},
"total": {
"currency": "USD",
"value": 90.99,
"number": 9099,
"symbol": "$"
},
"schema_version": "2.0"
}

​​ 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

Server-to-server auth was attempted but required headers are missing or malformed.


{ "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." }
404 — CartNotFound

No cart exists for this device on this domain.


{ "code": 404, "error": "CartNotFound", "description": "Cart was not found." }
412 — OperationNotSupported

The merchant’s platform does not support addon operations.


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

Request body fails schema validation. Each selections[] entry needs at minimum an addon_id string; selected_line_item_ids and selected_child_ids must be string arrays when present.


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

The body is well-formed, but the merchant rejected a value in it — typically an addon_id that does not exist on this cart, or a selection shape the merchant does not offer. Same error name as the 400 above, raised one layer deeper, so branch on code and not on error alone. description names the rejected value.


{ "code": 422, "error": "InvalidInputBody", "description": "No platform_addon_id found for addon_id: 8f3a2b1c-1d2e-4a5b-9c8d-1a2b3c4d5e6f" }
422 — UnprocessableEntity

The selections violate a constraint exposed by the merchant — e.g. an addon_id that isn’t in cart.addons.offers, a selected_line_item_id not in eligible_line_item_ids, a missing addon listed in another offer’s requires_addon_ids, or two simultaneously-selected offers sharing the same exclusive_group_id.


{ "code": 422, "error": "UnprocessableEntity", "description": "The given payload has unprocessable, invalid or non-existent data. Please check the data and try again." }
503 — StoreUnavailable

The merchant’s API returned an unexpected response and the request could not be fulfilled. Retry with backoff.


{ "code": 503, "error": "StoreUnavailable", "description": "Store is unavailable." }