# leaguesync-api v0.20261015.3

Read-only query API over the leaguesync SQLite database, plus a few
shaped endpoints (meetups, sync logs, service schedules).

## Authoritative reference

- `/openapi.json` — full OpenAPI 3.x spec. Every endpoint, every
  request and response schema, every security scheme. If you're an
  agent or a client generator figuring out capabilities, start here.
- `/docs` — Swagger UI rendering of the same spec, with an Authorize
  button to try protected routes in a browser.
- `/redoc` — alternate read-focused rendering.

## Auth

Three accepted ways to present the operator-supplied API token; any
one is sufficient. Full details under `components.securitySchemes`
in `/openapi.json`.

    Authorization: Bearer <token>                  # canonical
    Authorization: Basic <base64(x:<token>)>       # username ignored
    ?token=<token>                                 # query string

`/`, `/docs`, `/redoc`, `/openapi.json` are public. Everything else
requires one of the above.

## MCP

The same database is available over the Model Context Protocol, on the
streamable-HTTP transport:

    https://sync.jtlapp.net/mcp

In Claude (desktop, web, mobile): **Add custom connector**, paste that
URL, leave **Sign in now** selected, click Connect. A page on this host
asks for the API token once; Claude then holds an access token issued by
this server, and the API token itself never lives in Claude. To revoke
every issued token at once, rotate `API_TOKEN`.

Anything else that speaks MCP (Claude Code, the `mcp` CLI, scripts) can
skip OAuth and send the API token directly as `Authorization: Bearer
<token>`. Both credentials are accepted on every request.

Tools: `query` (read-only SELECT/WITH), `list_tables`, `describe_table`.

OAuth discovery: `/.well-known/oauth-protected-resource/mcp` and
`/.well-known/oauth-authorization-server`.

## Example SELECTs

`/query` accepts any read-only SELECT or WITH statement. Send via
`POST /query` with JSON `{"sql": "...", "params": [...]}` or
`GET /query?sql=...`. `Accept: text/csv` returns CSV; default is JSON.
Each statement has a 10-second time limit (HTTP 408 when exceeded).

    SELECT name FROM sqlite_master WHERE type='table' ORDER BY name
    SELECT COUNT(*) FROM meetup_events WHERE deleted_at IS NULL
    SELECT title, date_time FROM meetup_events
        ORDER BY date_time DESC LIMIT 10
    SELECT strftime('%Y-%m', completed_at) AS month, COUNT(*) AS n
        FROM visits GROUP BY month ORDER BY month
    SELECT object_type, status, COUNT(*) AS n FROM sync_blocks
        GROUP BY object_type, status
