All guides

Technical

Headless landing page API

Use this guide when you want a custom marketing site (your domain, your stack) that still reads branding and page content from Member Access OS and optionally captures early-interest leads.

For most facilities, the built-in options are enough:

ApproachBest forWhere to work
Block editorNo-code pages on the hosted portalPortal admin → Editor (/portal/{facility-slug}/admin/editor)
Hosted landingDefault public page at your portal URL/portal/{facility-slug} (same blocks as the editor)
Headless API (this doc)Agencies, Next.js/React sites, AI-generated pagesGET/POST below + optional AI Tools in admin

Base URL and facility slug

Replace {facility-slug} with the facility’s slug (e.g. tye-fitness). Replace {origin} with your deployment root (production default: https://pass.qrtick.com).

MethodURL
GET (content + branding + offers){origin}/api/facility/{facility-slug}/branding
GET (packages + day-pass rules){origin}/api/facility/{facility-slug}/packages
POST (interest / lead capture){origin}/api/facility/{facility-slug}/branding

Authentication: None. These routes are public read/write for lead capture—treat the slug as a public identifier, not a secret.

CORS: Browser calls from other origins are not enabled by default. Prefer:

  • Server-side fetch from your Next.js app, or
  • A same-origin proxy route on your site that forwards to Member Access OS

To allow browser fetch from a standalone marketing site, set on the Member Access OS deployment:

HEADLESS_LANDING_ALLOWED_ORIGINS=https://www.example.com,https://example.com

Comma-separated origins only (no trailing paths). Both /branding and /packages honor this variable and respond to OPTIONS preflight.


Example

curl -s "https://pass.qrtick.com/api/facility/tye-fitness/branding" | jq .

Response shape

{
  "facility": {
    "id": "…",
    "name": "Tye Fitness Gym",
    "slug": "tye-fitness",
    "currency": "JMD",
    "timezone": "America/Jamaica",
    "dayPassesEnabled": true
  },
  "branding": {
    "name": "Tye Fitness",
    "logoUrl": "https://…",
    "brandColorPrimary": "#1935A5",
    "brandColorAccent": "#6366f1",
    "whatsappNumber": null,
    "instagramHandle": "tyefitnetss"
  },
  "content": {
    "sections": []
  },
  "links": {
    "portal": "https://pass.qrtick.com/portal/tye-fitness",
    "join": "https://pass.qrtick.com/portal/tye-fitness/join",
    "dashboard": "https://pass.qrtick.com/portal/tye-fitness/dashboard"
  },
  "endpoints": {
    "packages": "https://pass.qrtick.com/api/facility/tye-fitness/packages"
  },
  "offers": {
    "dayPass": {
      "enabled": true,
      "package": {
        "id": "…",
        "name": "Day Pass",
        "priceCents": 100000,
        "currency": "JMD",
        "billingModel": "day_pass",
        "accessDurationHours": 18,
        "joinUrl": "https://pass.qrtick.com/portal/tye-fitness/join?package_id=…",
        "memberDisplay": {
          "headline": "JMD 1,000 day pass",
          "detailLines": ["…"]
        }
      },
      "purchaseRules": {
        "summary": ["…"],
        "timeZone": "America/Jamaica",
        "closingHour": 23,
        "cutoffHoursBeforeClose": 2,
        "maxAdvanceDays": 4,
        "selectableDates": []
      }
    },
    "featured": []
  }
}
  • branding comes from the organization (logo, colors, social).
  • content.sections is the published block list from the landing page editor (slug index for that facility). An empty array means no blocks published yet—your site should still apply branding and link to links.join.
  • links are absolute URLs for portal, join, and member dashboard.
  • endpoints.packages is the full public packages feed (same shape as GET /packages).
  • offers.dayPass surfaces the day-pass product when dayPassesEnabled is true. Use offers.dayPass.package.joinUrl for a pre-selected join flow.
  • offers.featured lists other joinable packages (memberships, promos, visit packs).

Errors

StatusMeaning
404Unknown facility-slug
500Server error

GET — packages and day-pass rules

Use this endpoint when you only need joinable products (or want the raw package list without CMS blocks).

curl -s "https://pass.qrtick.com/api/facility/tye-fitness/packages" | jq .

Pre-select a package on the hosted join page with ?package_id=<uuid> (also embedded in offers.*.joinUrl from the branding feed).


POST — capture interest (lead)

Creates a lead (not a full member). Staff review leads in Portal admin → Leads. The visitor receives a branded email with a link to complete registration at links.join.

Body (JSON)

FieldRequiredNotes
firstNameYes
emailYes
lastNameNoDefaults to empty
phoneNumberNo

Example

curl -s -X POST "https://pass.qrtick.com/api/facility/tye-fitness/branding" \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Alex","lastName":"Rivera","email":"alex@example.com","phoneNumber":"+18765551234"}'

Success

{ "success": true, "message": "Registration successful" }

Errors

StatusMeaning
400Missing firstName or email
404Unknown facility
500Insert or email failure

Content blocks (content.sections)

Each item in sections has a type field. Supported types:

typePurpose
navTop navigation, logo, links, primary CTA
heroHeadline, subcopy, primary/secondary CTAs, optional background image
showcaseGrid of feature cards
pricingPlan name, price, feature list, CTA
coachesStaff/coach cards
ctaFull-width call-to-action band
footerCopyright, columns, social, links

Minimal example (two blocks)

{
  "sections": [
    {
      "type": "hero",
      "heading": "Train with purpose",
      "subheading": "Modern equipment and expert coaching.",
      "primaryCtaLabel": "Join",
      "primaryCtaLink": "/portal/tye-fitness/join",
      "secondaryCtaLabel": "Member sign in",
      "secondaryCtaLink": "/login?next=/portal/tye-fitness/dashboard"
    },
    {
      "type": "footer",
      "copyright": "© 2026 Tye Fitness",
      "links": [
        { "label": "Join", "href": "/portal/tye-fitness/join" }
      ]
    }
  ]
}

Use relative paths for in-portal links (/portal/..., /login?next=...) or absolute URLs for external assets. The hosted portal renderer maps these types to React components; your custom site should handle the same type values or ignore unknown types.

Field reference (by type)

nav: logoUrl?, links[] (label, href), ctaLabel, ctaLink

hero: eyebrow?, heading, accentHeading?, subheading, primaryCtaLabel, primaryCtaLink, secondaryCtaLabel?, secondaryCtaLink?, backgroundImageUrl?

showcase: eyebrow?, title, linkLabel?, linkHref?, items[] — each item: title, description, imageUrl?, tag?, size (small | medium | large), variant? (image | solid)

pricing: title, subtitle, features[] (text), valuePropItems? (icon, title, description), planName, price, priceSuffix, ctaLabel, ctaLink

coaches: eyebrow?, title, coaches[] (name, role, tag?, credentials?, imageUrl)

cta: heading, ctaLabel, ctaLink, backgroundText?

footer: logoUrl?, tagline?, copyright, socialIcons?, columns? (title, links[]), newsletterPlaceholder?, links[]

New fields should be optional with sensible defaults so existing landing pages keep working.


Next.js fetch example (server component)

const slug = 'tye-fitness'
const origin = process.env.NEXT_PUBLIC_ROOT_DOMAIN?.includes('localhost')
  ? `http://${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`
  : `https://${process.env.NEXT_PUBLIC_ROOT_DOMAIN ?? 'pass.qrtick.com'}`

const res = await fetch(`${origin}/api/facility/${slug}/branding`, {
  next: { revalidate: 60 },
})
if (!res.ok) notFound()
const data = await res.json()

Render data.branding for theme tokens and iterate data.content.sections by type, or send sections to your AI/layout layer.


Copy-paste AI prompts

Replace {facility-slug} and {origin} before pasting into ChatGPT, Claude, Cursor, etc. The same prompts are available under Portal admin → AI Tools for your signed-in facility.

Build a full landing page

I want to build a high-conversion landing page for my facility.

My brand is associated with the slug: "{facility-slug}"
Data source: "{origin}/api/facility/{facility-slug}/branding"

Please generate a modern, premium landing page using Next.js and Tailwind CSS.
Focus:
1. Emotionally resonant copy: Speak to the member's transformation, productivity, or wellness goals.
2. Exclusive vibe: Use brandColorPrimary and brandColorAccent from the API for a VIP atmosphere.
3. Seamless sign-up: Wire an interest form that POSTs JSON { firstName, email, lastName?, phoneNumber? } to the same branding URL.
4. Social proof and trust: Member benefits, space highlights, FAQ.
5. Instant connection: Logo and branding front and center.
6. If content.sections is non-empty, render each block by its type field; otherwise design from branding + links.join.

Design tokens and CSS

Help me define the digital personality of my space.
Facility slug: "{facility-slug}"
Color and brand data: "{origin}/api/facility/{facility-slug}/branding"

Generate design tokens and Tailwind classes for this facility (gym, pool, or co-working).
Include styles for primary CTAs, venue cards, and gradients using brandColorPrimary and brandColorAccent from the JSON.

Marketing copy

Act as a venue marketing strategist for facility slug "{facility-slug}".
Brand context: "{origin}/api/facility/{facility-slug}/branding"

Generate 3 variations of:
1. A "Join the early access list" headline with opening-day energy.
2. A welcome email for new leads that points them to complete registration.
3. Five founding-member perks (priority booking, exclusive access, etc.)

Focus on community, results, and the premium experience of this space.

Render CMS blocks from GET

I have a Next.js app. Fetch "{origin}/api/facility/{facility-slug}/branding" on the server.
For each object in content.sections, switch on type (nav, hero, showcase, pricing, coaches, cta, footer) and render a matching React component.
Use branding.logoUrl, brandColorPrimary, and brandColorAccent as CSS variables.
Primary conversion: links.join for full membership; optional POST to the same URL for early-interest leads only.

Operations checklist

  1. Publish content in the block editor so GET returns the sections you expect.
  2. Set organization branding (logo, colors) in organization settings—those flow into branding.
  3. Test POST with a test email; confirm the lead appears under Leads and the welcome email arrives.
  4. Share docs with agencies: this page and the facility slug + {origin}.
  5. Member conversion — leads are prompted to finish at links.join; full membership still follows your Join → Pay → Visit flow.

  • Professional services — optional help for custom sites and journeys
  • Reading paths by role — marketing and implementation reading order
  • Portal AI Tools/portal/{facility-slug}/admin/tools (copy endpoint and prompts in-product)