Storefront API

Run your own website or app on a FrontDesk merchant's data: their catalogue, events, forms and checkout. You build the frontend. FrontDesk stays the source of truth and handles the money. Base URL https://api.frontdesk.africa/v1. Also available as llms.txt for AI agents, openapi.json for client generation, and lovable.txt to paste into an AI app builder.

Authentication

Send the key as Authorization: Bearer <key>. There are two kinds and the difference is the whole design.

fd_pk_…publishable

Reads, plus one write: registering a guest for a free event. Safe to ship in browser code. Locked to the exact origins the merchant registered. An empty origin list refuses every call. That is deliberate, not a bug.

fd_sk_…secret

Can open checkouts. Server side only. Anyone who can view source can otherwise take payments as that merchant.

Keys carry their environment. fd_pk_test_… works against staging, fd_pk_live_… against production, and a key from the wrong one is refused. You never choose which workspace you are reading. The key decides.

Errors

Success returns the resource itself. Errors look like this:

{ "error": { "code": "...", "message": "...", "retryable": false, "requestId": "..." } }
CodeMeaning
UNAUTHORIZEDMissing, unknown, revoked or wrong-environment key.
ORIGIN_NOT_ALLOWEDPublishable key used from an origin the merchant has not registered. That includes no origin at all.
INSUFFICIENT_SCOPEA publishable key tried to do something only a secret key may.
KEY_PAUSEDA real key the merchant has paused. Ask them to resume it. The value is still good.
STORE_API_SECRET_DISABLEDSecret keys are switched off for this workspace. The merchant can turn them on under Developers.
STORE_API_DISABLEDThe whole workspace is switched off.
TIER_REQUIRES_PAYMENT402. A free registration asked for a tier that costs money. Open a checkout for it from your server instead.
TIER_SOLD_OUT409. The tier has no capacity left. details.remaining says how many are.
RATE_LIMITED429. Honour Retry-After.

Rendering a product page

GET /store/products/:slug returns everything a full product page needs. Every block below is optional: render it only when its field is present, and a bare product should show none of this chrome.

Product info — the info object. highlights is the checkmark list under the price. Build a Details table from specs plus brand, modelName, manufacturer, countryOfOrigin and sku. sustainabilityText and careInstructions are their own accordions; longDescription is the prose block and descriptionBlocks renders after it, in order.

Variation groups — the options array, present when the merchant sells along several axes (Size × Colour). Render one picker per group, drawn per that group's own display: dropdown a select, text chips, color swatches from the value's swatchHex, image tiles from the value's media[0]. The buyer picks one value per group, and the chosen set resolves to the one variant whose optionValueRefs holds exactly those refs — send that variant's ref to checkout, unchanged. Dim a value whose every remaining variant is sold out or unpriced in the buyer's currency, and swap the gallery to a value's media when it is picked. When options is absent, use the flat variant picker below.

Variant picker (no options) — switch the UI on variantDisplay: text renders name chips, dropdown renders a single select (for long or numerous names), image renders square swatches from each variant's media[0] (selecting one swaps the gallery to that variant's media), color renders circles from swatchHex, falling back to a text chip when it is null. A sold-out variant stays visible and selectable, just not addable.

The call to action, in this order:

StateRender
In stockAdd to cart, quantity capped on the variant's availableQty (null = no cap).
preorderableA pre-order CTA replaces the sold-out state. Cap quantity on preorderRemaining. Show preorderEtaText only when present — never hardcode shipping copy. A set preorderDepositType reads "Pay N% now"; the exact charge is struck server-side on the order total.
saveForItSaveableOffer the savings-plan path.
OtherwiseSold out; offer a restock alert when restockAlertsEnabled is true.

Reviews render only when reviews is present and reviewsEnabled is true — no stars otherwise. Stale carts: recheck on return with GET /store/availability?variantRefs=…; a ref missing from the map is gone (drop the line), a ref listed in preorderable is still buyable at 0 stock (cap on preorderRemaining). On cards, PublicProductSummary carries the pill states (preorderable, saveForItSaveable), the sale badge (compareAtMinorFrom, showSaleBadge) and the star line (ratingAvg/ratingCount).

Taking payment

You never touch card details. Your server opens a checkout with the secret key and an Idempotency-Key, you redirect the buyer to the hostedUrl you get back, and they pay on FrontDesk. They return to your returnUrl with ?checkout=<ref>.

Never treat the return redirect as proof of payment. It is a browser navigation and can be lost, replayed or forged. Confirm with GET /store/checkouts/:ref, or wait for the checkout.completed webhook.

Registering for a free event

A free RSVP needs no server. POST /store/events/:slug/register is the one write a publishable key may make, so your page calls it straight from the browser.

Read the event first: GET /store/events/:slug lists the tiers, and a tier whose kind is free is what you offer. Each tier carries formFields (the questions to ask, keyed by key), groupSize, minPerOrder/maxPerOrder, requiresAttendeeDetails and soldOut; the event carries minAge and dobMode. Then send tickets (the same shape as checkout, with positional attendees to name each seat), contact (a name and an email and/or phone) and responses keyed by each field's key. A plus-one is quantity 2 with the guest named in the second attendees entry.

You get back the guest's reference and status. Show them and tell the guest to check their email: FrontDesk sends the confirmation, the calendar invite and the ticket PDF itself.

Free tiers only. A paid tier answers 402 TIER_REQUIRES_PAYMENT: open a checkout for it from your server. Duplicates are quiet: an email that already holds a seat gets the same 201 as a first registration and its confirmation again (at most every 10 minutes), never a second seat, so a retry is always safe and no Idempotency-Key is needed. To read registrations back, subscribe to the attendee.registered webhook.

Webhooks

The merchant registers up to five destinations, each with its own signing secret and its own event subscriptions, in their FrontDesk portal under Developers. Every delivery is signed:

X-FD-Signature: sha256=HMAC_SHA256(secret, timestamp + "." + rawBody)
X-FD-Timestamp: <unix seconds>

Verify against the raw body with THAT destination's secret, compare in constant time, and reject a timestamp far from now. Deliveries carry X-FD-Webhook-Id and the body's id field — store it with a unique constraint so a retried delivery is processed once. A ping event may arrive when the merchant tests the endpoint; acknowledge it with a 2xx. Payloads carry refs only. Re-fetch through the API rather than trusting figures in the event.

EventWhenData
checkout.completedA hosted checkout was paid.checkoutRef, orderRef
checkout.expiredA checkout link lapsed unpaid.checkoutRef
order.updatedAn order's fulfilment status changed.orderRef, status, fulfillmentStatus
form.submittedA form submission was recorded.submissionRef, formSlug, formRef
attendee.registeredA guest registered for a free event: through your own page, the hosted storefront, or the public RSVP link. Never fires for a test key.attendeeRef, eventRef, eventSlug, ticketTypeRef, status, attendeeCount, groupRef, source
customer.linkedA buyer account was linked to one of your contacts.customerRef, contactRef
product.deletedA product was deleted from the store. Remove it from any local copy.productRef, slug
order.deletedAn order was removed. Today: sandbox retention hard-deleting test orders after 90 days.orderRef, test, reason

Sandbox (test mode)

A checkout opened with a test key runs in test mode: the hosted page shows the real rails wired to the provider sandbox, and paying with a provider test card runs the whole loop. The order it creates is real and fetchable, marked test everywhere — it moves no money, credits no wallet, holds no stock or seats, issues no tickets, and never appears in the merchant's live views or sales figures. Rails without a sandbox (bank transfer, Flutterwave) are not offered in test mode. Test orders are retained for 90 days, then hard-deleted.

Supabase backend

If your stack is Supabase you do not have to build the server side yourself. The merchant connects their own Supabase project under Developers → Supabase, and FrontDesk installs Edge Functions holding a dedicated scoped secret key (server-side only — your frontend never sees a key at all), a signed webhook receiver, and a frontdesk schema with realtime checkout rows behind default-deny RLS. Your frontend calls those functions instead of this API directly.

Since v2 the schema also mirrors the catalogue (frontdesk.products) and the merchant's own orders (frontdesk.orders, with a test flag): webhooks keep the mirrors fresh, a scheduled frontdesk-sync function pulls the delta reads for truth, and product.deleted / order.deleted events remove rows. Nothing money-shaped is ever mirrored.

One Supabase project per workspace: installing a second workspace into the same project overwrites the first one's secrets. Disconnecting revokes the key and FrontDesk's access; the tables, functions and data in the project stay exactly as they are.

The credentials rule holds inside your own code too: the browser uses fd_pk_… from an allowed origin; anything server-side (Node, Deno, an Edge Function) uses fd_sk_…. A publishable key called from a server carries no browser origin and is refused.

Storefront

The whole storefront: brand, theme, sections, pages, and the embedded event, collection and product lists

One call is enough to render a home page. Events, collections and products also ride inside this, so you can render everything from one response.

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Returns Storefront

