> ## Documentation Index
> Fetch the complete documentation index at: /docs/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](/docs/sdks/ruby/quickstart.md).

```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.

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`.


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