Technical
For coding agents
Headless API agent brief
Copy the prompt below into Cursor, Claude Code, Copilot, or another agent building your marketing site. Open your facility control centre for URLs with your actual slug filled in.
Replace {facility-slug} in the prompt, or use Facility → Headless API in the control centre for resolved endpoints.
You are integrating a custom marketing site with Member Access OS using the Headless Landing API.
Facility: {facility-slug}
Facility slug: {facility-slug}
API origin: https://pass.qrtick.com
## Endpoints (no authentication required)
- Branding + content + offers (GET/POST): https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/branding
- Packages + day-pass rules (GET): https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/packages
- Programs (GET): https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/programs
Query filters: audience=youth|adult|all, scheduleKind=weekly|monthly|other, otherSubtype=drop_in|custom
Each program includes title, description (max 300 chars), bannerUrl (2:1 JPEG when set), priceCents, currency, scheduleSummary, audience, enrolledCount.
Member handoff URLs:
- Join: https://pass.qrtick.com/portal/%7Bfacility-slug%7D/join
- Dashboard: https://pass.qrtick.com/portal/%7Bfacility-slug%7D/dashboard
- Hosted portal: https://pass.qrtick.com/portal/%7Bfacility-slug%7D
Full documentation: https://pass.qrtick.com/docs/headless-landing-api
Replace {facility-slug} with the venue slug, or open Facility → Headless API in the control centre for resolved URLs.
## Implementation rules
1. Prefer server-side fetch (Next.js route handler, server component, or edge function). Browser fetch from another domain requires HEADLESS_LANDING_ALLOWED_ORIGINS on the Member Access OS deployment.
2. Start with GET branding — use branding.logoUrl, brandColorPrimary, brandColorAccent, content.sections, links, endpoints, and offers.dayPass / offers.featured.
3. Use GET packages when you need the full catalog or day-pass purchaseRules.selectableDates.
4. Use GET programs for camps/classes listings; honor isVisible programs only (API already filters hidden).
5. POST lead capture to the branding URL with JSON: { "firstName", "email", "lastName?", "phoneNumber?" }.
6. Do not treat the facility slug as a secret. Link checkout and enrollment to links.join or package joinUrl values from the API.
7. Handle empty content.sections gracefully — still apply branding and CTA to links.join.
## Suggested first tasks
- Fetch branding and render hero from content.sections (fallback to branding.name + links.join).
- Surface offers.dayPass and offers.featured with join deep links.
- List programs with scheduleSummary and description on a schedule page.
- Add a server-side proxy route if the marketing site is on a different origin and needs client-side fetch.API endpoints
Branding (GET/POST)
https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/brandingPackages (GET)
https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/packagesPrograms (GET)
https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/programsJoin URL
https://pass.qrtick.com/portal/%7Bfacility-slug%7D/joinPortal URL
https://pass.qrtick.com/portal/%7Bfacility-slug%7Dcurl examples
# Branding, content, offers
curl -s "https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/branding" | jq .
# Packages
curl -s "https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/packages" | jq .
# Programs
curl -s "https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/programs" | jq .
# Youth weekly programs
curl -s "https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/programs?audience=youth&scheduleKind=weekly" | jq .
# Lead capture (POST)
curl -s -X POST "https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/branding" \
-H "Content-Type: application/json" \
-d '{"firstName":"Alex","email":"alex@example.com"}'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 + Headless API in the facility control centre |
Coding agents (Cursor, Claude Code, Copilot)
At /docs/headless-landing-api use Copy agent prompt for a generic brief (replace {facility-slug} with your venue slug).
For resolved URLs with your actual slug, open /facility/{facility-slug}/headless-api in the facility control centre (Configuration → Headless API). That page includes:
- Copy-ready agent prompt with your slug, endpoints, and join URLs filled in
- Per-endpoint copy buttons and curl examples
- Link back to this full reference
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 |
| GET (youth/adult programs) | {origin}/api/facility/{facility-slug}/programs |
| 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). /branding, /packages, and /programs 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,
"programsEnabled": true
},
"branding": {
"name": "Tye Fitness",
"logoUrl": "https://…",
"brandColorPrimary": "#1935A5",
"brandColorAccent": "#6366f1",
"email": "hello@tyefitness.com",
"whatsappNumber": null,
"instagramHandle": "tyefitness"
},
"content": {
"sections": []
},
"links": {
"landing": "https://pass.qrtick.com/portal/tye-fitness",
"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",
"programs": "https://pass.qrtick.com/api/facility/tye-fitness/programs"
},
"offers": {
"dayPass": {
"enabled": true,
"package": {
"id": "…",
"name": "Day Pass",
"description": "Single-day gym access. Valid 5am–11pm on your scheduled visit day after staff approval or payment confirmation.",
"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": [
"Create an account on this page to purchase — day passes are not sold without signup",
"Choose one or more visit days (today through 4 days ahead)",
"5am–11pm facility access on each selected day · price is per day"
]
}
},
"purchaseRules": {
"summary": ["…same as memberDisplay.detailLines…"],
"timeZone": "America/Jamaica",
"closingHour": 23,
"cutoffHoursBeforeClose": 2,
"maxAdvanceDays": 4,
"selectableDates": []
}
},
"featured": [
{
"id": "…",
"name": "Monthly Membership",
"priceCents": 1000000,
"currency": "JMD",
"billingModel": "recurring",
"joinUrl": "https://pass.qrtick.com/portal/tye-fitness/join?package_id=…",
"memberDisplay": {
"headline": "JMD 10,000 per month",
"detailLines": []
}
}
]
}
}
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 marketing share, portal, join, and member dashboard.links.landingis the URL staff share via QR (custom marketing site when configured in Headless API → Landing page; otherwise the same aslinks.portal).endpoints.packagesis the full public packages feed (same shape asGET /packages).endpoints.programsis the public programs feed whenprogramsEnabledis true; otherwisenull.offers.dayPasssurfaces the day-pass product whendayPassesEnabledis true. Useoffers.dayPass.package.joinUrl(includespackage_id) for 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 .
Response shape
{
"packages": [
{
"id": "…",
"name": "Day Pass",
"priceCents": 100000,
"currency": "JMD",
"billingModel": "day_pass",
"accessDurationHours": 18,
"memberDisplay": {
"headline": "JMD 1,000 day pass",
"detailLines": ["5am–11pm facility access on each selected day · price is per day", "…"]
}
}
],
"dayPassPurchase": {
"timeZone": "America/Jamaica",
"closingHour": 23,
"cutoffHoursBeforeClose": 2,
"maxAdvanceDays": 4,
"selectableDates": []
},
"captureMemberDateOfBirth": false,
"visitCreditsUseCurrencyBalance": false,
"memberDisclaimerText": null,
"memberDisclaimerVersion": 0
}
Pre-select a package on the hosted join page with ?package_id=<uuid> (also embedded in offers.*.joinUrl from the branding feed).
Day-pass access window: For each scheduled visit day, valid_from / valid_until on the member pass are facility-local 05:00 through 23:00 (11pm), not activation + 24 hours. accessDurationHours on the package (typically 18) reflects that operating span for display; purchase cutoff uses closingHour (23) minus cutoffHoursBeforeClose (2 → same-day sales end at 9pm facility time).
GET — programs (youth, adult, schedules)
Use this endpoint when the facility has programs enabled (camps, classes, youth activities). Enrollment still happens on the hosted portal at links.dashboard; this feed is for marketing pages and schedule listings.
curl -s "https://pass.qrtick.com/api/facility/tye-fitness/programs" | jq .
When programsEnabled is false, the route returns { "programs": [] }.
Query filters
All filters are optional and combinable.
| Param | Values | Notes |
|---|---|---|
audience | youth, adult, all | Aliases: child → youth, everyone → all |
scheduleKind | weekly, monthly, other | Weekly/monthly term programs vs drop-in/custom |
otherSubtype | drop_in, custom | Only applies when scheduleKind is other (or omitted while filtering drop-in/custom) |
Examples:
# Youth-only programs
curl -s ".../programs?audience=child"
# Weekly term programs
curl -s ".../programs?scheduleKind=weekly"
# Custom multi-session blocks (not drop-ins)
curl -s ".../programs?otherSubtype=custom"
Invalid filter combinations return 400 with an error message.
Response shape
{
"programs": [
{
"id": "…",
"title": "Summer Swim Camp",
"description": "Four-week swim camp with daily instruction and pool games.",
"bannerUrl": "https://go.qrtick.com/facilities/…/banner.jpg",
"capacity": 24,
"priceCents": 2500000,
"currency": "JMD",
"minAge": 6,
"maxAge": 12,
"enrollmentStart": "2026-05-01T00:00:00.000Z",
"enrollmentEnd": "2026-06-01T00:00:00.000Z",
"audience": "youth",
"audienceLabel": "Youth (Family)",
"scheduleKind": "weekly",
"scheduleKindLabel": "Weekly",
"otherSubtype": null,
"otherSubtypeLabel": null,
"classification": "full_program",
"classificationLabel": "Full Program",
"programStartDate": "2026-06-15T00:00:00.000Z",
"programEndDate": "2026-07-10T00:00:00.000Z",
"startTime": "09:00",
"endTime": "12:00",
"weekdays": [1, 3, 5],
"durationWeeks": 4,
"durationMonths": null,
"sessionCount": null,
"scheduleNote": null,
"scheduleSummary": "Mon, Wed & Fri · 09:00 - 12:00 · 4 weeks · starts Jun 15, 2026",
"enrolledCount": 8
}
]
}
Program type fields
Audience (audience):
| Value | Label | Meaning |
|---|---|---|
youth | Youth (Family) | Child/family programs — enroll via guardian dashboard |
adult | Adult | Adult-only programs |
all | All Ages | Open to youth and adults |
Schedule kind (scheduleKind):
| Value | Label | Supporting fields |
|---|---|---|
weekly | Weekly | durationWeeks, weekdays, startTime, endTime |
monthly | Monthly | durationMonths, weekdays, startTime, endTime |
other | Drop-in & custom | See otherSubtype |
Other subtype (otherSubtype) — present only when scheduleKind is other:
| Value | Label | Rule |
|---|---|---|
drop_in | Drop-in | Single session (sessionCount === 1) |
custom | Custom session block | Multi-session block (sessionCount > 1) |
Program classification (classification):
| Value | Label | Rule |
|---|---|---|
full_program | Full Program | Term program where registration is for the full duration (e.g. durationWeeks > 1 or monthly) |
individual_weeks | Individual Weeks | Weekly program where registration is on a pick-a-week basis (durationWeeks === 1) |
day_pass | Day Pass | Single drop-in session |
special_bundle | Special Bundle | Custom multi-session bundle |
Use scheduleSummary for display copy; it mirrors the formatted schedule shown in the member portal. scheduleNote holds freeform staff notes when set. description is optional marketing copy (max 300 characters) for landing pages and listings. bannerUrl is a 2:1 JPEG (≤60KB) when the program has a banner uploaded.
Errors
| Status | Meaning |
|---|---|
400 | Invalid query filter |
404 | Unknown facility-slug |
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)