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

Set Shipping Method

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

Select a shipping method on a specific shipment. The cart returned in the response carries updated shipping cost, tax, and totals. After Set Shipping Info, each shipment is initialized with the merchant’s default method; call this endpoint when the buyer wants a different one — and, for SCHEDULED_DELIVERY shipments, to record the selected date/time slot.

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

  • shipment_id (string, required) — Target shipment identifier (from the cart’s shipments[].shipment_id).

  • shipping_method_id (string, required) — ID of the method to select. Must match an entry in the shipment’s shipping_method_options[*].id.

  • notes (string) — Optional delivery instructions for this shipment.

  • selected_date (string) — For SCHEDULED_DELIVERY shipments — the chosen date in YYYY-MM-DD format.

  • selected_time_slot (string) — For SCHEDULED_DELIVERY shipments — the chosen time-slot identifier (the slot_id from Get Availability). Send the slot_id string here; the response echoes the full slot object back on the shipment’s selected_time_slot.

​​ Response

Returns the full cart — same schema as Get Cart. The target shipment’s shipping_method is set (plus notes, and selected_date / selected_time_slot for SCHEDULED_DELIVERY); cart totals are recalculated.

  • shipments (object[]) — The cart’s shipments. Each shipment has the shape below.

    Shipment
    • shipment_id (string, required) — Unique shipment identifier.
    • platform_shipment_id (string) — Merchant-side shipment identifier, when available.
    • line_item_ids (string[], required) — IDs of the line items grouped into this shipment.
    • fulfillment_type (object, required) — The selected fulfillment type: { id, name, description }, where id is one of SHIP_TO_ADDRESS, SCHEDULED_DELIVERY, PICKUP_IN_STORE.
    • fulfillment_type_options (object[]) — Fulfillment types available for this shipment (same shape as fulfillment_type).
    • shipping_method (object) — The selected shipping method.
    shipping_method
    • id (string, required) — Method identifier.
    • description (string, required) — Method description.
    • price (object) — Cost (Amount). Required unless hide_price is true.
    • hide_price (boolean) — When true, suppress the price in the UI.
    • message (string) — Optional merchant note about this method.
    • estimated_delivery (string) — Delivery estimate.
    • shipping_method_options (object[]) — Shipping methods available for this shipment (same shape as shipping_method).
    • selected_location (object) — Pickup location — present for PICKUP_IN_STORE. { location_id, name, address, phone?, operating_hours?, distance?, pickup_options }.
    • selected_date (string) — Selected delivery date for SCHEDULED_DELIVERY (YYYY-MM-DD).
    • selected_time_slot (object) — Selected time slot for SCHEDULED_DELIVERY: { slot_id?, start_time, end_time, description?, price? }.
    • pickup_option (object) — Selected pickup option: { id, description, price? }.
    • notes (string) — Delivery notes persisted on this shipment.

  • shipping_total (object) — Total shipping cost across all shipments (Amount).

  • tax_total (object) — Total tax (Amount).

  • total (object) — Grand total (Amount).

​​ Examples


curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipment/methods \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"shipment_id": "8568658c-bee9-9666-4547-a545fe27391b",
"shipping_method_id": "express-2day",
"notes": "Please leave at front door if no answer"
}'

curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipment/methods \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"shipment_id": "840b0477-b913-191a-2b4f-dc232a779f0c",
"shipping_method_id": "white-glove-setup",
"selected_date": "2026-06-15",
"selected_time_slot": "morning-premium",
"notes": "Please call 30 minutes before delivery"
}'

​​ Response Example


