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) — Theaddon_idfrom one ofcart.addons.offers.selected_line_item_ids(string[]) — ForPER_ITEM/FREE_GROUPINGoffers, 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 withchild_offers, theaddon_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 sameexclusive_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." }