Skip to content

Storefront REST API

@adocommercekit/storefront-api publishes 14 host-owned endpoints under /api/commerce.

@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-api

Conventions

  • Request and response bodies are JSON.
  • Money is serialized as an object containing integer minor-unit amount, currency, and a display-oriented formatted value.
  • 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/commerce prefix.
{
  "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/:slug

Returns 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/cart

For 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/:provider

commerceRawBody 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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close