Docs/Competitions API
Competitions API
Read the competitions dataset programmatically: GET /api/competitions, filters, pagination, and errors.
Every competition StudyMap lists is hand-curated and committed to data/competitions/*.json - one file per category, including the real national qualifying pathways in country_tracks where one exists. The API below exposes that dataset read-only, mirroring the places API exactly, so anything can be built on top of it without cloning the repo.
# Everything (paginated at 50 rows)
curl "https://studyymap.com/api/competitions"
# Free, individual STEM competitions
curl "https://studyymap.com/api/competitions?category=stem&participation=individual&fee=free"
# Competitions with a real qualifying pathway for India
curl "https://studyymap.com/api/competitions?country=IN"Responses are plain JSON with permissive CORS (Access-Control-Allow-Origin: *), so browser code can call it directly. Responses are cached for 6 hours (the dataset changes a few times a week at most) via Cache-Control headers.
category(enum)One of the 15 competition categories (stem, mathematics, coding, essay_writing, and so on). Anything else is a 400.
format(enum)One of `online`, `in_person`, `hybrid`.
participation(enum)One of `individual`, `team`, `individual_or_team`.
region(string)Matched exactly as stored: `"international"`, or an ISO-3166 alpha-2 code like `US`. Free-form, not a fixed enum, so it is not validated against a list - a region with no matches just returns an empty `data` array.
country(enum)Matches `country_tracks[].country`: one of the 13 countries with a real qualifying pathway (IN, US, GB, CA, AU, SG, DE, FR, CN, JP, KR, BR, ZA). Unlike the places API, where `country` is rejected outright because that dataset has no such field, this filter genuinely works here.
fee("free")Only accepted value is `free`, which keeps competitions with a fee of zero. Any other value is a 400.
age(non-negative integer)Kept only when the age falls within the competition's own age_min/age_max, inclusive at both ends.
deadline_before(ISO date (YYYY-MM-DD))Keeps competitions with at least one `dates[]` entry of type `"deadline"` on or before this date.
limit(positive integer)Default 50; values above the hard maximum of 200 are clamped, never dumped.
offset(non-negative integer)Zero-based. Combine with `limit` to page through results.
data/competitions/*.json, as defined by the schema.{
"data": [
{
"id": "breakthrough-junior-challenge",
"name": "Breakthrough Junior Challenge",
"organizer": "Breakthrough Prize Foundation",
"organizer_url": "https://breakthroughjuniorchallenge.org",
"category": "stem",
"subjects": ["physics", "science communication"],
"description": "One to three sentences, written by us.",
"format": "online",
"age_min": 13,
"age_max": 18,
"participation": "individual",
"region": "international",
"fee": { "amount": 0, "currency": "USD" },
"prize": "USD 250,000 scholarship plus a lab grant",
"official_url": "https://breakthroughjuniorchallenge.org",
"cycle_year": 2026,
"dates": [
{
"label": "Submission deadline",
"date": "2026-06-25",
"type": "deadline",
"timezone": "UTC-4",
"estimated": false,
"source_url": "https://breakthroughjuniorchallenge.org/rules"
}
],
"added_by": "your-github-username"
}
],
"total": 1,
"limit": 50,
"offset": 0
}total is the number of matches before pagination, so consumers know the full result set size. The canonical record shape lives in data/competitions.schema.json.
error message instead of being ignored.{
"error": "unknown country \"ZZ\"; expected one of IN, US, GB, CA, AU, SG, DE, FR, CN, JP, KR, BR, ZA"
}400 cases: an unknown category, format, participation, country, or fee value; a malformed age, deadline_before, limit, or offset; or a repeated parameter. A filter combination with no matches is an empty result, not an error.
data/competitions/*.json following the competitions contributing guide.Ready to use what you just read?