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

Set Shipping Info

POST https://api.firmly.work/api/v2/domains/{domain}/cart/shipping-info

​​ Overview

Sets buyer shipping information for cart delivery. The endpoint validates the address, calculates shipping costs based on available shipping methods, and updates cart totals. It supports multi-shipment scenarios where different items may ship from different locations.

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

  • first_name (string, required) — Buyer’s first name

  • last_name (string, required) — Buyer’s last name

  • address1 (string, required) — Primary address line (street address)

  • address2 (string) — Secondary address line (apartment, suite, unit, etc.)

  • city (string, required) — City name

  • state_or_province (string, required) — State or province code (e.g., “CA”, “NY”) or full name

  • postal_code (string, required) — ZIP or postal code

  • country (string, required) — Country code in ISO 3166-1 alpha-2 format (e.g., “US”, “CA”)

  • phone (string, required) — Contact phone number for delivery

  • email (string, required) — Contact email address

​​ Response

Returns a complete ShoppingCart object with:

  • Updated shipping information
  • Calculated shipping costs per shipment
  • Updated tax calculations based on shipping address
  • Recalculated cart totals

​​ Multi-Shipment Behavior

The shipping address applies to all shipments in the cart. Key behaviors:

  1. Address Application: The provided address is used for all shipments
  2. Shipping Calculation: Each shipment’s shipping cost is calculated independently
  3. Tax: The merchant’s platform recalculates tax for the shipping destination and returns it in the cart
  4. Validation: Address is validated against merchant’s supported regions

​​ Code Examples


curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipping-info \
-H "x-firmly-authorization: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Smith",
"address1": "123 Main Street",
"address2": "Apt 4B",
"city": "Anytown",
"state_or_province": "CA",
"postal_code": "12345",
"country": "US",
"phone": "555-123-4567",
"email": "john.smith@staging.luma.gift"
}'

const shippingInfo = {
first_name: "John",
last_name: "Smith",
address1: "123 Main Street",
address2: "Apt 4B",
city: "Anytown",
state_or_province: "CA",
postal_code: "12345",
country: "US",
phone: "555-123-4567",
email: "john.smith@staging.luma.gift"
};
const response = await fetch('https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipping-info', {
method: 'POST',
headers: {
'x-firmly-authorization': 'YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify(shippingInfo)
});
const updatedCart = await response.json();

import requests
shipping_info = {
"first_name": "John",
"last_name": "Smith",
"address1": "123 Main Street",
"address2": "Apt 4B",
"city": "Anytown",
"state_or_province": "CA",
"postal_code": "12345",
"country": "US",
"phone": "555-123-4567",
"email": "john.smith@staging.luma.gift"
}
response = requests.post(
'https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipping-info',
headers={
'x-firmly-authorization': 'YOUR_TOKEN',
'Content-Type': 'application/json'
},
json=shipping_info
)
updated_cart = response.json()

$shippingInfo = [
'first_name' => 'John',
'last_name' => 'Smith',
'address1' => '123 Main Street',
'address2' => 'Apt 4B',
'city' => 'Anytown',
'state_or_province' => 'CA',
'postal_code' => '12345',
'country' => 'US',
'phone' => '555-123-4567',
'email' => 'john.smith@staging.luma.gift'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipping-info');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'x-firmly-authorization: YOUR_TOKEN',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($shippingInfo));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$updatedCart = json_decode($response, true);
curl_close($ch);

​​ Response Example


{
"line_items": [
{
"line_item_id": "a02a3e67-b50e-8dcd-5970-8fff52117e43",
"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": [
"a02a3e67-b50e-8dcd-5970-8fff52117e43"
],
"fulfillment_type": {
"id": "SHIP_TO_ADDRESS",
"name": "Ship to Address",
"description": "Standard shipping to your address"
},
"shipping_method": {
"id": "STANDARD_GROUND",
"description": "Standard Ground (5-7 business days)",
"price": {
"currency": "USD",
"value": 9.99,
"number": 999,
"symbol": "$"
},
"estimated_delivery": "5-7 business days"
}
}
],
"shipping_info": {
"first_name": "John",
"last_name": "Smith",
"address1": "123 Main Street",
"address2": "Apt 4B",
"city": "Anytown",
"state_or_province": "CA",
"postal_code": "12345",
"country": "US",
"phone": "555-123-4567",
"email": "john.smith@staging.luma.gift"
},
"sub_total": {
"currency": "USD",
"value": 44.0,
"number": 4400,
"symbol": "$"
},
"shipping_total": {
"currency": "USD",
"value": 9.99,
"number": 999,
"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": 60.39,
"number": 6039,
"symbol": "$"
},
"schema_version": "2.0"
}

After setting shipping information, typical next steps include:

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

Address fields fail merchant-level validation (e.g. PO Box where prohibited, line length exceeded, format rejected by the merchant).


{ "code": 400, "error": "InvalidShippingInfo", "description": "Invalid shipping info." }
400 — InvalidState

The state_or_province value is not valid for the given country (e.g. "ZZ" for country: "US").


{ "code": 400, "error": "InvalidState", "description": "Invalid state." }
400 — ErrorInvalidEmail

The email field is not a syntactically valid email address.


{ "code": 400, "error": "ErrorInvalidEmail", "description": "Invalid email." }
400 — ShippingNotNeeded

The cart contains only items that do not require shipping (e.g. digital-only). Skip this endpoint.


{ "code": 400, "error": "ShippingNotNeeded", "description": "Shipping is not needed." }
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. Add an item first via Add Line Item.


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

No shipping method is available for the supplied address — usually because the merchant does not ship to that region or the cart contents are restricted there.


{ "code": 409, "error": "ShipmentNotAvailable", "description": "Shipment is not available." }
409 — ShippingAddressNotSupported

The supplied address is rejected by the merchant for reasons beyond country/state validation (e.g. APO/FPO, US territories, embargoed regions). Retry with a different address.


{ "code": 409, "error": "ShippingAddressNotSupported", "description": "Shipping address is not supported." }
409 — MultipleDiscountCodesNotSupported

Setting shipping triggered a re-evaluation of cart promotions and the merchant rejected the combination of currently-applied discount codes. Remove one or more promos and retry.


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

One or more line items no longer have enough stock after the shipping update. 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 — CountryNotSupported

The merchant does not ship to the supplied country. Use one of the merchant’s supported countries.


{ "code": 412, "error": "CountryNotSupported", "description": "Country is not supported." }
412 — ProductNotSupported

One or more items in the cart cannot be shipped to the supplied address (e.g. age-restricted, hazardous, region-locked).


{ "code": 412, "error": "ProductNotSupported", "description": "Product is not supported." }
412 — NoLineItemError

The cart is empty after the merchant call — typically the merchant rejected the cart contents on shipping update. Refresh cart state.


{ "code": 412, "error": "NoLineItemError", "description": "No line item." }
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 all required fields (first_name, last_name, address1, city, state_or_province, postal_code, country, phone, email) are present and well-formed.


{ "code": 400, "error": "InvalidInputBody", "description": "The body is not processable" }
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." }