Cart sessions
Create and own a Throttle cart directly from the browser — no backend required. Cart sessions are the front door for JAMstack / static storefronts that don't run a server with a secret key.
CartSessionClient in @usethrottle/cart wraps every endpoint below — create/resume a session, then addItem, selectShipping, applyDiscount, checkout. In React, the useCartSession hook in @usethrottle/checkout-react adds reactive cart state + automatic localStorage persistence. The raw HTTP API is documented here for reference.How it works
Server-side integrations build carts with a secret sk_ key. A frontend-only storefront can't hold a secret key, so it uses a cart session instead:
- You create a cart session with your publishable quote token (
pk_…— the same token used for shipping/tax quotes) from an allowed origin. - Throttle creates the cart and returns an opaque
cart_…session id. That id is a bearer capability scoped to exactly one cart — hold it client-side (e.g. inlocalStorage) and use it to add, update, and remove items. - Because the cart lives in Throttle from the first add-to-cart, you get abandoned-cart recovery and a clean hand-off to checkout for free.
cart_… id + origin. No secret key is ever exposed to the browser, and a session can only ever touch its own cart.Set your allowed origins first
Cart-session requests are rejected unless the request Origin is on the application's allowlist (the same allowlist used by embedded checkout and quotes). Set it via PUT /api/v1/embed-config.
Create a cart session
curl -X POST https://api.usethrottle.dev/api/v1/cart-sessions \
-H "Origin: https://shop.example.com" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "7f9d4c8a-5b2e-4f16-9a73-2d1e5c8b6f40",
"environmentId": "a1b2c3d4-...",
"quoteToken": "pk_live_...",
"currency": "USD"
}'Returns cartSessionId (the cart_… token), expiresAt, and a cart snapshot.
Build the cart
# Add an item
curl -X POST https://api.usethrottle.dev/api/v1/cart-sessions/cart_<id>/items \
-H "Origin: https://shop.example.com" \
-H "Content-Type: application/json" \
-d '{ "name": "Premium Widget", "unitPrice": 2999, "quantity": 2 }'
# Update an item
curl -X PATCH https://api.usethrottle.dev/api/v1/cart-sessions/cart_<id>/items/<itemId> \
-H "Origin: https://shop.example.com" -H "Content-Type: application/json" \
-d '{ "quantity": 3 }'
# Remove an item
curl -X DELETE https://api.usethrottle.dev/api/v1/cart-sessions/cart_<id>/items/<itemId> \
-H "Origin: https://shop.example.com"
# Read the current cart
curl https://api.usethrottle.dev/api/v1/cart-sessions/cart_<id> \
-H "Origin: https://shop.example.com"Shipping & discounts
Fetch live rates from POST /api/v1/shipping-tax/quotes (same publishable token), then select a method on the session. Apply a promo code the same way.
# Select a shipping method
curl -X POST https://api.usethrottle.dev/api/v1/cart-sessions/cart_<id>/shipping \
-H "Origin: https://shop.example.com" -H "Content-Type: application/json" \
-d '{ "methodId": "fedex_ground", "displayName": "FedEx Ground", "rateAmount": 599 }'
# Apply a discount code
curl -X POST https://api.usethrottle.dev/api/v1/cart-sessions/cart_<id>/discount \
-H "Origin: https://shop.example.com" -H "Content-Type: application/json" \
-d '{ "code": "SAVE10" }'Hand off to checkout
When the buyer is ready, convert the cart session into a checkout session and redirect them to the returned checkoutUrl. This marks the cart session converted (no further edits).
curl -X POST https://api.usethrottle.dev/api/v1/cart-sessions/cart_<id>/checkout-session \
-H "Origin: https://shop.example.com" -H "Content-Type: application/json" \
-d '{
"returnUrl": "https://shop.example.com/thanks",
"cancelUrl": "https://shop.example.com/cart"
}'One hook for the whole flow: useThrottleCheckout
useThrottleCheckout (in @usethrottle/checkout-react) wraps all of the above: it binds totals and selectedMethod straight to the cart (no shadow copy to drift), locks shipping in one call via selectMethod, exposes a status state machine, and auto-recovers from a stale cart — if the cart was converted by a completed order or abandoned, it rebuilds a fresh session from the last-known line items and retries (status === 'recovering'). createSession returns the checkoutSessionId for <PaymentEmbed>.
import { useThrottleCheckout, PaymentEmbed } from '@usethrottle/checkout-react';
function Checkout() {
const {
addItem, selectMethod, // one-call select — returns the recomputed cart
totals, selectedMethod, // bound to the cart; never a stale copy
status, // 'idle' | 'loading' | 'recovering' | 'ready' | 'error'
createSession, // → { checkoutSessionId } for <PaymentEmbed>
} = useThrottleCheckout({ applicationId, environmentId, quoteToken });
const start = async () => {
const { checkoutSessionId } = await createSession({
returnUrl: 'https://shop.example.com/thanks',
cancelUrl: 'https://shop.example.com/cart',
});
setSessionId(checkoutSessionId);
};
return sessionId
? <PaymentEmbed sessionId={sessionId} parentOrigin={location.origin} />
: <button onClick={start} disabled={status === 'loading'}>Pay {totals?.total}</button>;
}Endpoints
POST /api/v1/cart-sessions— create a cart + session.GET /api/v1/cart-sessions/{id}— read the session + cart.POST /api/v1/cart-sessions/{id}/items— add an item.PATCH /api/v1/cart-sessions/{id}/items/{itemId}— update an item.DELETE /api/v1/cart-sessions/{id}/items/{itemId}— remove an item.POST /api/v1/cart-sessions/{id}/shipping— select a shipping method.DELETE /api/v1/cart-sessions/{id}/shipping— clear the selected method.POST /api/v1/cart-sessions/{id}/discount— apply a discount code.DELETE /api/v1/cart-sessions/{id}/discount— remove the discount.POST /api/v1/cart-sessions/{id}/checkout-session— hand off to checkout.
invalid_quote_token (401) — bad token, app/environment, or origin. origin_not_allowed (403) — origin not on the allowlist. cart_session_expired (410) — session is no longer active. item_not_found (404) — the item isn't in this session's cart. rate_limit_exceeded (429) — too many POST /cart-sessions creates from one client; back off and retry.