Developers
Glow API Documentation
The Glow API lets you search the catalog, retrieve rich product data, run natural-language search, read reviews, and manage a shopper's cart, discounts, checkout, and account. All endpoints return JSON.
Getting started
All requests are made over HTTPS to your deployment's domain. Read-only product endpoints (search, products, reviews) are public and require no authentication. Every other endpoint — anything that reads or changes user, cart, order, or account data �� is marked Admin token and belongs to the Admin API, authenticated with a single secret bearer token. There is no cookie or per-user session authentication on this API.
https://your-domain.com/apiAuthentication: Send the admin token on every non-public request as Authorization: Bearer $ADMIN_API_TOKEN. These endpoints identify the target user from an explicit userId parameter rather than a session, so a single trusted backend can act on behalf of any customer.
/api/products/searchSearch products
Full-text product search with filtering, sorting, and pagination. Returns a lightweight product shape plus the available filter options (skin types, concerns, and price range) for building filter UIs.
Query parameters
| Parameter | Type | Description |
|---|---|---|
q | string | Full-text search query matched against name, description, and key ingredients. |
category | string | Category slug to filter by (e.g. cleansers). |
brand | string | Brand slug to filter by. |
skinType | string[] | Repeatable. Filter by one or more skin types (e.g. skinType=oily&skinType=combination). |
concern | string[] | Repeatable. Filter by one or more skin concerns (e.g. concern=acne). |
priceMin | number | Minimum price (inclusive). |
priceMax | number | Maximum price (inclusive). |
rating | number | Minimum average rating (0-5). |
sort | string | One of price-asc, price-desc, rating, newest, bestseller. Defaults to bestseller. |
bestseller | boolean | Set to true to only return bestsellers. |
new | boolean | Set to true to only return new arrivals. |
page | number | Page number, 1-indexed. Defaults to 1. |
limit | number | Results per page. Defaults to 24. |
Example request
curl "https://your-domain.com/api/products/search?q=vitamin+c&skinType=dry&sort=rating&page=1"Example response
{
"products": [
{
"id": 12,
"name": "Radiance Vitamin C Serum",
"slug": "radiance-vitamin-c-serum",
"price": "38.00",
"salePrice": null,
"shortDescription": "Brightening serum with 15% vitamin C.",
"imageUrl": "https://.../serum.jpg",
"skinTypes": ["dry", "normal"],
"skinConcerns": ["dullness", "dark spots"],
"keyIngredients": ["Vitamin C", "Ferulic Acid"],
"routineStep": "serum",
"isBestseller": true,
"isNew": false,
"averageRating": "4.6",
"reviewCount": 42,
"brand": { "id": 3, "name": "Radiance", "slug": "radiance" },
"category": { "id": 2, "name": "Serums", "slug": "serums" }
}
],
"total": 8,
"page": 1,
"limit": 24,
"totalPages": 1,
"filters": {
"skinTypes": ["combination", "dry", "normal", "oily", "sensitive"],
"skinConcerns": ["acne", "dark spots", "dryness", "dullness"],
"priceRange": { "min": 12, "max": 120 }
}
}- The top-level filters object reflects all available options across the active catalog, not just the current result set. Use it to render filter controls.
/api/products/full-searchFull product search (detailed)
Same filtering and sorting capabilities as /products/search, but returns a fully structured product object including pricing with computed discount, ingredients, clinical studies, usage guidelines, shades, inventory, ratings, and embedded reviews. Ideal for catalog exports and rich integrations.
Query parameters
| Parameter | Type | Description |
|---|---|---|
q | string | Full-text search query. |
category | string | Category slug. |
brand | string | Brand slug. |
skinType | string[] | Repeatable skin type filter. |
concern | string[] | Repeatable skin concern filter. |
priceMin | number | Minimum price (uses sale price when present). |
priceMax | number | Maximum price (uses sale price when present). |
rating | number | Minimum average rating. |
sort | string | price-asc, price-desc, rating, newest, or bestseller (default). |
bestseller | boolean | Only bestsellers when true. |
new | boolean | Only new arrivals when true. |
page | number | Page number, 1-indexed. Defaults to 1. |
limit | number | Results per page. Max 100, defaults to 24. |
includeReviews | boolean | Set to false to omit embedded reviews. Defaults to true. |
reviewLimit | number | Number of reviews embedded per product. Defaults to 5. |
Example request
curl "https://your-domain.com/api/products/full-search?q=vitamin+c+serum&limit=1&reviewLimit=1"Example response
{
"success": true,
"data": {
"products": [
{
"id": 12,
"name": "Radiance Vitamin C Serum",
"slug": "radiance-vitamin-c-serum",
"pricing": {
"price": "38.00",
"salePrice": null,
"currency": "USD",
"discount": null
},
"description": { "full": "...", "short": "Brightening serum..." },
"images": { "main": "https://.../serum.jpg", "gallery": [] },
"brand": {
"id": 3, "name": "Radiance", "slug": "radiance",
"description": "...", "country": "France"
},
"category": { "id": 2, "name": "Serums", "slug": "serums" },
"skinProfile": {
"skinTypes": ["dry", "normal"],
"skinConcerns": ["dullness", "dark spots"]
},
"ingredients": {
"full": ["Vitamin C", "..."],
"key": ["Vitamin C", "Ferulic Acid"],
"sensitivities": ["vegan", "fragrance-free"]
},
"science": { "clinicalStudies": [] },
"guidelines": {
"usageInstructions": "...",
"whenToUse": "morning",
"whenToAvoid": null,
"routineStep": "serum"
},
"shades": { "available": false, "options": [] },
"inventory": { "inStock": true, "quantity": 120 },
"badges": { "isBestseller": true, "isNew": false },
"ratings": { "average": 4.6, "count": 42 },
"reviews": [
{
"id": 88,
"reviewer": {
"name": "Ava",
"skinType": "dry",
"skinConcerns": ["dullness"],
"ageRange": "25-34"
},
"rating": 5,
"title": "Glowing!",
"content": "...",
"pros": ["brightening"],
"cons": [],
"wouldRecommend": true,
"verifiedPurchase": true,
"helpfulCount": 12,
"createdAt": "2026-05-01T10:00:00.000Z"
}
]
}
],
"pagination": {
"page": 1,
"limit": 1,
"total": 8,
"totalPages": 8,
"hasMore": true
},
"filters": {
"query": "vitamin c serum",
"category": null,
"brand": null,
"skinTypes": [],
"skinConcerns": [],
"priceMin": null,
"priceMax": null,
"rating": null,
"sortBy": "bestseller",
"bestseller": false,
"isNew": false
}
}
}- The pricing.discount field is a computed integer percentage when a sale price is present, otherwise null.
- Set includeReviews=false for faster responses when you only need product data.
/api/products/ai-searchAI / natural-language search
Intelligent search that detects natural-language questions (e.g. "best serum for dry sensitive skin") and answers them with semantic vector search. Simple keyword queries fall back to standard full-text search automatically.
Query parameters
| Parameter | Type | Description |
|---|---|---|
qrequired | string | The search query. Natural-language questions trigger semantic search; short keywords use full-text search. |
limit | number | Maximum number of results. Defaults to 24. |
Example request
curl "https://your-domain.com/api/products/ai-search?q=what%20helps%20with%20redness%20and%20sensitive%20skin"Example response
{
"products": [
{
"id": 21,
"name": "Calm Cica Repair Cream",
"slug": "calm-cica-repair-cream",
"price": "29.00",
"salePrice": null,
"shortDescription": "Soothing barrier cream with centella.",
"imageUrl": "https://.../cica.jpg",
"skinTypes": ["sensitive"],
"skinConcerns": ["redness", "irritation"],
"keyIngredients": ["Centella Asiatica", "Panthenol"],
"routineStep": "moisturizer",
"averageRating": "4.8",
"reviewCount": 64,
"brand": { "id": 5, "name": "Calm", "slug": "calm" },
"category": { "id": 4, "name": "Moisturizers", "slug": "moisturizers" },
"similarity": 0.83
}
],
"total": 12
}- When semantic search is used, each product includes a similarity score between 0 and 1 (higher is more relevant).
- If embeddings are unavailable, semantic search returns an empty product list rather than an error.
- Requires the q parameter. Returns 400 if it is missing.
/api/products/{slug}Get a product by slug
Returns full details for a single product identified by its URL slug, including brand, category, up to 10 reviews, and up to 6 related products from the same category.
Path parameters
| Parameter | Type | Description |
|---|---|---|
slugrequired | string | The product's URL slug (e.g. radiance-vitamin-c-serum). |
Example request
curl "https://your-domain.com/api/products/radiance-vitamin-c-serum"Example response
{
"id": 12,
"name": "Radiance Vitamin C Serum",
"slug": "radiance-vitamin-c-serum",
"price": "38.00",
"salePrice": null,
"description": "...",
"shortDescription": "Brightening serum with 15% vitamin C.",
"imageUrl": "https://.../serum.jpg",
"images": ["https://.../serum-2.jpg"],
"skinTypes": ["dry", "normal"],
"skinConcerns": ["dullness", "dark spots"],
"ingredients": ["Vitamin C", "..."],
"keyIngredients": ["Vitamin C", "Ferulic Acid"],
"clinicalStudies": [],
"usageInstructions": "Apply 3-4 drops in the morning.",
"whenToUse": "morning",
"whenToAvoid": null,
"routineStep": "serum",
"hasShades": false,
"shades": null,
"stockQuantity": 120,
"averageRating": "4.6",
"reviewCount": 42,
"brand": {
"id": 3, "name": "Radiance", "slug": "radiance",
"description": "...", "country": "France"
},
"category": { "id": 2, "name": "Serums", "slug": "serums" },
"reviews": [ /* up to 10 review objects */ ],
"relatedProducts": [ /* up to 6 product summaries */ ]
}- Returns 404 with { "error": "Product not found" } when the slug does not match an active product.
/api/products/{id}/reviewsList product reviews
Returns reviews for a product along with aggregate statistics (total count, average rating, and a 5-bucket rating distribution). Supports sorting and rating filters.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | number | The numeric product ID. Non-numeric values return 400. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
sort | string | One of newest (default), oldest, highest, lowest, or helpful. |
rating | number | Filter returned reviews to a single star rating (1-5). Stats are still computed across all reviews. |
Example request
curl "https://your-domain.com/api/products/12/reviews?sort=helpful&rating=5"Example response
{
"reviews": [
{
"id": 88,
"productId": 12,
"reviewerName": "Ava",
"reviewerSkinType": "dry",
"rating": 5,
"title": "Glowing!",
"content": "...",
"helpfulCount": 12,
"verifiedPurchase": true,
"createdAt": "2026-05-01T10:00:00.000Z"
}
],
"stats": {
"total": 42,
"average": 4.6,
"distribution": [1, 2, 3, 10, 26]
}
}- distribution is an array of five counts for 1-star through 5-star reviews respectively.
- The rating filter narrows the reviews array only; stats always reflect the full review set.
Admin API
The Admin API lets a trusted backend manage the entire fleet of users — view accounts and shopping history, recommend products, and add to cart, apply discounts, and check out on any customer's behalf. Unlike the endpoints above, it is not tied to a browser session: every request is authenticated with a single secret bearer token.
Authorization: Bearer $ADMIN_API_TOKENKeep it secret. The token is read from the ADMIN_API_TOKEN environment variable and grants full access to every account. Never expose it in client-side code — call these endpoints only from a server.
Requests with a missing or incorrect token receive 401 Unauthorized.
/api/admin/usersList all users
Returns the full user fleet with per-user order counts, lifetime spend, and current cart size. Supports search and pagination.
Query parameters
| Parameter | Type | Description |
|---|---|---|
search | string | Filter by name or email (case-insensitive substring match). |
limit | number | Page size, 1–100. Defaults to 25. |
offset | number | Number of records to skip. Defaults to 0. |
Example request
curl "https://your-domain.com/api/admin/users?search=jane&limit=25" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"users": [
{
"id": "FqaorcazJ3WnhpkK8LtFkOlbAHday5Gb",
"name": "Jane Doe",
"email": "jane@example.com",
"emailVerified": true,
"createdAt": "2026-07-23T02:31:28.746Z",
"orderCount": 2,
"totalSpent": "184.20",
"cartItemCount": 1
}
],
"pagination": { "total": 1, "limit": 25, "offset": 0, "hasMore": false }
}- The user id returned here is used as the :id path parameter on every other admin user endpoint.
/api/admin/users/:idGet a user profile
Returns a single user's account details along with their current cart and an order summary (count, lifetime spend, and five most recent orders).
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | The user's id. |
Example request
curl "https://your-domain.com/api/admin/users/USER_ID" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"user": { "id": "USER_ID", "name": "Jane Doe", "email": "jane@example.com" },
"cart": [ { "product": { "id": 59, "name": "Vitamin C Serum" }, "quantity": 2, "lineTotal": 96 } ],
"orders": { "count": 2, "totalSpent": "184.20", "recent": [] }
}- Returns 404 if no user matches the id.
/api/admin/users/:id/ordersGet shopping history
Returns the user's complete order history for the given user id, newest first. Each order includes its numeric id and order number, status, full pricing breakdown, promo code, tracking number and estimated delivery, the shipping address, and every line item (product id, name, slug, shade, unit price, and quantity). The response also carries orderCount and totalSpent aggregates.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | The user's id. |
Example request
curl "https://your-domain.com/api/admin/users/USER_ID/orders" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"userId": "USER_ID",
"orderCount": 2,
"totalSpent": "184.20",
"orders": [
{
"id": 16,
"orderNumber": "GLW-MRYEL3MF-9DOJ",
"status": "processing",
"subtotal": "126.40",
"discount": "18.96",
"shipping": "0.00",
"total": "107.44",
"promoCode": "GLOW15",
"trackingNumber": "TRK1034370149",
"estimatedDelivery": "2026-07-29T21:18:17.557Z",
"createdAt": "2026-07-24T21:18:17.557Z",
"shipping_address": {
"name": "Iris Demo",
"address": "12 Herengracht",
"city": "Amsterdam",
"postalCode": "1015BR",
"country": "Netherlands"
},
"items": [
{
"productId": 59,
"productName": "Vitamin C Serum",
"productSlug": "radiance-vitamin-c-serum",
"shadeName": null,
"unitPrice": "48.00",
"quantity": 2
}
]
}
]
}- Orders are sorted newest first by creation date.
- Returns 404 if no user exists with the given id.
/api/admin/users/:id/cartView a user's cart
Returns the products currently in a user's persistent cart, with per-line totals.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | The user's id. |
Example request
curl "https://your-domain.com/api/admin/users/USER_ID/cart" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"userId": "USER_ID",
"items": [ { "product": { "id": 59, "name": "Vitamin C Serum" }, "quantity": 2, "lineTotal": 96 } ]
}/api/admin/users/:id/cartAdd an item to a user's cart
Adds a product to the user's cart on their behalf. If the same product/shade is already present, the quantity is incremented.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | The user's id. |
Request body
| Parameter | Type | Description |
|---|---|---|
productIdrequired | number | Id of the product to add. |
quantity | number | Positive integer. Defaults to 1. |
shadeName | string | Optional shade for products that offer shades. |
Example request
curl -X POST "https://your-domain.com/api/admin/users/USER_ID/cart" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"productId": 59, "quantity": 2}'Example response
{
"userId": "USER_ID",
"items": [ { "product": { "id": 59, "name": "Vitamin C Serum" }, "quantity": 2, "lineTotal": 96 } ]
}- Returns 404 if the product or user does not exist, and 409 if the product is inactive.
/api/admin/users/:id/cartClear a user's cart
Removes every item from the user's persistent cart.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | The user's id. |
Example request
curl -X DELETE "https://your-domain.com/api/admin/users/USER_ID/cart" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{ "userId": "USER_ID", "items": [] }/api/admin/users/:id/checkoutCheck out on a user's behalf
Places an order from the user's current cart. Prices, discount, and shipping are all computed server-side from trusted database values, the cart is cleared on success, and a unique shareable payment link is returned in the payment field.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | The user's id. |
Request body
| Parameter | Type | Description |
|---|---|---|
shippingNamerequired | string | Recipient name. |
shippingAddressrequired | string | Street address. |
shippingCityrequired | string | City. |
shippingPostalCoderequired | string | Postal / ZIP code. |
shippingCountryrequired | string | Country. |
promoCode | string | Optional promo code applied to the order. |
Example request
curl -X POST "https://your-domain.com/api/admin/users/USER_ID/checkout" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"shippingName": "Jane Doe", "shippingAddress": "1 Test St", "shippingCity": "London", "shippingPostalCode": "SW1A 1AA", "shippingCountry": "United Kingdom", "promoCode": "GLOW15"}'Example response
{
"userId": "USER_ID",
"orderNumber": "GLW-MRWWCSWQ-IS26",
"status": "processing",
"subtotal": "126.40",
"discount": "18.96",
"shipping": "0.00",
"total": "107.44",
"promoCode": "GLOW15",
"trackingNumber": "TRK7174354153",
"payment": {
"token": "pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"status": "pending",
"amount": "107.44",
"currency": "USD",
"expiresAt": "2026-07-30T21:18:17.557Z",
"url": "https://your-domain.com/pay/pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8"
}
}- Returns 409 if the user's cart is empty and 400 if a required shipping field is missing or the promo code is invalid.
- payment.url is a unique, shareable link the customer can open to pay for this order.
/api/admin/users/:id/recommendationsRecommend products for a user
Suggests products based on the categories the user has purchased or added to cart, excluding items they already own. Falls back to top-rated bestsellers when there is no history.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | The user's id. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit | number | Number of recommendations, 1–24. Defaults to 6. |
Example request
curl "https://your-domain.com/api/admin/users/USER_ID/recommendations?limit=6" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"userId": "USER_ID",
"reason": "Based on the categories this customer has purchased or added to cart",
"recommendations": [ { "id": 57, "name": "Squalane + Vitamin F Serum", "averageRating": "4.8" } ]
}/api/admin/productsBrowse the catalog
Searches the product catalog so you can pick items to recommend or add to a customer's cart. Wraps the same search engine used by the storefront.
Query parameters
| Parameter | Type | Description |
|---|---|---|
q | string | Full-text search query. |
category | string | Category slug filter. |
brand | string | Brand slug filter. |
limit | number | Page size, 1–100. Defaults to 24. |
page | number | 1-based page number. Defaults to 1. |
Example request
curl "https://your-domain.com/api/admin/products?q=serum&limit=24" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"products": [ { "id": 59, "name": "Vitamin C Serum", "price": "48.00" } ],
"pagination": { "total": 30, "page": 1, "limit": 24, "totalPages": 2 }
}/api/admin/discountValidate & preview a discount
Validates a promo code and, when a subtotal is supplied, returns the full pricing breakdown (discount amount, shipping, and total). Send a GET to list every available code.
Request body
| Parameter | Type | Description |
|---|---|---|
coderequired | string | The promo code to validate. |
subtotal | number | Optional cart subtotal to compute a full breakdown. |
Example request
curl -X POST "https://your-domain.com/api/admin/discount" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"code": "GLOW15", "subtotal": 142}'Example response
{
"valid": true,
"code": "GLOW15",
"discount": 0.15,
"label": "15% off",
"breakdown": { "subtotal": 142, "discountAmount": 21.3, "shipping": 0, "total": 120.7 }
}- GET /api/admin/discount returns all available codes plus the free-shipping threshold and shipping fee.
- The same validation is also available at POST /api/discount (also admin-token authenticated).
- Returns 404 when the code is unknown or expired.
/api/admin/orders/:orderNumber/paymentTrack an order's payment
Returns the current payment for an order along with its full status timeline. This is the server-side counterpart to the public pay status endpoint — use it to reconcile whether a shared payment link has been paid, failed, or refunded.
Path parameters
| Parameter | Type | Description |
|---|---|---|
orderNumberrequired | string | The order number (e.g. GLW-MRWL3QVP-WN7Y). |
Example request
curl "https://your-domain.com/api/admin/orders/GLW-MRWL3QVP-WN7Y/payment" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"orderNumber": "GLW-MRWL3QVP-WN7Y",
"userId": "USER_ID",
"payment": {
"url": "https://your-domain.com/pay/pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"token": "pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"status": "paid",
"statusLabel": "Paid",
"amount": "47.60",
"currency": "USD",
"card": { "brand": "Visa", "last4": "4242" },
"paidAt": "2026-07-24T09:12:04.220Z"
},
"events": [ { "status": "paid", "description": "Payment received via Visa ending 4242.", "createdAt": "2026-07-24T09:12:04.220Z" } ]
}- Returns 404 if the order has no payment link yet — create one with the POST endpoint below.
/api/admin/orders/:orderNumber/paymentCreate link, refund & manage payment
Creates or manages an order's payment link via an action field. 'create' returns the existing open link or mints a new unique, shareable one; 'refund' refunds a paid payment; 'mark_paid' reconciles an offline payment; 'cancel' and 'expire' void an unpaid link. Returns the updated payment and its timeline.
Path parameters
| Parameter | Type | Description |
|---|---|---|
orderNumberrequired | string | The order number to operate on. |
Request body
| Parameter | Type | Description |
|---|---|---|
action | string | One of create (default), refund, mark_paid, cancel, or expire. |
regenerate | boolean | Only with action: create. When true, voids the current open link and issues a brand-new one. |
Example request
# Refund a paid order
curl -X POST "https://your-domain.com/api/admin/orders/GLW-MRWL3QVP-WN7Y/payment" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "refund"}'
# Create (or fetch) a shareable payment link
curl -X POST "https://your-domain.com/api/admin/orders/GLW-MRWL3QVP-WN7Y/payment" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "create"}'Example response
{
"orderNumber": "GLW-MRWL3QVP-WN7Y",
"payment": {
"url": "https://your-domain.com/pay/pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"token": "pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"status": "refunded",
"statusLabel": "Refunded",
"amount": "47.60",
"currency": "USD",
"refundedAt": "2026-07-24T10:02:11.000Z"
},
"events": [ { "status": "refunded", "description": "Payment refunded to the customer.", "createdAt": "2026-07-24T10:02:11.000Z" } ]
}- action: create returns 200 with reused: true when an open link already exists, or 201 when a new link is minted.
- refund requires a paid payment (409 otherwise); mark_paid, cancel, and expire have similar state guards.
- Returns 404 if the order does not exist, or if a non-create action is used before any link has been created.
/api/admin/products/:id/imagesUpload or add a product image
Adds an image to a product. Send multipart/form-data with a file field to upload the image to Blob storage, or send JSON with a url field to attach an already-hosted image. The new image is appended to the product's gallery. Pass primary to also make it the main image; the very first image on a product with no main image becomes primary automatically.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | number | The product's numeric id. |
Request body
| Parameter | Type | Description |
|---|---|---|
file | file | multipart/form-data only. The image to upload (JPEG, PNG, WebP, AVIF, or GIF; max 8MB). |
url | string | application/json only. An already-hosted https image URL to attach instead of uploading. |
primary | boolean | When true, also sets this image as the product's main imageUrl. In multipart requests pass the string "true". |
Example request
# Upload a file to Blob storage
curl -X POST "https://your-domain.com/api/admin/products/180/images" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-F "file=@serum.jpg;type=image/jpeg" \
-F "primary=true"
# Or attach an already-hosted image
curl -X POST "https://your-domain.com/api/admin/products/180/images" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://cdn.example.com/serum.jpg", "primary": true}'Example response
{
"id": 180,
"slug": "radiance-vitamin-c-serum",
"imageUrl": "https://xxxx.public.blob.vercel-storage.com/products/180/1784863656519-abc123.jpg",
"images": [
"https://xxxx.public.blob.vercel-storage.com/products/180/1784863656519-abc123.jpg"
],
"added": "https://xxxx.public.blob.vercel-storage.com/products/180/1784863656519-abc123.jpg"
}- Returns 400 for a missing file/url, an unsupported file type, or a file over 8MB; 404 if the product does not exist.
- Uploaded files are stored in the public Blob store, so the returned URLs are directly usable in <img> tags.
/api/admin/products/:id/imagesReplace gallery or set primary image
Updates a product's image set wholesale. Provide images to replace the entire gallery and/or primary to choose the main image. When the gallery is replaced and the old primary is no longer present, the primary falls back to the first image.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | number | The product's numeric id. |
Request body
| Parameter | Type | Description |
|---|---|---|
images | string[] | Replaces the full gallery. Every entry must be a valid https URL. |
primary | string | Sets the main imageUrl. Must be one of the product's images. |
Example request
curl -X PUT "https://your-domain.com/api/admin/products/180/images" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"images": ["https://cdn.example.com/a.jpg", "https://cdn.example.com/b.jpg"],
"primary": "https://cdn.example.com/b.jpg"
}'Example response
{
"id": 180,
"slug": "radiance-vitamin-c-serum",
"imageUrl": "https://cdn.example.com/b.jpg",
"images": ["https://cdn.example.com/a.jpg", "https://cdn.example.com/b.jpg"]
}- Provide at least one of images or primary, or the request returns 400.
- primary must be one of the images in the (new) gallery, otherwise 400.
/api/admin/products/:id/imagesRemove a product image
Removes a single image from a product's gallery by URL. If the removed image was the primary, the primary falls back to the next remaining image (or null). Images stored in our Blob store are also deleted from storage.
Path parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | number | The product's numeric id. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
urlrequired | string | The exact image URL to remove from the product. |
Example request
curl -X DELETE "https://your-domain.com/api/admin/products/180/images?url=https%3A%2F%2Fcdn.example.com%2Fa.jpg" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"id": 180,
"slug": "radiance-vitamin-c-serum",
"imageUrl": "https://cdn.example.com/b.jpg",
"images": ["https://cdn.example.com/b.jpg"],
"removed": "https://cdn.example.com/a.jpg"
}- Returns 400 if the url query parameter is missing and 404 if that image is not associated with the product.
- Blob cleanup is best-effort: the product update always succeeds even if the underlying blob delete fails.
/api/cartView a cart (by userId)
Returns a user's persistent cart with full product data, per-line pricing, and a summary (quantities, subtotal, and a shipping estimate). Each line includes unitPrice (the sale price when on sale, else list price) and lineTotal. The summary mirrors how checkout prices the order.
Query parameters
| Parameter | Type | Description |
|---|---|---|
userIdrequired | string | The id of the user whose cart to load. |
Example request
curl "https://your-domain.com/api/cart?userId=USER_ID" \
-H "Authorization: Bearer $ADMIN_API_TOKEN"Example response
{
"userId": "USER_ID",
"items": [
{
"product": { "id": 12, "name": "Radiance Vitamin C Serum", "price": "38.00", "salePrice": null, "isActive": true, "brand": { "name": "Aurelia", "slug": "aurelia" } },
"quantity": 2,
"shadeName": "Warm Ivory",
"unitPrice": 38,
"lineTotal": 76
}
],
"summary": {
"itemCount": 1,
"totalQuantity": 2,
"subtotal": 76,
"shippingEstimate": 0,
"estimatedTotal": 76,
"freeShippingEligible": true,
"freeShippingRemaining": 0,
"currency": "USD"
}
}- Returns 400 if userId is missing and 404 if the user does not exist.
- An empty cart returns items: [] with a zeroed summary (freeShippingRemaining equals the free-shipping threshold).
- shippingEstimate is an estimate only; the authoritative discount and shipping are computed at checkout.
/api/cartAdd to a cart (by userId)
Adds one or more products to a user's persistent cart on their behalf. Send a single item on the body, or an items array to add several at once. By default quantities are combined with any matching line (same product + shade); pass mode: "set" to replace the quantity instead. Returns the full, updated cart with its summary.
Request body
| Parameter | Type | Description |
|---|---|---|
userIdrequired | string | The id of the user whose cart to modify. |
productId | number | ID of the product to add. Use for a single item (omit when sending items). |
quantity | number | Quantity for the single item. Must be a positive integer. Defaults to 1. |
shadeName | string | Optional shade/variant name for products that have shades. |
items | array | Batch alternative: an array of { productId, quantity?, shadeName? } objects. Takes precedence over the single-item fields. |
mode | string | "increment" (default) adds to any existing quantity; "set" replaces it with the given quantity. |
Example request
# Single item
curl -X POST "https://your-domain.com/api/cart" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId": "USER_ID", "productId": 12, "quantity": 2, "shadeName": "Warm Ivory"}'
# Batch add, replacing quantities
curl -X POST "https://your-domain.com/api/cart" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId": "USER_ID", "mode": "set", "items": [{"productId": 12, "quantity": 1}, {"productId": 34, "quantity": 3}]}'Example response
{
"userId": "USER_ID",
"items": [
{
"product": { "id": 12, "name": "Radiance Vitamin C Serum", "price": "38.00", "salePrice": null, "isActive": true, "brand": { "name": "Aurelia", "slug": "aurelia" } },
"quantity": 2,
"shadeName": "Warm Ivory",
"unitPrice": 38,
"lineTotal": 76
}
],
"summary": {
"itemCount": 1,
"totalQuantity": 2,
"subtotal": 76,
"shippingEstimate": 0,
"estimatedTotal": 76,
"freeShippingEligible": true,
"freeShippingRemaining": 0,
"currency": "USD"
}
}- Returns 404 if the user or any product does not exist, and 409 if any product is no longer available.
- Batch adds are validated atomically: if any item is invalid or unavailable, nothing is written.
- Returns 400 for an empty items array or a non-positive quantity.
/api/checkoutCheck out (by userId)
Places an order from a user's persistent cart. Line-item prices, the discount, and shipping are all computed server-side from trusted database values — the caller cannot set the order total. On success the cart is cleared and the order is returned with a tracking number and a unique, shareable payment link (payment.url) the customer can use to pay. A top-level alias of /api/admin/users/:id/checkout.
Request body
| Parameter | Type | Description |
|---|---|---|
userIdrequired | string | The id of the user to check out. |
shippingNamerequired | string | Recipient's full name. |
shippingAddressrequired | string | Street address. |
shippingCityrequired | string | City. |
shippingPostalCoderequired | string | Postal / ZIP code. |
shippingCountryrequired | string | Country. |
promoCode | string | Optional promo code to apply to the order. |
Example request
curl -X POST "https://your-domain.com/api/checkout" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "USER_ID",
"shippingName": "Jane Doe",
"shippingAddress": "10 Downing St",
"shippingCity": "London",
"shippingPostalCode": "SW1A 2AA",
"shippingCountry": "United Kingdom",
"promoCode": "GLOW15"
}'Example response
{
"userId": "USER_ID",
"orderNumber": "GLW-MRWL3QVP-WN7Y",
"status": "processing",
"subtotal": "56.00",
"discount": "8.40",
"shipping": "0.00",
"total": "47.60",
"trackingNumber": "TRK9270178675",
"estimatedDelivery": "2026-07-27T21:18:17.557Z",
"payment": {
"token": "pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"status": "pending",
"amount": "47.60",
"currency": "USD",
"expiresAt": "2026-07-30T21:18:17.557Z",
"url": "https://your-domain.com/pay/pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8"
}
}- Returns 404 if the user does not exist, 409 when the cart is empty, and 400 when a shipping field is missing or the promo code is invalid.
- payment.url is a unique, unguessable link (a 48-hex-character token) that can be shared with the customer to collect payment. See the Payment links endpoints below for the full pay and status-tracking flow.
/api/account/change-passwordSet a user's password
Sets a new password for any user. The admin token is the authority, so no current password is required. The new password is hashed with the same algorithm the sign-in flow expects.
Request body
| Parameter | Type | Description |
|---|---|---|
userIdrequired | string | The id of the user whose password to set. |
newPasswordrequired | string | The new password. Must be at least 8 characters. |
revokeSessions | boolean | When true, signs the user out of every device. Defaults to false. |
Example request
curl -X POST "https://your-domain.com/api/account/change-password" \
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId": "USER_ID", "newPassword": "new-strong-pass", "revokeSessions": true}'Example response
{
"success": true,
"userId": "USER_ID"
}- Returns 404 if the user does not exist and 400 if the new password is shorter than 8 characters.
/api/track/order/{orderNumber}Track a shipment by order number
Public, unauthenticated shipment tracking keyed by the order number printed on the customer's confirmation. Returns a customer-safe view of the package: current status, estimated delivery, destination city/country, and the full event timeline. This is the counterpart to the tracking-number lookup for customers who have their order number rather than a carrier tracking number.
Path parameters
| Parameter | Type | Description |
|---|---|---|
orderNumberrequired | string | The order number from the confirmation (e.g. GLW-MRYEL3MF-9DOJ). |
Example request
curl "https://your-domain.com/api/track/order/GLW-MRYEL3MF-9DOJ"Example response
{
"trackingNumber": "TRK1034370149",
"status": "shipped",
"statusLabel": "Shipped",
"orderNumber": "GLW-MRYEL3MF-9DOJ",
"estimatedDelivery": "2026-07-29T03:51:22.263Z",
"destination": { "city": "Amsterdam", "country": "Netherlands" },
"events": [
{
"status": "shipped",
"statusLabel": "Shipped",
"description": "Package has shipped and is on its way to the carrier network.",
"location": "Columbus, OH",
"createdAt": "2026-07-25T14:02:00.000Z"
},
{
"status": "processing",
"statusLabel": "Processing",
"description": "Order confirmed. Your package is being prepared at our fulfillment center.",
"location": "Glow Fulfillment Center, Columbus, OH",
"createdAt": "2026-07-24T03:51:22.289Z"
}
]
}- No authentication required — the order number is the key, like a carrier tracking page.
- Events are ordered oldest first. Returns 404 when no order matches the number.
/api/track/{trackingNumber}Track a shipment by tracking number
Public, unauthenticated shipment tracking keyed by the carrier tracking number. Returns the same customer-safe package view as the order-number lookup: current status, estimated delivery, destination, and event timeline.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trackingNumberrequired | string | The carrier tracking number (e.g. TRK1034370149). |
Example request
curl "https://your-domain.com/api/track/TRK1034370149"Example response
{
"trackingNumber": "TRK1034370149",
"status": "shipped",
"statusLabel": "Shipped",
"orderNumber": "GLW-MRYEL3MF-9DOJ",
"estimatedDelivery": "2026-07-29T03:51:22.263Z",
"destination": { "city": "Amsterdam", "country": "Netherlands" },
"events": [
{
"status": "processing",
"statusLabel": "Processing",
"description": "Order confirmed. Your package is being prepared at our fulfillment center.",
"location": "Glow Fulfillment Center, Columbus, OH",
"createdAt": "2026-07-24T03:51:22.289Z"
}
]
}- No authentication required — the tracking number is the key.
- Returns 404 when no package matches the tracking number.
/api/pay/{token}Get payment link status
Public, unauthenticated view of a payment link identified by its unique token. Returns the order summary, amount, current status, and the full status timeline. This is the primary endpoint for tracking whether a shared payment link has been paid. The token is unguessable, so possession of the link is the only credential required.
Path parameters
| Parameter | Type | Description |
|---|---|---|
tokenrequired | string | The payment link's unique token (the pay_… value returned by checkout). |
Example request
curl "https://your-domain.com/api/pay/pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8"Example response
{
"token": "pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"status": "paid",
"statusLabel": "Paid",
"payable": false,
"merchant": "Glow",
"orderNumber": "GLW-MRWL3QVP-WN7Y",
"amount": "47.60",
"currency": "USD",
"expiresAt": "2026-07-30T21:18:17.557Z",
"paidAt": "2026-07-24T09:12:04.220Z",
"card": { "brand": "Visa", "last4": "4242" },
"items": [ { "name": "Radiance Vitamin C Serum", "quantity": 1, "unitPrice": "38.00" } ],
"events": [
{ "status": "paid", "statusLabel": "Paid", "description": "Payment received via Visa ending 4242.", "createdAt": "2026-07-24T09:12:04.220Z" },
{ "status": "pending", "statusLabel": "Awaiting payment", "description": "Payment link created and awaiting payment.", "createdAt": "2026-07-23T21:18:17.557Z" }
]
}- status is one of pending, paid, failed, refunded, expired, or canceled.
- payable is true only while the link can still be paid (pending or failed).
- Returns 404 if the token does not resolve to a payment. An expired pending link is reported with status: expired.
/api/pay/{token}Pay a payment link
Submits a card payment against a shared payment link (mock processor). On success the link flips to paid; the response mirrors the status view. Use this to build a hosted checkout page on top of a shared link.
Path parameters
| Parameter | Type | Description |
|---|---|---|
tokenrequired | string | The payment link's unique token. |
Request body
| Parameter | Type | Description |
|---|---|---|
cardNumberrequired | string | A 12–19 digit card number (spaces allowed). A number ending in 0002 is always declined; any other valid number succeeds. |
name | string | Cardholder name (optional). |
Example request
curl -X POST "https://your-domain.com/api/pay/pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8" \
-H "Content-Type: application/json" \
-d '{"cardNumber": "4242 4242 4242 4242", "name": "Jane Doe"}'Example response
{
"success": true,
"payment": {
"token": "pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8",
"status": "paid",
"statusLabel": "Paid",
"payable": false,
"amount": "47.60",
"currency": "USD",
"card": { "brand": "Visa", "last4": "4242" },
"paidAt": "2026-07-24T09:12:04.220Z"
}
}- A declined card returns success: true with status: failed and a failureReason — the link stays payable so the customer can retry.
- Returns 400 for an invalid card number, 404 for an unknown token, and 409 if the link is already paid or is no longer payable (expired/canceled/refunded).
Error responses
Errors return an appropriate HTTP status code and a JSON body with anerror message.
{
"error": "Product not found"
}| Status | Meaning |
|---|---|
400 | Missing or invalid parameter or request body (e.g. no q on ai-search, an invalid promo code, or a too-short new password). |
401 | Authentication required — no valid session for a cart/checkout/account endpoint, or a missing/incorrect Admin API token. |
404 | The requested product does not exist, or the promo code is unknown. |
409 | Conflicting request — e.g. checking out with an empty cart or adding an unavailable product. |
500 | Unexpected server error while processing the request. |