ZeroClick::Sellers::Middleware is plain Rack middleware, so it composes with Rails, Sinatra, Hanami and Roda alike. Nothing in it requires the rack gem — a Rack middleware is duck-typed — so mounting the guard adds no dependency to your bundle.
Meter
Guards a billable endpoint: verifies the signature, confirms the buyer can pay, and — if your app responds2xx — settles the usage.
Every item needs an explicit quantity
Meter settles usage from what it declared, so each item must carry a definite quantity. Anything else has no settled amount to report, and inventing one would silently mis-bill a delivered 200 — the exact failure this SDK exists to prevent. It therefore raises when you build the middleware, not in production:
guard, which only asks whether the buyer could pay. It is settlement that needs a definite number. Declare the fixed part in the middleware and report the variable part afterwards with report_usage.
Only delivered responses are billed
zc-usage is attached only when your app answers 2xx. A 4xx or 5xx gets no usage header, because the work was not delivered.
Identify
Guards a free endpoint that must still know which buyer is calling — a limits or account route. It makes no network call.zc-agent-id) is refused with a 402 carrying an empty usage list. That is the free identity-scoped refusal: on a pay-as-you-go seller the proxy answers it with a $0 identity challenge and retries with an agent attached, so the buyer is identified without being charged.
On a plan-priced seller the buyer never reaches that challenge. Identity is checked before anything is priced, so an unidentified caller gets 401 bearer_required from ZeroClick and must register a credential first. Either way your endpoint’s contract is the same — it refused an unidentified caller — but the buyer-side recipe differs, so point plan-priced buyers at the seller’s /auth.md.
The verified caller
A guarded request carries what it proved on the Rack env:request.env["zeroclick.context"].
The request body
Verification covers the whole body, so the middleware must read it. It then replacesrack.input with a fresh stream over the same bytes, so your app reads the body normally.
It deliberately does not call #rewind: Rack 3 dropped the requirement that input be rewindable, and against a streaming server #rewind either raises or silently does nothing — handing the application an empty body.
Mounting on a subset of routes
Rack middleware runs on every request through the stack. To guard only some paths, mount it on a scoped app rather than globally:routes.rb, or guard inside the controller with guard when routing is more naturally expressed there.