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:
| Approach | Best for | Where to work |
|---|---|---|
| Block editor | No-code pages on the hosted portal | Portal admin → Editor (/portal/{facility-slug}/admin/editor) |
| Hosted landing | Default 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 pages | GET/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).
| Method | URL |
|---|---|
| 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
fetchfrom 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.
GET — branding, content, links
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": []
}
}
brandingcomes from the organization (logo, colors, social).content.sectionsis the published block list from the landing page editor (slugindexfor that facility). An empty array means no blocks published yet—your site should still applybrandingand link tolinks.join.linksare absolute URLs for portal, join, and member dashboard.endpoints.packagesis the full public packages feed (same shape asGET /packages).offers.dayPasssurfaces the day-pass product whendayPassesEnabledis true. Useoffers.dayPass.package.joinUrlfor a pre-selected join flow.offers.featuredlists other joinable packages (memberships, promos, visit packs).
Errors
| Status | Meaning |
|---|---|
404 | Unknown facility-slug |
500 | Server 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)
| Field | Required | Notes |
|---|---|---|
firstName | Yes | |
email | Yes | |
lastName | No | Defaults to empty |
phoneNumber | No |
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
| Status | Meaning |
|---|---|
400 | Missing firstName or email |
404 | Unknown facility |
500 | Insert or email failure |
Content blocks (content.sections)
Each item in sections has a type field. Supported types:
type | Purpose |
|---|---|
nav | Top navigation, logo, links, primary CTA |
hero | Headline, subcopy, primary/secondary CTAs, optional background image |
showcase | Grid of feature cards |
pricing | Plan name, price, feature list, CTA |
coaches | Staff/coach cards |
cta | Full-width call-to-action band |
footer | Copyright, 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
- Publish content in the block editor so
GETreturns the sections you expect. - Set organization branding (logo, colors) in organization settings—those flow into
branding. - Test POST with a test email; confirm the lead appears under Leads and the welcome email arrives.
- Share docs with agencies: this page and the facility slug +
{origin}. - Member conversion — leads are prompted to finish at
links.join; full membership still follows your Join → Pay → Visit flow.
Related
- 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)