Skip to main content

Events

Events is a multi-sport discovery feature for competitions, demos, community sessions, clinics, premieres, and live broadcasts. A narrow web MVP and production API are live; alerts, map/calendar discovery, source administration, and the broader ingestion model remain planned.

Implementation status: partial MVP

The production API currently contains 24 records as of August 31, 2026: 13 completed X Games records and 11 upcoming The Boardr records. The default next 30 days filter returns five upcoming events.

Current Implementation

LayerCurrent behavior
Web/events list, filters, cards, local-browser saves, and /events/[slug] details
APIList/detail endpoints and authenticated save/unsave endpoints
DataOne events collection and one event_saves collection
IngestionX Games WordPress endpoint parser and The Boardr server-rendered Next.js data parser
SchedulingThe production records show ingestion running and refreshing lastSeenAt; scheduling configuration is operational rather than represented in the application repository
Not shippedEvent alerts, source registry, raw snapshots, admin review, map, calendar, similar events, submissions, relationships, mobile UI

The web frontend can be switched to five curated development fixtures with NEXT_PUBLIC_EVENTS_USE_FIXTURES=true. Fixture data is not a production source.

Current API

  • GET /api/events
  • GET /api/events/:slugOrId
  • POST /api/events/:id/save (authenticated)
  • DELETE /api/events/:id/save (authenticated)

The list endpoint returns:

{
events: Event[],
nextCursor: string | null,
totalCount: number
}

Supported query parameters are q, sport, location, date, intent, registration, and cursor. The current cursor is a numeric offset with a fixed page size of 20, despite its name. radius is sent by the web client but is not implemented by the API.

Current Stored Event Shape

This is the shared shape emitted by the active X Games and The Boardr connectors and returned by production. Fields may be absent or empty because there is no schema validator yet. The Boardr connector additionally populates disciplines, eventKinds, intents, level, and sourceTrust.

{
_id: ObjectId,
slug: string,
title: string,
description: string,
sports: string[],
startAt: Date,
endAt: Date | null,
timezone: string | null,
timeTba: boolean,
status: 'scheduled' | 'completed',
isOnline: boolean,
venue: {
name: string,
city: string,
region: string,
country: string,
lat: number | null,
lng: number | null
},
organizer: { name: string, verified: boolean },
participation: object,
spectating: {
inPerson: boolean,
ticketUrl: string,
streamUrl: string,
streamStatus: string
},
image: string,
series: string,
resultsUrl: string,
source: string,
sourceId: string,
sourceUrl: string,
dedupeKey: string,
createdAt: Date,
updatedAt: Date,
lastSeenAt: Date
}

The save relationship currently stores string IDs rather than MongoDB references:

{ userId: string, eventId: string, createdAt: Date }

event_saves has a unique compound index on { userId, eventId }. The current events indexes cover unique dedupeKey, unique slug, startAt, sports, and status.

Current Normalization and Dedupe

  • X Games records are read from https://www.xgames.com/wp-json/xgames/v1/events.
  • The connector parses event-card HTML embedded inside the JSON response.
  • sourceId is based on the X Games event URL slug.
  • dedupeKey is normalized-title|YYYY-MM-DD|normalized-city.
  • The public slug adds a six-character MD5 suffix derived from dedupeKey.
  • Sport assignment is inferred from month: November through March becomes snowboarding/skiing; all other months become skateboarding/BMX.
  • Dates are parsed at noon UTC and timezone remains null.
  • Upserts set updatedAt and lastSeenAt; inserts also set createdAt.

The Boardr connector reads the eventList array from the page's server-rendered __NEXT_DATA__ payload. It uses the numeric Boardr event ID for stable identity and links, parses advertised calendar days at noon UTC with timeTba: true, splits U.S. city/region text, and conservatively infers event kind, BMX inclusion, open/invite-only level, and participation intent from the title and description. Upstream-shape changes fail loudly rather than silently producing an empty feed.

This inference is intentionally crude and is the largest current data-quality limitation. It cannot describe per-discipline schedules, mixed seasonal events, accurate local times, or authoritative registration/watch state.

