Headless API
The Headless API module opens the shop's data over REST, so the storefront does not have to be the ECMS9 theme: a Next.js or Nuxt site, a mobile app, a kiosk or another company's system can read the catalog, fill a cart, place orders and show a customer their order history.
Everything is configured in Modules → Headless API.
Where it starts
The base address is https://your-shop/api/v1. Two paths are worth opening in
a browser right away:
/api/v1/meta— shop name, locales, currency, API version and which optional features this shop exposes;/api/v1/docs/ui— the full endpoint list;/api/v1/docsis the same thing as an OpenAPI file you can feed to Postman, Insomnia or a code generator.
Paths without /v1 (/api/products, /api/cart, …) answer exactly as before,
with the same fields. Nothing has to be rewritten when the module is updated.
What the API can do
| Area | Endpoints |
|---|---|
| Catalog | products with variants, gallery, stock, rating and parcel dimensions; categories; filters; search; banners; related products and promotions |
| Cart | read, add, change quantity, remove, clear |
| Checkout | delivery methods, payment methods, the shop's custom fields, placing the order |
| Customer | registration, login, profile, order list and order details with the waybill number; bonus balance (when the system_bonus module is installed) |
| Guest orders | order status by order number + phone, and the waybill/tracking of such an order — for shops where most orders are placed without an account |
| Shop content | delivery and payment terms, contacts, published info pages and a relevance search across them — the same answers the on-site AI chat quotes |
| Delivery | Nova Poshta city and branch lookup, so a headless checkout can offer the same pickers the storefront has |
Access tokens
By default the API answers anyone, exactly like the storefront pages it mirrors. Two switches tighten that:
- Require a token for reading — the catalog is closed; only clients with a token see it;
- Require a token for writing — cart, checkout and login require a token
with the
writeright.
Create tokens in the same screen. Each token has a name and scopes: read-only by default, plus write (cart, checkout, login) and chat — the scope the on-site chat widget uses; it allows order status lookup by phone and a "talk to a human" handoff, and nothing else, so a token that ends up in the browser cannot become a write key. The token is shown once, at creation — the shop stores only its fingerprint. Give each integration its own token: you can then see how many requests it made, switch it off, or delete it without touching the others.
A token travels in a header:
Authorization: Bearer hk_xxxxxxxxxxxxxxxx
If something in front of the shop already uses Authorization (HTTP basic
auth, for example), send X-Api-Key: hk_… instead — both are accepted.
Customers
POST /api/v1/auth/login returns a customer token (hc_…) that identifies
the shopper in later requests — no cookies, which browsers block across
domains anyway. The token's lifetime is a setting (30 days by default), and
/auth/logout revokes it immediately.
The cart for an external front end
A cart normally lives in the PHP session, which a site on another domain cannot
keep. So the API also has a server-side cart: the first write with
use_key=1 returns a cart_key, and the client sends it back in the
X-Cart-Key header. Checkout accepts the same key, so the order is created
from exactly what the customer collected.
Guardrails
- Request limit — per token, or per IP address for anonymous callers
(120 per minute by default;
0turns it off). A client that exceeds it gets HTTP 429 and the time to wait. - CORS — list the domains of your front ends. Empty means "same domain only", which is what a shop without an external storefront wants.
- Stock numbers — you may expose exact stock, or only the "in stock" flag.
php spark headless:selftest calls every endpoint of the shop and prints the
result line by line — the fastest way to see that the API works on this site
after switching the module on.