Cost Observability
On this page
The worker instruments per-client cost telemetry using Cloudflare Analytics Engine.
All events are written to the fourai_cost_metrics dataset.
Billing independence: Cost tracking is separate from client billing. Billing uses credits consumed (messages, automations) only. Cost observability tracks all platform events (AI queries, container activity, R2 ops, cron triggers, WebSocket sessions, action completions, action steps) for internal cost analysis and infrastructure optimization. Billing events are a subset of cost tracking events. See VISION.md.
Setup
The ANALYTICS binding must exist in your Cloudflare account before deploying.
It is declared in wrangler.jsonc and maps to the fourai_cost_metrics dataset.
Customer identity is read from the AGENT_TENANT env var (e.g. arua).
If unset it defaults to default.
Event Schema
ai-query
Emitted for each AI response received over the WebSocket proxy.
| Field | Type | Description |
|---|---|---|
blob1 | string | Event type: "ai-query" |
blob2 | string | Model name (e.g. claude-3-5-sonnet) |
blob3 | string | Provider (e.g. anthropic) |
blob4 | string | Customer identifier |
blob5 | string | Action type |
blob6 | string | Request ID |
blob7 | string | Session ID (empty if not set) |
double1 | number | Input tokens |
double2 | number | Output tokens |
double3 | number | Estimated cost in USD |
double4 | number | 1 if cached, 0 otherwise |
container-active
Emitted on every ensureGateway() call (≈ every request) with a 10-second
index bucket to allow deduplication.
| Field | Type | Description |
|---|---|---|
blob1 | string | Event type: "container-active" |
blob2 | string | Customer identifier |
blob3 | string | Instance type (e.g. standard-1) |
blob4 | string | Action: start | heartbeat | stop |
blob5 | string | Gateway process ID |
double1 | number | Always 1 (count) |
r2-operation
Emitted for R2 backup/restore operations.
| Field | Type | Description |
|---|---|---|
blob1 | string | Event type: "r2-operation" |
blob2 | string | Customer identifier |
blob3 | string | Operation type: backup | restore | sync | head | delete |
blob4 | string | R2 pricing class: ClassA | ClassB |
double1 | number | Bytes transferred |
double2 | number | Duration in milliseconds |
cron-trigger
Emitted on every scheduled cron invocation.
| Field | Type | Description |
|---|---|---|
blob1 | string | Event type: "cron-trigger" |
blob2 | string | Customer identifier |
blob3 | string | Trigger reason |
double1 | number | Always 1 (count) |
double2 | number | 1 if container was woken, 0 otherwise |
ws-session
Emitted when a WebSocket session closes.
| Field | Type | Description |
|---|---|---|
blob1 | string | Event type: "ws-session" |
blob2 | string | Customer identifier |
blob3 | string | Session ID |
double1 | number | Session duration in seconds |
double2 | number | Total messages exchanged |
action-completed
Emitted when an agent action completes (message or automation).
| Field | Type | Description |
|---|---|---|
blob1 | string | Event type: "action-completed" |
blob2 | string | Customer identifier |
blob3 | string | Instance ID (empty if not set) |
blob4 | string | Action type: message | automation |
blob5 | string | Channel type (e.g. slack, api) |
blob6 | string | Agent identifier |
blob7 | string | Status: success | failure |
blob8 | string | Failure reason (empty on success) |
double1 | number | Input tokens |
double2 | number | Output tokens |
double3 | number | Cost in USD |
double4 | number | Duration in milliseconds |
action-step (v0.3.0)
Emitted for each individual step within an action (AI query, tool call, exec call, etc.).
| Field | Type | Description |
|---|---|---|
blob1 | string | Event type: "action-step" |
blob2 | string | Customer identifier |
blob3 | string | Action ID |
blob4 | string | Step type: ai_query | tool_call | etc. |
blob5 | string | Tool name (empty if not a tool step) |
blob6 | string | Outcome: completed | error | blocked |
double1 | number | Duration in milliseconds |
double2 | number | AI cost in USD (0 for non-AI steps) |
double3 | number | Infrastructure cost in USD |
double4 | number | Credits consumed (1 if completed step) |
SQL Queries
Analytics Engine uses a SQL-like syntax. Replace fourai_cost_metrics with
your actual dataset name if it differs.
Per-customer AI spend (last 24 hours)
SELECT
blob4 AS customer,
blob2 AS model,
SUM(double1) AS tokens_in,
SUM(double2) AS tokens_out,
SUM(double3) AS cost_usd
FROM fourai_cost_metrics
WHERE blob1 = 'ai-query'
AND timestamp > NOW() - INTERVAL 1 DAY
GROUP BY customer, model
ORDER BY cost_usd DESCContainer active time per customer
Each heartbeat covers a 10-second bucket. Multiply count by 10 for seconds.
SELECT
blob2 AS customer,
blob3 AS instance_type,
COUNT(*) * 10 AS estimated_active_seconds
FROM fourai_cost_metrics
WHERE blob1 = 'container-active'
AND blob4 = 'heartbeat'
AND timestamp > NOW() - INTERVAL 1 DAY
GROUP BY customer, instance_typeContainer start events
SELECT
blob2 AS customer,
COUNT(*) AS cold_starts
FROM fourai_cost_metrics
WHERE blob1 = 'container-active'
AND blob4 = 'start'
AND timestamp > NOW() - INTERVAL 7 DAY
GROUP BY customer
ORDER BY cold_starts DESCR2 bandwidth per customer
SELECT
blob2 AS customer,
blob3 AS op_type,
SUM(double1) AS bytes_transferred,
AVG(double2) AS avg_duration_ms,
COUNT(*) AS op_count
FROM fourai_cost_metrics
WHERE blob1 = 'r2-operation'
AND timestamp > NOW() - INTERVAL 7 DAY
GROUP BY customer, op_typeCron wake rate (container sleep efficiency)
A high woke_rate means the container is frequently sleeping between crons —
consider lowering SANDBOX_SLEEP_AFTER.
SELECT
blob2 AS customer,
COUNT(*) AS total_crons,
SUM(double2) AS woke_count,
SUM(double2) / COUNT(*) AS woke_rate
FROM fourai_cost_metrics
WHERE blob1 = 'cron-trigger'
AND timestamp > NOW() - INTERVAL 7 DAY
GROUP BY customerWebSocket session duration distribution
SELECT
blob2 AS customer,
COUNT(*) AS sessions,
AVG(double1) AS avg_duration_s,
MAX(double1) AS max_duration_s,
AVG(double2) AS avg_messages
FROM fourai_cost_metrics
WHERE blob1 = 'ws-session'
AND timestamp > NOW() - INTERVAL 7 DAY
GROUP BY customerCost Estimation
The GET /api/admin/costs endpoint returns the pricing constants used by
estimateContainerCost() and an example GraphQL query for the AI Gateway
dashboard.
Container pricing (Cloudflare, as of Jul 2026)
| Resource | Rate | Billing model |
|---|---|---|
| CPU | $0.00002 / vCPU-second | Active usage × 0.2 utilisation |
| Memory | $0.0000025 / GiB-second | Provisioned (always billed) |
| Disk | $0.00000007 / GB-second | Provisioned (always billed) |
Instance types
| Type | vCPU | Memory | Disk |
|---|---|---|---|
lite | 1/16 | 0.25 GiB | 2 GB |
basic | 1/4 | 1 GiB | 4 GB |
standard-1 | 1/2 | 4 GiB | 8 GB |
standard-2 | 1 | 6 GiB | 12 GB |
standard-3 | 2 | 8 GiB | 16 GB |
standard-4 | 4 | 12 GiB | 20 GB |
The default instance is standard-1. Memory and disk are billed continuously
regardless of CPU activity, so the sleep setting (SANDBOX_SLEEP_AFTER)
does not reduce memory/disk costs.