Audit Findings

  1. The docs previously described the target architecture as if it were current. The live collections and endpoints are much smaller than the canonical plan below.
  2. Upcoming coverage is narrow but working. The Boardr supplies 11 upcoming U.S. skateboarding/BMX events through April 2027; five fall within the default 30-day window.
  3. Two connectors produce data. X Games covers marquee historical records and The Boardr supplies the current upcoming calendar. Other sports and organizers still have no live connector.
  4. Source provenance is thin. Current records have one flat source, not sourceRefs, raw snapshots, payload hashes, source health, or licensing metadata.
  5. Filtering is only partially implemented. Radius is ignored; community searches the series text; date windows use UTC calendar boundaries; results are sorted by absolute distance from now and intentionally include historical records.
  6. Frontend and backend saves are disconnected. The list uses localStorage; the detail Save control is disabled even though API save endpoints exist.
  7. The map toggle is a placeholder. Current venues use lat/lng, not GeoJSON, and most active records have no coordinates.
  8. No runtime schema validation exists. The fixture/canonical model contains fields the active connector does not emit, while the connector emits flat fields not represented in the previous canonical example.
  9. One legacy backend working copy is behind production for Events. Events development now uses an isolated worktree based on the current remote master; do not build from the older local checkout until its unpublished spot-enrichment commits are reconciled.

Product Goal

Events should answer three questions for every rider:

  1. What is happening near me in the sports I care about?
  2. Can I enter, attend, or watch it online?
  3. How can I make sure I do not miss it?

TrickBook will initially own discovery, personalization, saving, and notifications. Registration, ticketing, waivers, payments, and streaming remain on the authoritative organizer or platform through clearly labeled external links.

Initial product boundary

The first release is an event discovery and subscription layer, not a replacement for LiveHeats, federation registration systems, or ticketing providers.

User Experience

Events Home

The default view is a chronological For You feed based on a rider's sports and location.

Primary controls:

  • Location and search radius
  • Date: this week, this weekend, next 30 days, or custom
  • Sport and discipline
  • Intent: enter, watch in person, watch online, or community
  • Level: open, amateur, professional, youth, or all ages
  • Registration state: open, opening soon, closing soon, free, or paid
  • View: list, map, or calendar

Recommended discovery sections:

  • Happening near you
  • Registration closing soon
  • Watch live this week
  • From organizers you follow
  • Major events in your sports

The default is a list, not a map. Event discovery is primarily time-sensitive; map and calendar views are secondary tools.

Event Cards and Details

Cards include the date, local start time, sport, city/distance, organizer, trust badge, event status, and a state-specific action: Register, Get tickets, Watch, or View details.

The detail page presents information in decision order:

  1. Title, status, date/time, venue, sport, and discipline
  2. Sticky primary action and Save control
  3. Eligibility, divisions, price, registration deadline, and spectator details
  4. Schedule for multi-day events
  5. Venue map and linked TrickBook Spot when available
  6. Organizer, rules, and authoritative external links
  7. Source attribution and last-checked timestamp
  8. Similar nearby events

An event can expose multiple actions simultaneously, such as both Register and Watch live.

Saving and Subscriptions

Two levels of notification intent are supported:

  • Save this event: reminders and material changes for one event
  • Create event alert: a reusable rule such as "Skateboarding contests within 100 miles of Philadelphia"
TriggerDefault delivery
New matching eventWeekly digest; instant is opt-in
Registration opensInstant
Registration closes72 hours before
Saved event7 days and 24 hours before
Saved event same-day reminder2 hours before
Time, venue, postponement, or cancellationImmediate
Livestream starts10 minutes before or at start

All notifications respect quiet hours. Recommendation notifications are grouped, and duplicate delivery across push and email is suppressed unless the user requests both.

Data Pipeline

Source connector

Raw snapshot

Normalize and classify

Geocode and link Spot

Deduplicate and merge

Trust and quality gate

Publish event

Match subscriptions

Notify riders

Each connector follows the same interface:

fetchSince(cursor)
normalize(rawRecord)
getStableExternalId(rawRecord)
getSourceUpdatedAt(rawRecord)

Connectors may read licensed APIs, partner feeds, JSON, CSV, iCalendar, RSS/Atom, or schema.org Event markup. Page monitoring is used only when permitted and no structured integration exists.

Target Canonical Event Model

