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

> Keys, signing secrets, the allowance-unavailable policy, and timeouts for zeroclick-sellers.

Every option is passed to `ZeroClick::Sellers.create`, or set on `config.zeroclick` under [Rails](/sdks/ruby/quickstart).

```ruby theme={null}
SELLER = ZeroClick::Sellers.create(
  api_key: ENV.fetch("ZEROCLICK_API_KEY"),
  signing_secrets: ZeroClick::Sellers.secrets_from_env
)
```

## Options

| Option                         | Default                    | Purpose                                                                  |
| ------------------------------ | -------------------------- | ------------------------------------------------------------------------ |
| `api_key`                      | —                          | A both-scopes credential (`usage:read` and `usage:write`).               |
| `usage_read_key`               | falls back to `api_key`    | Covers allowance checks: `guard`, `check_allowance`.                     |
| `usage_write_key`              | falls back to `api_key`    | Covers `report_usage`.                                                   |
| `signing_secrets`              | —                          | A `kid => secret` Hash. Exactly one of this or `resolve_signing_secret`. |
| `resolve_signing_secret`       | —                          | A callable taking a `kid`, returning the secret or `nil`.                |
| `api_base_url`                 | `https://api.zeroclick.io` | Point at a mock in tests.                                                |
| `tolerance_seconds`            | `300`                      | Accepted clock skew on the signature timestamp.                          |
| `allowance_unavailable_policy` | `"allow"`                  | `"allow"`, `"deny"` or `"throw"`. See below.                             |
| `check_timeout_seconds`        | `1.5`                      | The allowance check sits in your request path.                           |
| `on_allowance_unavailable`     | —                          | A callable receiving the `Error` on every fail-open event.               |
| `clock`                        | `-> { Time.now.to_i }`     | Freeze time in tests.                                                    |
| `http_post`                    | `Usage.method(:post)`      | Inject a transport in tests.                                             |

## Signing secrets

```sh theme={null}
ZEROCLICK_SIGNING_SECRETS=hsec_old:zcsec_old,hsec_new:zcsec_new
```

```ruby theme={null}
ZeroClick::Sellers.secrets_from_env
# => { "hsec_old" => "zcsec_old", "hsec_new" => "zcsec_new" }
```

A map rather than a single secret, because rotation is the normal case: while ZeroClick is switching, requests arrive signed with either `kid`, and both must verify. Remove the old entry only once no traffic carries it.

If your secrets live in a vault rather than the environment, pass a resolver instead:

```ruby theme={null}
SELLER = ZeroClick::Sellers.create(
  api_key: ENV.fetch("ZEROCLICK_API_KEY"),
  resolve_signing_secret: ->(kid) { Vault.read("zeroclick/#{kid}") }
)
```

Provide exactly one of `signing_secrets` or `resolve_signing_secret` — giving both, or neither, raises at construction rather than at the first request.

## Split usage keys

Useful when a separate process reports usage — a Sidekiq worker settling variable work, say. The reporting process then needs no read scope, and the serving process needs no write scope.

```ruby theme={null}
SELLER = ZeroClick::Sellers.create(
  usage_read_key: ENV.fetch("ZEROCLICK_USAGE_READ_KEY"),
  usage_write_key: ENV.fetch("ZEROCLICK_USAGE_WRITE_KEY"),
  signing_secrets: ZeroClick::Sellers.secrets_from_env
)
```

## The allowance-unavailable policy

When the allowance API cannot give an answer — a timeout, a 5xx, an unparseable body — this decides what happens.

| Policy              | Behaviour                                                | Choose it when                                             |
| ------------------- | -------------------------------------------------------- | ---------------------------------------------------------- |
| `"allow"` (default) | Serve the request. `Allow#allowance` is `"unavailable"`. | Serving occasional unbilled work is better than an outage. |
| `"deny"`            | Refuse with `503 allowance_unavailable`.                 | Unbilled work is worse than a refusal.                     |
| `"throw"`           | Raise the `Error`.                                       | You want to handle it yourself.                            |

<Warning>
  The policy applies **only after a signature verifies**. A fail-open allowance policy never becomes a fail-open signature policy — an unsigned or badly signed request is refused with `401` under every policy.
</Warning>

A recognised `allowed: false` is always a `402`, never an outage — the API answered, and the answer was no.

An `Allow` reached through the policy reports `allowance == "unavailable"` rather than `"allowed"`, so you can distinguish served-because-we-could-not-check from served-because-they-paid:

```ruby theme={null}
SELLER = ZeroClick::Sellers.create(
  api_key: ENV.fetch("ZEROCLICK_API_KEY"),
  signing_secrets: ZeroClick::Sellers.secrets_from_env,
  allowance_unavailable_policy: "allow",
  on_allowance_unavailable: ->(error) { Sentry.capture_exception(error) }
)
```

## Timeouts

`check_timeout_seconds` bounds the allowance check, which sits in your request path — it applies to connect, read and write. The default of `1.5` seconds trades a slow check for a fast fail-open. Raise it only if your outage policy is `"deny"` and you would rather wait than refuse.

## Body size

The middleware buffers the request body to verify it, bounded at 10 MiB by default:

```ruby theme={null}
use ZeroClick::Sellers::Middleware::Meter,
    seller: SELLER, service_slug: "extractor", usage: [...],
    max_body_bytes: 1024 * 1024
```

The ceiling is enforced **before** verification, on purpose: an attacker needs no valid signature to make you buffer. An over-size body is refused with `413`.
