API reference
Every route the API serves, who may call it, and how sign-in, permissions, errors and pagination work.
For the Full Stack package
Base URL and response format
Every route is served under the /api prefix. Locally the base URL is http://localhost:8000/api. On a server it is your API's address followed by /api, the same value both frontends read from NEXT_PUBLIC_API_BASE_URL (Environment variables).
The API serves two audiences that never share a token: customers, who use the storefront's routes, and staff, who use the admin dashboard's. Request bodies are JSON, except file uploads. Every answer comes in the same envelope:
{
"success": true,
"data": { },
"message": ""
}data holds the result. message is a short sentence some writes fill in, translated to the request's language, and empty otherwise.
The health check is the one route outside the envelope. It needs no token:
curl http://localhost:8000/api/health{"status":"ok"}It answers 503 with {"status":"unavailable"} while the database cannot be reached or holds no tables, so a load balancer can use it as a readiness probe.
Errors
A failed request answers with the matching HTTP status and the same envelope, with success: false, data: null, and for validation the problems per field:
{
"success": false,
"data": null,
"message": "<the first field's problem>",
"errors": {
"email": ["<the problem with email>"]
}
}| Status | When |
|---|---|
400 | A field failed validation, or the body holds a field the route does not accept. Unknown fields are refused, not ignored. |
401 | The token is missing, expired, or belongs to the other audience. Also a failed sign-in, whether the email has no account or the password is wrong. |
402 | The fitting room in a public demo, when the shopper sent no key of their own. |
403 | The admin's roles do not grant the route's permission: "You do not have permission to perform this action". |
404 | No such record. |
409 | The write clashes with an existing record, such as an email, slug or SKU already in use. |
429 | Too many attempts from one address on a sign-in, registration, password or tracking route, or too many fittings. The Retry-After header says how many seconds to wait on the sign-in routes. |
503 | A feature that is not set up: uploads without a bucket, an AI feature without its key, or the health check before the tables exist. |
500 | An unexpected failure. The message stays generic and the details go to the API's log. |
Successful writes answer 201 for POST and 200 for the other methods.
Pagination and sorting
Lists take page, from 1, and page_count, the page size. A page holds at most 100 rows: a larger size is read as 100, a smaller one as 1, and a missing or unreadable one falls back to the list's default. A list answers in one shape:
{
"success": true,
"data": { "data": [ ], "page": 1, "limit": 15, "total": 35, "totalPages": 3 },
"message": ""
}| List | Default page size |
|---|---|
| Products, categories, customers, admin orders | 15 |
| Staff, roles, settings, reviews, a customer's own orders | 10 |
/api/products/featured (limit) | 8 |
/api/products/:id/related (limit) | 4 |
order=ascororder=descsets the direction on products, categories, customers and orders. Categories default to ascending, the others to newest first.- Products also sort with
sort:created_at,updated_at,price,ratingorbest_selling. An unknown value falls back to the default, newest-updated first with ties broken byid.order_by_featured=trueputs featured and best-selling products first. - The product list filters by
search(name in either language, or SKU),category_id,is_active,is_featured,is_best_seller,min_price,max_price,tagandon_sale.
Language
Send the reader's language in the Accept-Language header: en or ar as shipped. ar-SA counts as Arabic, and any language the API does not have is answered in English. There is no language query parameter.
- Error messages and the
messageof a successful write are translated. - Text stored in both languages, such as product and category names, comes back as
{ "en": "...", "ar": "..." }and the client picks one.
Authentication
Customers and staff sign in at different endpoints, against different tables, and receive tokens only their own routes accept.
| Audience | Sign in | Token in the answer | Accepted on |
|---|---|---|---|
| Customer | POST /api/auth/customer/login | data.token, with data.user | Customer routes |
| Staff | POST /api/auth/login | data.access_token, with data.admin (roles and permissions) | Admin routes |
- Both are JWTs signed with
JWT_SECRET. Send them asAuthorization: Bearer <token>. - A token lasts for
JWT_EXPIRATION,7dby default (Environment variables). The staff sign-in reports the same value asexpires_in. - There is no refresh endpoint. When a token expires, the next request answers
401and the client signs in again. - A customer token is refused on admin routes and a staff token on customer routes, although both use the same secret.
- A failed sign-in answers
401with one message, whether the email has no account or the password is wrong. - The sign-in, registration, password and order-tracking routes accept 10 attempts a minute from one address, counted per route. Past that they answer
429with aRetry-Afterheader. The count is kept in the API's memory, so it starts again on a restart and is counted separately by each instance. Behind a proxy, seeTRUST_PROXY(Troubleshooting).
Sign in as the seeded Super Admin
Terminalcurl -X POST http://localhost:8000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"admin@example.com","password":"Admin@123"}'Response{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "Bearer", "expires_in": "7d", "admin": { "id": 1, "email": "admin@example.com", "roles": [{ "id": 1, "name": "Super Admin", "guard_name": "web" }], "permissions": ["admins.view", "admins.create", "..."] } }, "message": "..." }Call a protected route with the token
Terminalcurl "http://localhost:8000/api/orders?page=1&page_count=5" \ -H "Authorization: Bearer <access_token>"Expected result: The five newest orders, in the list shape.
Sign in as a seeded customer
The demo store also holds customers, such as
john.doe@example.comwith the passwordpassword123:Terminalcurl -X POST http://localhost:8000/api/auth/customer/login \ -H "Content-Type: application/json" \ -d '{"email":"john.doe@example.com","password":"password123"}'Then pass
data.tokenthe same way, for example toGET /api/orders/my.
Change the seeded passwords
The demo store's accounts use published passwords: Admin@123 for the Super Admin, admin123 for the other staff and password123 for the customers. Change them, or start from empty tables, before a site goes live.
Permissions
Every admin route checks the token first, then the permission it names. An admin holds every permission of every role they have; where a route names several, any one of them is enough. In the admin tables below, a permission name in the Who can call it column means an admin whose roles grant it.
| Module | Permissions |
|---|---|
| Staff | admins.view, admins.create, admins.edit, admins.delete, admins.assign_roles |
| Roles | roles.view, roles.create, roles.edit, roles.delete, roles.assign_permissions |
| Settings | settings.view, settings.edit |
| Customers | users.view, users.create, users.update, users.delete, users.restore, users.verify |
| Categories | categories.view, categories.create, categories.edit, categories.delete, categories.restore |
| Products | products.view, products.create, products.edit, products.delete, products.restore |
| Orders | orders.view, orders.edit |
| AI assistant | ai_chat.use, ai_chat.view_models |
The homepage sections and the AI studio use the products permissions. Reviews have no permission of their own: the dashboard reads them through the products.
| Seeded role | Grants |
|---|---|
| Super Admin | Every permission |
| Viewer | Every permission ending in .view |
| Admin, Manager, Editor | None until you grant them |
Running the seed again creates what is missing and leaves existing roles' permissions as you set them.
Public catalogue
No token needed. Shoppers only ever see active products: without an admin token, an inactive or deleted product does not exist.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/products | Anyone | The product list, with the filters and sorting above. Without an admin token it lists active products only, whatever is_active asks; with one, every filter works as sent, inactive products included. |
GET | /api/products/featured | Anyone | Active featured products (limit, 8 by default). |
GET | /api/products/slug/:slug | Anyone | One product by its slug, with images, variants and category. An inactive or deleted product answers 404 without an admin token. |
GET | /api/products/:id | Anyone | One product by id, with the same 404 rule. |
GET | /api/products/:id/related | Anyone | Active products related to it (limit, 4 by default); 404 for an inactive product without an admin token. |
GET | /api/categories | Anyone | The category list (search, is_active, parent_id, order). |
GET | /api/categories/roots | Anyone | Categories without a parent. |
GET | /api/categories/slug/:slug | Anyone | One category by its slug. |
GET | /api/categories/:id | Anyone | One category by id. |
GET | /api/homepage-sections/:key/products | Anyone | The products chosen for one home page section, such as best-sellers. |
GET | /api/reviews/product/:productId | Anyone | A product's reviews, 10 per page. |
GET | /api/reviews/product/:productId/summary | Anyone | Its average rating and the count per star. |
GET | /api/helpers/countries | Anyone | Countries for a select box, in the request's language. |
Customer sign-up and accounts
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/auth/customer/register | Anyone | Creates a customer (first_name, last_name, email, password, optional phone) and signs them in. Rate-limited. |
POST | /api/auth/customer/login | Anyone | Signs a customer in. Rate-limited. |
GET | /api/auth/customer/me | Customer | The signed-in customer. |
PATCH | /api/auth/customer/me | Customer | Updates their details. |
PATCH | /api/auth/customer/me/password | Customer | Changes their password. |
POST | /api/auth/customer/forgot-password | Anyone | Issues a single-use reset token, valid for one hour. Outside production it is written to the API's log; no email is sent. Rate-limited. |
POST | /api/auth/customer/reset-password | Anyone | Sets a new password with that token. Rate-limited. |
GET | /api/addresses | Customer | Their saved addresses. |
GET | /api/addresses/:id | Customer | One of them. |
POST | /api/addresses | Customer | Saves an address. |
PATCH | /api/addresses/:id | Customer | Edits it. |
PATCH | /api/addresses/:id/default | Customer | Makes it the default. |
DELETE | /api/addresses/:id | Customer | Deletes it. |
POST | /api/reviews | Customer | Reviews a product, one review per customer per product: posting again updates it. |
PATCH | /api/reviews/:id | Customer | Edits their review. |
DELETE | /api/reviews/:id | Customer | Deletes their review. |
A server-side cart is also available to signed-in customers. The shipped storefront keeps its cart in the browser and does not call it.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/cart | Customer | The customer's cart. |
POST | /api/cart/items | Customer | Adds product_id, optional variant_id, and quantity. |
PATCH | /api/cart/items/:id | Customer | Changes a line's quantity. |
DELETE | /api/cart/items/:id | Customer | Removes a line. |
DELETE | /api/cart | Customer | Empties the cart. |
Orders, checkout and tracking
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/orders | Anyone; a customer token is read when sent | Places an order. A guest sends items, email and the address; a signed-in customer may send no items to order their server-side cart. payment_method is stripe or cod (paypal is accepted but nothing processes it). Prices come from the catalogue, not the request. |
GET | /api/orders/track | Anyone | An order's progress by order_number and email together. Rate-limited. |
GET | /api/orders/my | Customer | The customer's orders, 10 per page. |
GET | /api/orders/number/:orderNumber | Customer | One of their orders by its number. |
POST | /api/payments/webhook | Stripe, signed with STRIPE_WEBHOOK_SECRET | Marks a card order paid (payment_intent.succeeded) or failed (payment_intent.payment_failed). |
With payment_method: "stripe" and Stripe configured, the answer carries a client_secret that the storefront confirms with Stripe. Shipping is free from a subtotal of 75 and 9.99 below it; tax is 0. A promo_code is stored on the order but not applied.
Staff sign-in and accounts
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/auth/login | Anyone | Signs a staff member in. Rate-limited. |
GET | /api/auth/me | Any signed-in admin | The signed-in admin, with roles and permissions. |
GET | /api/admins | admins.view | The staff list. |
GET | /api/admins/statistics | admins.view | Staff counts. |
GET | /api/admins/:id | admins.view | One admin. |
POST | /api/admins | admins.create | Creates an admin. |
PATCH | /api/admins/:id | admins.edit | Edits an admin. |
PATCH | /api/admins/:id/roles | admins.assign_roles | Sets an admin's roles. |
DELETE | /api/admins/:id | admins.delete | Deletes an admin. |
PATCH | /api/admins/profile | admins.edit | Edits the signed-in admin's own profile. |
PATCH | /api/admins/profile/password | Any signed-in admin | Changes the signed-in admin's own password. |
Roles
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/roles | roles.view | The roles. Without page parameters, all of them. |
GET | /api/roles/statistics | roles.view | Role counts. |
GET | /api/roles/select | roles.view | Roles for a select box. |
GET | /api/roles/permissions | roles.view | Every permission, grouped by module. |
GET | /api/roles/:id | roles.view | One role with its permissions. |
POST | /api/roles | roles.create | Creates a role. |
PUT | /api/roles/:id | roles.edit | Renames or edits a role. |
POST | /api/roles/:id/permissions | roles.assign_permissions | Sets a role's permissions. Signed-in admins holding it get the change live. |
DELETE | /api/roles/:id | roles.delete | Deletes a role. |
Customers
Customers are addressed by their username. The list filters by search, email, phone, country_id, username, first_name, last_name, verified, from_date and to_date.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/users | users.view | The customer list. |
GET | /api/users/statistic | users.view | Customer counts. |
GET | /api/users/deleted | users.view | Deleted customers. |
GET | /api/users/deleted/:username | users.view | One deleted customer. |
GET | /api/users/:username | users.view | One customer. |
POST | /api/users | users.create | Creates a customer. |
PATCH | /api/users/:username | users.update | Edits a customer. |
PATCH | /api/users/:username/change-password | users.update | Sets a customer's password. |
POST | /api/users/:username/resend-verification-email | users.update | Answers success and sends nothing: the template ships no mail transport. Wire your mailer here. |
POST | /api/users/:username/make-verified | users.verify | Marks the email verified. |
POST | /api/users/:username/make-unverified | users.verify | Marks it unverified. |
DELETE | /api/users/:username | users.delete | Moves a customer to the deleted list. |
POST | /api/users/deleted/:username/restore | users.restore | Restores a deleted customer. |
Products and categories
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/products | products.create | Creates a product with its images and variants. |
PATCH | /api/products/:id | products.edit | Edits a product. |
DELETE | /api/products/:id | products.delete | Moves it to the deleted list. |
GET | /api/products/deleted | products.delete | Deleted products. |
POST | /api/products/deleted/:id/restore | products.restore | Restores one. |
GET | /api/products/statistic | products.view | Product counts. |
GET | /api/products/drafts | Any signed-in admin | The admin's unsaved draft of the product form (product_id for an existing product). |
PUT | /api/products/drafts | Any signed-in admin | Saves that draft. |
DELETE | /api/products/drafts | Any signed-in admin | Discards it. |
POST | /api/categories | categories.create | Creates a category. |
PATCH | /api/categories/:id | categories.edit | Edits a category, its sort order included. |
DELETE | /api/categories/:id | categories.delete | Moves it to the deleted list. |
GET | /api/categories/deleted | categories.delete | Deleted categories. |
POST | /api/categories/deleted/:id/restore | categories.restore | Restores one. |
GET | /api/categories/statistic | categories.view | Category counts. |
GET | /api/homepage-sections/:key | products.view | A home page section's settings and products. |
PUT | /api/homepage-sections/:key | products.edit | Sets them. |
The section keys are style-pillars, editorial-split, new-drops, best-sellers, spotlight, lux-difference and editorial-slider.
Orders
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/orders | orders.view | The order list: search, status (one or several, comma separated), payment_status, from_date, to_date, order. |
GET | /api/orders/statistic | orders.view | Counts per status and revenue. |
GET | /api/orders/:id | orders.view | One order with its items. |
PATCH | /api/orders/:id/status | orders.edit | Sets the status (pending, confirmed, processing, shipped, delivered, cancelled, refunded) and the tracking number. |
The API accepts any status change; the dashboard only offers the next step or a cancellation.
Settings
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/settings | settings.view | The app settings, 10 per page. |
GET | /api/settings/:key | settings.view | One setting. |
PATCH | /api/settings/:key | settings.edit | Changes its value. |
DELETE | /api/settings/:key | settings.edit | Deletes it. |
There is no route to create a setting: the seed creates them. Nothing in the shipped code reads their values.
Media uploads
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/helpers/upload | Any signed-in admin | Uploads one file to the bucket. |
- Multipart form: the file in
file, the folder inpath(uploadsby default), and optionally a size preset infor. - Up to 150 MB. Videos (MP4, WebM, MOV, M4V) are stored as they are and answer
{ "original": url }. - Images are re-encoded to JPEG, at most 1920 pixels wide, plus the preset's size, and answer with each URL, such as
{ "original": url, "250x250": url }. - Without the five
R2_*variables it answers503: "File uploads are not set up yet."
Notifications
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/notifications | Any signed-in admin | The admin's latest 30 notifications, kept for 30 days. |
PATCH | /api/notifications/read-all | Any signed-in admin | Marks them all read. |
PATCH | /api/notifications/:id/read | Any signed-in admin | Marks one read. |
DELETE | /api/notifications/:id | Any signed-in admin | Removes one. |
- Live notifications use Socket.IO on the namespace
/notifications, on the API's address without/api. Send the staff token inauth.tokenof the handshake; customer tokens are disconnected. - Each new one arrives as
notification:created. The types areorder_created, for every admin who can view orders, andstudio_generation_completedandstudio_generation_failed, for the admin who started the generation. - A second namespace,
/auth, sendspermissions-updatedwhen an admin's roles or permissions change. - The socket accepts connections only from
FRONTEND_URL.
AI assistant
Included with your purchase. Sign in to read, or open it in your download.
The assistant's chat, model and chat-history routes.
AI studio
Included with your purchase. Sign in to read, or open it in your download.
The AI product studio's generation and media routes.
Fitting room
Included with your purchase. Sign in to read, or open it in your download.
The storefront fitting room's public routes and their limits.
Demo mode routes
Included with your purchase. Sign in to read, or open it in your download.
The route a public demo uses to give each visitor an account.