Skip to the article
Aniq-UI

E-CommerceAPI reference

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:

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:

Terminal
curl http://localhost:8000/api/health
Response
{"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:

Response (400)
{
  "success": false,
  "data": null,
  "message": "<the first field's problem>",
  "errors": {
    "email": ["<the problem with email>"]
  }
}
StatusWhen
400A field failed validation, or the body holds a field the route does not accept. Unknown fields are refused, not ignored.
401The 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.
402The fitting room in a public demo, when the shopper sent no key of their own.
403The admin's roles do not grant the route's permission: "You do not have permission to perform this action".
404No such record.
409The write clashes with an existing record, such as an email, slug or SKU already in use.
429Too 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.
503A feature that is not set up: uploads without a bucket, an AI feature without its key, or the health check before the tables exist.
500An 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:

Response
{
  "success": true,
  "data": { "data": [ ], "page": 1, "limit": 15, "total": 35, "totalPages": 3 },
  "message": ""
}
ListDefault page size
Products, categories, customers, admin orders15
Staff, roles, settings, reviews, a customer's own orders10
/api/products/featured (limit)8
/api/products/:id/related (limit)4
  • order=asc or order=desc sets 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, rating or best_selling. An unknown value falls back to the default, newest-updated first with ties broken by id. order_by_featured=true puts 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, tag and on_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 message of 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.

AudienceSign inToken in the answerAccepted on
CustomerPOST /api/auth/customer/logindata.token, with data.userCustomer routes
StaffPOST /api/auth/logindata.access_token, with data.admin (roles and permissions)Admin routes
  • Both are JWTs signed with JWT_SECRET. Send them as Authorization: Bearer <token>.
  • A token lasts for JWT_EXPIRATION, 7d by default (Environment variables). The staff sign-in reports the same value as expires_in.
  • There is no refresh endpoint. When a token expires, the next request answers 401 and 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 401 with 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 429 with a Retry-After header. 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, see TRUST_PROXY (Troubleshooting).
  1. Sign in as the seeded Super Admin

    Terminal
    curl -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": "..."
    }
  2. Call a protected route with the token

    Terminal
    curl "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.

  3. Sign in as a seeded customer

    The demo store also holds customers, such as john.doe@example.com with the password password123:

    Terminal
    curl -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.token the same way, for example to GET /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.

ModulePermissions
Staffadmins.view, admins.create, admins.edit, admins.delete, admins.assign_roles
Rolesroles.view, roles.create, roles.edit, roles.delete, roles.assign_permissions
Settingssettings.view, settings.edit
Customersusers.view, users.create, users.update, users.delete, users.restore, users.verify
Categoriescategories.view, categories.create, categories.edit, categories.delete, categories.restore
Productsproducts.view, products.create, products.edit, products.delete, products.restore
Ordersorders.view, orders.edit
AI assistantai_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 roleGrants
Super AdminEvery permission
ViewerEvery permission ending in .view
Admin, Manager, EditorNone 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.

MethodPathWho can call itWhat it does
GET/api/productsAnyoneThe 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/featuredAnyoneActive featured products (limit, 8 by default).
GET/api/products/slug/:slugAnyoneOne product by its slug, with images, variants and category. An inactive or deleted product answers 404 without an admin token.
GET/api/products/:idAnyoneOne product by id, with the same 404 rule.
GET/api/products/:id/relatedAnyoneActive products related to it (limit, 4 by default); 404 for an inactive product without an admin token.
GET/api/categoriesAnyoneThe category list (search, is_active, parent_id, order).
GET/api/categories/rootsAnyoneCategories without a parent.
GET/api/categories/slug/:slugAnyoneOne category by its slug.
GET/api/categories/:idAnyoneOne category by id.
GET/api/homepage-sections/:key/productsAnyoneThe products chosen for one home page section, such as best-sellers.
GET/api/reviews/product/:productIdAnyoneA product's reviews, 10 per page.
GET/api/reviews/product/:productId/summaryAnyoneIts average rating and the count per star.
GET/api/helpers/countriesAnyoneCountries for a select box, in the request's language.

