Skip to main content

ZeroClick::Sellers.create

Builds a client. See configuration for every option.
Invalid configuration raises ZeroClick::Sellers::Error with code malformed_input at construction, not at the first request.

#guard

Verifies the request, then checks the buyer can pay before you do the work.
Returns Allow or Deny — never raises for an unpayable or unsigned request, because both are expected events.

#guard_identity

Guards a free endpoint that must still know the caller. Makes no network call.
Allow#allowance is "not_required". A signed anonymous probe is denied with reason identity_required and a 402 carrying empty usage.

#verify_request

Signature verification alone, with no allowance check.
A verified request whose zc_agent_id is nil is a signed anonymous probe — valid, not a failure.

#check_allowance

The allowance check on its own, without verification.
Unlike guard, this raises on an API failure rather than applying the outage policy — it has no verified context to fall back to.

#report_usage

Reports usage asynchronously, after the work is done.
The idempotency key is yours to derive, and must be stable for the request — derive it from zc_request_id. The SDK never invents one and never retries on your behalf, because a retry under a changed key double-bills.
A replay of the same key returns duplicate? == true. That is a success, not a failure.

Response helpers

ZeroClick::Sellers::Responses exposes the same functions plus invalid_zeroclick_signature, allowance_unavailable and usage_header.

Value types

UsageItem

What a request will be charged for.
Give at most one of quantity or max_quantity; neither defers to the meter’s configured per-request ceiling. Declaring both raises. A fixed quantity lets ZeroClick offer the exact x402 scheme. A max_quantity forces upto, which fewer clients can pay — prefer a fixed charge and report variable work afterwards. The ceiling never overcharges: ZeroClick authorises up to it and bills what you settle. But it does gate the buyer’s authorisation, which is easy to miss. A ceiling of 50 units at $0.002 plus a $0.01 request charge makes every call reserve $0.11, and a buyer whose per-call cap is below that is refused before any work happens — a client capped at $0.10 is simply turned away. The unspent remainder never leaves their wallet, but an ambitious ceiling still prices out small callers, so keep it near a realistic worst case. Middleware::Meter accepts only items with an explicit quantity, because it settles usage automatically. The other two forms are fine for guard, which only asks whether the buyer could pay.

SyncUsageItem

Actual usage, reported alongside a successful response via the zc-usage header.

Request

A framework-neutral inbound request. Build one from a Rack env with Middleware.request_from_env(env), or directly:
path_and_query must be the raw, percent-encoded target, and body the raw bytes. Decode nothing before verifying — the signature covers exactly what arrived.

Response

ZeroClickContext

Module helpers

Encrypted request bodies

A buyer can encrypt its request body end to end with your public key. The buyer can also request an encrypted reply. The helpers load only after this separate require:
Nothing new is added to your dependency graph. The gem implements the whole suite on the standard library — OpenSSL supplies P-256 ECDH, AES key wrap and AES-256-GCM, and the one missing piece, the NIST SP 800-56A Concat KDF, is a short SHA-256 loop.
Verify the signature before decrypting: the signature covers the ciphertext as it arrived on the wire, not the plaintext. The cipher suite is pinned — ECDH-ES+A256KW key management, A256GCM content encryption — so a sender cannot negotiate something weaker. Two ordering rules are worth stating outright, because both are silently wrong in a way that still returns 200:
  • Attach usage before encrypting. zc-usage is a header, which is what lets an encrypted call settle through a proxy that cannot read the body.
  • Encrypt refusals too. A buyer who asked for confidentiality does not stop wanting it because the answer is a 4xx.
Unlike the Go SDK, there is no empty-plaintext limitation here: an encrypted request whose plaintext is empty decrypts normally, matching the TypeScript and Python SDKs.
The errors page lists the encryption error codes.

The stateful helpers

require "zeroclick/sellers/stateful" implements the account routes for sellers whose purchases create accounts and API keys:
  • ZeroClick::Sellers::Stateful.handle_access_request(request, on_write:, on_mint:, base_path:, remint_policy:, ...): routes, verifies, and validates both account calls, then hands off to your two handlers. Returns nil for anything that is not one of its routes.
  • Stateful::Entitlement.should_apply?, .derive_credit_delta, .period_advanced?, and .parse: the dedup and never-regress arithmetic an account write must be applied with.
  • Stateful::Entitlement.serviceable?(state, now = Time.now): the one request-time gate — true only while the account is active and its period, when it has an end, has not lapsed.
  • Stateful::Money.parse_money_usd and .format_money_usd: six-decimal money strings to integer micros and back, so no floating-point math ever touches money.
  • Stateful::Verify.verify_access_signature, .canonical_access_string, and .sign_access_request: the purpose-carrying access signature on its own, plus a signer for integration tests.
  • Stateful::Contracts: the wire shapes — parsed entitlements expose lifetime_credit_granted_usd and lifetime_credit_reversed_usd (plus _micros variants) alongside the period and status.
Do not route these calls through #guard or #verify_request: account calls carry a different, purpose-separated signature by design. The account and key reference documents every field, status, and retry rule.