Free shipping on orders over $50|Use code GLOW15 for 15% off your first order

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.

Base URL
https://your-domain.com/api

Authentication: 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.

GET/api/products/search

Search 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

ParameterTypeDescription
qstringFull-text search query matched against name, description, and key ingredients.
categorystringCategory slug to filter by (e.g. cleansers).
brandstringBrand slug to filter by.
skinTypestring[]Repeatable. Filter by one or more skin types (e.g. skinType=oily&skinType=combination).
concernstring[]Repeatable. Filter by one or more skin concerns (e.g. concern=acne).
priceMinnumberMinimum price (inclusive).
priceMaxnumberMaximum price (inclusive).
ratingnumberMinimum average rating (0-5).
sortstringOne of price-asc, price-desc, rating, newest, bestseller. Defaults to bestseller.
bestsellerbooleanSet to true to only return bestsellers.
newbooleanSet to true to only return new arrivals.
pagenumberPage number, 1-indexed. Defaults to 1.
limitnumberResults per page. Defaults to 24.

Example request

cURL
curl "https://your-domain.com/api/products/search?q=vitamin+c&skinType=dry&sort=rating&page=1"

Example response

200 OK · application/json
{
  "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.
GET/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

ParameterTypeDescription
slugrequiredstringThe product's URL slug (e.g. radiance-vitamin-c-serum).

Example request

cURL
curl "https://your-domain.com/api/products/radiance-vitamin-c-serum"

Example response

200 OK · application/json
{
  "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.
GET/api/products/{id}/reviews

List 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

ParameterTypeDescription
idrequirednumberThe numeric product ID. Non-numeric values return 400.

Query parameters

ParameterTypeDescription
sortstringOne of newest (default), oldest, highest, lowest, or helpful.
ratingnumberFilter returned reviews to a single star rating (1-5). Stats are still computed across all reviews.

Example request

cURL
curl "https://your-domain.com/api/products/12/reviews?sort=helpful&rating=5"

Example response

200 OK · application/json
{
  "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 header
Authorization: Bearer $ADMIN_API_TOKEN

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

GETAdmin token/api/admin/users

List all users

Returns the full user fleet with per-user order counts, lifetime spend, and current cart size. Supports search and pagination.

Query parameters

ParameterTypeDescription
searchstringFilter by name or email (case-insensitive substring match).
limitnumberPage size, 1–100. Defaults to 25.
offsetnumberNumber of records to skip. Defaults to 0.

Example request

cURL
curl "https://your-domain.com/api/admin/users?search=jane&limit=25" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "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.
GETAdmin token/api/admin/users/:id

Get 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

ParameterTypeDescription
idrequiredstringThe user's id.

Example request

cURL
curl "https://your-domain.com/api/admin/users/USER_ID" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "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.
GETAdmin token/api/admin/users/:id/orders

Get 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

ParameterTypeDescription
idrequiredstringThe user's id.

Example request

cURL
curl "https://your-domain.com/api/admin/users/USER_ID/orders" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "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.
GETAdmin token/api/admin/users/:id/cart

View a user's cart

Returns the products currently in a user's persistent cart, with per-line totals.

Path parameters

ParameterTypeDescription
idrequiredstringThe user's id.

Example request

cURL
curl "https://your-domain.com/api/admin/users/USER_ID/cart" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "userId": "USER_ID",
  "items": [ { "product": { "id": 59, "name": "Vitamin C Serum" }, "quantity": 2, "lineTotal": 96 } ]
}
POSTAdmin token/api/admin/users/:id/cart

Add 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

ParameterTypeDescription
idrequiredstringThe user's id.

Request body

ParameterTypeDescription
productIdrequirednumberId of the product to add.
quantitynumberPositive integer. Defaults to 1.
shadeNamestringOptional shade for products that offer shades.

Example request

cURL
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

201 Created · application/json
{
  "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.
DELETEAdmin token/api/admin/users/:id/cart

Clear a user's cart

Removes every item from the user's persistent cart.

Path parameters

ParameterTypeDescription
idrequiredstringThe user's id.

Example request

cURL
curl -X DELETE "https://your-domain.com/api/admin/users/USER_ID/cart" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{ "userId": "USER_ID", "items": [] }
POSTAdmin token/api/admin/users/:id/checkout

Check 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

ParameterTypeDescription
idrequiredstringThe user's id.

Request body

ParameterTypeDescription
shippingNamerequiredstringRecipient name.
shippingAddressrequiredstringStreet address.
shippingCityrequiredstringCity.
shippingPostalCoderequiredstringPostal / ZIP code.
shippingCountryrequiredstringCountry.
promoCodestringOptional promo code applied to the order.

Example request

cURL
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

201 Created · application/json
{
  "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.
GETAdmin token/api/admin/users/:id/recommendations

Recommend 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

ParameterTypeDescription
idrequiredstringThe user's id.

Query parameters

ParameterTypeDescription
limitnumberNumber of recommendations, 1–24. Defaults to 6.

Example request

cURL
curl "https://your-domain.com/api/admin/users/USER_ID/recommendations?limit=6" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "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" } ]
}
GETAdmin token/api/admin/products

Browse 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

ParameterTypeDescription
qstringFull-text search query.
categorystringCategory slug filter.
brandstringBrand slug filter.
limitnumberPage size, 1–100. Defaults to 24.
pagenumber1-based page number. Defaults to 1.

Example request

cURL
curl "https://your-domain.com/api/admin/products?q=serum&limit=24" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "products": [ { "id": 59, "name": "Vitamin C Serum", "price": "48.00" } ],
  "pagination": { "total": 30, "page": 1, "limit": 24, "totalPages": 2 }
}
POSTAdmin token/api/admin/discount