GET/store/storefront
curl https://api.frontdesk.africa/v1/store/storefront \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/storefront', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "type": "string",
  "displayName": "Matte Lipstick",
  "timezone": "Africa/Lagos",
  "description": "A long-wear matte lipstick.",
  "categorySlug": "string",
  "categorySlugs": [
    "string"
  ],
  "website": "string",
  "socials": {},
  "city": "Lagos",
  "state": "Lagos",
  "country": "Nigeria",
  "avatarUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
  "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
  "badges": [
    {
      "type": "string",
      "grantedAt": "2026-08-18T09:20:11.000Z"
    }
  ],
  "memberCount": 12,
  "renderConfig": {
    "templateId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
    "theme": {
      "fontPairing": "string",
      "palette": "string",
      "brandColor": "string",
      "accentSource": "brand",
      "spacing": "compact",
      "contentWidth": "compact",
      "customPalettes": {},
      "customFontPacks": {}
    },
    "sections": [
      {
        "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
        "type": "hero",
        "visible": false,
        "hasBeenEdited": false,
        "options": {}
      }
    ],
    "pages": [
      {
        "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
        "type": "portfolio",
        "title": "Matte Lipstick",
        "slug": "matte-lipstick",
        "visible": false,
        "metaTitle": "string",
        "sections": [
          {
            "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "type": "hero",
            "visible": false,
            "hasBeenEdited": false,
            "options": {}
          }
        ],
        "options": {}
      }
    ]
  },
  "content": {
    "header": {
      "websiteTitle": "string",
      "logoText": "string",
      "logoImageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "logoMode": "logo_text",
      "logoHeight": 1,
      "logoSvg": "string",
      "logoSvgFill": "string",
      "logoSvgStroke": "string",
      "logoSvgStrokeWidth": 1,
      "faviconUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "socialPreviewStyle": "custom",
      "socialCardTemplate": "scrim",
      "socialCardHeadline": "string",
      "socialCardSubline": "string",
      "socialPreviewImageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "layout": "brand_left",
      "pillStyle": {
        "tone": "auto",
        "bgColor": "string",
        "textColor": "string",
        "radius": 1,
        "paddingX": 1,
        "paddingY": 1,
        "width": "content",
        "topOffset": 1,
        "sideOffset": 1,
        "shadow": "none",
        "border": false,
        "borderColor": "string",
        "blur": false,
        "scroll": "stay"
      },
      "navPosition": "left",
      "ctas": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "label": "string",
          "visible": false,
          "fill": "solid",
          "bgColor": "string",
          "textColor": "string",
          "radius": 1,
          "fontScale": 1,
          "fontFamily": "string",
          "fontWeight": 1,
          "italic": false,
          "underline": false,
          "uppercase": false,
          "link": {
            "type": "none",
            "sectionId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "pageSlug": "string",
            "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
            "packageSlug": "string",
            "packageOptionRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
            "productSlug": "string",
            "collectionSlug": "string",
            "catalogSlug": "string",
            "eventSlug": "open-house"
          }
        }
      ],
      "linkStyle": {
        "color": "string",
        "fontScale": 1,
        "weight": 1,
        "uppercase": false,
        "textCase": "auto",
        "fontFamily": "string",
        "italic": false,
        "underline": false
      },
      "links": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "label": "string",
          "link": {
            "type": "none",
            "sectionId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "pageSlug": "string",
            "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
            "packageSlug": "string",
            "packageOptionRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
            "productSlug": "string",
            "collectionSlug": "string",
            "catalogSlug": "string",
            "eventSlug": "open-house"
          },
          "visible": false
        }
      ],
      "maxWidth": "compact",
      "overlayHeader": false,
      "overlayColor": "auto",
      "overlayLogoImageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "menuShowPhone": false,
      "menuShowEmail": false,
      "menuShowSocials": false,
      "hideCollectionsNav": false,
      "palette": "string",
      "fontPairing": "string"
    },
    "hero": {
      "eyebrow": "string",
      "title": "Matte Lipstick",
      "subtitle": "string",
      "buttons": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "label": "string",
          "visible": false,
          "fill": "solid",
          "bgColor": "string",
          "textColor": "string",
          "radius": 1,
          "fontScale": 1,
          "fontFamily": "string",
          "fontWeight": 1,
          "italic": false,
          "underline": false,
          "uppercase": false,
          "link": {
            "type": "none",
            "sectionId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "pageSlug": "string",
            "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
            "packageSlug": "string",
            "packageOptionRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
            "productSlug": "string",
            "collectionSlug": "string",
            "catalogSlug": "string",
            "eventSlug": "open-house"
          }
        }
      ],
      "backgroundMedia": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "type": "image",
          "url": "https://cdn.frontdesk.africa/media/lipstick.webp"
        }
      ],
      "backgroundMode": "image",
      "backgroundUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "backgroundType": "image",
      "overlay": 1,
      "slideDuration": 1
    },
    "heroSections": {},
    "fieldStyles": {},
    "footer": {
      "palette": "string",
      "fontPairing": "string",
      "template": "minimal",
      "brandText": "string",
      "tagline": "One night, three stages.",
      "copyright": "string",
      "statusText": "string",
      "showBackToTop": false,
      "backgroundColor": "string",
      "backgroundImageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "backgroundOverlay": 1,
      "logoMode": "logo_text",
      "logoImageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "logoSvg": "string",
      "logoSvgFill": "string",
      "logoSvgStroke": "string",
      "logoSvgStrokeWidth": 1,
      "logoHeight": 1,
      "customColors": false,
      "textColor": "string",
      "mutedTextColor": "string",
      "accentColor": "string",
      "copyrightBackgroundColor": "string",
      "copyrightTextColor": "string",
      "socialLinks": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "platform": "string",
          "label": "string",
          "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
          "visible": false
        }
      ],
      "linkGroups": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "title": "Matte Lipstick",
          "links": [
            {}
          ]
        }
      ],
      "maxWidth": "compact",
      "newsletter": {
        "enabled": true,
        "heading": "string",
        "body": "string",
        "showNameField": false,
        "namePlaceholder": "string",
        "emailPlaceholder": "string",
        "buttonLabel": "string",
        "successMessage": "string",
        "consentNote": "string",
        "tag": "client",
        "welcomeEmailEnabled": false,
        "welcomeSubject": "string",
        "welcomeBody": "string",
        "fieldSize": "small",
        "radius": 1,
        "backgroundColor": "string",
        "textColor": "string",
        "accentColor": "string",
        "spaceTop": 1,
        "spaceBottom": 1,
        "showDivider": false,
        "dividerOpacity": 1,
        "dividerHeight": 1
      },
      "payments": {
        "enabled": true,
        "title": "Matte Lipstick",
        "backgroundColor": "string",
        "textColor": "string",
        "accentColor": "string",
        "spaceTop": 1,
        "spaceBottom": 1
      },
      "address": {
        "enabled": true,
        "title": "Matte Lipstick",
        "showBusinessName": false,
        "showAddress": false,
        "showEmail": false,
        "showPhone": false,
        "backgroundColor": "string",
        "textColor": "string",
        "accentColor": "string"
      },
      "hours": {
        "enabled": true,
        "title": "Matte Lipstick",
        "closedLabel": "string",
        "backgroundColor": "string",
        "textColor": "string",
        "accentColor": "string"
      },
      "trust": {
        "enabled": true,
        "items": [
          {
            "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "icon": "package",
            "label": "string",
            "emphasis": "string",
            "iconColor": "string"
          }
        ],
        "backgroundColor": "string",
        "textColor": "string",
        "accentColor": "string",
        "spaceTop": 1,
        "spaceBottom": 1,
        "showDivider": false,
        "dividerOpacity": 1,
        "dividerHeight": 1
      }
    },
    "contact": {
      "addressLine1": "string",
      "addressLine2": "string",
      "city": "Lagos",
      "state": "Lagos",
      "country": "Nigeria",
      "googleMapsUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "email": "buyer@example.com",
      "phoneNumbers": [
        "string"
      ],
      "socials": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "platform": "string",
          "label": "string",
          "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
          "visible": false
        }
      ],
      "showMap": false
    },
    "logoStrips": {},
    "faqSections": {},
    "imageTextSections": {},
    "richTextSections": {},
    "blockSections": {},
    "mediaGallerySections": {},
    "socialSections": {},
    "reviewsSections": {},
    "profilesSections": {},
    "contentShowcaseSections": {},
    "newsletterSections": {},
    "customHtmlSections": {},
    "sectionButtons": {},
    "sectionButtonGroups": {},
    "tracking": {
      "facebookPixelId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "ga4Id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "gtmId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "tiktokPixelId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "snapPixelId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "xPixelId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "xPurchaseEventId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "xAddToCartEventId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "xLeadEventId": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "shareHashedEmail": false,
      "customHead": "string",
      "customBodyEnd": "string"
    },
    "promo": {
      "campaigns": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "name": "Matte Lipstick",
          "enabled": true,
          "trigger": "on_load",
          "delayMs": 1,
          "startAt": "2026-08-18T09:20:11.000Z",
          "endAt": "2026-08-18T09:20:11.000Z",
          "mediaType": "none",
          "imageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
          "videoUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
          "videoSoundEnabled": false,
          "heading": "string",
          "body": "string",
          "buttons": [
            {}
          ],
          "dismissLabel": "string",
          "emailCaptureEnabled": false,
          "emailPlaceholder": "string",
          "emailButtonLabel": "string",
          "emailSuccessMessage": "string",
          "emailTag": "client",
          "version": 1
        }
      ]
    }
  },
  "catalogs": {},
  "assets": {},
  "packages": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "type": "string",
      "name": "Matte Lipstick",
      "slug": "matte-lipstick",
      "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "priceMinorFrom": 200000,
      "currency": "NGN",
      "optionCount": 12
    }
  ],
  "packageDetails": {},
  "products": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "slug": "matte-lipstick",
      "isPackage": false,
      "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "currency": "NGN",
      "priceMinorFrom": 200000,
      "pricesFrom": {},
      "compareAtMinorFrom": 200000,
      "compareAtFrom": {},
      "showSaleBadge": false,
      "variantCount": 1,
      "soldOut": false,
      "saveForItSaveable": false,
      "preorderable": false,
      "ratingAvg": 4.8,
      "ratingCount": 12,
      "updatedAt": "2026-08-18T09:20:11.000Z"
    }
  ],
  "collections": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "slug": "matte-lipstick",
      "description": "A long-wear matte lipstick.",
      "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "coverKind": "image",
      "gallery": [
        "string"
      ],
      "productRefs": [
        "019f6f85-efbd-75e4-83e4-c2778a318189"
      ],
      "itemCount": 12,
      "seoTitle": "string",
      "seoDescription": "string"
    }
  ],
  "events": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "title": "Matte Lipstick",
      "slug": "matte-lipstick",
      "tagline": "One night, three stages.",
      "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "format": "physical",
      "mode": "rsvp",
      "startAt": "2026-08-18T09:20:11.000Z",
      "endAt": "2026-08-18T09:20:11.000Z",
      "publishedAt": "2026-08-18T09:20:11.000Z",
      "venueName": "Matte Lipstick",
      "city": "Lagos",
      "category": "music"
    }
  ],
  "primaryDomain": "string",
  "verifiedDomains": [
    "string"
  ],
  "mediaPolicy": {
    "maxDim": 1,
    "quality": 1,
    "format": "webp"
  },
  "paymentMethods": [
    "string"
  ],
  "businessHours": [
    {
      "day": "string",
      "start": "string",
      "end": "string"
    }
  ],
  "widgetBubble": {
    "key": "string"
  },
  "reviews": {
    "average": 1,
    "count": 12,
    "items": [
      {
        "author": "string",
        "rating": 1,
        "text": "string",
        "date": "2026-08-18",
        "productName": "Matte Lipstick",
        "productSlug": "string"
      }
    ]
  },
  "forms": [
    {
      "slug": "matte-lipstick",
      "name": "Matte Lipstick",
      "description": "A long-wear matte lipstick.",
      "submitLabel": "string",
      "thankYouMessage": "string",
      "fields": [
        {
          "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
          "label": "string",
          "type": "number",
          "required": false,
          "helpText": "string",
          "placeholder": "string",
          "options": {
            "choices": [],
            "file": {}
          },
          "identity": "email"
        }
      ]
    }
  ]
}

