46 · Public API completeness audit
Scope. REST surface under /api/v1/* and the published OpenAPI spec at /api/openapi.json. Goal: confirm every route is documented, has the right envelope, and returns consistent pagination + error shape. Performed without hitting prod (static code audit + spec diff). Date: 2026-05-17.
Per-endpoint pass/fail (top 15)
Legend: P = pass, F = fail, ∼ = partial. Columns are: documented in OpenAPI / consistent error envelope {error:{code,message,status}} / pagination shape (when listing) / sensible cache + CORS headers / response wrapped with meta.latencyMs + meta.dataFreshness.
| # | Route | Auth | Doc | Error env | Pagination | Headers | meta | Notes |
|---|---|---|---|---|---|---|---|---|
| 1 | GET /api/v1/me |
key | P | P | n/a | P | P | OK |
| 2 | GET /api/v1/health |
key | P | P | n/a | ∼ | P | No Cache-Control: no-store set explicitly (relies on dynamic = force-dynamic). |
| 3 | GET /api/v1/markets |
key | P | P | n/a | P | P | OK · apiJson() emits rate-limit headers. |
| 4 | GET /api/v1/stats |
key | P | P | n/a | ∼ | P | revalidate=60 but force-dynamic overrides — leftover dead config. |
| 5 | GET /api/v1/companies |
key | P | P | offset+limit | P | P | OK. No total field on payload (offset-based, hard to detect end). |
| 6 | GET /api/v1/companies/{slug} |
key | P | P | n/a | P | P | OK |
| 7 | GET /api/v1/companies/{slug}/declarations |
key | P | P | offset+limit | P | P | OK |
| 8 | GET /api/v1/companies/{slug}/insider-flow |
key | P | P | n/a | P | P | OK |
| 9 | GET /api/v1/declarations |
key | P | P | offset+limit | P | P | OK |
| 10 | GET /api/v1/declarations/{amfId} |
key | P | P | n/a | P | P | OK |
| 11 | GET /api/v1/scoring/explain/{amfId} |
key | P | P | n/a | P | P | OK |
| 12 | GET /api/v1/insiders |
key | P | P | offset+limit | P | P | OK |
| 13 | GET /api/v1/insiders/{slug} |
key | P | P | n/a | P | P | OK |
| 14 | GET /api/v1/insiders/{slug}/declarations |
key | P | P | offset+limit | P | P | OK |
| 15 | GET /api/v1/insiders/{slug}/timeline |
key | P | P | n/a | P | P | OK |
| 16 | GET /api/v1/signals |
key | P | P | limit-only | P | P | No offset → can't paginate beyond limit. |
| 17 | GET /api/v1/backtest |
key | P | P | n/a | P | P | OK |
| 18 | GET /api/v1/search |
key | P | P | n/a | P | P | OK |
| 19 | GET /api/v1/oos-performance |
none | F (was missing in spec, now added) | P | n/a | ∼ | F (returns raw computeOOSMetrics() without meta envelope) |
Inconsistent: no API key required; envelope drift. |
| 20 | GET /api/v1/strategy/winning |
key | F (was missing in spec, now added) | P | n/a | P | P | Doc page already referenced it, but it was absent from OpenAPI. Now fixed. |
Gap analysis (5 lines)
- No bulk export. Competitors (Finbox, Quiver) offer
?format=csvor a/exportendpoint with a 100 MB cap; we lack any. - No streaming. No SSE / WebSocket for "tail latest declarations" — every dashboard polls every 60 s. Cheap win to expose
/api/v1/stream/declarations. - No MCP tool wrapping the public v1.
/api/mcpexists but doesn't expose v1 surface; competitors (Tickeron, alphaSignal) ship one-call MCP packages. - No bulk lookup.
/api/v1/companies?slug=a,b,cis unsupported; clients have to N+1. - Anonymous sandbox tier was missing — addressed by this audit's Part B (10 calls/24h proxy, default-deny allowlist, layered defenses).
OpenAPI spec drift fixed in this pass
- Added
/api/v1/oos-performanceand/api/v1/strategy/winningto the published spec (they were referenced from/docsbut undocumented in the JSON). - Added
/api/v1/sandbox/{path},/api/v1/sandbox/challenge,/api/v1/sandbox/statusunder a newSandboxtag.
Outstanding inconsistencies (recommend fixing in a separate PR)
oos-performanceshould require an API key to be consistent with the rest of v1 (current state: anonymous, even ahead of the new sandbox route). Decide: either keep it free (honesty number) and document it as the only anon endpoint, OR gate it behind the sandbox proxy and remove the bare route.signalsis the only list endpoint without offset-based pagination.healthandstatsmixrevalidate = 60 / 300withdynamic = force-dynamic— the revalidate values are dead code and should be removed.- No
Cache-Control: no-storeon detail routes that return per-key data (declarations/insiders detail). Currently relies onforce-dynamic; explicit header preferable for downstream caches.