ZeroClick::Sellers::Error is reserved for what you got wrong, or what the environment did.
Decisions, not exceptions
Usage denial reasons
service_not_found, access_not_found, access_inactive, plan_expired, meter_not_found, meter_not_priced, usage_exhausted.
Any other reason on the wire is treated as a malformed response rather than passed through — a seller matching on an unknown reason would silently take the wrong branch.
ZeroClick::Sellers::Error
ZeroClick::Sellers.error?(e, "api_status_error") is the same check as a predicate.
Encryption error codes
require "zeroclick/sellers/encryption" adds its own codes on the same ZeroClick::Sellers::Error, with operation set to decrypt_request or encrypt_response:
These are raised, not returned: an undecryptable body is not a routine refusal the way an unsigned request is, so it surfaces as an exception for you to map to a response. Decide deliberately whether that response is encrypted — if the failure was in reading the header, there is no reply key to encrypt to.
Stateful refusal codes
The stateful rail answers401 with one of exactly four codes, which are response bodies rather than exceptions:
Deliberately fewer codes than the proxy rail: a caller learning why its signature failed learns something about the secret.
Which failures the outage policy covers
Only these mean “the allowance API did not give us an answer”:api_transport_error, api_status_error, api_response_invalid.
Only they are subject to allowance_unavailable_policy. Anything else — a malformed_input from your own call — propagates under every policy, because a bug in your integration must not be laundered into a fail-open allow.
This is where the SDKs legitimately differ. The Go SDK treats any 4xx from the allowance API (a revoked key, an unknown service) as a hard error, never an outage. The Ruby, TypeScript and Python SDKs route API errors through the policy. A revoked key under a
"allow" policy therefore serves unbilled work in Ruby, and refuses in Go — set the policy to "deny" or "throw" if that matters to you.Errors raised at construction
Invalid configuration raises immediately rather than at the first request:- no credential (neither
api_keynor the usage keys) - both or neither of
signing_secretsandresolve_signing_secret - an empty
signing_secrets - an
allowance_unavailable_policythat is not"allow","deny"or"throw"
Middleware::Meter likewise raises at wire-up when given a max_quantity item, because it would otherwise bill zero on a delivered 200. See middleware.
Errors that are not raised
The SDK never retries and never invents an idempotency key. A failedreport_usage is yours to retry, with the same key — which is exactly why the key must be derived from zc_request_id rather than generated.