Products

Every public product, as summaries

Delta reads only see PUBLIC products: a product that was deleted arrives as the product.deleted webhook instead, and one that was merely unpublished simply stops appearing — reconcile against the full list occasionally if you keep a mirror.

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Query parameters

updatedSincequeryoptional

ISO 8601. Only products changed at or after this moment, ordered oldest change first — for keeping a local copy fresh. Feed the largest updatedAt you received back as the next cursor; the >= comparison re-sends the boundary row, so upsert by ref.

limitqueryoptional

Page size for delta reads, max 200. Without updatedSince the list is the storefront display order, unpaged.

Returns PublicProductSummary[]

GET/store/products
curl https://api.frontdesk.africa/v1/store/products \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/products', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
[
  {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "name": "Matte Lipstick",
    "slug": "matte-lipstick",
    "isPackage": false,
    "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "currency": "NGN",
    "priceMinorFrom": 200000,
    "pricesFrom": {},
    "compareAtMinorFrom": 200000,
    "compareAtFrom": {},
    "showSaleBadge": false,
    "variantCount": 1,
    "soldOut": false,
    "saveForItSaveable": false,
    "preorderable": false,
    "ratingAvg": 4.8,
    "ratingCount": 12,
    "updatedAt": "2026-08-18T09:20:11.000Z"
  }
]

One product with its variants, media, specs and description blocks

When `options` is present the product sells along several axes (Size × Colour): render one picker per group and resolve the buyer's picks to the variant whose `optionValueRefs` holds exactly those value refs. Checkout still takes that variant's ref.

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The product's slug.

Returns PublicProduct

GET/store/products/matte-lipstick
curl https://api.frontdesk.africa/v1/store/products/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/products/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "name": "Matte Lipstick",
  "slug": "matte-lipstick",
  "description": "A long-wear matte lipstick.",
  "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
  "needsDelivery": false,
  "currency": "NGN",
  "feeMode": "pass_on",
  "priceMinorFrom": 200000,
  "media": [
    "string"
  ],
  "variants": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "priceMinor": 200000,
      "compareAtMinor": 200000,
      "currency": "NGN",
      "prices": [
        {
          "currency": "NGN",
          "priceMinor": 200000,
          "compareAtMinor": 200000
        }
      ],
      "media": [
        "string"
      ],
      "soldOut": false,
      "availableQty": 1,
      "swatchHex": "string",
      "optionValueRefs": [
        "019f6f85-efbd-75e4-83e4-c2778a318189"
      ],
      "description": "A long-wear matte lipstick.",
      "badge": "string",
      "features": [
        {
          "text": "string",
          "included": false
        }
      ],
      "fineprint": "string",
      "durationMinutes": 1,
      "pricingMode": "full_payment",
      "schedulingType": "slots",
      "availability": {
        "weekdays": [
          "string"
        ],
        "blocks": [
          {
            "type": "date",
            "date": "2026-08-18"
          }
        ],
        "overrideDates": [
          "2026-08-18"
        ],
        "windowDays": 1,
        "leadTimeMinutes": 1
      },
      "takenDates": [
        "2026-08-18"
      ],
      "formFields": [
        {
          "key": "string",
          "type": "number",
          "label": "string",
          "required": false,
          "hidden": false,
          "placeholder": "string",
          "helpText": "string",
          "options": [
            "string"
          ],
          "showIf": {
            "field": "string",
            "equals": "string"
          }
        }
      ]
    }
  ],
  "isPackage": false,
  "selectionMode": "choose_one",
  "variantDisplay": "text",
  "options": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "display": "text",
      "values": [
        {
          "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "label": "string",
          "swatchHex": "string",
          "media": [
            "string"
          ]
        }
      ]
    }
  ],
  "schedulingEnabled": false,
  "addonGroups": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "selectionType": "single",
      "required": false,
      "minSelect": 1,
      "maxSelect": 1,
      "sort": 1,
      "variantRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "options": [
        {
          "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "kind": "inline",
          "label": "string",
          "description": "A long-wear matte lipstick.",
          "priceMinor": 200000,
          "prices": [
            {}
          ],
          "imageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
          "allowQuantity": false,
          "maxQty": 1,
          "linkedProductRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "linkedVariantRef": "019f6f85-efbd-75e4-83e4-c2778a318189"
        }
      ]
    }
  ],
  "formFields": [
    {
      "key": "string",
      "type": "number",
      "label": "string",
      "required": false,
      "hidden": false,
      "placeholder": "string",
      "helpText": "string",
      "options": [
        "string"
      ],
      "showIf": {
        "field": "string",
        "equals": "string"
      }
    }
  ],
  "saveForItEnabled": false,
  "saveForItMaxDays": 1,
  "saveForItFixedDates": [
    "2026-08-18"
  ],
  "saveForItFreezePrice": false,
  "saveForItCrowdLabel": "string",
  "saveForItSaveable": false,
  "preorderable": false,
  "preorderEtaText": "string",
  "preorderRemaining": 1,
  "preorderDepositType": "percent",
  "preorderDepositValue": 1,
  "soldOut": false,
  "reviews": {
    "count": 12,
    "average": 1,
    "counts": {
      "1": 1,
      "2": 1,
      "3": 1,
      "4": 1,
      "5": 1
    },
    "aiSummary": "string"
  },
  "reviewsEnabled": false,
  "restockAlertsEnabled": false,
  "info": {
    "brand": "string",
    "infoCategory": "home",
    "longDescription": "string",
    "descriptionBlocks": [
      {
        "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
        "kind": "text",
        "text": "string",
        "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
        "platform": "instagram",
        "alt": null,
        "caption": "string",
        "width": "full",
        "sort": 1
      }
    ],
    "highlights": [
      "string"
    ],
    "sku": "MTL-001",
    "barcode": "string",
    "mpn": "string",
    "manufacturer": "string",
    "modelName": "Matte Lipstick",
    "weightValue": 1,
    "weightUnit": "kg",
    "dimLength": 1,
    "dimWidth": 1,
    "dimHeight": 1,
    "dimUnit": "cm",
    "countryOfOrigin": "string",
    "warrantyText": "string",
    "warrantyMonths": 1,
    "careInstructions": "string",
    "sustainabilityText": "string",
    "specs": [
      {
        "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
        "key": "string",
        "label": "string",
        "value": "string",
        "kind": "number",
        "unit": "string",
        "sort": 1
      }
    ]
  }
}

