Denial Event Pipeline
Every request that the proxy rejects produces a denial event. These events are queued separately from usage events and written to ClickHouse for audit, analytics, and alerting.
Denial Points
The proxy has 8 distinct points where a request can be denied:
Pre-Authentication (before key lookup)
| Denial | Reason | HTTP Status |
|---|---|---|
| Missing API key | No Authorization: Bearer header present | 401 |
| Invalid key prefix | Key does not match expected prefix format | 401 |
| Hash not found | HMAC hash of the key does not exist in KV | 401 |
Post-Authentication (key found but request rejected)
| Denial | Reason | HTTP Status |
|---|---|---|
| Inactive key | Key exists but is disabled/revoked | 403 |
| Unknown provider | Provider path segment not recognised | 400 |
| Provider blocked | Key's policy does not allow this provider | 403 |
| Dimension validation | X-ASO-* headers fail schema validation | 400 |
| Model blocked | Key's policy blocks this specific model | 403 |
Dedicated Queue
Denial events use a separate queue (denial-events-{env}) from usage events (usage-events-{env}). This separation ensures:
- Denial spikes (e.g., a misconfigured client hammering with bad keys) do not delay usage event processing
- Each consumer can scale independently
- Monitoring and alerting can be scoped per queue
Denial Consumer
The denial consumer (apps/denial-consumer) is a Cloudflare Worker bound to the denial-events-{env} queue. It receives batches of denial events and writes them to ClickHouse.
The consumer is simple compared to the usage consumer — no pricing enrichment is needed. It validates the event schema, adds server-side timestamps, and inserts into ClickHouse.
Denial Event Fields
| Field | Type | Description |
|---|---|---|
event_id | string | Unique event ID (UUID) for deduplication |
type | string | Denial type (e.g., missing_key, inactive_key, provider_blocked) |
reason | string | Human-readable denial reason |
http_status | number | HTTP status code returned to the client |
tenant_id | string | Tenant ID (null for pre-auth denials) |
api_key_id | string | API key ID (null for pre-auth denials) |
provider | string | Requested provider (null if not parsed) |
model | string | Requested model (null if not parsed from body) |
dims | object | Dimension headers present on the request |
timestamp | datetime | When the denial occurred |
env | string | Environment (dev or prod) |
source_ip | string | Client IP (hashed for privacy) |
user_agent | string | Client user-agent header |
ClickHouse Table
Denial events are stored in a dedicated ClickHouse table partitioned by month and ordered by (tenant_id, timestamp). The event_id field is used as the deduplication key to handle at-least-once delivery from the queue.
Pre-auth denials (where tenant_id is null) are stored with a sentinel tenant ID for queryability.