> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeroclick.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Ruby SDK middleware

> Meter and Identify — mounting the ZeroClick guard as Rack middleware in Rails, Sinatra, Hanami, or Roda.

`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 responds `2xx` — settles the usage.

```ruby theme={null}
use ZeroClick::Sellers::Middleware::Meter,
    seller: SELLER,
    service_slug: "extractor",
    usage: [ZeroClick::Sellers::UsageItem.new(meter_slug: "requests", quantity: 1)]
```

| Option            | Default | Purpose                                                         |
| ----------------- | ------- | --------------------------------------------------------------- |
| `seller:`         | —       | A client from `ZeroClick::Sellers.create`.                      |
| `service_slug:`   | —       | Which service this route belongs to. Per-route, not per-client. |
| `usage:`          | —       | The `UsageItem`s this route charges for.                        |
| `plan_slug:`      | `nil`   | Names a plan in the `402` challenge.                            |
| `max_body_bytes:` | 10 MiB  | The verification buffer ceiling.                                |

### 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:

```ruby theme={null}
# both raise malformed_input immediately

# a ceiling has no settled amount
usage: [ZeroClick::Sellers::UsageItem.new(meter_slug: "tokens", max_quantity: 1000)]

# and neither does an item that defers to the meter's configured default
usage: [ZeroClick::Sellers::UsageItem.new(meter_slug: "requests")]
```

Both forms remain valid for [`guard`](/sdks/ruby/api#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`](/sdks/ruby/api#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**.

```ruby theme={null}
use ZeroClick::Sellers::Middleware::Identify,
    seller: SELLER,
    service_slug: "extractor"
```

A signed anonymous probe (no `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:

```ruby theme={null}
zc = env["zeroclick.context"] # ZeroClick::Sellers::Middleware::CONTEXT_ENV_KEY

zc.zc_agent_id     # the buyer, or nil on a signed anonymous probe
zc.zc_request_id   # correlates with ZeroClick's logs; derive idempotency keys from it
zc.zc_buyer_id     # the buyer that owns the agent, once claimed
zc.kid             # which signing secret verified the request
zc.timestamp       # the signed timestamp, in epoch seconds
```

Under Rails that is `request.env["zeroclick.context"]`.

## The request body

Verification covers the whole body, so the middleware must read it. It then replaces `rack.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:

```ruby theme={null}
# config.ru
map "/v1/extract" do
  use ZeroClick::Sellers::Middleware::Meter,
      seller: SELLER, service_slug: "extractor",
      usage: [ZeroClick::Sellers::UsageItem.new(meter_slug: "requests", quantity: 1)]
  run ExtractApp
end

map "/health" do
  run HealthApp
end
```

Under Rails, mount a guarded Rack app in `routes.rb`, or guard inside the controller with [`guard`](/sdks/ruby/api#guard) when routing is more naturally expressed there.

## Ordering

Mount the guard **above** anything that reads or rewrites the request body, and above logging you want to carry the buyer id. Mount it **below** anything that must run for unauthenticated traffic too — health checks, and whatever terminates TLS or normalises the host.
