curl.
You need your seller’s signing secret (
zcsec_…) with its key id (hsec_…), a usage read key (zc_…, scope usage:read), and a usage write key (zc_…, scope usage:write). One API key with both usage scopes also works. See keys and secrets.product-watch and meters requests and output_tokens.
1
Read the three request headers
Every request ZeroClick forwards to your API carries three headers:
ZeroClick strips any
zc-* headers the agent sent before forwarding and sets its own. Once the signature verifies, these values are ZeroClick’s. Capture the raw body bytes and the raw request target (path plus query, still percent-encoded) before your framework decodes them; both feed verification. The headers reference lists every header on the wire.2
Verify the signature
zc-signature is an HMAC-SHA256 over six newline-joined fields, keyed by the signing secret whose id matches the header’s kid. The fields are the timestamp, the uppercased method, the raw path and query, the SHA-256 of the body bytes, the request id, and the agent id (or empty string). Verify the timestamp is within 300 seconds of your clock. Compare the digest in constant time. The zc-signature specification defines every rule byte by byte, with test vectors.A compact verifier in standard-library Python:3
Check the allowance
Before you do the work, ask ZeroClick whether this agent’s plan covers it. The check is bound to that request, so send the
zc-request-id you received:usage takes 1 to 20 items, one per meter, no duplicates. Each item declares quantity when the amount is known, or maxQuantity as a ceiling when it is not (see charge up to a maximum). An item that declares neither gates against the meter’s configured default ceiling. An item may not declare both.The response is one of two shapes:reason is one of service_not_found, access_not_found, access_inactive, plan_expired, meter_not_found, meter_not_priced, or usage_exhausted. See check allowances. Checking records nothing and burns no credit, so repeating a check is harmless.Two failure modes need different handling. No answer at all (a timeout, a connection failure, a 5xx) means you choose a policy: fail open and serve, or fail closed with the 503 below. The SDKs default to fail open, with a 1.5 second timeout. A 4xx from the API (a revoked key, a key missing usage:read) is a configuration error on your side. Fix it. Never fail open on it.4
Return the right refusal
Your API returns three refusals, each with an exact body ZeroClick recognizes.A missing, malformed, stale, or invalid signature gets a An empty
401. The body is the same for every signature failure. Keep the specific reason in your logs:"allowed": false gets a 402 naming what the request would cost. ZeroClick prices it and answers the agent with a payment challenge, so return the refusal before you do the work:"usage": [] marks a free, buyer-scoped endpoint that needs to know its caller. See free and identity endpoints.An unreachable allowance API under a fail-closed policy gets a 503:5
Serve and settle
On success, do the work. Settle usage synchronously by setting the Set it on 2xx responses only. A refusal must never carry usage. ZeroClick reads the header, records the usage, settles payment, and strips the header before the agent sees the response. This is the preferred settlement path; see settle usage.
zc-usage response header: a JSON array of what the response actually consumed.6
Report async usage
When you know the amount only after the response has gone out (a background job, a stream’s final token count), report it to the usage API with the write key:Reports are idempotent per service on
quantity is an integer from 1 to 2,147,483,647. occurredAt (ISO 8601) is optional; when omitted, the server sets it to now.idempotencyKey. Derive the key from what it bills (<zcRequestId>_<meterSlug>, as above). Never generate it randomly: a random key turns a retried report into a second charge, while a derived key makes the retry a replay. A replay returns "duplicate": true with the stored event; that is a success, not an error.Denials use the error envelope: 402 for access_inactive, plan_expired, and usage_exhausted; 409 for meter_not_priced; 404 for service_not_found, access_not_found, and meter_not_found.Ordering rules
Three rules hold the contract together:- Verify before everything. Nothing runs on an unverified request: not the allowance check, not your handler. The signature is the only thing that makes
zc-request-idandzc-agent-idtrustworthy. - Never bill a non-2xx.
zc-usagebelongs on successful responses only. The402refusal comes before the work, not after it. - Echo the received
zc-request-idinto the check. The id ties the challenge, the payment, and the usage record to one request; an invented id fails the check.
zc-signature specification
The byte-level spec: grammar, canonical string, and test vectors.
Errors
Every error code the platform returns, with statuses.