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

# Ruby SDK quickstart

> Install zeroclick-sellers, mount the Rack middleware, and guard a Rails, Sinatra, or Hanami route.

The [`zeroclick-sellers`](https://rubygems.org/gems/zeroclick-sellers) gem implements the ZeroClick billing guard for Ruby backends: it verifies that each request came from ZeroClick, checks the buyer's [allowance](/docs/concepts/usage-and-allowances.md) before you do the work, returns the refusal responses ZeroClick expects, and settles or reports what was used. The [integration overview](/docs/integrate/overview.md) describes the contract it implements.

It ships **Rack middleware**, so one implementation covers Rails, Sinatra, Hanami and Roda — Rails is a Rack app.

## Install

```ruby theme={null}
gem "zeroclick-sellers"
```

The gem requires Ruby 3.1 or later and has **no runtime dependencies**: the core is stdlib-only (`openssl`, `json`, `net/http`), and the middleware is duck-typed rather than requiring `rack`. Adding it cannot change how anything else in your bundle resolves.

> [!NOTE]
>
> You need three values from the [dashboard](https://dashboard.zeroclick.io): your signing secret (`zcsec_…`), its key id (`hsec_…`), and an API key (`zc_…`) with the `usage:read` and `usage:write` scopes. See [keys and secrets](/docs/integrate/keys-and-secrets.md).
>
> ```sh theme={null}
> ZEROCLICK_SIGNING_SECRETS=hsec_k5nq0v7m3d8p:zcsec_…
> ZEROCLICK_API_KEY=zc_…
> ```
>
> `ZEROCLICK_SIGNING_SECRETS` holds `kid:secret` pairs, comma-separated. It is a map rather than a single secret so that during a rotation both the old and the new `kid` verify — otherwise every in-flight request fails the moment you rotate.

## Guard a Rails route

The Railtie is opt-in, so the gem stays usable without Rails on the load path.

```ruby theme={null}
# config/application.rb
require "zeroclick/sellers/railtie"

module MyApp
  class Application < Rails::Application
    config.zeroclick.api_key = ENV.fetch("ZEROCLICK_API_KEY")
    config.zeroclick.signing_secrets = ZeroClick::Sellers.secrets_from_env

    config.middleware.use ZeroClick::Sellers::Middleware::Meter,
      service_slug: "extractor",
      usage: [ZeroClick::Sellers::UsageItem.new(meter_slug: "requests", quantity: 1)]
  end
end
```

> [!WARNING]
>
> Do not pass `seller: ZeroClick::Sellers.seller` here. `config.middleware.use`
> evaluates its arguments in the `Application` class body, which runs **before**
> the initializer that reads `config.zeroclick` — so no client exists yet and
> boot fails. Omit `seller:` and the middleware resolves the process-wide client
> on the first request, by which point configuration exists. Outside Rails,
> where you build the client yourself, pass it explicitly.

> [!NOTE]
>
> In **development only**, `ActionDispatch::HostAuthorization` sits at the top of
> the Rails stack and answers `403` before the guard is reached, so testing
> through a tunnel needs `config.hosts << "my-tunnel.ngrok.app"`. It is not in
> the production stack, so a deployed app needs nothing here.

Inside a controller, the verified caller is on the request env:

```ruby theme={null}
class ExtractController < ApplicationController
  def create
    zc = request.env["zeroclick.context"]
    Rails.logger.info("serving #{zc.zc_agent_id} for #{zc.zc_request_id}")
    render json: { ok: true }
  end
end
```

`zc_request_id` correlates with ZeroClick's own logs — use it when you derive an idempotency key.

## Guard a Sinatra or Rack route

The same middleware, mounted the Rack way:

```ruby theme={null}
# config.ru
require "zeroclick/sellers"

SELLER = ZeroClick::Sellers.create(
  api_key: ENV.fetch("ZEROCLICK_API_KEY"),
  signing_secrets: ZeroClick::Sellers.secrets_from_env
)

use ZeroClick::Sellers::Middleware::Meter,
    seller: SELLER,
    service_slug: "extractor",
    usage: [ZeroClick::Sellers::UsageItem.new(meter_slug: "requests", quantity: 1)]

run MyApp
```

## What the middleware does

1. Reads the body (bounded at 10 MiB) and **verifies `zc-signature`** before anything else.
2. Asks ZeroClick whether the buyer can pay for the declared usage.
3. On refusal, answers `402` with the payment challenge — your app is never called.
4. On success, calls your app, then attaches `zc-usage` **only if you answered `2xx`**.

A non-2xx answer is never billed: the work was not delivered.

Your app can still read the request body. Verification has to consume it, so the middleware replaces `rack.input` with a fresh stream over the same bytes. It deliberately does not rely on `#rewind`, which Rack 3 no longer guarantees.

## Guard without the middleware

When the charge depends on the request, guard inside the action:

```ruby theme={null}
request, = ZeroClick::Sellers::Middleware.request_from_env(env)

decision = SELLER.guard(
  request,
  service_slug: "extractor",
  usage: [ZeroClick::Sellers::UsageItem.new(meter_slug: "pages", quantity: pages)]
)

unless decision.allow?
  return [decision.response.status, decision.response.headers, [decision.response.body]]
end
```

For a free endpoint that still needs to know who is calling, use `Middleware::Identify` or `SELLER.guard_identity`. Neither makes a network call.

## Deployment note

The signature covers the **raw, percent-encoded** request target. The middleware prefers the server's raw target (`REQUEST_URI`, which Puma sets) over the decoded `PATH_INFO`. Behind a server that decodes and exposes no raw target, a route containing an encoded separator such as `%2F` cannot verify — deploy behind Puma if your routes can contain one.

## Next

> **[Configuration](/docs/sdks/ruby/configuration.md)**
>
> Keys, the outage policy, and timeouts.

> **[Middleware](/docs/sdks/ruby/middleware.md)**
>
> Meter, Identify, and settling variable usage.

> **[API](/docs/sdks/ruby/api.md)**
>
> Every public method and value type.

> **[Errors](/docs/sdks/ruby/errors.md)**
>
> What raises, what returns a decision.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.