Set Fulfillment Type
POST https://api.firmly.work/api/v2/domains/{domain}/cart/shipments/fulfillment-type
Switch a shipment’s fulfillment type. Each shipment exposes the values you can set via its fulfillment_type_options array. The shipment is initialized with the merchant’s default after Set Shipping Info; call this endpoint only when the buyer wants a different option.
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’sshipments[].shipment_id). -
fulfillment_type(string, required) — New fulfillment method. One of:SHIP_TO_ADDRESSSCHEDULED_DELIVERYPICKUP_IN_STOREMust also be present in the target shipment’sfulfillment_type_options; otherwise the request returns400 InvalidFulfillmentType.
-
location_id(string) — Store location identifier. Required whenfulfillment_typeisPICKUP_IN_STORE.
Response
Returns the full cart — same schema as Get Cart. The target shipment’s fulfillment_type is updated (and, for PICKUP_IN_STORE, its selected_location); shipping_method_options are refreshed for the new type and 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.
fulfillment_type
id(string, required) — One ofSHIP_TO_ADDRESS,SCHEDULED_DELIVERY,PICKUP_IN_STORE.name(string, required) — Display name.description(string, required) — Description.
fulfillment_type_options(object[]) — Fulfillment types available for this shipment (same shape asfulfillment_type).shipping_method_options(object[]) — Shipping methods available for the selected fulfillment type (same shape asshipping_method).shipping_method(object) — The selected shipping method, when one is set.
shipping_method
id(string, required) — Method identifier.description(string, required) — Method description.price(object) — Cost (Amount). Required unlesshide_priceistrue.hide_price(boolean) — Whentrue, suppress the price in the UI.message(string) — Optional merchant note about this method.estimated_delivery(string) — Delivery estimate.
selected_location(object) — Pickup location — present forPICKUP_IN_STORE.
selected_location
location_id(string, required) — Location identifier.name(string, required) — Location name.address(object, required) — Location address (same fields as a shipping address).phone(string) — Location phone number.operating_hours(object[]) — Per-day hours:{ day, open_time, close_time, is_closed? }.distance(object) — Distance from the buyer:{ value, unit, formatted }.pickup_options(object[]) — Available pickup dates/slots:{ date, time_slots }.
selected_date(string) — Selected delivery date forSCHEDULED_DELIVERY(YYYY-MM-DD).selected_time_slot(object) — Selected time slot forSCHEDULED_DELIVERY:{ slot_id?, start_time, end_time, description?, price? }.pickup_option(object) — Selected pickup option:{ id, description, price? }.notes(string) — Delivery notes for 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/shipments/fulfillment-type \-H "x-firmly-authorization: YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"shipment_id": "8568658c-bee9-9666-4547-a545fe27391b","fulfillment_type": "SHIP_TO_ADDRESS"}'
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipments/fulfillment-type \-H "x-firmly-authorization: YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"shipment_id": "840b0477-b913-191a-2b4f-dc232a779f0c","fulfillment_type": "SCHEDULED_DELIVERY"}'
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipments/fulfillment-type \-H "x-firmly-authorization: YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"shipment_id": "8568658c-bee9-9666-4547-a545fe27391b","fulfillment_type": "PICKUP_IN_STORE","location_id": "store-downtown-001"}'
The response below corresponds to the Switch to Standard Shipping example above — the target shipment’s fulfillment_type is now SHIP_TO_ADDRESS.
Response Example
{"cart_id": "a5600193-2c13-3356-d063-711da63b7cd7","platform_id": "example_commerce","shop_id": "staging.luma.gift","display_name": "Luma Store","cart_status": "active","line_items": [{"line_item_id": "c62279b9-3a72-0bbd-1377-be0033ad23dd","platform_line_item_id": "0","sku": "MH07-XS-Gray","description": "Hero Hoodie","base_sku": "MH07","quantity": 2,"price": {"currency": "USD","value": 49.99,"number": 4999,"symbol": "$"},"line_price": {"currency": "USD","value": 99.98,"number": 9998,"symbol": "$"},"msrp": {"currency": "USD","value": 49.99,"number": 4999,"symbol": "$"},"image": {"url": "https://cdn.staging.luma.gift/hero-hoodie.jpg","alt": "Hero Hoodie","type": "default"},"requires_shipping": true}],"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"},"fulfillment_type_options": [{"id": "SHIP_TO_ADDRESS","name": "Ship to Address","description": "Standard shipping to your address"},{"id": "SCHEDULED_DELIVERY","name": "Scheduled Delivery","description": "Choose a delivery date and time"}],"shipping_method_options": [{"id": "STANDARD_GROUND","description": "Standard Ground","price": {"currency": "USD","value": 9.99,"number": 999,"symbol": "$"},"estimated_delivery": "5-7 business days"},{"id": "EXPRESS_2DAY","description": "2-Day Express","price": {"currency": "USD","value": 19.99,"number": 1999,"symbol": "$"},"estimated_delivery": "2 business days"}],"shipping_method": null,"notes": ""}],"sub_total": {"currency": "USD","value": 99.98,"number": 9998,"symbol": "$"},"shipping_total": {"currency": "USD","value": 0,"number": 0,"symbol": "$"},"tax_total": {"currency": "USD","value": 8.00,"number": 800,"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": 109.98,"number": 10998,"symbol": "$"},"addons": {"offers": [],"selections": []},"addon_total": {"currency": "USD","value": 0,"number": 0,"symbol": "$"},"payment_method_options": [{"type": "CreditCard","wallet": "user"},{"type": "PayPal","wallet": "paypal"}],"schema_version": "2.0"}
Multi-Shipment
Each shipment in the cart carries its own fulfillment_type. A cart with multiple shipments can mix types — for example, heavy items on SCHEDULED_DELIVERY while small items ship via SHIP_TO_ADDRESS, or items picked up at different store locations. Call this endpoint once per shipment.
Next Steps
SHIP_TO_ADDRESSorSCHEDULED_DELIVERY→ Set Shipping Method (passselected_date/selected_time_slotforSCHEDULED_DELIVERY).SCHEDULED_DELIVERY(before the above) → Get Availability to retrieve eligible dates and time slots.PICKUP_IN_STORE→ no further call required;location_idis already set in this request.
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 — InvalidFulfillmentType
The supplied fulfillment_type is not in the target shipment’s fulfillment_type_options.
{ "code": 400, "error": "InvalidFulfillmentType", "description": "Invalid fulfillment type." }
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. Re-read the cart to obtain a current id.
{ "code": 404, "error": "ShipmentNotFound", "description": "Shipment not found." }
404 — LocationNotFound
The supplied location_id is not a valid pickup location for this merchant. Returned only when fulfillment_type is PICKUP_IN_STORE.
{ "code": 404, "error": "LocationNotFound", "description": "Location not found." }
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. Ensure shipment_id is a non-empty string and fulfillment_type is one of SHIP_TO_ADDRESS, SCHEDULED_DELIVERY, PICKUP_IN_STORE.
{ "code": 400, "error": "InvalidInputBody", "description": "The body is not processable" }
422 — UnprocessableEntity
The request is well-formed but cannot be processed — typically because the merchant API rejected the fulfillment-type 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." }
501 — NotImplemented
The merchant adapter is V1 and does not support this endpoint. Use V2 endpoints, or set the fulfillment configuration via Set Shipping Method where supported.
{ "code": 501, "error": "NotImplemented", "description": "Operation not implemented." }
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." }