# Plan: Discovery API documentation (docs/api.md) **Created:** 2026-08-11 21:52 ## Context The operator serves an HTTP discovery/lease API on `:8090` ([internal/discovery/](internal/discovery/)) that crawler clients use to list proxies, acquire TTL leases, release them, and report rate-limiting. There is no dedicated API reference today: README has four quickstart curls (no auth header, no schemas), and `docs/architecture.md` §7 has an ASCII route map. The user wants full documentation with curl examples for every feature. **Decisions made with user:** doc lives in a new `docs/api.md`; commit straight to `main` (no MR). ## Deliverable ### 1. New file `docs/api.md` — full API reference Content (all facts verified against code during planning): - **Overview & base URL** — what the API is; in-cluster FQDN `http://egress-proxies-operator-controller-manager-discovery-service.egress-proxies-operator-system.svc.cluster.local:8090` (Service: `config/default/discovery_service.yaml`); local access via `kubectl port-forward svc/egress-proxies-operator-controller-manager-discovery-service 8090:8090`. - **Authentication** — static bearer token from `DISCOVERY_TOKEN` env var (populated from the optional `discovery-token` Secret, key `token`; `config/manager/manager.yaml`). Empty token ⇒ auth disabled with startup warning. Curl: `-H "Authorization: Bearer $TOKEN"` on every example. `/healthz` always exempt. - **Conventions** — JSON everywhere; error envelope `{"error":"","message":""}`; request bodies capped at 64 KiB; proxies with a deletion timestamp are excluded from all responses. - **Configuration table** — `--discovery-addr` (default `:8090`), `--max-lease-ttl` (default 1h), `--lease-cooldown` (default 15m), `DISCOVERY_TOKEN`. Note the shipped Deployment passes none of these flags, so defaults apply. - **Endpoints**, each with request/response schema, status codes, and a copy-pasteable curl example: - `GET /healthz` — liveness, unauthenticated. - `GET /v1/proxies` — filters `healthy=true|false` (else 400 `invalid_query`) and repeatable `attr.=` (verbatim equality on `spec.attributes`, all pairs must match). Response `{"proxies":[proxyView...],"count":N}` sorted by id. Full `proxyView` field table: `id` (ns/name), `ip`, `port`, `attributes`, `phase` (Pending/Provisioning/Ready/Unhealthy/Deleting/Failed), `healthy` (condition `Healthy` == True), `latencyMillis`, `activeLeases`, `maxLeases` (default 5; explicit 0 = unleasable). - `POST /v1/leases` — body `{selector, ttlSeconds, target}` all optional; TTL defaults 5m, capped at max-lease-ttl (else 400 `invalid_ttl`). 201 `{leaseID, proxy, expiresAt, ttlSeconds}`; 409 `no_match` with `considered/atCapacity/inCooldown/unhealthy` counts (documented meanings). - `DELETE /v1/leases/{id}` — early release; always 204, idempotent. - `POST /v1/leases/{id}/report` — body `{result: ok|rate_limited|banned, target}`; 204, 404 `unknown_lease`, 400 `invalid_result`. `ok` is a pure ack; `rate_limited` and `banned` behave identically today (both start one cooldown window). - **Selection & cooldown semantics** (short section — this is the non-obvious part clients need): - Selection order: fewest active leases → lowest latency → lexicographic id; deterministic; only healthy, non-deleting proxies with free capacity are candidates. - Cooldown: 15m default (`--lease-cooldown`), keyed `{proxy, target}`. Report with a target blocks only leases requesting that target; report without a target (and lease without one) creates a **global** cooldown blocking all acquisitions of that proxy — call this footgun out explicitly. - Expired leases stay reportable for one cooldown window past TTL. - **End-to-end workflow example** — numbered curl walkthrough: acquire → use `proxy.ip:port` as HTTP proxy (`curl -x`) → report `rate_limited` on 429 → release. Using a `jq`-extracted `leaseID`. - **Caveats** — lease/cooldown state is in-memory and per-process: single replica only, operator restart drops all leases and cooldowns. ### 2. Pointers to the new doc (small edits) - `README.md`: one-line link near the quickstart curl section ("full reference: docs/api.md"). - `docs/architecture.md` §7: one-line link to `docs/api.md` as the detailed reference. ### 3. Housekeeping per CLAUDE.md - First action post-approval: copy this plan to `docs/plans/-discovery-api-docs.md` (timestamp from `date "+%Y-%m-%d-%H%M"`), commit it alone. - Then write the docs, commit to `main` with `Co-Authored-By: Claude ` trailer, push. - Append execution summary + status checklist to `docs/plans-executions/-discovery-api-docs.md` in the docs commit. - Add `CHANGELOG.md` entry (timestamp via `date "+%Y-%m-%d %H:%M %Z"`) once the user confirms. ## Key source files (facts source of truth) - [internal/discovery/handlers.go](internal/discovery/handlers.go), [internal/discovery/server.go](internal/discovery/server.go) — routes, schemas, status codes, auth, limits. - [internal/lease/store.go](internal/lease/store.go) — selection order, cooldown/retention, stats. - [api/v1alpha1/proxy_types.go](api/v1alpha1/proxy_types.go), [api/v1alpha1/helpers.go](api/v1alpha1/helpers.go) — defaults (port 3128, maxLeases 5), phases, conditions. - [cmd/main.go](cmd/main.go) — flags/env defaults. - [config/default/discovery_service.yaml](config/default/discovery_service.yaml), [config/manager/manager.yaml](config/manager/manager.yaml) — service DNS, token secret. ## Verification - Cross-check every documented status code / field name against `internal/discovery/server_test.go` expectations. - Sanity-run `go build ./... && go test -short ./internal/discovery/ ./internal/lease/` (no code changes expected — confirms docs match current behavior, not a stale tree). - Optionally lint the curl JSON bodies by piping each through `jq .` locally.