Skip to content

feat(competitions): public /api/competitions endpoint #209

Description

@AnayDhawan

Blocked by #195.

A public JSON endpoint for the competitions dataset, mirroring the one places already has.

This is close to free and it is the part of this vertical that is genuinely hard to copy. Anyone can list competitions on a web page; a schema-validated, machine-readable dataset of national qualifier pathways across 13 countries does not exist anywhere else. Publishing it makes the data addressable by other tools, by scripts, and by coding agents, which is a distribution channel nobody in this category is using.

Files

  • src/app/api/competitions/route.ts - new
  • src/lib/competitions-api.ts - new, query parsing and validation
  • src/app/api/competitions/route.test.ts - new

Copy the places API exactly

src/app/api/places/route.ts (43 lines) is the template:

  • :11 export const revalidate = 21600 (6 hours)
  • :14-18 wide-open CORS
  • :20-23 Cache-Control: public, max-age=3600, s-maxage=21600, stale-while-revalidate=86400

src/lib/places-api.ts (149 lines) is the query layer:

  • :13-16 PLACES_API_LIMITS { defaultLimit: 100, maxLimit: 500 }
  • :51-125 parsePlacesQuery, which rejects repeated params and unknown categories, and at :99-104 rejects a country filter because the places schema has no country field
  • :131-149 queryPlaces

Match those conventions rather than inventing new ones. An API that behaves differently from the sibling endpoint on the same site is worse than no second endpoint.

Query parameters

category, format, participation, region, country (matches country_tracks[].country), fee (free), age, deadline_before, limit, offset.

Unlike places, country is a valid filter here, since country_tracks exists. Note that difference explicitly in the code so the next reader does not assume it was copied by mistake.

Documentation

src/app/docs/(article)/places-api/page.tsx (210 lines) documents the places endpoint. Write the equivalent for competitions, or extend that page to cover both. Include a worked curl example and the response shape.

Acceptance criteria

  • GET /api/competitions returns all records with correct CORS and cache headers
  • Every filter works and composes
  • An unknown parameter returns 400 with a useful message, matching the places behaviour
  • limit is capped at the maximum
  • Pagination via offset is stable
  • Tests in src/app/api/competitions/route.test.ts, following src/app/api/places/route.test.ts
  • Documented in docs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions