Start PayPal Express Checkout
POST https://api.firmly.work/api/v1/domains/{domain}/express/paypal/start
Overview
Opens a PayPal Express Checkout session against the merchant’s own PayPal account. Returns the cart enriched with a payment_method object carrying the PayPal EC token — the value the buyer’s PayPal approval is tied to, and the value you pass back to Authorize.
Availability and the host this endpoint runs on are covered in the PayPal Express Checkout overview.
Authentication
x-firmly-authorization(string, required) — Device access token from Browser Session
Path Parameters
domain(string, required) — The merchant domain (e.g., “staging.luma.gift”)
Prerequisites
- PayPal must be enabled for this merchant — see Checking availability. A merchant without PayPal returns
404 GatewayNotFound. - A cart must already exist for
{domain}on this device session (created via Browser Session → Add Line Item) and hold at least one line item. A missing cart returns404 CartNotFound. - Shipping info should be set (Set Shipping Info) so the cart total is final before the PayPal session is created. The EC token is bound to the amount at the time it is created, so changing the total afterwards can cause the merchant to reject the order at complete-order.
Request Body
attributes(object) — Optional gateway-specific attributes. Send an empty object, or omit the body entirely, for standard PayPal express checkout.
Response
Returns the cart, enriched with the PayPal session details.
-
cart_id(string) — Unique identifier for the cart -
line_items(array) — Array of items in the cart -
payment_method(object) — PayPal session detailsPayment Method
payment_type(string) — The gateway label; case varies by merchantattributes(object) — PayPal session values
Attributes
paypal_token(string | null) — The PayPal EC token (e.g.,EC-XXXXXXXXXXXXXXXXX), and the value you carry into Authorize and Complete Order. It can benullwhen no PayPal session could be opened with the merchant — the call still returns200, so check the field before continuing (see below).url(string) — The PayPal approval URL for the token. Present on most merchants; treat as optional.sandbox(boolean) — Whether the merchant’s PayPal account is in sandbox mode. Present on most merchants; treat as optional.requires_review(boolean) — Merchant-specific; present only on some adapters.
-
shop_properties(object) — Merchant-level configuration.shop_properties.paypalcarries the PayPal client credentials your surface needs to render the PayPal button —clientId, and where the merchant’s integration supplies them,merchantId,sandbox,intentandintegration_version. See Get Cart for the full field list. -
payment_method_options(array) — Payment methods this merchant supports -
shipping_info(object) — Delivery address details -
shipping_method(object) — Selected shipping method with pricing -
total/sub_total/tax/shipping_total(object) — Cart totals
Code Example
const response = await fetch('https://api.firmly.work/api/v1/domains/staging.luma.gift/express/paypal/start',{method: 'POST',headers: {'x-firmly-authorization': authToken,'Content-Type': 'application/json'},body: JSON.stringify({})});const cart = await response.json();// The EC token is the value the buyer's PayPal approval binds toconst paypalToken = cart.payment_method.attributes.paypal_token;if (!paypalToken) {// PayPal is not usable on this merchant right now — offer another methodthrow new Error('No PayPal token returned for this merchant');}// Client credentials for the PayPal JS SDK live on the cart, not in your configconst { clientId, sandbox } = cart.shop_properties.paypal;
import requestsresponse = requests.post('https://api.firmly.work/api/v1/domains/staging.luma.gift/express/paypal/start',headers={'x-firmly-authorization': auth_token,'Content-Type': 'application/json'},json={})cart = response.json()paypal_token = cart['payment_method']['attributes']['paypal_token']client_id = cart['shop_properties']['paypal']['clientId']
curl -X POST https://api.firmly.work/api/v1/domains/staging.luma.gift/express/paypal/start \-H "x-firmly-authorization: YOUR_AUTH_TOKEN" \-H "Content-Type: application/json" \-d '{}'
Response Example
{"cart_id": "a8e61139-3045-470f-b211-90820e862335","line_items": [{"line_item_id": "d2d196b2-733c-457a-a7c9-001b68517807","sku": "MT12","base_sku": "MT12","quantity": 1,"requires_shipping": true,"description": "Cassius Sparring Tank","price": { "currency": "USD", "value": 18, "number": 1800, "symbol": "$" },"line_price": { "currency": "USD", "value": 18, "number": 1800, "symbol": "$" },"msrp": { "currency": "USD", "value": 21.6, "number": 2160, "symbol": "$" },"image": {"url": "https://staging.luma.gift/cassius-sparring-tank.jpg","alt": "Cassius Sparring Tank","type": "default"}}],"payment_method": {"payment_type": "paypal","attributes": {"paypal_token": "EC-XXXXXXXXXXXXXXXXX","url": "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-XXXXXXXXXXXXXXXXX","sandbox": false}},"payment_method_options": [{ "type": "CreditCard", "wallet": "user" },{ "type": "PayPal", "wallet": "paypal" }],"shop_properties": {"paypal": {"clientId": "YOUR_MERCHANT_PAYPAL_CLIENT_ID","sandbox": false,"intent": "order","integration_version": "v2"},"order_status_supported": true},"shipping_info": {"first_name": "John","last_name": "Smith","email": "john@example.com","phone": "2065551212","address1": "123 Main St","city": "Seattle","state_or_province": "WA","state_name": "Washington","country": "US","postal_code": "98101"},"shipping_method": {"sku": "standard_shipping","description": "Standard Shipping","price": { "currency": "USD", "value": 10, "number": 1000, "symbol": "$" }},"sub_total": { "currency": "USD", "value": 18, "number": 1800, "symbol": "$" },"shipping_total": { "currency": "USD", "value": 10, "number": 1000, "symbol": "$" },"tax": { "currency": "USD", "value": 2, "number": 200, "symbol": "$" },"total": { "currency": "USD", "value": 30, "number": 3000, "symbol": "$" },"cart_status": "active","display_name": "Luma (Staging)","shop_id": "staging.luma.gift"}
Next Steps
start does not charge anything. Between this call and Authorize, the buyer approves the payment in PayPal’s own window — you either load the PayPal JS SDK with the clientId from shop_properties.paypal and let it drive the approval, or send the buyer to the url returned above. PayPal returns a payer ID on approval. That payer ID plus the paypal_token are what Authorize needs.
Related Endpoints
- Authorize — Confirm the buyer’s PayPal approval
- Complete Order — Place the order
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 — InvalidInputBody
The request body is present but not a JSON object.
{ "code": 400, "error": "InvalidInputBody", "description": "The body is not processable" }
400 — ErrorStartExpressCheckout
Firmly could not open a PayPal session — either PayPal rejected the request or the merchant adapter failed to translate it.
{ "code": 400, "error": "ErrorStartExpressCheckout", "description": "Could not start checkout." }
400 — InvalidToken
The x-firmly-authorization header is missing, empty, or not a well-formed JWT. Device-auth can also surface as MissingAuthHeader or PartnerNotFound; see Authentication errors for the full set.
{ "code": 400, "error": "InvalidToken", "description": "Jwt token is invalid." }
401 — InvalidJWTToken
The device JWT is well-formed but its signature does not verify, or required claims are missing.
{ "code": 401, "error": "InvalidJWTToken", "description": "Jwt token is invalid." }
404 — CartNotFound
No active cart exists for this device on this domain.
{ "code": 404, "error": "CartNotFound", "description": "Cart was 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 — GatewayNotFound
PayPal is not enabled as a payment gateway for this merchant. Check payment_method_options before offering it.
{ "code": 404, "error": "GatewayNotFound", "description": "Payment gateway 'paypal' not found" }
412 — OperationNotSupported
The merchant adapter does not implement PayPal express checkout.
{ "code": 412, "error": "OperationNotSupported", "description": "This operation is not supported for this store." }
503 — StoreUnavailable
The merchant’s own API was unavailable or timed out. Retry with backoff.
{ "code": 503, "error": "StoreUnavailable", "description": "Store temporarily unavailable. Please try again later." }
429 — RateLimited
Too many requests from this device. Back off and retry after the Retry-After window — see Rate Limits.
{ "code": 429, "error": "RateLimited", "description": "Too many requests. Please try again later." }