Cost receipts
The X-Cost response header: exact format, how to enable it, and why receipts are permission-gated.
#Cost receipts
A cost receipt is a per-request breakdown of everything the request consumed, returned in the X-Cost response header. Where the Performance dashboard shows trends, a receipt shows one request in full — the fastest way to answer "what did that call just cost?"
#Format
X-Cost: dur=142;d1q=9;rr=1420;rw=2;rret=27;d1ms=38;kvr=2;kvw=0;kvd=0;kvl=0;do=1;r2=0;sub=0
X-Cost-Slowest: rows=1380;ms=22;sql=SELECT * FROM records WHERE ...
X-Cost is a semicolon-separated list of key=value pairs, always in this order:
| Key | Meaning |
|---|---|
dur |
Total request duration, milliseconds (wall clock, not CPU time) |
d1q |
D1 queries executed |
rr |
D1 rows read across all queries |
rw |
D1 rows written across all queries |
rret |
Rows actually returned to your code. rr ÷ rret is the read amplification |
d1ms |
Time spent in D1, milliseconds |
kvr |
KV reads |
kvw |
KV writes |
kvd |
KV deletes |
kvl |
KV list operations |
do |
Durable Object calls |
r2 |
R2 operations |
sub |
Outbound subrequests (webhooks, external APIs) |
X-Cost-Slowest describes the request's single most expensive D1 query, ranked by rows read:
| Key | Meaning |
|---|---|
rows |
Rows this query read |
ms |
This query's duration, milliseconds |
sql |
The SQL text, truncated to 160 characters. Bound parameter values are never included |
#How to enable receipts
In development (ENVIRONMENT=development), receipts are always on — every response carries X-Cost.
In production, a response includes a receipt only when all three conditions hold:
- The request sends the header
X-Cost-Debug: 1. - The caller is authenticated and holds the
system/cost-receipts:readpermission. Admins have it by default; it can be granted to any role through standard role permissions. - The
analytics.cost_receiptssetting is on (it's the global kill switch, on by default).
Anonymous requests never receive a receipt, regardless of settings.
#Example
$ curl -i -H "X-Cost-Debug: 1" -H "Authorization: Bearer sk-your-api-key" "https://your-api.example.com/api/v1/collections/products/records?limit=25"
HTTP/2 200
content-type: application/json
X-Cost: dur=96;d1q=7;rr=612;rw=0;rret=25;d1ms=24;kvr=1;kvw=0;kvd=0;kvl=0;do=1;r2=0;sub=0
X-Cost-Slowest: rows=540;ms=11;sql=SELECT * FROM c_products WHERE json_extract(...)
Reading this receipt: the request ran 7 D1 queries reading 612 rows to return 25 records — roughly 24× read amplification. The slowest query read 540 of those rows, and its SQL shows a json_extract filter, which can't use an index. The 1 KV read is the session cache; the 1 DO call is the rate limiter — both part of the normal per-request baseline.
Both headers are exposed via CORS, and X-Cost-Debug is an allowed request header — so you can read receipts from browser DevTools or a fetch() call, not only from curl.
#Why receipts are permission-gated
A receipt reveals internals: how many queries an endpoint runs, the shape of its slowest SQL, which tables it touches. That's exactly what you want when tuning your own instance, and exactly what you don't want to hand to an anonymous visitor probing your API.
Two design rules keep receipts safe to enable:
- A receipt only ever describes your own request. There is no way to fetch a receipt for someone else's traffic — the header is computed for, and attached to, the response you triggered. For historical traffic, the dashboard shows recorded numbers without SQL text.
- SQL is truncated and parameters are never captured. Even with the permission, a receipt can't leak the values in a query — only its shape, cut to 160 characters.
Because gating uses the standard permission system, you can grant system/cost-receipts:read to a developer role on a production instance without making anyone an admin.