RegattaStream API v1
Timing partner webhooks, live results feed, Speed Order™ rankings, and WebSocket events — everything you need to integrate.
The RegattaStream API is a REST + WebSocket API for live race result ingestion, rankings retrieval, and fan donation processing. It is designed to receive webhooks from any of 8 timing partners and broadcast results in real time to all connected clients.
Public read endpoints (/api/results, /api/rankings, /api/library) require no authentication. Timing partner webhooks are currently open — we recommend adding X-RS-Partner-Key validation before going live.
Admin endpoints require a JWT Bearer token obtained from POST /api/admin/login.
# Admin endpoints
Authorization: Bearer <jwt_token>
# Recommended timing partner auth (add to server.js)
X-RS-Partner-Key: <partner_secret>Each timing platform posts results to its own endpoint. RegattaStream normalizes all payloads, runs the Speed Order™ update, generates an AI story via Claude, and broadcasts to all WebSocket clients within ~2 seconds.
:partner must be one of: herenow, regattamaster, regattacentral, crewtimer, timeteam, regattatiming, rowtimer, clockcaster
Primary partner for SDCC 2026. Configure your HereNOW account to POST results to:
{
"regatta_name": "San Diego Crew Classic 2026",
"event_name": "Men's Varsity 8+",
"heat": "Final",
"distance_m": 2000,
"start_time": "2026-04-05T09:14:00Z",
"results": [
{
"place": 1,
"club_name": "Cal Bears",
"time_display": "5:48.2",
"time_ms": 348200,
"margin": ""
},
{
"place": 2,
"club_name": "Washington",
"time_display": "5:48.6",
"time_ms": 348600,
"margin": "+0.4"
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
| regatta_name | string | Required | Full regatta name — used for grouping and display |
| event_name | string | Required | Event label e.g. "Men's Varsity 8+" |
| heat | string | Optional | Heat identifier — "Final", "Heat 1", etc. |
| distance_m | number | Optional | Race distance in meters (default 2000) |
| start_time | ISO 8601 | Optional | Race start time in UTC |
| results | array | Required | Array of finisher objects (see below) |
| results[].place | number | Required | Finishing position (1-based) |
| results[].club_name | string | Required | Club/team name — matched to Club database |
| results[].time_ms | number | Required | Elapsed time in milliseconds |
| results[].time_display | string | Optional | Human-readable time e.g. "5:48.2" |
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | number | 1 | Page number |
| limit | number | 20 | Results per page (max 100) |
| regatta | string | — | Filter by regatta name (partial match) |
| event | string | — | Filter by event name |
| category | string | — | collegiate, junior, masters, para, lightweight, club |
| club | string | — | Filter by club name |
{
"races": [
{
"_id": "abc123",
"regattaName": "SDCC 2026",
"event": "Men's Varsity 8+",
"timingPartner": "herenow",
"officialAt": "2026-04-05T09:14:28Z",
"aiStory": "Cal holds off Washington...",
"results": [
{
"place": 1,
"club": "Cal Bears",
"time": "5:48.2",
"eloBefore": 1740,
"eloAfter": 1759,
"eloDelta": 19
}
]
}
],
"total": 142,
"page": 1,
"pages": 8
}| Parameter | Type | Default | Description |
|---|---|---|---|
| category | string | collegiate | collegiate · junior · masters · para · lightweight · club · novice |
| limit | number | 25 | Number of clubs to return (max 200) |
Connect to the same host as the HTTP server — no separate port required. All race results, AI stories, and ranking updates are broadcast in real time.
const ws = new WebSocket(
`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}`
);
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
switch (msg.type) {
case 'race_result': handleResult(msg.data); break;
case 'story': handleStory(msg.data); break;
case 'speed_order': updateRankings(msg.data); break;
case 'new_content': showLibraryCard(msg.data); break;
}
};
ws.onclose = () => setTimeout(connect, 3000); // auto-reconnectBroadcast immediately when a timing partner POST is received and processed. Contains full results array with Speed Order™ deltas, regatta name, event name, and timing partner.
Broadcast ~2 seconds after race_result once Claude generates the AI narrative. Contains raceId and story string. Match to result card by raceId.
Broadcast after Speed Order™ recalculation. Contains array of { club, elo, delta } objects for all clubs affected by the race.
Broadcast when admin publishes a new article or video. Contains { type, title, slug, category }.
Add https://yourdomain.com/api/stripe/webhook in your Stripe Dashboard → Webhooks. Listen for: checkout.session.completed, customer.subscription.deleted, payment_intent.succeeded
Fan donations route 88% directly to the club via Stripe Connect. Each donation creates a PaymentIntent with automatic transfer to the club's connected Stripe account.
| Field | Type | Required | Description |
|---|---|---|---|
| clubSlug | string | Required | Club identifier — must match a Club document with stripeAccountId |
| amount | number | Required | Amount in USD (minimum $1) |
| message | string | Optional | Optional message from donor to club |
| Event | Action | Notes |
|---|---|---|
| checkout.session.completed | Activate subscription | Sets club.stripeStatus = "active" |
| customer.subscription.deleted | Deactivate subscription | Sets club.stripeStatus = "inactive" |
| payment_intent.succeeded | Record donation receipt | Logs to Donation collection, triggers thank-you email |
All errors return a JSON body with an error field.
| HTTP Status | Meaning | Notes |
|---|---|---|
| 200 | Success | Request processed normally |
| 400 | Bad Request | Missing required fields — check payload structure |
| 401 | Unauthorized | Missing or invalid JWT — required for admin routes |
| 404 | Not Found | Resource does not exist |
| 500 | Server Error | Check server logs — often MongoDB or Stripe connection |