Bookable slots for a product that is scheduled rather than shipped

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The product's slug.

Returns PublicProductSlots

GET/store/products/matte-lipstick/slots
curl https://api.frontdesk.africa/v1/store/products/matte-lipstick/slots \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/products/matte-lipstick/slots', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "date": "2026-08-18",
  "slots": [
    {
      "startUtc": "string",
      "startLocal": "string"
    }
  ]
}

Live stock and slot availability

A cart lives in the buyer's storage, so its lines can be days old. Call this when the buyer returns: `variants` maps each ref to units left (null = untracked, no cap), and refs listed in `preorderable` are still buyable at 0 stock — cap those on `preorderRemaining` instead of dropping them.

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Query parameters

variantRefsqueryrequired

Comma-separated variant refs your cart holds (max 100). A ref missing from the response map is no longer purchasable, so drop that line.

Returns PublicStoreAvailability

GET/store/availability
curl https://api.frontdesk.africa/v1/store/availability \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/availability', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "variants": {},
  "preorderable": [
    "string"
  ],
  "preorderRemaining": {}
}

Collections

Every public collection, as summaries

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Returns PublicCollectionSummary[]

GET/store/collections
curl https://api.frontdesk.africa/v1/store/collections \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/collections', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
[
  {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "name": "Matte Lipstick",
    "slug": "matte-lipstick",
    "description": "A long-wear matte lipstick.",
    "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "coverKind": "image",
    "gallery": [
      "string"
    ],
    "productRefs": [
      "019f6f85-efbd-75e4-83e4-c2778a318189"
    ],
    "itemCount": 12,
    "seoTitle": "string",
    "seoDescription": "string"
  }
]

One collection with its products

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The collection's slug.

Returns PublicCollectionDetail

GET/store/collections/matte-lipstick
curl https://api.frontdesk.africa/v1/store/collections/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/collections/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "collection": {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "name": "Matte Lipstick",
    "slug": "matte-lipstick",
    "description": "A long-wear matte lipstick.",
    "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "coverKind": "image",
    "gallery": [
      "string"
    ],
    "productRefs": [
      "019f6f85-efbd-75e4-83e4-c2778a318189"
    ],
    "itemCount": 12,
    "seoTitle": "string",
    "seoDescription": "string"
  },
  "products": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "slug": "matte-lipstick",
      "isPackage": false,
      "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "currency": "NGN",
      "priceMinorFrom": 200000,
      "pricesFrom": {},
      "compareAtMinorFrom": 200000,
      "compareAtFrom": {},
      "showSaleBadge": false,
      "variantCount": 1,
      "soldOut": false,
      "saveForItSaveable": false,
      "preorderable": false,
      "ratingAvg": 4.8,
      "ratingCount": 12,
      "updatedAt": "2026-08-18T09:20:11.000Z"
    }
  ]
}

Events

Every public event, as summaries

Same list that rides inside /v1/store/storefront. Use this when you only need the events.

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Returns PublicEventSummary[]

GET/store/events
curl https://api.frontdesk.africa/v1/store/events \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/events', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
[
  {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "title": "Matte Lipstick",
    "slug": "matte-lipstick",
    "tagline": "One night, three stages.",
    "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "format": "physical",
    "mode": "rsvp",
    "startAt": "2026-08-18T09:20:11.000Z",
    "endAt": "2026-08-18T09:20:11.000Z",
    "publishedAt": "2026-08-18T09:20:11.000Z",
    "venueName": "Matte Lipstick",
    "city": "Lagos",
    "category": "music"
  }
]

One event with its ticket types, days and location

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The event's slug.

Returns PublicEvent

GET/store/events/matte-lipstick
curl https://api.frontdesk.africa/v1/store/events/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/events/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "title": "Matte Lipstick",
  "slug": "matte-lipstick",
  "tagline": "One night, three stages.",
  "description": "A long-wear matte lipstick.",
  "mode": "rsvp",
  "format": "physical",
  "location": {
    "venueName": "Matte Lipstick",
    "address": "string",
    "city": "Lagos",
    "state": "Lagos",
    "country": "Nigeria",
    "lat": 1,
    "lng": 3.4881,
    "onlineUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "onlineProvider": "string"
  },
  "venueVisibility": "public",
  "timezone": "Africa/Lagos",
  "startAt": "2026-08-18T09:20:11.000Z",
  "endAt": "2026-08-18T09:20:11.000Z",
  "doorsAt": "2026-08-18T09:20:11.000Z",
  "capacity": 1,
  "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
  "media": [
    {
      "type": "image",
      "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "thumbnail": "string"
    }
  ],
  "category": "music",
  "minAge": 1,
  "dobMode": "order",
  "attendeeEmailRequired": false,
  "frontdeskFee": {
    "pct": 1,
    "flatMinor": 200000
  },
  "customerFee": {
    "pct": 1,
    "flatMinor": 200000
  },
  "customerFeeFlatByCurrency": {},
  "days": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "label": "string",
      "dayDate": "2026-08-18",
      "startAt": "2026-08-18T09:20:11.000Z",
      "endAt": "2026-08-18T09:20:11.000Z",
      "timezone": "Africa/Lagos",
      "location": {
        "venueName": "Matte Lipstick",
        "address": "string",
        "city": "Lagos",
        "state": "Lagos",
        "country": "Nigeria",
        "lat": 1,
        "lng": 3.4881,
        "onlineUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
        "onlineProvider": "string"
      },
      "capacity": 1,
      "remaining": 1,
      "soldOut": false
    }
  ],
  "saveForItEnabled": false,
  "saveForItCrowdLabel": "string",
  "ticketTypes": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "description": "A long-wear matte lipstick.",
      "imageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "kind": "deposit",
      "frontdeskFeeMode": "pass_on",
      "priceMinor": 200000,
      "currency": "NGN",
      "prices": [
        {
          "currency": "NGN",
          "priceMinor": 200000,
          "compareAtMinor": 200000
        }
      ],
      "compareAtMinor": 200000,
      "minPriceMinor": 200000,
      "donationPresets": [
        1
      ],
      "depositType": "percent",
      "depositValue": 1,
      "depositDueNowMinor": 200000,
      "depositBalanceMode": "online",
      "depositBalanceDueAt": "2026-08-18T09:20:11.000Z",
      "priceEndsAt": "2026-08-18T09:20:11.000Z",
      "priceRemaining": 1,
      "capacity": 1,
      "soldCount": 12,
      "remaining": 1,
      "soldOut": false,
      "lowStockThreshold": 1,
      "minPerOrder": 1,
      "maxPerOrder": 1,
      "minAge": 1,
      "approvalRequired": false,
      "groupSize": 1,
      "validDays": [
        "string"
      ],
      "requiresAttendeeDetails": false,
      "formFields": [
        {
          "key": "string",
          "type": "number",
          "label": "string",
          "required": false,
          "hidden": false,
          "placeholder": "string",
          "helpText": "string",
          "options": [
            "string"
          ],
          "showIf": {
            "field": "string",
            "equals": "string"
          }
        }
      ],
      "saveForIt": {
        "enabled": true,
        "holdPolicy": "none",
        "holdThresholdPct": 1,
        "priceMode": "freeze",
        "cutoffHours": 1
      },
      "saveForItSaveable": false
    }
  ],
  "attachedPackages": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "type": "string",
      "name": "Matte Lipstick",
      "slug": "matte-lipstick",
      "description": "A long-wear matte lipstick.",
      "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "currency": "NGN",
      "options": [
        {
          "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "name": "Matte Lipstick",
          "pricingMode": "full_payment",
          "priceMinor": 200000,
          "compareAtMinor": 200000,
          "currency": "NGN",
          "badge": "string",
          "media": [
            "string"
          ],
          "features": [
            "string"
          ],
          "fineprint": "string",
          "occupancyMode": "exclusive",
          "schedulingType": "slots",
          "availability": {
            "weekdays": [],
            "blocks": [],
            "overrideDates": [],
            "windowDays": 1,
            "leadTimeMinutes": 1
          },
          "addonGroups": [
            {}
          ],
          "formFields": [
            {}
          ],
          "takenDates": [
            "2026-08-18"
          ]
        }
      ]
    }
  ],
  "attachedProducts": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "name": "Matte Lipstick",
      "slug": "matte-lipstick",
      "description": "A long-wear matte lipstick.",
      "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "needsDelivery": false,
      "currency": "NGN",
      "feeMode": "pass_on",
      "priceMinorFrom": 200000,
      "media": [
        "string"
      ],
      "variants": [
        {
          "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "name": "Matte Lipstick",
          "priceMinor": 200000,
          "compareAtMinor": 200000,
          "currency": "NGN",
          "prices": [
            {}
          ],
          "media": [
            "string"
          ],
          "soldOut": false,
          "availableQty": 1,
          "swatchHex": "string",
          "optionValueRefs": [
            "019f6f85-efbd-75e4-83e4-c2778a318189"
          ],
          "description": "A long-wear matte lipstick.",
          "badge": "string",
          "features": [
            {}
          ],
          "fineprint": "string",
          "durationMinutes": 1,
          "pricingMode": "full_payment",
          "schedulingType": "slots",
          "availability": {
            "weekdays": [],
            "blocks": [],
            "overrideDates": [],
            "windowDays": 1,
            "leadTimeMinutes": 1
          },
          "takenDates": [
            "2026-08-18"
          ],
          "formFields": [
            {}
          ]
        }
      ],
      "isPackage": false,
      "selectionMode": "choose_one",
      "variantDisplay": "text",
      "options": [
        {
          "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "name": "Matte Lipstick",
          "display": "text",
          "values": [
            {}
          ]
        }
      ],
      "schedulingEnabled": false,
      "addonGroups": [
        {
          "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "name": "Matte Lipstick",
          "selectionType": "single",
          "required": false,
          "minSelect": 1,
          "maxSelect": 1,
          "sort": 1,
          "variantRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
          "options": [
            {}
          ]
        }
      ],
      "formFields": [
        {
          "key": "string",
          "type": "number",
          "label": "string",
          "required": false,
          "hidden": false,
          "placeholder": "string",
          "helpText": "string",
          "options": [
            "string"
          ],
          "showIf": {
            "field": "string",
            "equals": "string"
          }
        }
      ],
      "saveForItEnabled": false,
      "saveForItMaxDays": 1,
      "saveForItFixedDates": [
        "2026-08-18"
      ],
      "saveForItFreezePrice": false,
      "saveForItCrowdLabel": "string",
      "saveForItSaveable": false,
      "preorderable": false,
      "preorderEtaText": "string",
      "preorderRemaining": 1,
      "preorderDepositType": "percent",
      "preorderDepositValue": 1,
      "soldOut": false,
      "reviews": {
        "count": 12,
        "average": 1,
        "counts": {
          "1": 1,
          "2": 1,
          "3": 1,
          "4": 1,
          "5": 1
        },
        "aiSummary": "string"
      },
      "reviewsEnabled": false,
      "restockAlertsEnabled": false,
      "info": {
        "brand": "string",
        "infoCategory": "home",
        "longDescription": "string",
        "descriptionBlocks": [
          {
            "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "kind": "text",
            "text": "string",
            "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
            "platform": "instagram",
            "alt": null,
            "caption": "string",
            "width": "full",
            "sort": 1
          }
        ],
        "highlights": [
          "string"
        ],
        "sku": "MTL-001",
        "barcode": "string",
        "mpn": "string",
        "manufacturer": "string",
        "modelName": "Matte Lipstick",
        "weightValue": 1,
        "weightUnit": "kg",
        "dimLength": 1,
        "dimWidth": 1,
        "dimHeight": 1,
        "dimUnit": "cm",
        "countryOfOrigin": "string",
        "warrantyText": "string",
        "warrantyMonths": 1,
        "careInstructions": "string",
        "sustainabilityText": "string",
        "specs": [
          {
            "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "key": "string",
            "label": "string",
            "value": "string",
            "kind": "number",
            "unit": "string",
            "sort": 1
          }
        ]
      }
    }
  ],
  "brand": {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "displayName": "Matte Lipstick",
    "avatarUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "handle": "beauty-pro-1"
  },
  "storefrontPublished": false,
  "sponsorship": {
    "slotType": "official_ticket_sponsor",
    "brandName": "Matte Lipstick",
    "logoUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "tagline": "One night, three stages.",
    "clickUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "sponsorshipRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "qrColor": "string",
    "bannerUrl": "https://cdn.frontdesk.africa/media/lipstick.webp"
  }
}

