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

Single Product Purchase

This page is the flow-level walkthrough for Branded Commerce. For a runnable script that executes the same multi-step /api/v2 sequence end-to-end, see the Advanced Checkout Guide — the API calls are identical across all solutions; this page covers the Branded-specific framing.

​​ The five canonical phases

Every Firmly purchase moves through five phases. In Branded Commerce, the experience runs on Firmly-hosted partner-branded pages (the partner’s brand identity layered on Firmly’s commerce backend).

Phase What happens What you do
1. Hosting Firmly-hosted partner-branded pages — your domain, your brand, Firmly’s commerce backend One-time configuration with Firmly
2. Authentication A Firmly session is established POST /api/v1/browser-session (or use S2S directly)
3. Discovery Buyer clicks an ad and lands on a Firmly-hosted page for a specific product or curated landing Page-load context resolves the product/SKU; usually no live discovery/search call — the link itself names the SKU
4. Checkout Cart, address, shipping, payment, place order Five sub-calls (see below)
5. Order placement Order lands in the merchant’s existing OMS as a native order, with attribution tagged POST /payment/complete-order

​​ Sequence diagram

​​ The implementation steps (Phase 4 broken out)

​​ Ad click captured

Buyer clicks an ad and lands on a Firmly-hosted branded page (e.g., partner.example.com/holiday-deals/hoodie). The page captures attribution params (utm_source, utm_campaign, utm_content, fbclid, gclid, etc.) from the URL and persists them client-side for the session. No API call yet.

​​ Discover (Phase 3) — page-load context

For Branded Commerce, the landing URL usually names the SKU directly, so an explicit discovery/search call isn’t always required. If you do need search (e.g., a curated landing with multiple options):


curl -X POST https://api.firmly.work/api/v1/discovery/search \
-H "x-firmly-authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "hoodie", "page_size": 5}'

Response contains products[] (across all merchants your App ID can reach). Each product has a domain field identifying its merchant, plus variants[]. Each variant has an add_to_cart_ref — keep it as an opaque handle.

​​ Add to cart (Phase 4 begins)

Buyer clicks Buy Now on the branded PDP.


curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/line-items \
-H "x-firmly-authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"add_to_cart_ref": {...}, "quantity": 1}'

Cart is created implicitly. Response is the full cart object.

​​ Set shipping address (Phase 4)

Buyer fills the address on the branded checkout page.


curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipping-info \
-H "x-firmly-authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Consumer", "last_name": "Tester",
"email": "consumer@example.com", "phone": "+15551234567",
"address1": "500 Howard St", "city": "San Francisco",
"state_or_province": "CA", "country": "US", "postal_code": "94105"
}'

Response includes updated shipments[] with shipment_ids.

Optimization tip: pre-set the postal code from the buyer’s geo IP via Set Postal Code — Firmly stores it session-wide and the merchant often has shipping rates pre-warmed by the time the full address is submitted.

​​ Pick a shipping method (Phase 4)


# Read available methods from cart.shipments[].shipping_method_options (set in step 4),
# then record the choice
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipment/methods \
-H "x-firmly-authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"shipment_id": "01e232ec-6202-5cb1-25fa-6fe46cc569a9", "shipping_method_id": "method_standard"}'
# Optional: for scheduled-delivery / pickup, fetch delivery dates & time slots first
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipments/get-availability \
-H "x-firmly-authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"shipment_id": "01e232ec-6202-5cb1-25fa-6fe46cc569a9"}'

​​ (If required) Capture merchant consents

Some merchants require the buyer to agree to terms-of-sale, warranty, or delivery acknowledgments before order placement. Before complete-order:

  1. GET /api/v2/domains/{domain}/cart/consents — returns an array of required consents with id, html/text to display, required, explicit flags
  2. If the array is non-empty, surface each text on the branded checkout page, get explicit agreement, then PUT /api/v2/domains/{domain}/cart/consents with {consents: [{id, revoke: false}, ...]}

