Skip to main content
The Ruby SDK draws one line: an expected event is a value, a mistake is an exception. A request with a bad signature, or a buyer who cannot pay, is an expected event — the SDK returns a decision carrying the exact response to send. 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 answers 401 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.
Match on code, never on the message. Messages are written for humans reading logs and will change.

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.
The policy is applied only after a signature verifies, so a fail-open allowance policy never becomes a fail-open signature policy.

Errors raised at construction

Invalid configuration raises immediately rather than at the first request:
  • no credential (neither api_key nor the usage keys)
  • both or neither of signing_secrets and resolve_signing_secret
  • an empty signing_secrets
  • an allowance_unavailable_policy that 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 failed report_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.