Developer documentation

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

Authentication

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, and level = varsity
  • People with is_public = true on 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"
}
StatusMeaning
400Missing or invalid query parameter (usually account_id).
404Requested resource not found (account, event, or player slug doesn't exist or isn't public).
500Server-side error. Retry with backoff.

Endpoints

GET/api/public/events#

Returns published events for an account — anything from informational announcements to multi-session camps with hosted registration. Whether an event takes signups is driven by the is_registerable flag, not by event_type. Use event_type and team filters to scope to a specific schedule.

Query parameters

ParameterTypeRequiredDescription
account_idstring (UUID)requiredYour account identifier.
slugstringoptionalIf provided, returns a single event under an event key. Returns 404 if not found.
event_typestring (csv)optionalFilter to one or more types: camp, practice, game, other. Comma-separated for multiple (e.g. practice,game).
team_idstring (UUID)optionalFilter to events tied to one team. Useful for embedding a team's schedule.
team_slugstringoptionalAlternative to team_id — resolved against the account's teams.
starts_afterISO datetimeoptionalOnly return events with starts_at on or after this timestamp.
starts_beforeISO datetimeoptionalOnly return events with starts_at on or before this timestamp.
limitintegeroptionalMax number of events to return (default 50, max 200).
include_pastbooleanoptionalPass 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",
      "event_type": "camp",
      "description": "Three-day skills intensive…",
      "location": "Main Gym",
      "starts_at": "2026-07-15T09:00:00Z",
      "ends_at": "2026-07-17T16:00:00Z",
      "arrival_time": null,
      "image_url": "https://…/event-images/{account_id}/{uuid}.jpg",
      "team": null,
      "opponent_name": null,
      "is_home": null,
      "is_registerable": true,
      "is_paid": true,
      "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",
      "registration_open": true,
      "register_url": "https://yourname.athletes.app/register/summer-camp-2026",
      "sessions": [
        {
          "id": "uuid",
          "title": "Day 1 — Skills",
          "description": "Ball-handling and shooting fundamentals",
          "location": "Main Gym",
          "starts_at": "2026-07-15T15:00:00Z",
          "ends_at": "2026-07-15T21:00:00Z",
          "ordering": 0
        },
        {
          "id": "uuid",
          "title": "Day 2 — Team play",
          "description": null,
          "location": null,
          "starts_at": "2026-07-16T15:00:00Z",
          "ends_at": "2026-07-16T21:00:00Z",
          "ordering": 1
        }
      ]
    },
    {
      "id": "uuid",
      "slug": "practice-boys-varsity-20260915-1600",
      "name": "Practice",
      "event_type": "practice",
      "description": null,
      "location": "Main Gym",
      "starts_at": "2026-09-15T22:00:00Z",
      "ends_at": "2026-09-16T00:00:00Z",
      "arrival_time": "2026-09-15T21:45:00Z",
      "image_url": null,
      "team": { "id": "uuid", "slug": "boys-varsity", "name": "Boys Varsity Basketball" },
      "opponent_name": null,
      "is_home": null,
      "is_registerable": false,
      "is_paid": false,
      "registration_opens_at": null,
      "registration_closes_at": null,
      "capacity": null,
      "fee_amount": null,
      "fee_description": null,
      "registration_open": null,
      "register_url": null,
      "sessions": []
    },
    {
      "id": "uuid",
      "slug": "game-boys-varsity-vs-lone-peak-20260919",
      "name": "vs Lone Peak",
      "event_type": "game",
      "description": null,
      "location": "Lone Peak HS",
      "starts_at": "2026-09-20T01:00:00Z",
      "ends_at": null,
      "arrival_time": "2026-09-20T00:00:00Z",
      "image_url": null,
      "team": { "id": "uuid", "slug": "boys-varsity", "name": "Boys Varsity Basketball" },
      "opponent_name": "Lone Peak",
      "is_home": false,
      "is_registerable": false,
      "is_paid": false,
      "registration_opens_at": null,
      "registration_closes_at": null,
      "capacity": null,
      "fee_amount": null,
      "fee_description": null,
      "registration_open": null,
      "register_url": null,
      "sessions": []
    }
  ]
}

Notes

  • event_type is a presentation hint — one of camp, practice, game, or other. Whether an event takes signups is driven by is_registerable, not by event_type.
  • is_registerable is true when the event has hosted registration. When false, all the registration-only fields (fee_amount, capacity, registration_open, register_url, etc.) are null.
  • is_paid is true when registration charges a fee through Stripe. A registerable event with is_paid: false is free to sign up for. fee_description is only returned when both flags are true.
  • team is null for account-level events. For team-level events it contains id, slug, and name. Events for non-public teams are hidden from this endpoint.
  • opponent_name and is_home are populated only for event_type = "game".
  • arrival_time is a separate "be there by" timestamp common for practices and games. null when not set.
  • fee_amount is in the smallest currency unit (cents for USD). 15000 = $150.00. 0 means a free registration.
  • registration_open is computed server-side based onregistration_opens_at and registration_closes_at. Use it to disable buttons in your UI.
  • sessions is an array of optional sub-events with their own title, description, location, starts_at, ends_at, and ordering. Empty array when the event has no sessions. Sorted by ordering, then starts_at.
  • register_url resolves 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_url is the event's cover image — a public URL hosted on Supabase Storage. null if the event has no cover. Safe to use directly in <img> tags or CSS background-image. Recommended display ratio is 16:9.
  • When slug is provided the response key is event (singular) instead of events.

Examples

# All upcoming events for an account
curl 'https://athletes.app/api/public/events?account_id=YOUR_ACCOUNT_ID'

# A specific team's full schedule (practices + games)
curl 'https://athletes.app/api/public/events?account_id=YOUR_ACCOUNT_ID&team_slug=boys-varsity&event_type=practice,game'

# Just upcoming games in a date window
curl 'https://athletes.app/api/public/events?account_id=YOUR_ACCOUNT_ID&event_type=game&starts_after=2026-09-01&starts_before=2026-12-01'
GET/api/public/players#

Returns players currently rostered on public, active, varsity teams, including their profile data, awards, and season stats.

Query parameters

ParameterTypeRequiredDescription
account_idstring (UUID)requiredYour account identifier.
slugstringoptionalIf provided, returns a single player under a player key. Returns 404 if not found.
awardstringoptionalFilter 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 teams array.
  • stats.is_career_total is true for all-time career totals, false for individual season rows.
  • Shooting percentages (fg_pct, three_pct, ft_pct) are decimals — multiply by 100 to display.
  • When slug is provided the response key is player (singular).

Example

curl 'https://athletes.app/api/public/players?account_id=YOUR_ACCOUNT_ID&award=all-conference'
GET/api/public/teams#

Returns public, active, varsity teams with their full roster and coaching staff embedded.

Query parameters

ParameterTypeRequiredDescription
account_idstring (UUID)requiredYour 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 teams array includes every public team they're also on, so you can show cross-team affiliations.
  • Use /api/public/players instead 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-05-01GET /api/public/events: registration fields (fee_amount, capacity, register_url, etc.) are now populated for both camp and other events, not just camps. An other event with a fee or registration window is now exposed as registerable.
  • 2026-05-01Extended GET /api/public/events to cover team-level practices and games. New filters: event_type, team_id, team_slug, starts_after, starts_before, limit. Each event now includes event_type, team, arrival_time, and (for games) opponent_name + is_home.
  • 2026-04-28Added GET /api/public/events with register_url for hosted checkout embedding.
  • EarlierInitial release of GET /api/public/players and GET /api/public/teams.

Need an endpoint we don't have?

Email us and we'll usually ship it within a week.