channel-surf-tv API

Version 1.0.0 · OpenAPI JSON

Read-only JSON API served by the `channel-surf-tv` Cloudflare Worker in front of the D1 database (`channel-surf-tv-spots`). All endpoints are `GET`; any other method returns `405 METHOD_NOT_ALLOWED`. Unknown `/api/*` paths return `404 NOT_FOUND`. Errors share the envelope `{error, code}`. Successful responses are cacheable (`Cache-Control: public, max-age=300, stale-while-revalidate=600`) and CORS-enabled for allow-listed origins. This spec is the source of truth for the API surface: `npm run api:gen` converts it to `src/worker/openapi.json` (served live at `/api/openapi.json`) and renders `docs/api/API.md`. `npm run health` diffs worker routes against these paths and flags drift.

Endpoints

GET /api/spots

Paginated, filterable surf spot list

Full-text search via `q` uses SQLite FTS5 with bm25 ranking; without `q`, ordering is tier-weighted (iconic → regional → other) then alphabetical.

ParameterInTypeDescription
limitqueryinteger, default 20
offsetqueryinteger, default 0
qquerystringFull-text search (FTS5)
countryquerystringISO 3166-1 alpha-2 country code
regionquerystring
wave_typequerystringe.g. reef, beach, point
skill_levelquerystringe.g. beginner, intermediate, expert
tagquerystringTag slug
tierquerystringCuration tier, e.g. iconic, regional
StatusDescription
200Page of spots plus total match count
400Invalid query parameter

GET /api/spots/map

Lightweight spot markers for the Leaflet map

Unpaginated; optionally constrained to a bounding box. Same filters as the list endpoint.

ParameterInTypeDescription
bboxquerystringBounding box as `min_lat,min_lng,max_lat,max_lng`
qquerystringFull-text search (FTS5)
countryquerystringISO 3166-1 alpha-2 country code
regionquerystring
wave_typequerystring
skill_levelquerystring
tagquerystring
tierquerystringCuration tier, e.g. iconic, regional
StatusDescription
200Marker payload
400Invalid query parameter

GET /api/spots/meta

Facet counts for the spot filter sidebar

ParameterInTypeDescription
qquerystringFull-text search (FTS5)
countryquerystringISO 3166-1 alpha-2 country code
regionquerystring
wave_typequerystring
skill_levelquerystring
tagquerystring
tierquerystringCuration tier, e.g. iconic, regional
StatusDescription
200Filtered total plus facet value/count pairs
400Invalid query parameter

GET /api/spots/{slug}

Spot detail with images, tags, and linked surfers

ParameterInTypeDescription
slugpathstring, required
StatusDescription
200Spot detail payload
404Resource not found

GET /api/surfers

Paginated, filterable surfer list

ParameterInTypeDescription
limitqueryinteger, default 20
offsetqueryinteger, default 0
qquerystringFull-text search (FTS5)
countryquerystringISO 3166-1 alpha-2 country code
disciplinequerystring
eraquerystring
tierquerystringCuration tier, e.g. iconic, regional
StatusDescription
200Page of surfers plus total match count
400Invalid query parameter

GET /api/surfers/meta

Facet counts for the surfer filter sidebar

ParameterInTypeDescription
qquerystringFull-text search (FTS5)
countryquerystringISO 3166-1 alpha-2 country code
disciplinequerystring
eraquerystring
tierquerystringCuration tier, e.g. iconic, regional
StatusDescription
200Filtered total plus facet value/count pairs
400Invalid query parameter

GET /api/surfers/{slug}

Surfer detail with images and linked spots

ParameterInTypeDescription
slugpathstring, required
StatusDescription
200Surfer detail payload
404Resource not found

GET /api/quotes

Quotes wall collection

Full collection, optionally filtered; ordered by id.

ParameterInTypeDescription
categoryquerystring
surferquerystringSurfer slug
StatusDescription
200Quotes plus count

GET /api/quotes/random

Quote of the Day

Deterministic day-indexed pick from the featured pool (same quote for everyone on a given UTC day). 404 if the featured pool is empty.

StatusDescription
200The day's quote
404Resource not found

GET /api/clips

Video clip inventory

Clips ordered featured-first; `src` is a public R2 URL.

StatusDescription
200Clip list

GET /api/events

Surf event calendar with derived status

Season calendar of surf competitions (WSL CT, ISA, big-wave windows). Status (upcoming/live/window-open/completed) is derived from each event's date window at request time, never stored.

ParameterInTypeDescription
orgquerystringFilter by organization slug (e.g. wsl, isa).
tourquerystringFilter by tour/series (e.g. ct, isa-wsg, big-wave).
yearquerystringFilter by start-date year (e.g. 2026).
statusquerystringFilter by derived status.
StatusDescription
200Events ordered by start date

GET /api/events/live-media

Recent Instagram posts from the live event's organizer

While an event is live, recent media from the organizing account via Business Discovery (1h D1 cache). Empty payload when no event is live, the organization has no Instagram handle, or Instagram is unconfigured.

StatusDescription
200Live event media rail payload

GET /api/instagram/feed

Curated Instagram feed

Served from the D1 cache (refreshed by a 6-hourly cron); lazily refreshed on cache miss. Returns an empty post list when Instagram credentials are not configured; 503 when a live refresh fails with no cache to fall back on.

StatusDescription
200Feed payload
503Upstream dependency unavailable

GET /api/instagram/accounts

Curated account scores with follower history

Latest score snapshot per curated account, ranked by score then followers, each with a recent follower-count history series. Empty list when Instagram credentials are not configured.

StatusDescription
200Scored accounts

GET /api/openapi.json

This OpenAPI specification as JSON

The spec bundled into the deployed worker at build time, so it always matches the running version.

StatusDescription
200OpenAPI 3.1 document

GET /api/docs

Human-readable API reference

Dependency-free HTML rendered from the bundled spec at request time.

StatusDescription
200HTML documentation page