Open a hosted checkout for event tickets

Send Idempotency-Key: a retry returns the SAME checkout instead of two. Tiers are validated here, like product variants: a ref that is unknown, hidden without its unlockCode, sold out, or asked for outside its minPerOrder/maxPerOrder returns 400 VALIDATION_ERROR with the refs in details.invalid, details.soldOut or details.quantity, so the failure lands on your server and never on your buyer. You collect what the organiser asks for, and GET /v1/store/events/:slug tells you what that is: each tier carries formFields, requiresAttendeeDetails, groupSize and minPerOrder/maxPerOrder, and the event carries minAge and dobMode. Everything you send is re-validated here, so prices, availability and age limits are decided by the server, not by your form. Three of those rules REFUSE the order rather than warn, so collect them before you send: a required field left unanswered returns 400 "Please answer: <label>"; a tier with requiresAttendeeDetails returns 400 "Enter a name for each \"<tier>\" ticket" unless every seat carries a name; and an age-gated tier fails the age check without a date of birth, on the buyer when dobMode is order and on each attendee when it is attendee. Our own hosted checkout collects all three for you, and lets the buyer name each guest even when the tier does not demand it, so this only applies when you build your own form.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Path parameters

slugpathrequired

The event's slug.

Body parameters

ticketsbodyrequired

Ticket lines: ticketTypeRef, quantity, optional amountMinor for donation tiers, and attendees to issue seats to named guests. attendees is POSITIONAL and its length must be quantity x groupSize: leave an entry blank to keep that seat under the buyer, never drop it, or the following names shift onto the wrong seats.

productsbodyoptional

NOT SUPPORTED YET: sending a non-empty array returns 400. Sell merchandise with a separate POST /v1/store/checkouts.

contactbodyrequired

Buyer name, email and optional phone. The email receives the tickets.

dateOfBirthbodyoptional

Buyer DOB as YYYY-MM-DD. Required when a chosen tier sets minAge and the event dobMode is order.

responsesbodyoptional

Answers to the questions on the chosen tiers, keyed by the field key. Required whenever one of those fields is required.

discountCodebodyoptional

Promo code. Re-validated server-side.

unlockCodesbodyoptional

Codes that reveal hidden or invite-only tiers. Re-verified server-side, and REQUIRED here for such a tier: without the code the tier is not on sale and the request is refused.

promoterCodebodyoptional

Affiliate code from a share link. Ignored if unknown.

currencybodyoptional

Must be a currency the event sells in. Omit for its primary.

returnUrlbodyrequired

Where the buyer lands after paying.

Returns StoreCheckoutSessionView

POST/store/events/matte-lipstick/checkout
curl -X POST https://api.frontdesk.africa/v1/store/events/matte-lipstick/checkout \
  -H "Authorization: Bearer fd_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
  "tickets": "value",
  "products": "optional",
  "contact": "value",
  "dateOfBirth": "optional",
  "responses": "optional",
  "discountCode": "optional",
  "unlockCodes": "optional",
  "promoterCode": "optional",
  "currency": "optional",
  "returnUrl": "value"
}'
const res = await fetch('https://api.frontdesk.africa/v1/store/events/matte-lipstick/checkout', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "tickets": "value",
    "products": "optional",
    "contact": "value",
    "dateOfBirth": "optional",
    "responses": "optional",
    "discountCode": "optional",
    "unlockCodes": "optional",
    "promoterCode": "optional",
    "currency": "optional",
    "returnUrl": "value"
  }),
});
const data = await res.json();
Sample response201 Created
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "status": "open",
  "mode": "live",
  "hostedUrl": "https://beauty-pro-1.sme.pro/checkout/01a00e95-873c-7056-b31a-aad4c70191e0",
  "returnUrl": "https://yourbrand.com/thanks",
  "currency": "NGN",
  "orderRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "expiresAt": "2026-08-18T09:20:11.000Z",
  "completedAt": "2026-08-18T09:20:11.000Z",
  "createdAt": "2026-08-18T09:20:11.000Z"
}

Register a guest for a FREE event tier from your own page

FrontDesk sends the confirmation, the calendar invite and the ticket.

The one write a publishable key may make, so call it straight from the browser, from an origin registered on the key. FREE tiers only: a paid, donation or deposit tier answers 402 TIER_REQUIRES_PAYMENT, and that is your cue to open POST /v1/store/events/:slug/checkout from your server instead. Every rule the hosted storefront applies runs here too: an unknown, hidden-without-its-code, or not-yet-on-sale tier is refused, capacity answers 409 TIER_SOLD_OUT, minPerOrder/maxPerOrder are enforced, a required question left unanswered returns 400 "Please answer: <label>", a tier with requiresAttendeeDetails needs a name on every seat, and an age gate needs the date of birth. Duplicates are quiet: when the email already holds a seat on this event you get the SAME 201 as a first registration (their existing reference and status), the confirmation is re-sent at most once every 10 minutes, and no new seat is issued, so a retry is always safe when an email is given and no Idempotency-Key is needed. A test key runs every one of those checks and answers test:true with a test_ reference, writing nothing and sending nothing. There is no read-back endpoint: subscribe to the attendee.registered webhook, and the organiser sees every guest in their portal.

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The event's slug.

Body parameters

ticketsbodyrequired

Same shape as checkout: ticketTypeRef, quantity, and optional attendees to name each seat. attendees is POSITIONAL and its length must be quantity x groupSize: leave an entry blank ({}) to keep that seat under the registrant, never drop it. Every tier must be free.

contactbodyrequired

The registrant: name, and an email and/or phone. The email receives the confirmation, the .ics invite and the ticket PDF; a phone-only guest gets an SMS.

dateOfBirthbodyoptional

YYYY-MM-DD. Required when a chosen tier sets minAge and the event dobMode is order; per attendee when it is attendee.

responsesbodyoptional

Answers to the chosen tiers' questions, keyed by each formFields[].key from GET /v1/store/events/:slug. Required whenever one of those fields is required.

unlockCodesbodyoptional

Codes that reveal hidden or invite-only tiers. Required for such a tier.

promoterCodebodyoptional

Affiliate code from a share link. Ignored if unknown.

Returns StoreEventRegistrationView

POST/store/events/matte-lipstick/register
curl -X POST https://api.frontdesk.africa/v1/store/events/matte-lipstick/register \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com" \
  -H "Content-Type: application/json" \
  -d '{
  "tickets": [
    {
      "ticketTypeRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "quantity": 2,
      "attendees": [
        {},
        {
          "name": "Tunde Obi",
          "email": "tunde@example.com"
        }
      ]
    }
  ],
  "contact": {
    "name": "Ada Obi",
    "email": "ada@example.com",
    "phone": "+2348120700080"
  },
  "responses": {
    "piggyvest_user": "Yes",
    "first_time": "No"
  }
}'
const res = await fetch('https://api.frontdesk.africa/v1/store/events/matte-lipstick/register', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "tickets": [
      {
        "ticketTypeRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
        "quantity": 2,
        "attendees": [
          {},
          {
            "name": "Tunde Obi",
            "email": "tunde@example.com"
          }
        ]
      }
    ],
    "contact": {
      "name": "Ada Obi",
      "email": "ada@example.com",
      "phone": "+2348120700080"
    },
    "responses": {
      "piggyvest_user": "Yes",
      "first_time": "No"
    }
  }),
});
const data = await res.json();
Sample response201 Created
{
  "eventSlug": "open-house",
  "eventRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "ticketTypeRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "status": "confirmed",
  "reference": "EV-7F3A2C",
  "attendeeCount": 1,
  "test": false,
  "message": "Human readable detail about what happened."
}

Open a ticket checkout you render yourself, including the payment step

Seats are held until expiresAt. Name ONE rail: minting every rail at once would leave several live payment paths on one order, and a buyer could pay twice. Branch on the provider you asked for, never on which payment field is filled in: Paystack returns an accessCode AND a checkoutUrl, so code that redirects whenever checkoutUrl is present sends every card buyer to a Paystack page and never opens the popup. Paystack returns an accessCode for its inline popup: load js.paystack.co/v2/inline.js and call new PaystackPop().resumeTransaction({ accessCode, onSuccess, onCancel }). Pass ONE options object, because resumeTransaction takes a single argument and the older-looking resumeTransaction(accessCode, callbacks) silently falls back to a full-page redirect to Paystack. Stripe returns a checkoutUrl to send the buyer to. Bank transfer returns an account once the provider confirms it, so poll GET /v1/store/checkouts/:ref if it is null. Requires headless to be switched on for the workspace. Release the seats early with POST /v1/store/checkouts/:ref/cancel when the buyer walks away. Every rail returns the buyer to your returnUrl with ?checkout=<ref> appended, so they never see a FrontDesk page. Treat that redirect as a hint and confirm with GET /v1/store/checkouts/:ref. With a TEST key this creates a REAL order marked test (no money, no stock, no tickets) and charges against the provider sandbox — pay it with a provider test card. Where sandbox credentials are not wired, it validates and prices only: payment fields are null and orderRef is a synthetic test_ord_ value that resolves to nothing.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Path parameters

slugpathrequired

The event's slug.

Body parameters

providerbodyrequired

The rail to use, from GET /v1/store/payment-methods.

ticketsbodyrequired

Ticket lines: ticketTypeRef, quantity, optional amountMinor for donation tiers.

contactbodyrequired

Buyer name, email and optional phone.

pricingbodyoptional

Your computed totalMinor. We re-price and return PRICE_MISMATCH if it disagrees.

returnUrlbodyrequired

Your own page, where the buyer lands after paying. Must be https, or localhost while you build.

Returns StoreHeadlessCheckoutView

POST/store/events/matte-lipstick/checkout/headless
curl -X POST https://api.frontdesk.africa/v1/store/events/matte-lipstick/checkout/headless \
  -H "Authorization: Bearer fd_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
  "provider": "value",
  "tickets": "value",
  "contact": "value",
  "pricing": "optional",
  "returnUrl": "value"
}'
const res = await fetch('https://api.frontdesk.africa/v1/store/events/matte-lipstick/checkout/headless', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "provider": "value",
    "tickets": "value",
    "contact": "value",
    "pricing": "optional",
    "returnUrl": "value"
  }),
});
const data = await res.json();
Sample response201 Created
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "orderRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "status": "open",
  "mode": "live",
  "amount": 200000,
  "provider": "paystack",
  "payment": {
    "accessCode": "string",
    "checkoutUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "account": {
      "accountNumber": "string",
      "accountName": "Matte Lipstick",
      "bankName": "Matte Lipstick"
    },
    "reference": "EV-7F3A2C"
  },
  "expiresAt": "2026-08-18T09:20:11.000Z"
}

Content

A published form and its field schema, so you can render it yourself

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The form's slug.

Returns PublicForm

GET/store/forms/matte-lipstick
curl https://api.frontdesk.africa/v1/store/forms/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/forms/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "slug": "matte-lipstick",
  "name": "Matte Lipstick",
  "description": "A long-wear matte lipstick.",
  "status": "draft",
  "fields": [
    {
      "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "label": "string",
      "type": "number",
      "required": false,
      "helpText": "string",
      "placeholder": "string",
      "options": {
        "choices": [
          {
            "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "label": "string",
            "color": "string"
          }
        ],
        "file": {
          "minFiles": 1,
          "maxFiles": 1,
          "kinds": "image"
        }
      },
      "identity": "email"
    }
  ],
  "settings": {
    "displayTitle": "string",
    "displayDescription": "string",
    "sideTitle": "string",
    "sideDescription": "string",
    "thankYouMessage": "string",
    "redirectUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "submitButtonLabel": "string"
  },
  "coverUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
  "workspace": {
    "name": "Matte Lipstick",
    "handle": "beauty-pro-1",
    "logoUrl": "https://cdn.frontdesk.africa/media/lipstick.webp"
  },
  "uploadToken": "string"
}

A published data table: its columns and rows

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The table's slug.

Returns PublicTable

GET/store/tables/matte-lipstick
curl https://api.frontdesk.africa/v1/store/tables/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/tables/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "name": "Matte Lipstick",
  "description": "A long-wear matte lipstick.",
  "icon": "string",
  "slug": "matte-lipstick",
  "publicPath": "string",
  "columns": [
    {
      "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
      "name": "Matte Lipstick",
      "type": "string",
      "options": {
        "choices": [
          {
            "id": "01a00e97-62c6-73cc-9fa2-7ba986691c25",
            "label": "string",
            "color": "string"
          }
        ],
        "currency": "NGN",
        "agg": "string"
      },
      "width": 1
    }
  ],
  "rows": [
    {}
  ],
  "total": 1,
  "truncated": false,
  "aggregates": {},
  "updatedAt": "2026-08-18T09:20:11.000Z",
  "lastEditedAt": "2026-08-18T09:20:11.000Z",
  "brand": {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "displayName": "Matte Lipstick",
    "avatarUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "handle": "beauty-pro-1"
  },
  "storefrontPublished": false
}

A public media catalog (a gallery)

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The catalog's slug.

Returns PublicCatalog

GET/store/catalogs/matte-lipstick
curl https://api.frontdesk.africa/v1/store/catalogs/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/catalogs/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "catalog": {
    "name": "Matte Lipstick",
    "slug": "matte-lipstick",
    "description": "A long-wear matte lipstick.",
    "allowDownloads": false,
    "fullScreenMasonry": false,
    "showGalleryName": false,
    "showGalleryDescription": false,
    "seoIndexable": false,
    "type": "standard",
    "brief": {
      "theme": "string",
      "palette": [
        "string"
      ],
      "occasion": "string",
      "notes": "string"
    }
  },
  "items": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "type": "image",
      "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "thumbnailUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "title": "Matte Lipstick",
      "description": "A long-wear matte lipstick.",
      "linkUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "tags": [
        "string"
      ],
      "width": 1,
      "height": 1
    }
  ],
  "brand": {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "displayName": "Matte Lipstick",
    "avatarUrl": "https://cdn.frontdesk.africa/media/lipstick.webp"
  },
  "storefrontPublished": false
}

