Audience: Provider integration engineers building API endpoints for Recspert to consume.
Purpose: Documents the API surface Recspert needs from a partner provider system in order to integrate inventory, registrations, and transaction lifecycle.
Status: v1.1 — informed by Recspert's existing partner integrations as of 2026-05-20. Optional capabilities are marked.
Recspert is a discovery and registration aggregator. We pull a provider's full activity inventory into our search index, render it on web and mobile, and pass user registrations back to the provider's source-of-truth system in real time. The provider remains the system of record for activities, registrations, packages, and payment.
We classify all activity inventory into three entity types:
| Type | Code | Description | Examples |
|---|---|---|---|
| Class | C |
A single-occurrence drop-in instance, usually group exercise | Yoga at 6am Tuesday |
| Program | P |
A multi-session enrollment with a defined start/end date | 6-week swim lessons, summer camp week |
| Package | K |
A pre-purchased bundle of redemptions used to attend classes or schedule individual appointments | 10-class pass, 5-pack of personal training |
A complete integration supports all three. Partial integrations (e.g. C+P only, no K) are viable but limit user value.
| Phase | What works | What's missing |
|---|---|---|
| Catalog-only (read) | Discovery, search, share | No registration on Recspert — users deep-link out |
| Registration (read + write C/P) | Discovery + in-app class/program registration | No package purchase, no Wallet |
| Packages (read + write C/P/K) | Full activity + package commerce | Optional advanced features (reservations, audit) |
| Advanced | Reservations, capacity, membership sales | — |
This spec describes the Packages tier. Catalog-only is a valid subset that requires only Section 4.
Recspert authenticates to your APIs server-to-server.
POST {auth_base}/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=...
&client_secret=...
&scope=catalog.read registrations.write packages.write users.write
Response:
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600
}
All subsequent API calls carry Authorization: Bearer {access_token}.
| Scope | Purpose |
|---|---|
catalog.read |
List clubs, locations, categories, programs, classes, packages |
users.read |
Look up users for linkage and payment-method listing |
users.write |
Create users (guest + member) |
registrations.read |
Query registration status, roster |
registrations.write |
Create, cancel registrations |
packages.read |
List a user's owned packages |
packages.write |
Buy packages on behalf of a user |
payment.read |
List payment methods on file |
payment.write |
(Optional) Add new payment methods |
memberships.write |
(Optional) Sell memberships |
401 — Recspert will request a new token and retry once.client_id/client_secret. Do not require Recspert to broker tokens between organizations.Acceptable for catalog-read-only integrations, not for write paths.
YYYY-MM-DD (e.g. 2026-05-19)2026-05-19T14:30:00-04:00 or 2026-05-19T18:30:00ZAmerica/New_York) so Recspert can render correctly across DST and timezones. Returning local time without a timezone is ambiguous and unacceptable.2500 = $25.00), OR as decimal strings ("25.00"). Pick one and be consistent across the API. Avoid floats.currency: "USD" field on every price object even if USD-only — future-proofs multi-currency.?cursor=...&limit=100) over offset (?page=2).nextCursor: null (or omit) on the final page.Accept-Language header.These endpoints feed Recspert's search index. Recspert polls them on a schedule and caches results (cadence configurable per partner; webhook-driven sync per Section 12 is preferred where supported).
GET /clubs
A "club" is a top-level partner organization (e.g. a Y branch, a fitness studio chain, a campus rec department). One credential set may grant access to one or many clubs.
Response:
{
"clubs": [
{
"id": "12345",
"name": "Cleveland State University Recreation Center",
"shortName": "CSU Rec",
"websiteUrl": "https://www.csurec.com",
"imageUrl": "https://...",
"phone": "+1-216-555-0100",
"email": "rec@csu.edu",
"address": {
"street1": "2420 Chester Ave",
"city": "Cleveland",
"state": "OH",
"zip": "44115",
"country": "US"
},
"active": true
}
]
}
GET /clubs/{clubId}/locations
Locations are the physical facilities within a club where activities happen. A program lists one location; a class lists one location and timezone.
Response:
{
"locations": [
{
"id": "loc-456",
"clubId": "12345",
"name": "Main Pool",
"address": { /* same shape as club */ },
"geo": { "lat": 41.5017, "lng": -81.6792 },
"timezone": "America/New_York",
"imageUrl": "https://..."
}
]
}
GET /clubs/{clubId}/categories
Categories are how partners group activities (Aquatics, Group Fitness, Personal Training, Youth Sports, etc.). Recspert uses these for filter chips.
Response:
{
"categories": [
{ "id": "cat-1", "name": "Aquatics", "parentId": null },
{ "id": "cat-2", "name": "Swim Lessons", "parentId": "cat-1" }
]
}
GET /clubs/{clubId}/classes?from=2026-05-19&to=2026-08-19
Returns scheduled occurrences — one row per class instance. Recurring classes expand into N rows. Recspert calls this with a forward-looking date window.
Response (per class instance):
{
"classes": [
{
"id": "class-789",
"clubId": "12345",
"name": "Sunrise Yoga",
"description": "All-levels yoga to start your day.",
"categoryIds": ["cat-3"],
"locationId": "loc-456",
"instructors": [
{ "id": "instr-1", "name": "Jane Doe", "imageUrl": "https://..." }
],
"startTimestamp": "2026-05-20T10:00:00Z",
"endTimestamp": "2026-05-20T11:00:00Z",
"startTimeOfDay": "06:00",
"endTimeOfDay": "07:00",
"capacity": 20,
"availableSpots": 14,
"status": "open",
"registrationOpensAt": "2026-05-13T04:00:00Z",
"registrationClosesAt": "2026-05-20T09:45:00Z",
"pricing": {
"memberFee": "0.00",
"nonMemberFee": "15.00",
"currency": "USD"
},
"imageUrl": "https://...",
"tags": ["all-levels", "morning"]
}
],
"nextCursor": null
}
Required fields: id, clubId, name, locationId, startTimestamp, endTimestamp, status, pricing.
GET /clubs/{clubId}/programs?from=2026-05-19&to=2026-12-31
Programs are multi-session enrollments. Recspert ingests the program-level record plus its child sessions.
Response:
{
"programs": [
{
"id": "prog-321",
"clubId": "12345",
"name": "Learn-to-Swim Level 2 (Tues/Thurs)",
"description": "...",
"categoryIds": ["cat-2"],
"locationId": "loc-456",
"instructors": [ /* same shape as class */ ],
"startDate": "2026-06-02",
"endDate": "2026-07-09",
"registrationOpensAt": "2026-05-01T04:00:00Z",
"registrationClosesAt": "2026-06-01T23:59:00Z",
"pricing": { /* same shape as class */ },
"capacity": 12,
"availableSpots": 8,
"sessions": [
{
"id": "sess-1",
"startTimestamp": "2026-06-02T22:00:00Z",
"endTimestamp": "2026-06-02T22:45:00Z"
}
],
"ageRange": { "min": 6, "max": 8 },
"tags": ["youth", "swim"]
}
]
}
GET /clubs/{clubId}/packages
Packages are bundles of redemptions a user pre-buys. They link to one or more activities (Group Ex) or are appointment-based (PT, semi-private).
Response:
{
"packages": [
{
"id": "pkg-654",
"clubId": "12345",
"name": "10-Class Group Ex Pass",
"description": "...",
"packageType": 1,
"packageTypeName": "Group Exercise",
"rates": [
{
"id": "rate-1",
"sessionCount": 1,
"price": "25.00",
"currency": "USD"
},
{
"id": "rate-2",
"sessionCount": 10,
"price": "200.00",
"currency": "USD"
}
],
"memberPrice": "200.00",
"nonMemberPrice": "250.00",
"currency": "USD",
"canExpire": true,
"expirationPolicy": "12 months after purchase",
"includedActivities": [
{
"seasonName": "Season 2026",
"activities": ["Sunrise Yoga", "HIIT Bootcamp", "Spin Class"]
}
],
"imageUrl": "https://..."
}
]
}
packageType values:
1 — Group Exercise (redeemable against class registrations)2 — Individual / Personal Training (redeemed via in-person appointment)includedActivities is required for packageType=1 so users can confirm coverage before purchase.
GET /clubs/{clubId}/instructors
Recspert renders instructor cards on program/class detail pages.
Response:
{
"instructors": [
{
"id": "instr-1",
"name": "Jane Doe",
"bio": "...",
"imageUrl": "https://...",
"certifications": ["RYT-200", "CPR"]
}
]
}
GET /clubs/{clubId}/programs/{programId}
Returns the full program record including waiver content, custom field definitions, prerequisites.
Recspert maintains its own user accounts. When a Recspert user registers at your club for the first time, we create a corresponding user record on your system and store the partnerUserId linkage on our member record.
POST /clubs/{clubId}/users/search
{
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Smith",
"dateOfBirth": "1990-04-15"
}
Recspert calls this before creating a new user, to avoid duplicates when a Recspert user already has an account at the club from a prior in-person visit.
Field semantics: email is the primary lookup. firstName, lastName, and dateOfBirth are optional search criteria sent when available. Recspert does not collect dateOfBirth from all users (it is not required at Recspert account creation), so the search endpoint must support email-only matches as well as combined-criteria matches. Partners should not reject the search request when only email is provided.
Response:
{
"users": [
{
"id": "user-9999",
"clubId": "12345",
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Smith",
"isMember": true,
"membershipExpiresAt": "2026-12-31",
"dependents": [
{ "id": "user-9998", "firstName": "Kid", "relationship": "child" }
]
}
]
}
POST /clubs/{clubId}/users
{
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Smith",
"dateOfBirth": "1990-04-15",
"phone": "+1-555-555-5555",
"address": { /* standard address */ },
"emergencyContact": { "name": "...", "phone": "..." }
}
Required fields: email, firstName, lastName.
Optional fields: dateOfBirth, phone, address, emergencyContact. Recspert sends these when the user has provided them, but does not require them at Recspert signup. Partners must accept user-creation requests when these fields are absent. Partners may legitimately reject downstream actions that depend on the missing data (e.g., registration for an age-gated program where dateOfBirth is required) at the checkout/preview step — see Section 6 — but the user record itself must be creatable without them.
Response:
{ "id": "user-9999", "created": true }
POST /clubs/{clubId}/users/{userId}/dependents
{ "firstName": "Kid", "lastName": "Smith", "dateOfBirth": "2018-03-01", "relationship": "child" }
Field semantics: Same optionality as Section 5.2 — firstName and lastName required; dateOfBirth and relationship recommended but optional. Partners may require dateOfBirth at registration time for dependent eligibility checks (e.g., age-gated youth programs).
GET /clubs/{clubId}/users/{userId}/payment-methods
Response:
{
"paymentMethods": [
{
"id": "pm-111",
"type": "card",
"brand": "visa",
"last4": "4242",
"expMonth": 12,
"expYear": 2028,
"isDefault": true
}
]
}
POST /clubs/{clubId}/users/{userId}/payment-methods
{ /* PCI-safe tokenized card representation from your payment processor */ }
If you don't expose this, Recspert can only use cards added by the user directly on your portal.
The preview / checkout endpoint is the single most important write-path endpoint. It validates a proposed registration before money moves and returns a confirmed line-item breakdown.
POST /clubs/{clubId}/classes/{classId}/checkout
{
"classDate": "2026-05-20",
"userIds": ["user-9999", "user-9998"]
}
Response:
{
"checkout": {
"registrants": [
{
"userId": "user-9999",
"name": "Sam Smith",
"isMember": true,
"price": "0.00",
"discounts": [],
"fees": [],
"waiversRequired": ["waiver-abc"],
"customFieldsRequired": ["cf-1"],
"eligible": true
}
],
"subtotal": "0.00",
"tax": "0.00",
"total": "0.00",
"currency": "USD",
"requiredPayment": false,
"warnings": []
}
}
Critical fields:
eligible: false on any registrant — explain why (reason, code) so Recspert can surface a clear error.requiredPayment: true — Recspert will require a payment method on the next step.waiversRequired, customFieldsRequired — Recspert collects these before submitting.POST /clubs/{clubId}/programs/{programId}/checkout
{ "startDate": "2026-06-02", "userIds": [...] }
Same shape. Program previews include all sessions in scope.
POST /clubs/{clubId}/packages/{packageId}/checkout
{
"userId": "user-9999",
"rateId": "rate-2"
}
Response:
{
"checkout": {
"packageId": "pkg-654",
"packageName": "10-Class Group Ex Pass",
"rateId": "rate-2",
"sessionCount": 10,
"price": "200.00",
"tax": "0.00",
"total": "200.00",
"currency": "USD"
}
}
POST /clubs/{clubId}/classes/{classId}/registrations
{
"classDate": "2026-05-20",
"userIds": ["user-9999"],
"paymentMethodId": "pm-111",
"waiverSignatures": [
{ "waiverId": "waiver-abc", "signedAt": "2026-05-19T18:00:00Z", "signatureBase64": "..." }
],
"customFieldValues": [
{ "fieldId": "cf-1", "value": "Vegetarian" }
]
}
Response:
{
"registration": {
"id": "reg-555",
"classId": "class-789",
"classDate": "2026-05-20",
"status": "confirmed",
"participantList": [
{ "userId": "user-9999", "name": "Sam Smith", "status": "confirmed" }
],
"total": "0.00",
"paymentMethodId": "pm-111",
"createdAt": "2026-05-19T18:00:00Z"
}
}
Failure modes:
409 — class is full / no longer eligible. Include code and message for user display.422 — missing waivers, custom fields, or payment method.402 — payment declined. Include processor decline reason.POST /clubs/{clubId}/programs/{programId}/registrations
{ /* same shape; startDate instead of classDate */ }
DELETE /clubs/{clubId}/registrations/{registrationId}
GET /clubs/{clubId}/users/{userId}/registrations?from=2026-05-19
POST /clubs/{clubId}/users/{userId}/packages
{
"packageId": "pkg-654",
"rateId": "rate-2",
"startDate": "2026-05-19",
"paymentMethodId": "pm-111"
}
Critical: Reject the request with 422 if paymentMethodId is missing. At least one existing partner API exhibits the failure mode of accepting package buys without a payment method and silently creating an unpaid house-charge against the user's account. This has caused real operational issues. Do not replicate this behavior.
Response:
{
"packageInstance": {
"id": "pkginst-777",
"packageId": "pkg-654",
"userId": "user-9999",
"sessionsTotal": 10,
"sessionsRemaining": 10,
"purchasedAt": "2026-05-19T18:00:00Z",
"expiresAt": "2027-05-19",
"total": "200.00",
"paymentMethodId": "pm-111",
"status": "active"
}
}
GET /clubs/{clubId}/users/{userId}/packages
Response:
{
"packages": [
{
"id": "pkginst-777",
"packageId": "pkg-654",
"name": "10-Class Group Ex Pass",
"sessionsTotal": 10,
"sessionsRemaining": 7,
"purchasedAt": "2026-05-19T18:00:00Z",
"expiresAt": "2027-05-19",
"status": "active",
"redemptions": [
{ "registrationId": "reg-555", "redeemedAt": "2026-05-20T10:00:00Z" }
]
}
]
}
Two acceptable patterns; pick one and document it:
Pattern A (preferred): Auto-apply. When a class registration is created and the user owns an eligible package, the package is automatically debited. Surface this in the registration response (packageApplied: { packageInstanceId, sessionsDebited }).
Pattern B: Explicit. Recspert sends redemption: { packageInstanceId } in the registration body.
Existing partner integrations use Pattern A (with an opt-in keyword on the registration request to redeem a package). Pattern A is simpler to integrate and is preferred.
Lets Recspert sell memberships at signup, not just registrations. Skip if memberships are sold only in person.
GET /clubs/{clubId}/membership-groups
POST /clubs/{clubId}/users/{userId}/memberships
Full payload shape covers agreements, signature collection, and dependent-included pricing. Recspert can share its reference implementation against existing partner APIs during integration onboarding.
GET /clubs/{clubId}/classes/{classId}/roster?date=2026-05-20
GET /clubs/{clubId}/programs/{programId}/roster
Returns participants by user ID. Recspert uses this to render "Who's coming" features and check-in tools.
GET /clubs/{clubId}/reports/registrations?from=...&to=...&source=recspert
Lets either party reconcile Recspert-facilitated registrations against the partner's records. Useful for partnership reporting and dispute resolution. Requirement level depends on the commercial model agreed between Recspert and the partner (see Section 11).
This section covers two related but distinct capabilities: transaction lifecycle visibility (required for any write integration) and source tracking (recommended; specific contractual requirement depends on the commercial model). Recspert supports multiple commercial models and the integration spec is intentionally model-agnostic.
Required for any partner integration that includes registration or package writes. Recspert needs to know, with reasonable confidence and timeliness, the lifecycle state of every transaction it facilitates.
Every write endpoint (registration create, package buy, cancellation) must return a synchronous response confirming the operation's outcome and returning the canonical record. Recspert does not commit downstream actions (charging a service fee, issuing a Wallet pass, sending a confirmation email) until this confirmation is received. See Section 7 and Section 8 for required response shapes.
Recspert must be able to learn that a previously confirmed registration has been cancelled or refunded. Acceptable mechanisms:
registration.cancelled (Section 12)GET /clubs/{clubId}/users/{userId}/registrations?modifiedSince=... polled by Recspertcancelled (Recspert reconciles via periodic read)Required to enable Recspert to issue corresponding refunds, remove items from My Schedule, and update Wallet passes.
Recspert must be able to retrieve a registration record by its partner-issued ID or by a Recspert-supplied reference. Required for user-support escalations, dispute handling (where Recspert needs to evidence to a payment processor that a registration occurred), and reconciliation.
GET /clubs/{clubId}/registrations/{registrationId}
Recspert supports multiple commercial models with its partners:
Source tracking is contractually required only when the commercial model depends on it (revenue share, hybrid arrangements with revenue-share components). Under a pure platform-fee model, source tracking is strongly recommended but not contractually required, because Recspert's fee is collected at point-of-transaction and does not depend on partner-side reconciliation.
Independent of the commercial model, source tracking provides operational value to both parties:
source parameter on every write endpointPOST /clubs/{clubId}/classes/{classId}/registrations
{
/* ... */
"source": "recspert",
"sourceMetadata": {
"recspertMemberId": "rsm-12345",
"recspertReferralCode": "..."
}
}
The source should appear on:
GET /clubs/{clubId}/reports/registrations?source=recspert
Enables independent reconciliation by either party.
If the partner system can store a recspertMemberId on the user record (in addition to per-registration), even better — it preserves attribution across the user's lifetime at the partner, not just per-transaction.
The choice between platform-fee, revenue-share, or hybrid is made in the commercial agreement between Recspert and the partner, not in this integration spec. If the agreed model is revenue-share or hybrid, all 11.2 items become required rather than recommended, and the report endpoint in Section 10 is the contractual reconciliation surface.
Polling for catalog changes is wasteful and laggy. Webhooks let us stay in sync within seconds rather than hours.
| Event | Trigger | Payload |
|---|---|---|
class.created |
New class instance added | Full class object |
class.updated |
Capacity, time, instructor, or status change | Full class object |
class.cancelled |
Class cancelled | { classId, classDate, reason } |
program.created / .updated / .cancelled |
Same for programs | Full program object |
package.created / .updated |
Catalog package changes | Full package object |
registration.created |
New registration (any source) | Registration record incl. source |
registration.cancelled |
Cancellation | { registrationId, reason } |
user.updated |
Membership status change | User record |
POST JSON to a Recspert-provided URLX-Webhook-Signature: sha256={hmac} header for verification (shared secret per partner)5xx or timeout — at least 3 retries over 1 hour| Code | Meaning |
|---|---|
200 |
Success, body has data |
201 |
Created (on POST) |
400 |
Malformed request (Recspert bug — log it) |
401 |
Auth failed (Recspert will refresh token + retry once) |
403 |
Auth OK but not authorized for this resource |
404 |
Entity not found |
409 |
Conflict (class full, duplicate registration, etc.) |
422 |
Validation error (missing waiver, bad date, etc.) |
429 |
Rate limited (see Section 14) |
5xx |
Server error (Recspert will retry idempotent reads) |
{
"error": {
"code": "CLASS_FULL",
"message": "Class is at capacity",
"details": { "classId": "class-789", "capacity": 20, "registered": 20 },
"userMessage": "Sorry, this class just filled up. Try a later date."
}
}
code — machine-readable, stable. Recspert maps these to UI states.message — for our logs.userMessage — displayed verbatim to end users. Write it for the end user, not the integrator.429 behaviorReturn:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Recspert respects Retry-After and backs off.
Catalog endpoints under heavier rate limits are tolerable. Write endpoints (registrations, package buys) under heavy throttling translate directly to checkout failures Recspert users see.
Required. Recspert cannot integrate against production for QA.
Recspert provides a development environment for partner integration testing.
Sandbox endpoints:
| Resource | URL |
|---|---|
| Web app | https://dev.recspert.com |
| API base | https://dev-apiv2.recspertservices.com |
Sandbox access is provisioned manually during onboarding. During the first 2–4 weeks of an integration, expect coordination with the Recspert team for the following:
| Item | How provided |
|---|---|
| Partner API key (for inbound catalog/registration sync) | Issued per integration, scoped to your test club |
| Test club ID + seeded test users | Created on request before integration kickoff |
| Test card numbers | Stripe test card list provided; standard 4242 4242 4242 4242 always succeeds |
| Data reset / fixture re-seed | Support request to sgill@recspert.com — 1 business day turnaround |
| Webhook delivery target for outbound events | Recspert-supplied URL specific to your integration |
| Rate limit headroom | Dedicated bucket so your load testing does not impact other dev users |
Self-service sandbox tooling — partner portal for credential management, ingestion log viewer, automated nightly resets, synthetic fixture seeding — is on the roadmap but not yet shipped. Plan on direct support contact during your initial integration.
What the Recspert sandbox does NOT provide:
Production cutover happens only after Section 16's onboarding checklist is complete. Production credentials are issued separately and never shared via the same channel as sandbox credentials.
Hand this back to Recspert when each item is done.
GET /clubs returns at least one clubGET /clubs/{id}/locations returns ≥ 1 location with timezoneGET /clubs/{id}/classes returns instances with startTimestamp, pricingGET /clubs/{id}/programs returns programs with sessions[]GET /clubs/{id}/packages returns packages (rates, packageType, includedActivities for groupex)POST /users/search works on emailPOST /users creates a guest userGET /users/{id}/payment-methods returns saved cardsPOST /classes/{id}/checkout returns eligibility + totalPOST /classes/{id}/registrations succeeds end-to-end with test cardPOST /programs/{id}/registrations succeeds end-to-endPOST /users/{id}/packages rejects missing paymentMethodId with 422POST /users/{id}/packages succeeds with valid paymentMethodIdsource=recspert persists on registration recordsThe flows below show the exact sequence of calls Recspert makes against a partner API during the most common end-user actions. Use these as integration test scenarios — if all four flows pass, you have a working integration.
Runs on a schedule per partner (cadence configurable; webhook-driven sync per Section 12 reduces or eliminates polling). One sync per club.
Failure handling:
401 on any call → refresh token, retry once429 → respect Retry-After, resume from same cursor5xx → exponential backoff up to 3 retries, then abort sync for this club and alertEnd-to-end latency target: under 5 seconds wall-clock.
Critical failure cases:
| Case | Partner response | Recspert behavior |
|---|---|---|
| Class fills between preview and registration | 409 CLASS_FULL |
Surface partner's userMessage; suggest alternate sessions |
| Payment declined | 402 PAYMENT_DECLINED with decline reason |
Surface decline reason; user picks different card |
| Waiver missing | 422 WAIVER_REQUIRED with waiverId |
Re-fetch waiver, prompt user to sign, retry |
| Partner timeout (> 10s) | (no response) | Recspert aborts and shows "Provider unavailable, try again." NEVER retries a write without user confirmation. |
Critical: Partner MUST reject the POST /packages call with 422 if paymentMethodId is missing. See Section 8.1 for the rationale — a known partner failure mode in this scenario silently creates unpaid house-charges that surface as operational and user-trust problems downstream.
Recspert webhook receiver expectations:
200 on success or already-processed (idempotent)401 on signature mismatch — partner must rotate shared secret if this happens unexpectedly5xx only on real outages — partner should retryRequired vs. optional fields per entity. For full schemas see the section noted in the rightmost column.
| Field | Type | Req? | Notes |
|---|---|---|---|
id |
string | ✅ | Opaque, stable |
name |
string | ✅ | Display name |
shortName |
string | ⬜ | Used in dense UI |
address |
object | ✅ | Standard postal address |
geo.lat / geo.lng |
number | ⬜ | Falls back to address geocoding if absent |
timezone |
string | ✅ | IANA, e.g. America/New_York |
imageUrl |
string | ⬜ | Recspert uses generic placeholder if absent |
phone / email |
string | ⬜ | For contact section |
websiteUrl |
string | ⬜ | Linked in detail page |
active |
boolean | ✅ | Inactive clubs are excluded from search |
| Field | Type | Req? | Notes |
|---|---|---|---|
id |
string | ✅ | Opaque, stable |
clubId |
string | ✅ | Parent club |
name |
string | ✅ | "Main Pool", "Studio B" |
address |
object | ✅ | Used for map + driving directions |
geo.lat / geo.lng |
number | ⬜ | Geocoded from address if absent |
timezone |
string | ✅ | Critical: each location may differ from club |
imageUrl |
string | ⬜ |
C (Section 4.4)| Field | Type | Req? | Notes |
|---|---|---|---|
id |
string | ✅ | Opaque, stable across instance lifetime |
clubId |
string | ✅ | |
name |
string | ✅ | Display title |
description |
string | ⬜ | Markdown or plain |
locationId |
string | ✅ | Drives timezone |
categoryIds |
string[] | ✅ | At least one required for filtering |
startTimestamp |
ISO-8601 | ✅ | With timezone offset |
endTimestamp |
ISO-8601 | ✅ | With timezone offset |
startTimeOfDay / endTimeOfDay |
string HH:MM |
⬜ | Local time, redundant with timestamps but resilient |
status |
enum | ✅ | open, full, cancelled, closed |
capacity |
integer | ⬜ | Hidden if absent |
availableSpots |
integer | ⬜ | Hidden if absent; required for waitlist UX |
registrationOpensAt |
ISO-8601 | ⬜ | Used for "Register opens..." countdown |
registrationClosesAt |
ISO-8601 | ⬜ | |
pricing.memberFee |
string/cents | ✅ | |
pricing.nonMemberFee |
string/cents | ✅ | Equal to memberFee if no distinction |
pricing.currency |
string | ✅ | Always "USD" today |
instructors[] |
object[] | ⬜ | Shown on detail page |
imageUrl |
string | ⬜ | Falls back to category default |
tags |
string[] | ⬜ | For "All Levels", "Morning", etc. |
P (Section 4.5)Same fields as Class except:
| Field | Type | Req? | Notes |
|---|---|---|---|
startDate / endDate |
ISO-8601 date | ✅ | Replaces startTimestamp/endTimestamp for the program-level record |
sessions[] |
object[] | ✅ | Each session has id, startTimestamp, endTimestamp; at least one required |
ageRange.min / .max |
integer | ⬜ | For youth program filtering |
K (Section 4.6)| Field | Type | Req? | Notes |
|---|---|---|---|
id |
string | ✅ | |
clubId |
string | ✅ | |
name |
string | ✅ | |
description |
string | ⬜ | |
packageType |
integer | ✅ | 1 = Group Ex, 2 = Individual/PT |
packageTypeName |
string | ⬜ | Display label for packageType |
rates[] |
object[] | ⬜ | Multi-tier packages list here; single-rate packages use top-level memberPrice |
rates[].id |
string | ✅ | (when rates present) |
rates[].sessionCount |
integer | ✅ | |
rates[].price |
string/cents | ✅ | |
memberPrice |
string/cents | ✅ | Top-level fallback / single-rate price |
nonMemberPrice |
string/cents | ⬜ | |
canExpire |
boolean | ✅ | |
expirationPolicy |
string | ⬜ | Human-readable, e.g. "12 months after purchase" |
includedActivities[] |
object[] | ✅ for packageType=1 |
Each entry: seasonName, activities: string[] |
imageUrl |
string | ⬜ |
| Field | Type | Req? | Notes |
|---|---|---|---|
id |
string | ✅ | Opaque, stable |
clubId |
string | ✅ | |
email |
string | ✅ | Used for duplicate detection |
firstName / lastName |
string | ✅ | |
dateOfBirth |
ISO-8601 date | ⬜ | Optional in user record; conditionally required at registration for age-gated programs (enforce at checkout/preview, not at user create) |
phone |
string | ⬜ | E.164 preferred |
address |
object | ⬜ | Required by some partners for membership sales |
isMember |
boolean | ✅ | Affects pricing tier |
membershipExpiresAt |
ISO-8601 date | ⬜ | Required if isMember and membership has an expiration |
dependents[] |
object[] | ⬜ | For family registrations |
| Field | Type | Req? | Notes |
|---|---|---|---|
registrants[] |
object[] | ✅ | One per submitted userId |
registrants[].userId |
string | ✅ | |
registrants[].eligible |
boolean | ✅ | Hard gate on registration |
registrants[].reason |
string | ✅ if eligible=false |
Machine-readable reason code |
registrants[].userMessage |
string | ✅ if eligible=false |
Displayed to end user |
registrants[].price |
string/cents | ✅ | Per-registrant line item |
registrants[].waiversRequired |
string[] | ⬜ | Waiver IDs |
registrants[].customFieldsRequired |
string[] | ⬜ | Custom field IDs |
subtotal / tax / total |
string/cents | ✅ | |
currency |
string | ✅ | |
requiredPayment |
boolean | ✅ | Drives whether Recspert prompts for payment method |
warnings[] |
string[] | ⬜ | Non-blocking notices for the user |
| Field | Type | Req? | Notes |
|---|---|---|---|
id |
string | ✅ | Used by Recspert for cancellation, roster lookup |
status |
enum | ✅ | confirmed, waitlisted, pending |
participantList[] |
object[] | ✅ | Per-user confirmation |
total |
string/cents | ✅ | Final amount charged |
paymentMethodId |
string | ⬜ | Echoes what was sent |
packageApplied |
object | ⬜ | If Pattern A redemption applied |
createdAt |
ISO-8601 | ✅ |
| Field | Type | Req? | Notes |
|---|---|---|---|
id |
string | ✅ | Distinct from the catalog packageId |
packageId |
string | ✅ | Reference back to the catalog |
userId |
string | ✅ | Owner |
sessionsTotal |
integer | ✅ | |
sessionsRemaining |
integer | ✅ | Used by Recspert to render "X of Y remaining" |
purchasedAt |
ISO-8601 | ✅ | |
expiresAt |
ISO-8601 date | ⬜ | Required if canExpire=true on the catalog package |
status |
enum | ✅ | active, expired, consumed, cancelled |
redemptions[] |
object[] | ⬜ | Per-redemption history |
"recspert")sourceMetadata for cross-system attributionDocument version 1.0 — 2026-05-19. Maintainer: Stuart Gill, Recspert. Send corrections / questions to sgill@recspert.com.