StudyMap

Docs/Competitions API

Competitions API

Read the competitions dataset programmatically: GET /api/competitions, filters, pagination, and errors.

StudentSuite Team · · 4 min read

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.

GET /api/competitions
The merged dataset, optionally filtered, with bounded pagination.
# 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.

Query parameters
All optional; every value is validated, and invalid values are a 400 with a message - never silently ignored.
  • 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.

Response shape
Each record is exactly one entry from 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.

Errors
Invalid filter values return 400 with an 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.

No API key, no write access
This endpoint is read-only and deliberately unauthenticated - the dataset is public by design. To add or correct a competition, open a pull request against data/competitions/*.json following the competitions contributing guide.