{
_id,
slug,
title,
description,
sports: ['skateboarding'],
disciplines: ['street'],
eventKinds: ['competition'],
intents: ['compete', 'spectate_in_person', 'spectate_online'],
level: ['open', 'amateur', 'pro', 'youth'],
status: 'scheduled',
startAt,
endAt,
timezone,
schedule: [],
venue: {
name, address, city, region, country,
location: { type: 'Point', coordinates: [longitude, latitude] },
spotId
},
organizer: { name, canonicalId, url, verified },
participation: {
registrationStatus, registrationOpensAt, registrationClosesAt,
registrationUrl, priceText, divisions, eligibilityText
},
spectating: {
inPerson, ticketUrl, ticketPriceText,
streamStatus, streamUrl, broadcaster
},
sourceRefs: [{ sourceId, externalId, url, sourceUpdatedAt, fetchedAt, payloadHash }],
sourceTrust,
freshness: { lastVerifiedAt, staleAfter, changedAt },
dedupeKey,
approvalStatus,
createdAt,
updatedAt
}

Supporting collections:

  • eventSources: connector configuration, licensing notes, cursors, polling cadence, and health
  • eventSourceRecords: raw snapshots for replay and audit
  • eventSubscriptions: user alert rules
  • eventSaves: saved, attending, or competing relationships
  • eventChangeLog: material changes used for notifications
  • eventNotificationJobs: idempotent scheduled deliveries
  • organizers: canonical organizer identities and source mappings

Events use GeoJSON and a 2dsphere index. The optional spotId links an event venue to the existing spot catalog.

Target API

Public endpoints:

  • GET /api/events
  • GET /api/events/map-pins
  • GET /api/events/:slugOrId
  • GET /api/events/:id/similar
  • GET /api/events/calendar.ics

Authenticated endpoints:

  • POST /api/events/:id/save
  • DELETE /api/events/:id/save
  • PATCH /api/events/:id/relationship
  • GET|POST|PATCH|DELETE /api/event-subscriptions
  • POST /api/events/submit

Admin capabilities include source status, import review, duplicate resolution, event editing/publishing, and freshness monitoring.

The target API should use cursor pagination based on startAt and _id. Dates should be stored in UTC alongside an IANA timezone. This is not the current offset-based behavior.

Deduplication and Trust

Exact identity uses (sourceId, externalId). Cross-source candidates are scored using normalized title, start time, venue proximity, organizer, and matching action URLs.

Only high-confidence matches merge automatically. Ambiguous records enter an admin review queue. Authoritative organizer updates override aggregator copies for cancellations, date changes, and registration status.

User-facing source badges:

  • Official organizer
  • Registration partner
  • Governing body
  • Community submitted

Records without a title, date, sport, location or online designation, and authoritative URL are not published.

Delivery Plan

Phase 0: Validate Sources

  • Confirm API and display rights with initial partners
  • Build a source coverage matrix by sport and region
  • Assemble 100 representative future events
  • Validate that the canonical schema handles at least 95%

Phase 1: Web MVP

  • Event collections, indexes, public API, and admin CRUD/review
  • Manual and structured imports plus one production connector
  • /events and /events/[slug]
  • Sport, date, location, radius, and intent filters
  • Registration, ticket, and watch deep links
  • Source trust, freshness, cancellation, and postponement states
  • Impression, save, and outbound-action analytics

Phase 2: Alerts

  • Saves, event relationships, and alert rules
  • Dedicated event notification preferences
  • Matching engine and idempotent notification jobs
  • Registration, change, reminder, and live-start alerts
  • My Events and iCalendar export

Phase 3: Scale Ingestion

  • Reusable connectors and persisted cursors
  • Backoff, health metrics, and replayable raw records
  • Automated geocoding and cross-source deduplication
  • Admin source-health and duplicate dashboards
  • Staleness and expiration jobs

Phase 4: Personalization and Native Mobile

  • Map and calendar views
  • Deterministic relevance ranking
  • Native location, Events screens, push registration, and deep links
  • Organizer profiles and claim/submission workflows

Native checkout or registration is deferred until outbound conversion data proves a specific workflow is worth owning.

Success Metric

The primary MVP metric is weekly active riders who save an event or click through to register, buy tickets, or watch. Guardrails include duplicate rate, stale or cancelled event exposure, corrections, notification opt-outs, and zero-result searches.

Research Basis

See Event Source and Coordinator Map for industry ownership and ingestion priorities.