Customer sign-up and accounts

MethodPathWho can call itWhat it does
POST/api/auth/customer/registerAnyoneCreates a customer (first_name, last_name, email, password, optional phone) and signs them in. Rate-limited.
POST/api/auth/customer/loginAnyoneSigns a customer in. Rate-limited.
GET/api/auth/customer/meCustomerThe signed-in customer.
PATCH/api/auth/customer/meCustomerUpdates their details.
PATCH/api/auth/customer/me/passwordCustomerChanges their password.
POST/api/auth/customer/forgot-passwordAnyoneIssues 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-passwordAnyoneSets a new password with that token. Rate-limited.
GET/api/addressesCustomerTheir saved addresses.
GET/api/addresses/:idCustomerOne of them.
POST/api/addressesCustomerSaves an address.
PATCH/api/addresses/:idCustomerEdits it.
PATCH/api/addresses/:id/defaultCustomerMakes it the default.
DELETE/api/addresses/:idCustomerDeletes it.
POST/api/reviewsCustomerReviews a product, one review per customer per product: posting again updates it.
PATCH/api/reviews/:idCustomerEdits their review.
DELETE/api/reviews/:idCustomerDeletes 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.

MethodPathWho can call itWhat it does
GET/api/cartCustomerThe customer's cart.
POST/api/cart/itemsCustomerAdds product_id, optional variant_id, and quantity.
PATCH/api/cart/items/:idCustomerChanges a line's quantity.
DELETE/api/cart/items/:idCustomerRemoves a line.
DELETE/api/cartCustomerEmpties the cart.

Orders, checkout and tracking

MethodPathWho can call itWhat it does
POST/api/ordersAnyone; a customer token is read when sentPlaces 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/trackAnyoneAn order's progress by order_number and email together. Rate-limited.
GET/api/orders/myCustomerThe customer's orders, 10 per page.
GET/api/orders/number/:orderNumberCustomerOne of their orders by its number.
POST/api/payments/webhookStripe, signed with STRIPE_WEBHOOK_SECRETMarks 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

MethodPathWho can call itWhat it does
POST/api/auth/loginAnyoneSigns a staff member in. Rate-limited.
GET/api/auth/meAny signed-in adminThe signed-in admin, with roles and permissions.
GET/api/adminsadmins.viewThe staff list.
GET/api/admins/statisticsadmins.viewStaff counts.
GET/api/admins/:idadmins.viewOne admin.
POST/api/adminsadmins.createCreates an admin.
PATCH/api/admins/:idadmins.editEdits an admin.
PATCH/api/admins/:id/rolesadmins.assign_rolesSets an admin's roles.
DELETE/api/admins/:idadmins.deleteDeletes an admin.
PATCH/api/admins/profileadmins.editEdits the signed-in admin's own profile.
PATCH/api/admins/profile/passwordAny signed-in adminChanges the signed-in admin's own password.

Roles

MethodPathWho can call itWhat it does
GET/api/rolesroles.viewThe roles. Without page parameters, all of them.
GET/api/roles/statisticsroles.viewRole counts.
GET/api/roles/selectroles.viewRoles for a select box.
GET/api/roles/permissionsroles.viewEvery permission, grouped by module.
GET/api/roles/:idroles.viewOne role with its permissions.
POST/api/rolesroles.createCreates a role.
PUT/api/roles/:idroles.editRenames or edits a role.
POST/api/roles/:id/permissionsroles.assign_permissionsSets a role's permissions. Signed-in admins holding it get the change live.
DELETE/api/roles/:idroles.deleteDeletes 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.