{
"cart_id": "a5600193-2c13-3356-d063-711da63b7cd7",
"platform_id": "example_commerce",
"shop_id": "staging.luma.gift",
"cart_status": "active",
"line_items": [
{
"line_item_id": "c62279b9-3a72-0bbd-1377-be0033ad23dd",
"sku": "WS12-XS-Orange",
"description": "Radiant Tee",
"quantity": 2,
"price": {
"currency": "USD",
"value": 22.0,
"number": 2200,
"symbol": "$"
},
"line_price": {
"currency": "USD",
"value": 44.0,
"number": 4400,
"symbol": "$"
},
"msrp": {
"currency": "USD",
"value": 26.4,
"number": 2640,
"symbol": "$"
},
"image": {
"url": "https://staging.luma.gift/radiant-tee.jpg",
"alt": "Radiant Tee",
"type": "default"
}
}
],
"shipments": [
{
"shipment_id": "8568658c-bee9-9666-4547-a545fe27391b",
"line_item_ids": [
"c62279b9-3a72-0bbd-1377-be0033ad23dd"
],
"fulfillment_type": {
"id": "SHIP_TO_ADDRESS",
"name": "Ship to Address",
"description": "Standard shipping to your address"
},
"shipping_method_options": [
{
"id": "standard-ground",
"description": "Standard Ground Shipping",
"price": {
"currency": "USD",
"value": 9.99,
"number": 999,
"symbol": "$"
},
"estimated_delivery": "5-7 business days"
},
{
"id": "express-2day",
"description": "Express 2-Day Shipping",
"price": {
"currency": "USD",
"value": 19.99,
"number": 1999,
"symbol": "$"
},
"estimated_delivery": "2 business days"
}
],
"shipping_method": {
"id": "express-2day",
"description": "Express 2-Day Shipping",
"price": {
"currency": "USD",
"value": 19.99,
"number": 1999,
"symbol": "$"
},
"estimated_delivery": "2 business days"
},
"notes": "Please leave at front door if no answer"
}
],
"sub_total": {
"currency": "USD",
"value": 44.0,
"number": 4400,
"symbol": "$"
},
"shipping_total": {
"currency": "USD",
"value": 19.99,
"number": 1999,
"symbol": "$"
},
"tax_total": {
"currency": "USD",
"value": 4.4,
"number": 440,
"symbol": "$"
},
"fees": [
{
"description": "Recycle fee",
"currency": "USD",
"value": 2.00,
"number": 200,
"symbol": "$"
}
],
"fee_total": {
"currency": "USD",
"value": 2.00,
"number": 200,
"symbol": "$"
},
"total": {
"currency": "USD",
"value": 70.39,
"number": 7039,
"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." }
400 — InvalidShippingMethod

The supplied shipping_method_id is not in the shipment’s shipping_method_options.


{ "code": 400, "error": "InvalidShippingMethod", "description": "Invalid shipping method." }
400 — InvalidDeliveryDate

The supplied selected_date and/or selected_time_slot is not available for this shipment.


{ "code": 400, "error": "InvalidDeliveryDate", "description": "Invalid delivery date." }
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." }
404 — ShipmentNotFound

The supplied shipment_id does not match any shipment on the current cart. Refresh cart state and retry with a current id.


{ "code": 404, "error": "ShipmentNotFound", "description": "Shipment not found." }
409 — MultipleDiscountCodesNotSupported

Setting the shipping method triggered re-evaluation of cart promotions and the merchant rejected the combined discount codes currently applied.


{ "code": 409, "error": "MultipleDiscountCodesNotSupported", "description": "Multiple discount codes are not supported." }
409 — NotEnoughStockError

One or more line items no longer have enough stock to fulfill the selected method. Lower the quantity or remove the item, then retry.


{ "code": 409, "error": "NotEnoughStockError", "description": "The amount of the required item is not available in stock." }
412 — MissingShippingInfo

Shipping address is not yet set on the cart. Call Set Shipping Info first.


{ "code": 412, "error": "MissingShippingInfo", "description": "Shipping info is missing." }
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. shipment_id and shipping_method_id must be non-empty strings.


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

The request is well-formed but cannot be processed — typically the merchant API rejected the method change for a reason not covered by the more specific errors above.


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

Too many requests in a short window. Back off and retry, honoring the Retry-After header.


{ "code": 429, "error": "RateLimited", "description": "Too many requests. Please try again later." }
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." }