@adocommercekit/storefront-api publishes 14 host-owned endpoints under /api/commerce. The package is optional: install it when its REST contract is a useful starting point, then change the generated controllers, validators, middleware, and routes in the host application.
pnpm add @adocommercekit/storefront-api
node ace configure @adocommercekit/storefront-apiConventions
- Request and response bodies are JSON.
- Money is serialized as an object containing integer minor-unit
amount,currency, and a display-orientedformattedvalue. - Product and cart transformers include only relationships preloaded by the controller.
- Validation failures use Adonis/VineJS error rendering.
- Commerce domain failures use the stable envelope below.
- Route paths in this page include the
/api/commerceprefix.
{
"error": {
"code": "E_INSUFFICIENT_STOCK",
"message": "Insufficient stock",
"details": {
"available": 1
}
}
}Branch on error.code, not the human-readable message. See Troubleshooting for common codes.
Endpoint summary
| Method | Path | Route name | Input |
|---|---|---|---|
GET |
/api/commerce/products |
commerce.products.index |
Query filters |
GET |
/api/commerce/products/:slug |
commerce.products.show |
Product slug |
GET |
/api/commerce/cart |
commerce.cart.show |
Cart credential |
POST |
/api/commerce/cart/lines |
commerce.cart.lines.store |
{ variantId, quantity } |
PATCH |
/api/commerce/cart/lines/:id |
commerce.cart.lines.update |
{ quantity } |
DELETE |
/api/commerce/cart/lines/:id |
commerce.cart.lines.destroy |
Line ID |
PUT |
/api/commerce/cart/email |
commerce.cart.email |
{ email } |
PUT |
/api/commerce/cart/addresses |
commerce.cart.addresses |
{ shipping?, billing? } |
GET |
/api/commerce/cart/shipping-methods |
commerce.cart.shippingMethods |
Cart credential |
PUT |
/api/commerce/cart/shipping-method |
commerce.cart.shippingMethod |
{ methodId } |
POST |
/api/commerce/checkout |
commerce.checkout.place |
Payment selection |
GET |
/api/commerce/orders/:number |
commerce.orders.show |
Guest token or authenticated user |
GET |
/api/commerce/orders/:number/payment-instructions |
commerce.orders.paymentInstructions |
Same order authorization |
POST |
/api/commerce/webhooks/payments/:provider |
commerce.webhooks.payments |
Raw provider body |
Products
List products
GET /api/commerce/products?page=1&perPage=24&categorySlug=apparel&q=shirt| Query | Default | Constraint |
|---|---|---|
page |
1 |
Positive integer |
perPage |
24 |
Integer from 1 through 100 |
categorySlug |
none | Maximum 255 characters |
q |
none | Name/slug search, maximum 255 characters |
Only active products assigned to the configured default channel are returned. The paginated response includes variants, option values, categories, ordered product images, storefront price, and availability loaded by the published controller. Each image carries url, altText, dimensions, MIME type, position, and optional variantIds; the first product-wide or matching variant image is the primary display image.
Show a product
GET /api/commerce/products/:slugReturns one active product for the default channel. A missing, inactive, or inaccessible product returns:
{
"error": {
"code": "E_PRODUCT_NOT_FOUND",
"message": "Product not found"
}
}Cart credentials
Cart endpoints can be anonymous. With the generated sessionCartToken() configuration, GET /cart creates or resolves an opaque token in the Adonis session. Browsers must send credentials; command-line clients must reuse a cookie jar:
curl -c .cart-cookie -b .cart-cookie \
http://localhost:3333/api/commerce/cartFor a stateless client, configure headerCartToken(). Read the opaque token returned by the cart transformer and send it in the configured header. A cart database ID is not a valid credential.
Every cart mutation returns the refreshed cart, including recalculated lines, adjustments, totals, and currently preloaded relationships.
Cart lines
Add a line:
{
"variantId": "5fca3a83-9235-4779-b862-0fd59d450503",
"quantity": 2
}variantId must be a UUID. quantity must be an integer from 1 through 999.
Update a line:
{
"quantity": 3
}An update quantity may be 0 through 999; zero removes the line through the service contract. DELETE /cart/lines/:id removes it explicitly.
The service locks and recalculates the cart for each mutation. Price and availability can therefore change between requests.
Email and addresses
Set email:
{
"email": "buyer@example.com"
}Set one or both addresses:
{
"shipping": {
"firstName": "Ari",
"lastName": "Stone",
"company": "Example Co",
"line1": "18 Market Street",
"line2": "Suite 4",
"city": "Portland",
"region": "OR",
"postalCode": "97205",
"countryCode": "US",
"phone": "+1 555 0100"
},
"billing": {
"firstName": "Ari",
"lastName": "Stone",
"line1": "18 Market Street",
"city": "Portland",
"region": "OR",
"postalCode": "97205",
"countryCode": "US"
}
}firstName, lastName, line1, city, and a two-letter countryCode are required on each supplied address. postalCode is required for countries listed by the published validator. The validator is host-owned; change it if the application’s address contract differs.
Shipping
GET /cart/shipping-methods evaluates eligibility and calculates a storefront price for every returned method using the current cart.
Select one with:
{
"methodId": "f8398380-d6a0-4b6a-bf06-4f36c34a3150"
}The ID must be a UUID. Selection recalculates the cart. A method can become ineligible when the address or cart contents change; list methods again after either change.
Checkout
POST /api/commerce/checkout
Content-Type: application/json
Idempotency-Key: checkout-01J...{
"payment": {
"method": "manual"
}
}Payment input:
| Field | Required | Meaning |
|---|---|---|
payment.method |
Yes | Key registered in config/commerce.ts |
payment.token |
No | Provider token, such as a Stripe PaymentMethod ID; maximum 2,000 characters |
payment.intentId |
No | Existing Adocommerce Kit payment-intent UUID for a host-defined resumed flow |
shippingQuoteToken |
No | Signed token previously selected through a dynamic shipping integration; maximum 8,000 characters |
Send a unique Idempotency-Key for every logical checkout attempt. Repeating the same key and payload returns the same completed result. Reusing a key with a different payload returns E_IDEMPOTENCY_CONFLICT.
Success sets status 201 and returns the storefront order with a seven-day accessToken for guest retrieval. Checkout can reject stale prices, stock changes, shipping ineligibility, payment failures, or a non-active cart. Refresh the cart and show the specific error rather than blindly retrying.
Synchronous providers preserve the original authorize/capture checkout. Deferred providers return an order in pending_payment; COD returns pending_cod. Payment instructions and their expiry are included in the order response. Read the Stripe SCA limitation before using it for cards that may require additional customer action.
Read an order
Guest order:
GET /api/commerce/orders/:number?token=<access-token>The signed token must identify the same internal order ID and public number. The generated checkout token expires after seven days.
An authenticated user is also authorized when the order customer has a userId equal to String(auth.user.id). Adapt the host-owned controller when the application uses another ownership model.
Missing and unauthorized orders both return 404 E_ORDER_NOT_FOUND; the endpoint does not reveal whether an order number exists.
Read payment instructions
GET /api/commerce/orders/:number/payment-instructions?token=<access-token>This endpoint repeats the exact same guest-token or authenticated-customer ownership check as the order endpoint. Cross-order tokens and unauthorized customers receive 404 E_ORDER_NOT_FOUND.
The response is a discriminated array whose type is virtual_account, qr_code, redirect, retail_outlet, or bank_transfer. Every instruction includes id and expiresAt; variant fields contain only displayable payment data. Provider raw payloads and encrypted client secrets are never transformed.
Webhooks
POST /api/commerce/webhooks/payments/:providercommerceRawBody converts Adonis’s preserved request.raw() value to UTF-8 bytes. Signature verification occurs before normalization. Verified duplicates and unhandled events return 200; signature failures return 400.
If Shield is enabled, exempt /api/commerce/webhooks/payments/* from CSRF. Do not exempt authenticated browser routes without choosing another CSRF defense. See Stripe payments and operations.
Rate limits
Generated routes apply fixed-window limits per client IP:
| Scope | Limit |
|---|---|
| Cart mutations | 120 requests per minute |
| Checkout | 10 requests per minute |
| Order reads | 60 requests per minute |
Product reads, cart reads, shipping-method reads, and webhooks do not use this generated middleware policy. Add host-appropriate protection if needed.
The bounded in-process limiter is a secure single-process default. In a multi-instance deployment, replace the published middleware with a shared limiter while keeping equivalent route policies and correctly configured proxy trust.
CSRF, CORS, and ownership
The configurator only excludes external payment webhooks from standard Shield CSRF checks. Browser cart and checkout requests remain subject to the host application’s CSRF and CORS policy.
The generated files are a starting contract with customer ownership checks and bounded rate limits enabled. Add authentication, cache headers, locale selection, observability, and response extensions directly in the application. Run commerce:upgrade after package changes; conflicts are staged under .commercekit/incoming, never overwritten.