MethodPathWho can call itWhat it does
GET/api/usersusers.viewThe customer list.
GET/api/users/statisticusers.viewCustomer counts.
GET/api/users/deletedusers.viewDeleted customers.
GET/api/users/deleted/:usernameusers.viewOne deleted customer.
GET/api/users/:usernameusers.viewOne customer.
POST/api/usersusers.createCreates a customer.
PATCH/api/users/:usernameusers.updateEdits a customer.
PATCH/api/users/:username/change-passwordusers.updateSets a customer's password.
POST/api/users/:username/resend-verification-emailusers.updateAnswers success and sends nothing: the template ships no mail transport. Wire your mailer here.
POST/api/users/:username/make-verifiedusers.verifyMarks the email verified.
POST/api/users/:username/make-unverifiedusers.verifyMarks it unverified.
DELETE/api/users/:usernameusers.deleteMoves a customer to the deleted list.
POST/api/users/deleted/:username/restoreusers.restoreRestores a deleted customer.

Products and categories

MethodPathWho can call itWhat it does
POST/api/productsproducts.createCreates a product with its images and variants.
PATCH/api/products/:idproducts.editEdits a product.
DELETE/api/products/:idproducts.deleteMoves it to the deleted list.
GET/api/products/deletedproducts.deleteDeleted products.
POST/api/products/deleted/:id/restoreproducts.restoreRestores one.
GET/api/products/statisticproducts.viewProduct counts.
GET/api/products/draftsAny signed-in adminThe admin's unsaved draft of the product form (product_id for an existing product).
PUT/api/products/draftsAny signed-in adminSaves that draft.
DELETE/api/products/draftsAny signed-in adminDiscards it.
POST/api/categoriescategories.createCreates a category.
PATCH/api/categories/:idcategories.editEdits a category, its sort order included.
DELETE/api/categories/:idcategories.deleteMoves it to the deleted list.
GET/api/categories/deletedcategories.deleteDeleted categories.
POST/api/categories/deleted/:id/restorecategories.restoreRestores one.
GET/api/categories/statisticcategories.viewCategory counts.
GET/api/homepage-sections/:keyproducts.viewA home page section's settings and products.
PUT/api/homepage-sections/:keyproducts.editSets them.

The section keys are style-pillars, editorial-split, new-drops, best-sellers, spotlight, lux-difference and editorial-slider.

Orders

MethodPathWho can call itWhat it does
GET/api/ordersorders.viewThe order list: search, status (one or several, comma separated), payment_status, from_date, to_date, order.
GET/api/orders/statisticorders.viewCounts per status and revenue.
GET/api/orders/:idorders.viewOne order with its items.
PATCH/api/orders/:id/statusorders.editSets 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

MethodPathWho can call itWhat it does
GET/api/settingssettings.viewThe app settings, 10 per page.
GET/api/settings/:keysettings.viewOne setting.
PATCH/api/settings/:keysettings.editChanges its value.
DELETE/api/settings/:keysettings.editDeletes it.

There is no route to create a setting: the seed creates them. Nothing in the shipped code reads their values.

Media uploads

MethodPathWho can call itWhat it does
POST/api/helpers/uploadAny signed-in adminUploads one file to the bucket.
  • Multipart form: the file in file, the folder in path (uploads by default), and optionally a size preset in for.
  • 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 answers 503: "File uploads are not set up yet."

Notifications

MethodPathWho can call itWhat it does
GET/api/notificationsAny signed-in adminThe admin's latest 30 notifications, kept for 30 days.
PATCH/api/notifications/read-allAny signed-in adminMarks them all read.
PATCH/api/notifications/:id/readAny signed-in adminMarks one read.
DELETE/api/notifications/:idAny signed-in adminRemoves one.
  • Live notifications use Socket.IO on the namespace /notifications, on the API's address without /api. Send the staff token in auth.token of the handshake; customer tokens are disconnected.
  • Each new one arrives as notification:created. The types are order_created, for every admin who can view orders, and studio_generation_completed and studio_generation_failed, for the admin who started the generation.
  • A second namespace, /auth, sends permissions-updated when 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.

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.