How a request flows
Request lifecycle
middleware order — auth runs innermost, so the logger captures even rejected calls
Client
Authorization: Bearer <key>
→
CORS
origin allowlist
→
RequestLogger
writes api_request_log, never blocks
→
ApiKey auth
SHA-256 hash → client lookup, or admin shortcut
→
Route handler
reads request.state.client
Tier ladder
require_tier(min) gates premium reads
0freeauthenticated shared reads
1starter · basicstandard reads
2growth · proaffinity recompute, group refresh
Access & limits
how each route is protected
| shared | Bearer key required; shared catalog/data without a per-resource tenant gate |
| client | Bearer key required; caller-owned resources enforce client_id |
| tier | require_tier("pro") minimum level |
| admin | ADMIN_API_KEY shortcut → is_admin flag |
| va-scope | require_scope("va"); key locked to /va/* |
| limits | 300/min per-IP · LLM 5/min · VA 30/min · recs 10/min |
Conventions
| Pagination | Opaque base-64 cursor (wins over offset) on all feed/trending/search routes; responses carry has_more + next_cursor. |
| Windows | Velocity windows are 6h · 24h · 72h · 7d; search uses its own 1h/12h scale and resolves to nearest. |
| Size tiers | nano · micro · mini · small · medium · large · xl — audience buckets used across velocity/trending filters. |
| Ownership | Models & topic-groups are per-client. The niche registry exposes canonical, caller-owned, and legacy topics; foreign-private topic IDs fail closed as 404. |
| No fabrication | Empty result sets return empty — the API never invents rows to fill a page. |