zc-usage header on the successful response. ZeroClick records the usage as it returns the response to the agent. Reserve the asynchronous report for work that finishes after the response is gone.
Settle on the response
zc-usage is a response header carrying a JSON array of what the request used:
- 2xx only. A 4xx is the buyer’s bad input and a 5xx is your failure; neither delivered anything, so neither bills. The Go middleware enforces this. In TypeScript and Python, call
withUsageonly on the success path. - ZeroClick strips it. The agent never sees
zc-usage: the proxy consumes it and removes it from the response. - Settle the actual quantity. For a fixed
quantity, that is what you declared. For amaxQuantityceiling, it is anywhere from zero up to the ceiling. See charge up to a maximum. The platform accepts0(a delivered response that produced nothing settles the ceiling at $0). The TypeScript and Python helpers validate quantities as at least 1, so settle a zero-output ceiling by omitting that item.
Report usage
Some work outlives the response: a job that keeps extracting after you return202, tokens streamed over a connection that has already closed, a worker that settles from a queue. Report that usage asynchronously with POST /v1/usage, through reportUsage, report_usage, or ReportUsage, using the usage:write key (split keys put it in the worker):
idempotencyKey, and the key is yours to construct: the SDKs never generate one. Derive it from stable facts, such as the request id plus the meter (zcreq_8h2m4x0q9k1f_output_tokens). A random key turns every retry into a double bill. duplicate: true means the key already landed and the stored event was replayed: a success, not an error. There are no automatic retries. Retry yourself with the same key.
On the wire, the call and its response:
Request
Response
quantity is an integer of at least 1 (maximum 2,147,483,647); occurredAt is an optional ISO timestamp for when the usage happened. A report can fail with 402 (access_inactive, plan_expired, usage_exhausted), 409 (meter_not_priced), or otherwise 404 for a missing resource. The SDKs surface these as typed errors carrying the status and reason. See errors. A failed report after delivery means work went unbilled. Log it with the reason rather than failing a request the buyer already received.
Choose a path
- Settle on the response when you know the quantity before you respond: most requests, including ceilings whose actual you know by the end of the handler.
- Report asynchronously when the work finishes after the response, when settlement happens in a separate worker, or when there is no HTTP response to stamp (a connection upgraded away from HTTP, a queue consumer).
- Combine them for work you can’t size up front: a fixed part settled on the response, the variable part reported when you know it. That is the pattern in charge up to a maximum.