A public file folder and its files

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The folder's slug.

Returns PublicFolder

GET/store/files/matte-lipstick
curl https://api.frontdesk.africa/v1/store/files/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/files/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "folder": {
    "name": "Matte Lipstick",
    "slug": "matte-lipstick",
    "description": "A long-wear matte lipstick.",
    "allowDownloads": false,
    "seoIndexable": false
  },
  "files": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "fileKind": "image",
      "url": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "thumbnailUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
      "filename": "Matte Lipstick",
      "mimeType": "string",
      "sizeBytes": 1,
      "width": 1,
      "height": 1,
      "createdAt": "2026-08-18T09:20:11.000Z"
    }
  ],
  "brand": {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "displayName": "Matte Lipstick",
    "avatarUrl": "https://cdn.frontdesk.africa/media/lipstick.webp"
  },
  "storefrontPublished": false
}

A published note or document

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Path parameters

slugpathrequired

The note's slug.

Returns PublicNote

GET/store/notes/matte-lipstick
curl https://api.frontdesk.africa/v1/store/notes/matte-lipstick \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/notes/matte-lipstick', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "title": "Matte Lipstick",
  "slug": "matte-lipstick",
  "contentHtml": "string",
  "publicPath": "string",
  "updatedAt": "2026-08-18T09:20:11.000Z",
  "lastEditedAt": "2026-08-18T09:20:11.000Z",
  "approvals": [
    {
      "approverName": "Matte Lipstick",
      "approverEmail": "buyer@example.com",
      "note": "string",
      "approvedAt": "2026-08-18T09:20:11.000Z"
    }
  ],
  "approvalCount": 12,
  "comments": [
    {
      "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "authorKind": "client",
      "authorName": "Matte Lipstick",
      "body": "string",
      "createdAt": "2026-08-18T09:20:11.000Z"
    }
  ],
  "brand": {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "displayName": "Matte Lipstick",
    "avatarUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
    "handle": "beauty-pro-1"
  },
  "storefrontPublished": false
}

Delivery

Delivery zones and their fees, so a cart can quote shipping before checkout

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Returns DeliveryZone[]

GET/store/delivery-zones
curl https://api.frontdesk.africa/v1/store/delivery-zones \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/delivery-zones', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
[
  {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "name": "Matte Lipstick",
    "feeMinor": 200000,
    "currency": "NGN",
    "sort": 1,
    "prices": [
      {
        "currency": "NGN",
        "feeMinor": 200000
      }
    ]
  }
]

Checkout

Open a hosted checkout

Returns a URL to send the buyer to; you never handle payment.

Requires an Idempotency-Key header. A retry then returns the SAME session instead of giving the buyer two carts. Items are validated here: an unknown or unpublished variantRef returns 400 VALIDATION_ERROR with the bad refs in `details.invalid`, and a variant with no price in the requested currency returns 400 VALIDATION_ERROR with the refs in `details.unpriced`, so both fail on your server rather than in front of your buyer. Pricing still happens when the buyer pays, in the session currency. Only rails that can charge that currency are offered on the hosted page (NGN: card, bank transfer; anything else: international card). A buyer who backs out of a payment returns to the hosted page, never to your returnUrl: only a paid checkout reaches returnUrl. A retry there pays the same order rather than opening another, and reopening hostedUrl resumes it, including a bank transfer already started. There is no `provider` field: the buyer picks how to pay on our page, which is why you never touch card data.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Idempotency-Keystringrequired

Your own reference for this attempt. Sending it again returns the same checkout instead of a second one.

Body parameters

itemsbodyrequired

Array of { variantRef, quantity }. Every variantRef must resolve to a live product in this store; refs come from GET /v1/store/products.

returnUrlbodyrequired

Where the buyer lands after paying. https, or localhost while testing.

contactbodyoptional

Prefills the hosted page. The buyer can still correct it.

currencybodyoptional

The currency the buyer pays in. Must be one the store accepts, and every item must carry a price in it (see prices[] on GET /v1/store/products); otherwise 400 VALIDATION_ERROR with `details.accepted` or `details.unpriced`. Omit for the store primary. Fixed for the life of the session: the hosted page, the order and the payment are all in it, and nothing falls back to the store primary.

deliveryZoneRefbodyoptional

Required when any line needs delivery. The zone fee is charged in the checkout currency.

Returns StoreCheckoutSessionView

POST/store/checkouts
curl -X POST https://api.frontdesk.africa/v1/store/checkouts \
  -H "Authorization: Bearer fd_sk_live_xxx" \
  -H "Idempotency-Key: order-1042" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "variantRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "quantity": 1
    }
  ],
  "contact": {
    "name": "Ada Obi",
    "email": "ada@example.com"
  },
  "returnUrl": "https://yourbrand.com/thanks"
}'
const res = await fetch('https://api.frontdesk.africa/v1/store/checkouts', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
    'Idempotency-Key': 'order-1042',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "items": [
      {
        "variantRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
        "quantity": 1
      }
    ],
    "contact": {
      "name": "Ada Obi",
      "email": "ada@example.com"
    },
    "returnUrl": "https://yourbrand.com/thanks"
  }),
});
const data = await res.json();
Sample response201 Created
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "status": "open",
  "mode": "live",
  "hostedUrl": "https://beauty-pro-1.sme.pro/checkout/01a00e95-873c-7056-b31a-aad4c70191e0",
  "returnUrl": "https://yourbrand.com/thanks",
  "currency": "NGN",
  "orderRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "expiresAt": "2026-08-18T09:20:11.000Z",
  "completedAt": "2026-08-18T09:20:11.000Z",
  "createdAt": "2026-08-18T09:20:11.000Z"
}

The state of a checkout, including the order ref once it is paid

THIS is how you confirm a purchase. The return redirect is a browser navigation and can be lost, replayed or forged. Never fulfil on it alone. Safe to poll while a buyer is paying: when the checkout is still open we ask the payment provider directly before answering, so you get a real answer even if a webhook is late or never arrives. Polling every few seconds is fine and costs you nothing extra.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Path parameters

refpathrequired

The checkout ref.

Returns StoreCheckoutSessionView

GET/store/checkouts/REF
curl https://api.frontdesk.africa/v1/store/checkouts/REF \
  -H "Authorization: Bearer fd_sk_live_xxx"
const res = await fetch('https://api.frontdesk.africa/v1/store/checkouts/REF', {
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
  },
});
const data = await res.json();
Sample response200 OK
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "status": "open",
  "mode": "live",
  "hostedUrl": "https://beauty-pro-1.sme.pro/checkout/01a00e95-873c-7056-b31a-aad4c70191e0",
  "returnUrl": "https://yourbrand.com/thanks",
  "currency": "NGN",
  "orderRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "expiresAt": "2026-08-18T09:20:11.000Z",
  "completedAt": "2026-08-18T09:20:11.000Z",
  "createdAt": "2026-08-18T09:20:11.000Z"
}

Cancel an open checkout and release the seats it was holding

Call this when your buyer abandons the payment step. A headless ticket checkout holds its tiers against capacity until expiresAt, so cancelling puts them back on sale straight away instead of leaving them out of stock for the rest of the window. Cancelling a checkout that is already paid returns 409, and a checkout that lapses on its own is released by us.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Path parameters

refpathrequired

The checkout ref.

Returns StoreCheckoutSessionView

POST/store/checkouts/REF/cancel
curl -X POST https://api.frontdesk.africa/v1/store/checkouts/REF/cancel \
  -H "Authorization: Bearer fd_sk_live_xxx"
const res = await fetch('https://api.frontdesk.africa/v1/store/checkouts/REF/cancel', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
  },
});
const data = await res.json();
Sample response201 Created
{
  "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "status": "open",
  "mode": "live",
  "hostedUrl": "https://beauty-pro-1.sme.pro/checkout/01a00e95-873c-7056-b31a-aad4c70191e0",
  "returnUrl": "https://yourbrand.com/thanks",
  "currency": "NGN",
  "orderRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "expiresAt": "2026-08-18T09:20:11.000Z",
  "completedAt": "2026-08-18T09:20:11.000Z",
  "createdAt": "2026-08-18T09:20:11.000Z"
}

Orders

Your own store orders, newest change last — built for keeping a local copy (a Supabase mirror, a dashboard) in sync

The key's env picks the set: a live key never sees test orders and a test key never sees live ones, and `test` is also on every row. In-progress savings plans (`saving`) never appear — they are not orders yet. A page shorter than `limit` means you are caught up. Deletions arrive as the order.deleted webhook (today that is sandbox retention removing 90-day-old test orders). This is a sync and back-office read: to confirm a single purchase, keep using GET /v1/store/checkouts/:ref.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Query parameters

updatedSincequeryoptional