Validate & 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

ParameterTypeDescription
coderequiredstringThe promo code to validate.
subtotalnumberOptional cart subtotal to compute a full breakdown.

Example request

cURL
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

200 OK · application/json
{
  "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.
GETAdmin token/api/admin/orders/:orderNumber/payment

Track 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

ParameterTypeDescription
orderNumberrequiredstringThe order number (e.g. GLW-MRWL3QVP-WN7Y).

Example request

cURL
curl "https://your-domain.com/api/admin/orders/GLW-MRWL3QVP-WN7Y/payment" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "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.
POSTAdmin token/api/admin/orders/:orderNumber/payment

Create 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

ParameterTypeDescription
orderNumberrequiredstringThe order number to operate on.

Request body

ParameterTypeDescription
actionstringOne of create (default), refund, mark_paid, cancel, or expire.
regeneratebooleanOnly with action: create. When true, voids the current open link and issues a brand-new one.

Example request

cURL
# 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

200 OK / 201 Created · application/json
{
  "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.
GETAdmin token/api/newsletter

List newsletter subscribers

Returns newsletter subscribers with aggregate counts. Supports filtering by status and pagination. The subscribe (POST) and unsubscribe (DELETE) actions on this same path are public and documented above.

Query parameters

ParameterTypeDescription
statusstringFilter by subscribed or unsubscribed. Omit to return all.
limitnumberPage size, 1–200. Defaults to 50.
offsetnumberNumber of records to skip. Defaults to 0.

Example request

cURL
curl "https://your-domain.com/api/newsletter?status=subscribed&limit=50" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "subscribers": [
    { "id": 12, "email": "jane@example.com", "status": "subscribed", "source": "footer", "createdAt": "2026-07-24T09:12:04.220Z", "unsubscribedAt": null }
  ],
  "counts": { "total": 1, "subscribed": 1, "unsubscribed": 0 },
  "pagination": { "limit": 50, "offset": 0, "hasMore": false }
}
POSTAdmin token/api/admin/products/:id/images

Upload 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

ParameterTypeDescription
idrequirednumberThe product's numeric id.

Request body

ParameterTypeDescription
filefilemultipart/form-data only. The image to upload (JPEG, PNG, WebP, AVIF, or GIF; max 8MB).
urlstringapplication/json only. An already-hosted https image URL to attach instead of uploading.
primarybooleanWhen true, also sets this image as the product's main imageUrl. In multipart requests pass the string "true".

Example request

cURL
# 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

201 Created · application/json
{
  "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.
PUTAdmin token/api/admin/products/:id/images

Replace 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

ParameterTypeDescription
idrequirednumberThe product's numeric id.

Request body

ParameterTypeDescription
imagesstring[]Replaces the full gallery. Every entry must be a valid https URL.
primarystringSets the main imageUrl. Must be one of the product's images.

Example request

cURL
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

200 OK · application/json
{
  "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.
DELETEAdmin token/api/admin/products/:id/images

Remove 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

ParameterTypeDescription
idrequirednumberThe product's numeric id.

Query parameters

ParameterTypeDescription
urlrequiredstringThe exact image URL to remove from the product.

Example request

cURL
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

200 OK · application/json
{
  "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.
GETAdmin token/api/cart

View 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

ParameterTypeDescription
userIdrequiredstringThe id of the user whose cart to load.

Example request

cURL
curl "https://your-domain.com/api/cart?userId=USER_ID" \
  -H "Authorization: Bearer $ADMIN_API_TOKEN"

Example response

200 OK · application/json
{
  "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.
POSTAdmin token/api/cart

Add 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

ParameterTypeDescription
userIdrequiredstringThe id of the user whose cart to modify.
productIdnumberID of the product to add. Use for a single item (omit when sending items).
quantitynumberQuantity for the single item. Must be a positive integer. Defaults to 1.
shadeNamestringOptional shade/variant name for products that have shades.
itemsarrayBatch alternative: an array of { productId, quantity?, shadeName? } objects. Takes precedence over the single-item fields.
modestring"increment" (default) adds to any existing quantity; "set" replaces it with the given quantity.

Example request

cURL
# 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

201 Created · application/json
{
  "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.
POSTAdmin token/api/checkout

Check 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

ParameterTypeDescription
userIdrequiredstringThe id of the user to check out.
shippingNamerequiredstringRecipient's full name.
shippingAddressrequiredstringStreet address.
shippingCityrequiredstringCity.
shippingPostalCoderequiredstringPostal / ZIP code.
shippingCountryrequiredstringCountry.
promoCodestringOptional promo code to apply to the order.

Example request

cURL
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

201 Created · application/json
{
  "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.
POSTAdmin token/api/account/change-password

Set 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

ParameterTypeDescription
userIdrequiredstringThe id of the user whose password to set.
newPasswordrequiredstringThe new password. Must be at least 8 characters.
revokeSessionsbooleanWhen true, signs the user out of every device. Defaults to false.

Example request

cURL
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

200 OK · application/json
{
  "success": true,
  "userId": "USER_ID"
}
  • Returns 404 if the user does not exist and 400 if the new password is shorter than 8 characters.
GET/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

ParameterTypeDescription
orderNumberrequiredstringThe order number from the confirmation (e.g. GLW-MRYEL3MF-9DOJ).

Example request

cURL
curl "https://your-domain.com/api/track/order/GLW-MRYEL3MF-9DOJ"

Example response

200 OK · application/json
{
  "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.
GET/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

ParameterTypeDescription
trackingNumberrequiredstringThe carrier tracking number (e.g. TRK1034370149).

Example request

cURL
curl "https://your-domain.com/api/track/TRK1034370149"

Example response

200 OK · application/json
{
  "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.
GET/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

ParameterTypeDescription
tokenrequiredstringThe payment link's unique token (the pay_… value returned by checkout).

Example request

cURL
curl "https://your-domain.com/api/pay/pay_9f2c1b6d0e7a4c58b3a1d9e6f0c2b8a4d1e7f3c6b9a205e8"

Example response

200 OK · application/json
{
  "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.
POST/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

ParameterTypeDescription
tokenrequiredstringThe payment link's unique token.

Request body

ParameterTypeDescription
cardNumberrequiredstringA 12–19 digit card number (spaces allowed). A number ending in 0002 is always declined; any other valid number succeeds.
namestringCardholder name (optional).

Example request

cURL
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

200 OK · application/json
{
  "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).
POST/api/newsletter

Subscribe to the newsletter

Public endpoint to add an email address to the newsletter list. Idempotent: subscribing an address that is already on the list returns 200 with alreadySubscribed, and re-subscribing a previously unsubscribed address reactivates it.

Request body

ParameterTypeDescription
emailrequiredstringThe email address to subscribe. Validated for format and normalized to lowercase.
sourcestringOptional origin label for analytics (e.g. footer, checkout).

Example request

cURL
curl -X POST "https://your-domain.com/api/newsletter" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com", "source": "footer"}'

Example response

201 Created · application/json
{
  "email": "jane@example.com",
  "status": "subscribed",
  "createdAt": "2026-07-24T09:12:04.220Z"
}
  • Returns 201 for a new subscriber, 200 for an already-subscribed or reactivated address, and 400 for a missing or malformed email.
DELETE/api/newsletter

Unsubscribe from the newsletter

Public endpoint to remove an email address from the newsletter list. Idempotent and privacy-preserving: unknown or already-unsubscribed addresses still return 200, so the endpoint never reveals whether an address is on the list.

Query parameters

ParameterTypeDescription
emailrequiredstringThe email address to unsubscribe.

Example request

cURL
curl -X DELETE "https://your-domain.com/api/newsletter?email=jane@example.com"

Example response

200 OK · application/json
{
  "email": "jane@example.com",
  "status": "unsubscribed"
}
  • Returns 400 only when the email query parameter is missing or malformed.

Error responses

Errors return an appropriate HTTP status code and a JSON body with anerror message.

Error shape
{
  "error": "Product not found"
}
StatusMeaning
400Missing or invalid parameter or request body (e.g. no q on ai-search, an invalid promo code, or a too-short new password).
401Authentication required — no valid session for a cart/checkout/account endpoint, or a missing/incorrect Admin API token.
404The requested product does not exist, or the promo code is unknown.
409Conflicting request — e.g. checking out with an empty cart or adding an unavailable product.
500Unexpected server error while processing the request.