All guides

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/branding

Packages (GET)

https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/packages

Programs (GET)

https://pass.qrtick.com/api/facility/%7Bfacility-slug%7D/programs

Join URL

https://pass.qrtick.com/portal/%7Bfacility-slug%7D/join

Portal URL

https://pass.qrtick.com/portal/%7Bfacility-slug%7D

curl 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:

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 + 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).

MethodURL
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 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). /branding, /packages, and /programs 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,
    "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": []
        }
      }
    ]
  }
}
  • 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 marketing share, portal, join, and member dashboard. links.landing is the URL staff share via QR (custom marketing site when configured in Headless API → Landing page; otherwise the same as links.portal).
  • endpoints.packages is the full public packages feed (same shape as GET /packages).
  • endpoints.programs is the public programs feed when programsEnabled is true; otherwise null.
  • offers.dayPass surfaces the day-pass product when dayPassesEnabled is true. Use offers.dayPass.package.joinUrl (includes package_id) 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 .

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.

ParamValuesNotes
audienceyouth, adult, allAliases: childyouth, everyoneall
scheduleKindweekly, monthly, otherWeekly/monthly term programs vs drop-in/custom
otherSubtypedrop_in, customOnly 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):

ValueLabelMeaning
youthYouth (Family)Child/family programs — enroll via guardian dashboard
adultAdultAdult-only programs
allAll AgesOpen to youth and adults

Schedule kind (scheduleKind):

ValueLabelSupporting fields
weeklyWeeklydurationWeeks, weekdays, startTime, endTime
monthlyMonthlydurationMonths, weekdays, startTime, endTime
otherDrop-in & customSee otherSubtype

Other subtype (otherSubtype) — present only when scheduleKind is other:

ValueLabelRule
drop_inDrop-inSingle session (sessionCount === 1)
customCustom session blockMulti-session block (sessionCount > 1)

Program classification (classification):

ValueLabelRule
full_programFull ProgramTerm program where registration is for the full duration (e.g. durationWeeks > 1 or monthly)
individual_weeksIndividual WeeksWeekly program where registration is on a pick-a-week basis (durationWeeks === 1)
day_passDay PassSingle drop-in session
special_bundleSpecial BundleCustom 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

StatusMeaning
400Invalid query filter
404Unknown 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)

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)