Athletes App Public API
A read-only JSON API for embedding your roster, teams, and event registration links on any external site — your school's homepage, a club marketing page, or a fan portal.
Getting started
All endpoints live under https://athletes.app/api/public/* and return JSON. They're designed for read-only embedding — list your events on a school site, render a roster on a club page, link straight to a hosted registration checkout — without ever needing to authenticate or store credentials in client-side code.
Find your account ID
Every endpoint requires an account_id query parameter. Find yours in the dashboard under Settings → Account. It's a UUID and it's safe to expose publicly — it acts as a tenant identifier, not a secret.
Base URL
https://athletes.app/api/publicAuthentication
None. The public API is unauthenticated by design. Only data explicitly marked public in the dashboard is returned:
- Events with
is_published = true - Teams with
is_public = true,is_active = true, andlevel = varsity - People with
is_public = trueon a public team
Anything else stays private even if you have the right account_id. Toggle visibility per-record from the dashboard.
CORS & embedding
All public endpoints respond with Access-Control-Allow-Origin: * and handle OPTIONS preflight requests. You can call them directly from any browser on any domain — no proxy server needed.
Caching
Responses are served with Cache-Control: public, s-maxage=60, stale-while-revalidate=300. CDNs and browsers cache results for 60 seconds, and may serve stale-but-fresh-enough responses for up to 5 minutes while the cache revalidates in the background. Updates you make in the dashboard typically appear within a minute.
Errors
Errors return a JSON body with an error field and a standard HTTP status code:
{
"error": "account_id query parameter is required"
}| Status | Meaning |
|---|---|
400 | Missing or invalid query parameter (usually account_id). |
404 | Requested resource not found (account, event, or player slug doesn't exist or isn't public). |
500 | Server-side error. Retry with backoff. |
Endpoints
Returns published events for an account, with a precomputed register_url that points at a hosted Stripe-powered checkout page on the account's branded domain.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
account_id | string (UUID) | required | Your account identifier. |
slug | string | optional | If provided, returns a single event under an event key. Returns 404 if not found. |
include_past | boolean | optional | Pass true to include events whose ends_at is in the past. Defaults to upcoming only. |
Response shape
{
"events": [
{
"id": "uuid",
"slug": "summer-camp-2026",
"name": "Summer Skills Camp",
"description": "Three-day skills intensive…",
"location": "Main Gym",
"starts_at": "2026-07-15T09:00:00Z",
"ends_at": "2026-07-17T16:00:00Z",
"registration_opens_at": "2026-05-01T00:00:00Z",
"registration_closes_at": "2026-07-10T23:59:59Z",
"capacity": 50,
"fee_amount": 15000,
"fee_description": "Per camper",
"image_url": "https://…/event-images/{account_id}/{uuid}.jpg",
"registration_open": true,
"register_url": "https://yourname.athletes.app/register/summer-camp-2026"
}
]
}Notes
fee_amountis in the smallest currency unit (cents for USD).15000= $150.00.registration_openis computed server-side based onregistration_opens_atandregistration_closes_at. Use it to disable buttons in your UI.register_urlresolves to the account's custom domain when one is configured, falling back to{subdomain}.athletes.app. Always link to it as-is — don't reconstruct the URL yourself.image_urlis the event's cover image — a public URL hosted on Supabase Storage.nullif the event has no cover. Safe to use directly in<img>tags or CSSbackground-image. Recommended display ratio is 16:9.- When
slugis provided the response key isevent(singular) instead ofevents.
Example
curl 'https://athletes.app/api/public/events?account_id=YOUR_ACCOUNT_ID'Returns players currently rostered on public, active, varsity teams, including their profile data, awards, and season stats.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
account_id | string (UUID) | required | Your account identifier. |
slug | string | optional | If provided, returns a single player under a player key. Returns 404 if not found. |
award | string | optional | Filter to players who hold an award with this slug (e.g. all-conference). |
Response shape
{
"players": [
{
"id": "uuid",
"slug": "marcus-johnson",
"first_name": "Marcus",
"last_name": "Johnson",
"name": "Marcus Johnson",
"photo": "https://…",
"height": "6'3\"",
"weight_lbs": 185,
"grad_year": 2027,
"hometown": "Provo, UT",
"bio": "Three-year varsity guard…",
"instagram": "marcus.j",
"twitter": "marcusj",
"hudl_url": "https://hudl.com/…",
"teams": [
{
"id": "uuid",
"name": "Boys Varsity Basketball",
"slug": "boys-varsity",
"icon": "https://…",
"jersey_number": 23,
"position": "Guard",
"grade": "Junior",
"season_bio": "…",
"photo": "https://…",
"awards": [
{ "title": "All-Conference First Team", "slug": "all-conference", "category": "individual" }
]
}
],
"stats": [
{
"season_label": "2025-26",
"class_label": "Junior",
"gp": 24,
"ppg": 18.4,
"rpg": 6.1,
"apg": 4.2,
"spg": 1.8,
"bpg": 0.4,
"fg_pct": 0.487,
"three_pct": 0.382,
"ft_pct": 0.812,
"is_career_total": false
}
]
}
]
}Notes
- Players appear once and aggregate every public team they're rostered on under the
teamsarray. stats.is_career_totalistruefor all-time career totals,falsefor individual season rows.- Shooting percentages (
fg_pct,three_pct,ft_pct) are decimals — multiply by 100 to display. - When
slugis provided the response key isplayer(singular).
Example
curl 'https://athletes.app/api/public/players?account_id=YOUR_ACCOUNT_ID&award=all-conference'Returns public, active, varsity teams with their full roster and coaching staff embedded.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
account_id | string (UUID) | required | Your account identifier. |
Response shape
{
"teams": [
{
"id": "uuid",
"name": "Boys Varsity Basketball",
"level": "varsity",
"icon": "https://…",
"slug": "boys-varsity",
"awards": [
{ "title": "Region Champions 2025" }
],
"staff": [
{
"id": "uuid",
"slug": "coach-smith",
"first_name": "John",
"last_name": "Smith",
"name": "John Smith",
"photo": "https://…"
}
],
"players": [
{
"id": "uuid",
"slug": "marcus-johnson",
"first_name": "Marcus",
"last_name": "Johnson",
"name": "Marcus Johnson",
"photo": "https://…",
"height": "6'3\"",
"weight_lbs": 185,
"grad_year": 2027,
"hometown": "Provo, UT",
"bio": "…",
"season_bio": "…",
"jersey_number": 23,
"position": "Guard",
"grade": "Junior",
"awards": [{ "title": "All-Conference" }],
"teams": [
{ "id": "uuid", "name": "Boys Varsity Basketball", "slug": "boys-varsity" }
]
}
]
}
]
}Notes
- Only varsity, active, public teams are returned. Sub-varsity and private teams stay hidden.
- Each player's
teamsarray includes every public team they're also on, so you can show cross-team affiliations. - Use
/api/public/playersinstead if you want season stats — the teams endpoint omits stats for payload size.
Example
curl 'https://athletes.app/api/public/teams?account_id=YOUR_ACCOUNT_ID'Code examples
Browser / JavaScript
const ACCOUNT_ID = "00000000-0000-0000-0000-000000000000"
const res = await fetch(
`https://athletes.app/api/public/events?account_id=${ACCOUNT_ID}`
)
const { events } = await res.json()
for (const event of events) {
console.log(event.name, event.register_url)
}React component
import { useEffect, useState } from "react"
const ACCOUNT_ID = "00000000-0000-0000-0000-000000000000"
export function UpcomingEvents() {
const [events, setEvents] = useState([])
useEffect(() => {
fetch(`https://athletes.app/api/public/events?account_id=${ACCOUNT_ID}`)
.then((r) => r.json())
.then((data) => setEvents(data.events))
}, [])
return (
<ul>
{events.map((event) => (
<li key={event.id}>
<strong>{event.name}</strong>
{event.registration_open && (
<a href={event.register_url}> Register →</a>
)}
</li>
))}
</ul>
)
}Server-side (Node)
const ACCOUNT_ID = process.env.ATHLETES_APP_ACCOUNT_ID
const res = await fetch(
`https://athletes.app/api/public/players?account_id=${ACCOUNT_ID}`,
{ next: { revalidate: 60 } } // Next.js ISR
)
const { players } = await res.json()Embed a registration link in HTML
For the simplest case — linking to a single event from a static site — skip the API entirely. Use the registration URL directly:
<a href="https://yourname.athletes.app/register/summer-camp-2026">
Register for Summer Camp →
</a>Or use /api/public/events?slug=summer-camp-2026 to fetch metadata first if you want to show the price, capacity, or disable the link when registration is closed.
Changelog
- 2026-04-28Added
GET /api/public/eventswithregister_urlfor hosted checkout embedding. - EarlierInitial release of
GET /api/public/playersandGET /api/public/teams.