Files
Samuel AmarandClaude Fable 5 9a3feccc90 Add hierarchical key-scoped catalog API for machine consumers
GET /catalog walks agents through progressive disclosure: available
APIs with descriptions, then one API's tags, then a tag's endpoints,
then full detail for a single operation with $refs resolved inline.
Oversized tags (tag-poor upstreams) fall back to path-prefix groups,
drillable with ?prefix= and compressed through single-child chains.

Responses are filtered to the key's grants, carry ETags for cheap
revalidation, and reuse the discovery spec cache. Key auth is shared
with the docs portal via portal.resolve_api_key; 'catalog' and 'mcp'
are now reserved slugs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 11:39:07 +02:00

111 lines
5.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API Gateway
A FastAPI-based gateway that exposes a single entrypoint for multiple upstream APIs,
with a built-in web management console.
## Features
- **Single entrypoint** — consumers call `/<service-slug>/<path>` and the gateway
proxies to the registered upstream (all HTTP methods, query strings, bodies,
headers). `/admin`, `/static`, `/docs` and `/health` are reserved.
- **Endpoint catalog, auto-synced** — each service's callable endpoints are
mirrored from its OpenAPI document (`/openapi.json`, `/swagger.json` or
`/api-docs`) whenever the API Keys page loads: new operations appear, removed
ones are deleted (their grants with them). Services without a reachable spec
keep their existing catalog. Patterns support `{param}` (one segment), `*`
(within a segment) and `**` (any depth).
- **Per-key access rights, hierarchical picker** — every API key is granted a
specific set of endpoints, chosen in a path tree (service → path segments →
operations) where a parent checkbox selects everything beneath it. A request
must match a listed endpoint AND the key must hold a grant on it; otherwise
404 (`unknown_endpoint`) or 403 (`access_denied`). Keys are hashed at rest,
shown once at creation, revocable, with per-key rate limits.
- **Web management console** (`/admin`) — manage connected APIs, users, and API
keys; usage dashboards with charts. Every console user has full management
access.
- **Transaction capture & request browser** — every gateway request is recorded
with service, endpoint, key, status, latency, client IP, query string, and
the request/response payloads (textual bodies up to 64 KB; binary summarized).
The Requests page filters by service / user / key / status class / path and
links to a per-request detail view showing the complete transaction.
- **Usage monitoring** — charts: traffic and average latency over time (adaptive
buckets: 5 min / 1 h / 1 day), volume by service / user / endpoint, status
codes, latency by service, top keys — filterable by time range (1 h 30 d),
service, user and key.
- **Consumer docs** — `GET /openapi.json` (authenticated with `X-API-Key`)
returns a merged OpenAPI document scoped to that key: every operation the
key holds a grant on, across all services, rewritten to the gateway's
routes. `GET /docs` serves a Swagger UI portal around it — paste a key,
browse and try exactly what that key can call.
- **Machine catalog** — a hierarchical, key-scoped catalog for agents/MCP
clients: `GET /catalog` (available APIs with descriptions), then
`/catalog/{slug}` (tags), then `/catalog/{slug}/tags/{tag}` (endpoints;
oversized tags group by path prefix, drill with `?prefix=`), then
`/catalog/{slug}/operation?method=&path=` (full schema, `$ref`s resolved
inline). Authenticated with `X-API-Key`; responses carry ETags.
- **Retention** — a background purge runs every 6 hours: request/response
payloads are blanked after 7 days (the log row stays inspectable), whole log
rows are deleted after 90 days, and freed pages are returned to the
filesystem via incremental vacuum. Both windows are configurable; `0`
disables that tier.
## Quick start
```bash
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Open http://localhost:8000/admin — first startup seeds an admin account
(`admin` / `admin` by default — **change it immediately**).
## Docker
```bash
docker compose up -d
```
The console is served on http://localhost:8000/admin; the SQLite database
persists in the `gateway-data` volume. Set `GATEWAY_SECRET_KEY` (and the admin
credentials) in `docker-compose.yml` before deploying anywhere real. The image
runs a single uvicorn worker on purpose — the rate limiter and the OpenAPI
sync cache are in-memory.
## Configuration (environment variables)
| Variable | Default | Purpose |
|---|---|---|
| `GATEWAY_DATABASE_URL` | `sqlite:///./gateway.db` | SQLAlchemy database URL |
| `GATEWAY_SECRET_KEY` | dev value | Session cookie signing key — set in production |
| `GATEWAY_ADMIN_USER` / `GATEWAY_ADMIN_PASSWORD` | `admin` / `admin` | Seeded admin credentials |
| `GATEWAY_PAYLOAD_RETENTION_DAYS` | `7` | Blank stored payloads after N days (0 = keep forever) |
| `GATEWAY_LOG_RETENTION_DAYS` | `90` | Delete log rows after N days (0 = keep forever) |
## Using the gateway as a consumer
1. Register a service (e.g. slug `weather``https://api.example.com/v1`) and
list its endpoints (import from OpenAPI or add manually).
2. Issue an API key and tick the endpoints it may call.
3. The consumer calls:
```bash
curl -H "X-API-Key: gw_..." "http://localhost:8000/weather/forecast?city=Paris"
```
The gateway authenticates the key, matches the request against the service's
endpoint list, checks the key's grant and the rate limit, proxies the request to
`https://api.example.com/v1/forecast?city=Paris`, logs it, and returns the
upstream response. Gateway-generated errors are JSON with an `error` code:
`missing_api_key` (401), `invalid_api_key` / `access_denied` (403),
`unknown_service` / `unknown_endpoint` (404), `rate_limited` (429),
`upstream_unreachable` (502), `upstream_timeout` (504).
## Notes & limits
- The stats queries use SQLite date functions; if you point `GATEWAY_DATABASE_URL`
at Postgres/MySQL, adapt `app/admin/stats.py` (`strftime`, `iif`).
- The rate limiter is in-memory (single process). Run one worker, or swap in Redis
for multi-worker deployments.
- Streaming responses are buffered (fine for JSON APIs; not for large file proxying).