ISO 8601. Only orders changed at or after this moment, ordered by updatedAt asc. Feed the largest updatedAt you received back as the next cursor; the >= comparison re-sends the boundary row, so upsert by ref.

limitqueryoptional

Page size, 1 to 200. Defaults to 100.

Returns StoreOrderRecord[]

GET/store/orders
curl https://api.frontdesk.africa/v1/store/orders \
  -H "Authorization: Bearer fd_sk_live_xxx"
const res = await fetch('https://api.frontdesk.africa/v1/store/orders', {
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
  },
});
const data = await res.json();
Sample response200 OK
[
  {
    "ref": "019f6f85-efbd-75e4-83e4-c2778a318189",
    "shortReference": "string",
    "status": "paid",
    "fulfillmentStatus": "fulfilled",
    "test": false,
    "currency": "NGN",
    "subtotalMinor": 200000,
    "deliveryFeeMinor": 200000,
    "totalMinor": 200000,
    "buyer": {
      "name": "Matte Lipstick",
      "email": "buyer@example.com",
      "phone": "+2348120700080"
    },
    "needsDelivery": false,
    "shipping": {
      "address1": "string",
      "address2": "string",
      "city": "Lagos",
      "state": "Lagos",
      "country": "Nigeria",
      "zoneRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
      "zoneName": "Matte Lipstick"
    },
    "lines": [
      {
        "productRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
        "productName": "Matte Lipstick",
        "variantRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
        "variantName": "Matte Lipstick",
        "imageUrl": "https://cdn.frontdesk.africa/media/lipstick.webp",
        "quantity": 1,
        "unitPriceMinor": 200000,
        "subtotalMinor": 200000
      }
    ],
    "paidAt": "2026-08-18T09:20:11.000Z",
    "createdAt": "2026-08-18T09:20:11.000Z",
    "updatedAt": "2026-08-18T09:20:11.000Z"
  }
]

One-time passwords

Send a one-time password to your own customer over WhatsApp, from your own connected WhatsApp number

This costs money on every send, whether or not your customer ever reads it: an authentication template is business-initiated, so the free 24-hour reply window does not apply. The response quotes what was charged. WhatsApp OTP in Nigeria is several times the price of SMS — the reasons to choose it are deliverability and one-tap copy, not cost, and an international destination costs roughly 11x the Nigerian rate. THE CODE IS NEVER RETURNED, even when we minted it: hold the challengeRef and call /v1/store/otp/verify. `to` comes back masked. Before this works the workspace needs a connected WhatsApp number and an approved authentication template (Set up OTP in the portal), and the destination's country on its allow-list, which starts as the workspace's own country only. Throttled per destination independently of this key's rate limit: 60 seconds between codes to one number, 5 an hour, 10 a day. Each KEY also has its own ceiling on total sends per rolling 24 hours (100 by default, settable per key in the portal), so one leaked credential cannot spend the workspace's whole daily WhatsApp allowance and take your other keys down with it. Nothing is charged when a guard refuses. WITH `sender: 'frontdesk'` none of the setup applies — no number to connect, no business verification, no template — but the code identifies FrontDesk rather than you, and the price is exactly the same. If we cannot send from our number it returns 503 OTP_SENDER_UNAVAILABLE and charges nothing; it never quietly falls back to yours, because a code from an unexpected number that you paid for is worse than a refusal you can read.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Body parameters

tobodyrequired

Your customer's number in full international format, e.g. +2348012345678.

codebodyoptional

Your own code, digits only, up to 15 characters. Omit it and we mint a 6-digit one — which is also the ONLY way /v1/store/otp/verify can work, because a code you chose is one we never see in the clear.

purposebodyoptional

Your own label for what this code was for ("login", "payout"). Stored for your send log, never shown to the customer.

expiresInMinutesbodyoptional

How long the code stays valid. Default 10, maximum 60.

templateRefbodyoptional

Which authentication template to send. Omit it when the channel has one, which is what 'Set up OTP' creates.

senderbodyoptional

Which number carries the code: 'own' (your connected WhatsApp number) or 'frontdesk' (ours). Omit it and your workspace's own setting decides, which is 'own' unless you changed it — so an integration written before this existed is unaffected. On 'frontdesk' the code arrives from FrontDesk rather than your business, because an authentication template's wording is fixed by WhatsApp and has no place for your name. Cannot be combined with templateRef: the template on our number is ours.

Returns StoreOtpSendView

POST/store/otp/send
curl -X POST https://api.frontdesk.africa/v1/store/otp/send \
  -H "Authorization: Bearer fd_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "value",
  "code": "optional",
  "purpose": "optional",
  "expiresInMinutes": "optional",
  "templateRef": "optional",
  "sender": "optional"
}'
const res = await fetch('https://api.frontdesk.africa/v1/store/otp/send', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "to": "value",
    "code": "optional",
    "purpose": "optional",
    "expiresInMinutes": "optional",
    "templateRef": "optional",
    "sender": "optional"
  }),
});
const data = await res.json();
Sample response201 Created
{
  "challengeRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "to": "+234801****678",
  "expiresAt": "2026-08-18T09:20:11.000Z",
  "chargeRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "quote": {
    "category": "authentication",
    "market": "Nigeria",
    "baseUsdMicros": 1,
    "markupPct": 1,
    "chargeMinor": 200000,
    "currency": "NGN",
    "fxRate": 1
  },
  "sender": "frontdesk"
}

Check a code your customer typed against a challenge we minted

200 means verified. A failure is a RESULT rather than an API error, so it answers 422 with {verified:false, reason} instead of the usual error envelope — branch on `verified` either way. `reason` is invalid, expired or too_many_attempts. An unknown challengeRef, an expired challenge and a wrong code all answer identically on purpose, so this cannot be used to discover which refs exist. Five wrong guesses burns the challenge; so does one right one, because a code is good exactly once — send a new one rather than retrying. If you supplied your own `code` on send, this answers 409: we never saw it in the clear, and telling you `verified: true` for a check we cannot actually make would be worse than saying so.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Body parameters

challengeRefbodyrequired

The challengeRef from /v1/store/otp/send.

codebodyrequired

Exactly what your customer typed.

Returns StoreOtpVerifyView

POST/store/otp/verify
curl -X POST https://api.frontdesk.africa/v1/store/otp/verify \
  -H "Authorization: Bearer fd_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
  "challengeRef": "value",
  "code": "value"
}'
const res = await fetch('https://api.frontdesk.africa/v1/store/otp/verify', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "challengeRef": "value",
    "code": "value"
  }),
});
const data = await res.json();
Sample response201 Created
{
  "verified": false,
  "reason": "expired"
}

Other

The payment rails this workspace can charge, so you can render your own picker

Headers

Authorizationstringrequired

Set to Bearer fd_pk_live_xxx.

Originstringrequired

Must be one of the origins registered on the key. Sent automatically by the browser.

Query parameters

currencyqueryoptional

Defaults to the store primary.

Returns string[]

GET/store/payment-methods
curl https://api.frontdesk.africa/v1/store/payment-methods \
  -H "Authorization: Bearer fd_pk_live_xxx" \
  -H "Origin: https://yourbrand.com"
const res = await fetch('https://api.frontdesk.africa/v1/store/payment-methods', {
  headers: {
    Authorization: 'Bearer fd_pk_live_xxx',
    'Origin': 'https://yourbrand.com',
  },
});
const data = await res.json();
Sample response200 OK
[
  "paystack"
]

Attach your own signed-in user to this workspace's contacts, so their purchases and plans line up under one person

Only link identities your own auth verified (a confirmed email or OTP-verified phone) — this endpoint trusts your assertion because the key already proves you are the merchant's backend. Idempotent per externalRef: repeat calls return the SAME link and alreadyLinked=true, and the response shape is identical whether the contact existed before or not, so nothing here can be used to probe who is already a customer. Fires the customer.linked webhook exactly once, on the link. With a TEST key this validates fully and returns synthetic test_cus_/test_con_ refs that resolve to nothing, creating no contact.

Headers

Authorizationstringrequired

Set to Bearer fd_sk_live_xxx.

Body parameters

emailbodyrequired

The email YOUR auth system verified. Never a guessed or unverified address.

externalRefbodyrequired

Your own user id (e.g. a Supabase Auth user id). The idempotency key: the same value always returns the same link.

namebodyoptional

Display name. Fills an empty contact name, never overwrites a set one.

phonebodyoptional

Optional phone for the contact.

Returns StoreCustomerLinkView

POST/store/customers/link
curl -X POST https://api.frontdesk.africa/v1/store/customers/link \
  -H "Authorization: Bearer fd_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "value",
  "externalRef": "value",
  "name": "optional",
  "phone": "optional"
}'
const res = await fetch('https://api.frontdesk.africa/v1/store/customers/link', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer fd_sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "email": "value",
    "externalRef": "value",
    "name": "optional",
    "phone": "optional"
  }),
});
const data = await res.json();
Sample response201 Created
{
  "customerRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "contactRef": "019f6f85-efbd-75e4-83e4-c2778a318189",
  "alreadyLinked": false
}