Place an order
- Credentials, one of
- Store key
- Store key and Shopper session
- Store key and Shopper ID token
- Scopes
checkout:write- Idempotency
- Send an optional
Idempotency-Keyheader. A retry with the same key does the work once.
Re-derives prices server-side, finds or creates the customer, and reserves booking slots atomically. customer.name is required.
Members-only stores (store.membership.enabled): a shopper credential is required (401 shopper_required without one) and the shopper must be an active member (403 not_a_member). On other stores a shopper credential is optional; when sent, the order is linked to that shopper (it appears in GET /v1/store/me/orders and they can read it back with their credential).
The response carries accessToken, returned only here. Keep it for the confirmation page: GET /v1/store/orders/{id} and /confirm need it as X-Order-Token (or the shopper who placed the order). The id alone reads nothing.
Coming soon (the fields are in the schema and say so). discountCode, paymentMethod (eft answers bank details), event attendees, a price guard (expectedUnitMinor), a whole-basket stock check, giftNote, marketingOptIn, saveAddress, attribution, basketId. The refusals reuse 422 validation_error with details.reason (price_changed, items_unavailable with details.unavailableItemIds, out_of_stock, discount_invalid, payment_method_unavailable); a stale delivery stays 422 delivery_changed. When sent, customer.email is checked and lowercased and customer.phone is normalised to E.164 (422 validation_error names the field); customer.name stays the only required field, because requiring more would break existing sites.
Old path, deprecated, sunset 1 Feb 2027: POST /v1/orders.
AuthorizationBearer <sez_pub_... (publishable) or sez_sk_... (secret)>A per-store key from the console. Publishable keys are browser-safe; secret keys are server-only.
Scope: checkout:write
Idempotency-Key?stringOptional. Mint it once per order or payment attempt and reuse it on every retry: the work is done once and a retry gets the first 2xx response back with Idempotent-Replay: true. Belongs to the store key, remembered 24 hours. The same key with a different method, path or body is 409 idempotency_key_reused; a request still running is 409 in_flight; a malformed key is 422 validation_error; if the key store cannot be read nothing is processed and the answer is 503 idempotency_unavailable.
^[A-Za-z0-9_-]{8,128}$application/json- body
lines*array<>fulfilment?Fulfilment"delivery""collect"shippingAddress?deliveryChoice?The delivery service picked from a quote's delivery.options. Never a price to charge: the server re-quotes the courier and charges its price now. If that service is gone, or costs more than expectedPriceMinor, the call is 422 delivery_changed and the shopper chooses again. If no courier can quote at all, the store's flat fee is charged.
customer*note?stringdiscountCode?stringComing soon. The code the shopper typed. Evaluated again on the basket the server prices, and on an order its use is claimed (so the order's Idempotency-Key also protects the redemption). A code that does not apply is dropped and the totals carry no discount; Quote.discount says why. Never a price from the client.
length <= 40paymentMethod?stringComing soon. The shopper's pick when the store offers both (store.payments.methods). Checked again against what the store has switched on. eft answers the bank details in payment and needs no POST /v1/store/orders/{id}/pay. Absent is card.
"card""eft"giftNote?stringComing soon. A note for the recipient. Kept only when store.giftNotes is true; otherwise ignored.
length <= 280marketingOptIn?booleanComing soon. The shopper agrees to marketing mail from the store. Mirrored to the customer record (and the shopper's profile when signed in).
saveAddress?booleanComing soon. A signed-in shopper: also save shippingAddress to their address book.
attribution?Where the order came from, for the merchant's campaign reports. The hosted storefront reads these from its own cookies; a site sends what it kept from the landing URL (se_c is a campaign link's id). Never trusted for price or access: an unknown campaign is dropped. Coming soon.
basketId?stringComing soon. The saved basket this order was made from (createBasket). It is marked ordered, so it is no longer chased.
The order.
application/json- response
data*The order as placed, plus its access token. accessToken is returned only here.
curl -X POST 'https://api.shopeazy.co/v1/store/orders' \ -H 'Authorization: Bearer sez_pub_...' \ -H 'Idempotency-Key: <unique_key>' \ -H 'Content-Type: application/json' \ -d '{ "lines": [ { "itemId": "65myAK8IFZKwZBG84Gmw", "quantity": 1 } ], "fulfilment": "collect", "customer": { "name": "Thandi Mokoena", "email": "thandi.mokoena@example.com", "phone": "+27 82 000 0001" }, "paymentMethod": "card", "note": "Please wrap it as a gift."}'{ "data": { "id": "riEhGrBxE2kF04RzXyaz", "number": "ORD-00001", "status": "open", "paymentStatus": "unpaid", "fulfillmentStatus": "unfulfilled", "currency": "ZAR", "totals": { "subtotal": { "amount": 32900, "currency": "ZAR" }, "discount": { "amount": 0, "currency": "ZAR" }, "shipping": { "amount": 0, "currency": "ZAR" }, "tax": { "amount": 0, "currency": "ZAR" }, "total": { "amount": 32900, "currency": "ZAR" }, "paid": { "amount": 0, "currency": "ZAR" }, "balanceDue": { "amount": 32900, "currency": "ZAR" } }, "customer": { "name": "Thandi Mokoena", "email": "thandi.mokoena@example.com", "phone": "+27 82 000 0001" }, "lines": [ { "itemId": "65myAK8IFZKwZBG84Gmw", "title": "Monstera deliciosa, 20 cm pot", "variantTitle": null, "quantity": 1, "unitPrice": { "amount": 32900, "currency": "ZAR" }, "customisations": [], "fulfillmentStatus": "unfulfilled", "booking": null } ], "createdAt": 1792924396792, "paidAt": null, "accessToken": "ord_tok_EXAMPLE" }}Server-derived totals for a cart POST
Re-derives every line from the live catalogue and computes shipping and totals. Creates nothing. Lines for unknown or inactive items are dropped silently; if none are left the call is `422`. A service that takes online bookings needs `booking.at`. `customer` and `note` are accepted and ignored here. Fulfilment resolves to `delivery` only when the cart has a shippable line, the store offers delivery and the caller did not ask for `collect`. **Delivery** is priced by the same rule as the hosted checkout: the store's flat fee (free over its threshold), or live courier rates and the merchant's own delivery zones. With a `shippingAddress`, the quote's `delivery` lists the options; send the one the shopper picks back as `deliveryChoice` on the quote and on `POST /v1/store/orders`. A delivered cart needs a deliverable address (street, suburb, city and postal code): `422 validation_error` otherwise. A changed or dearer service is `422 delivery_changed`. Old path, deprecated, sunset 1 Feb 2027: `POST /v1/checkout/quote`.
An order and its payment status GET
The id alone is not enough. Send the order's `accessToken` from `POST /v1/store/orders` as `X-Order-Token`, or the shopper who placed it. Neither: `401 order_access_required`. A credential for a different order or shopper (or an expired token): `404 not_found`, the same answer as an unknown id. Old path, deprecated, sunset 1 Feb 2027: `GET /v1/orders/{id}`.