zeroclick-sellers package implements the ZeroClick billing guard for Python backends: it verifies that each request came from ZeroClick, checks the buyer’s allowance before you do the work, returns the refusal responses ZeroClick expects, and settles or reports what was used. This page wires the guard into FastAPI, Flask, and Django. The integration overview describes the contract it implements.
Install
py.typed, so type checkers see every signature). Its HTTP calls go through httpx.
You need three values from the dashboard: your signing secret (
zcsec_…), its key id (hsec_…), and an API key (zc_…) with the usage:read and usage:write scopes. See keys and secrets.Pick the right client
Use the async client in ASGI apps. The blocking client would stall the event loop during every allowance check. The two clients accept the same options and make the same decisions; they differ only in how they perform IO.
Guard a FastAPI route
- Adapt.
zc_request_from_asgi_scopeturns the framework request into aZcRequestbuilt from the raw path and raw body bytes. - Guard.
guardverifies thezc-signatureheader before it calls the allowance API, and returns a decision, not an exception. - Deny. A deny decision carries the exact response to return:
401for a bad signature, the402 payment_requiredbody ZeroClick converts into a payment challenge, or503under a fail-closed outage policy. An allow decision carries the verified context:decision.context.zc_agent_ididentifies the buyer. - Settle.
with_usagestamps thezc-usageresponse header with the actual usage. ZeroClick records that usage and strips the header before the agent sees the response.
Flask and Django
The handler keeps the same shape:create_seller, no await, and the WSGI adapter in place of the ASGI one.
Why the adapters exist
The signature covers the request target exactly as ZeroClick sent it, soZcRequest.path_and_query must be the raw, percent-encoded path and query. Every framework hands you a decoded one. These are the observed values for GET /v1/items/a%2Fb%20c:
Using the decoded path produces a different canonical string and fails verification. The adapters handle this. They also cover a spec difference between the two interfaces: ASGI’s
raw_path excludes the query string (the adapter appends scope["query_string"]), while WSGI’s RAW_URI and REQUEST_URI already include it.
The body has the same rule: pass the raw bytes exactly as received, before any framework parses or re-serializes them. Use await request.body() in FastAPI, request.get_data() in Flask, and request.body in Django.
Test the guard
The guard must deny a plain request with no ZeroClick headers. Only signed requests from ZeroClick reach your handler:Next steps
Configuration
Every constructor option: secrets, split usage keys, outage policy, timeouts.
API reference
Every public export, from guard to the encryption helpers.
Errors
Decisions versus exceptions, and every ZCError code.
Charge up to a maximum
Bill work you cannot size up front, like output tokens.