If you skip this and the merchant required consents, complete-order returns RequiredConsentsNotSigned (412). See Get Consents and Set Consents.

​​ Encrypt payment + complete order (Phase 4 ends)

Get the payment public key, JWE-encrypt the card, then submit. Branded Commerce checkout supports card via JWE encryption, Click to Pay (Visa/Mastercard), and PayPal as live payment methods; Google Pay is planned. Pick what aligns with the partner’s audience.

Payment calls go to the cc.firmly.work host, not the api.firmly.work host every prior call used — the payment surface is served from its own hostname.


curl -X POST https://cc.firmly.work/api/v2/payment/domains/staging.luma.gift/complete-order \
-H "x-firmly-authorization: $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"encrypted_card": "eyJh...",
"billing_info": { ... }
}'

Why complete-order and not place-order? The cart was built over the previous calls (add line item → shipping info → shipping method → consents), so complete-order finalizes that existing cart — the body carries only encrypted_card and billing_info (shipping was already set earlier). Use place-order instead only for the one-shot path: it creates a cart and places the order in a single call, and requires an items[] array plus shipping_info because there’s no prior cart to finalize. Verify the request body against Complete Order.

Response includes cart_status: "submitted", cart_id, and urls.order_status_page for the branded thank-you page. The merchant’s native order number is returned as platform_order_number.

Attribution note: Pass UTM params and ad-click IDs (fbclid, gclid, etc.) in the order metadata so the merchant’s downstream attribution reporting can reconcile. See Partner Onboarding → Attribution metadata fields for the fields the merchant expects.

​​ Render branded thank-you page

Firmly’s scope ends at order placement. The branded thank-you page typically:

  • Confirms the order with the merchant’s order number from platform_order_number
  • Maintains the partner’s brand identity (logo, colors, copy)
  • Links to the merchant’s own order status or support if the buyer needs post-order help

Anything post-order — tracking, returns, refunds — lives in the merchant’s native OMS. The buyer contacts the merchant directly through the merchant’s existing channels.

​​ What can go wrong at each step

Step Most common failure Recovery
Discover Empty results, StoreUnavailable, DomainNotFound Check domain spelling, confirm merchant is enabled for Branded
Add to cart ProductNotFound, NotEnoughStockError (409) Re-fetch product or surface “out of stock” on the branded page
Set shipping InvalidInputBody (400) Validate address fields client-side first
Shipping methods No methods returned Address may not be deliverable — ask buyer for an alternative
Place order CreditCardDeclined (422), NotEnoughStockError (race with another buyer) Offer alternative payment, or re-quote with new stock

See Errors & Conventions for the full catalog and the exact PascalCase error names.

​​ Attribution — keeping the partner’s identity intact

The Branded Commerce flow’s identity is the partner’s brand on the front end, the merchant’s order on the back end, and attribution data flowing both ways so reporting reconciles correctly.

Data Direction Why
UTM params (utm_source, utm_campaign, utm_content) Ad click → branded page → order metadata → merchant OMS Lets the merchant’s own attribution reports reflect partner-driven orders
Click IDs (fbclid, gclid, ttclid, etc.) Ad click → branded page → order metadata → ad platform postback Allows the ad platform to credit the conversion
Partner identifier Branded page session → Firmly’s attribution service → merchant OMS Lets the merchant see “orders from Partner X” without manual reconciliation

Pass-through is automatic when the partner page sets these on the cart session. See Partner Onboarding → Attribution metadata fields for the field mapping.

​​ What’s in a cart response

Every cart-mutation endpoint returns the full cart object in the response, so the partner’s page doesn’t have to re-fetch. The shape is identical across all Firmly solutions — see Cart Lifecycle for the full field reference.

​​ Runnable end-to-end

The Advanced Checkout Guide is the same multi-step /api/v2 sequence as a complete Node script you can run today. Start there if you want working code, return here for the conceptual walkthrough framed for Branded Commerce.