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

# TypeScript SDK configuration

> Every createSeller option, signing-secret rotation, split usage keys, the allowance outage policy, and per-call overrides.

`createSeller(config)` validates its configuration up front and returns a frozen client. Invalid configuration throws a `ZCError` with code `malformed_input` at construction, not at request time. Construction enforces two constraints:

* **Exactly one signing-secret source.** Provide `signingSecrets` or `resolveSigningSecret`: not both, not neither.
* **Both usage directions covered.** Allowance checks (`guard`, `checkAllowance`) use `usageReadKey`. Usage reporting (`reportUsage`) uses `usageWriteKey`. A single `apiKey` with both scopes backfills either side. `createSeller` rejects a config that leaves a direction without a key; `context.issues` names the missing side.

## Options

| Option                   | Required          | Default                    | Description                                                                                                                                          |
| ------------------------ | ----------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signingSecrets`         | One secret source | None                       | Record from ZeroClick signing-secret `kid` (`hsec_…`) to secret value (`zcsec_…`). Keep the current and previous keys in the record during rotation. |
| `resolveSigningSecret`   | One secret source | None                       | Async resolver `({ kid }) => secret`, returning `null` for an unknown `kid`. For a secret manager or other dynamic store.                            |
| `apiKey`                 | Key coverage      | None                       | API key with both the `usage:read` and `usage:write` scopes. Backfills whichever scoped key below is absent.                                         |
| `usageReadKey`           | Key coverage      | None                       | API key with `usage:read`, used for the allowance checks behind `guard` and `checkAllowance`.                                                        |
| `usageWriteKey`          | Key coverage      | None                       | API key with `usage:write`, used for `reportUsage`.                                                                                                  |
| `apiBaseUrl`             | No                | `https://api.zeroclick.io` | Absolute API base URL, as a string or `URL`.                                                                                                         |
| `fetch`                  | No                | `globalThis.fetch`         | Injected Fetch-compatible implementation, useful for private deployments and tests.                                                                  |
| `toleranceSeconds`       | No                | `300`                      | Maximum absolute age of a request signature, in seconds.                                                                                             |
| `clock`                  | No                | `Date.now`                 | Millisecond clock function, primarily for deterministic tests.                                                                                       |
| `checkTimeoutMs`         | No                | `1500`                     | Allowance-check timeout in milliseconds.                                                                                                             |
| `allowanceUnavailable`   | No                | `"allow"`                  | Outage policy when the allowance API gives no answer: `"allow"`, `"deny"`, or `"throw"`.                                                             |
| `onAllowanceUnavailable` | No                | None                       | Callback receiving the sanitized `ZCError` when the allowance API is unavailable.                                                                    |

## Usage keys

One API key with both usage scopes covers everything. To separate concerns (a request path that only checks, a worker that only reports), pass scoped keys:

```ts theme={null}
import { createSeller } from "@zeroclickai/sellers";

const zeroClick = createSeller({
  signingSecrets: {
    hsec_k5nq0v7m3d8p: process.env.ZEROCLICK_SIGNING_SECRET!,
  },
  usageReadKey: process.env.ZEROCLICK_USAGE_READ_KEY!,
  usageWriteKey: process.env.ZEROCLICK_USAGE_WRITE_KEY!,
});
```

A scoped key can also override one direction while `apiKey` backfills the other. See [keys and secrets](/integrate/keys-and-secrets) for minting scoped keys.

## Signing-secret rotation

ZeroClick identifies each signature with a `kid`: the signing secret's id, carried in every `zc-signature` header. Keep every key that may still sign an in-flight request available to the SDK:

```ts theme={null}
const zeroClick = createSeller({
  signingSecrets: {
    hsec_k5nq0v7m3d8p: process.env.ZEROCLICK_SIGNING_SECRET_CURRENT!,
    hsec_j2rw8t4c6y1z: process.env.ZEROCLICK_SIGNING_SECRET_PREVIOUS!,
  },
  apiKey: process.env.ZEROCLICK_API_KEY!,
});
```

If a request's `kid` is missing from the record, the SDK denies it with a `401` (reason `unknown_kid`). Never log or return a signing secret. Revoke an old key only after requests signed by it can no longer be in flight.

## Resolving secrets dynamically

For managed secret storage, resolve by `kid` instead of holding secrets in memory:

```ts theme={null}
const zeroClick = createSeller({
  resolveSigningSecret: async ({ kid }) => secretStore.get(kid),
  apiKey: process.env.ZEROCLICK_API_KEY!,
});
```

The resolver runs on every verification with the `kid` from the request's signature header. Return the secret, or `null` for a `kid` you do not recognize. The SDK denies that request as `unknown_kid`. A resolver that throws surfaces as a `ZCError` with code `signing_secret_resolution_failed` rather than a deny. An outage in your secret store shows up as an error instead of silently rejecting traffic.

## Allowance outage policy

The default policy is fail-open after the 1.5-second check timeout: the SDK allows a verified request with `allowance.status === "unavailable"`, and `onAllowanceUnavailable` receives the sanitized error. Configure `"deny"` to return `503 {"error":"allowance_unavailable"}` instead, or `"throw"` to handle the typed error in application code:

```ts theme={null}
const zeroClick = createSeller({
  signingSecrets: {
    hsec_k5nq0v7m3d8p: process.env.ZEROCLICK_SIGNING_SECRET!,
  },
  apiKey: process.env.ZEROCLICK_API_KEY!,
  allowanceUnavailable: "deny",
  onAllowanceUnavailable: (error) => {
    console.error("allowance check unavailable", error.code, error.context);
  },
});
```

The policy applies when the allowance check produces no usable answer: the call failed or timed out (`api_transport_error`), the API answered with an unsuccessful status (`api_status_error`), or the response body did not validate (`api_response_invalid`). It applies only after the signature verifies: the SDK never allows an unverified request because the allowance API is unavailable. A definite `allowed: false` answer is never subject to the policy; it always denies with the `402`.

## Per-call cancellation

The bound `checkAllowance` and `reportUsage` methods accept an optional `{ signal }`:

```ts theme={null}
await zeroClick.reportUsage(input, { signal: request.signal });
```

The signal combines with the built-in timeout; whichever fires first aborts the call. `guard` already ties its allowance check to the incoming request's own `AbortSignal`, so an agent that disconnects does not leave a check running.

Through the client, the allowance check times out after `checkTimeoutMs`, and `reportUsage` after the fixed 1,500 ms default. To raise the reporting timeout, call the standalone export with `timeoutMs`.

## Standalone function options

The `checkAllowance` and `reportUsage` named exports hold no client state. Each call carries its own options, for middleware layers or workers that never construct a client:

| Option       | Required | Default                    | Description                                                                                                |
| ------------ | -------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `apiKey`     | Yes      | None                       | API key with the scope the call needs: `usage:read` for `checkAllowance`, `usage:write` for `reportUsage`. |
| `apiBaseUrl` | No       | `https://api.zeroclick.io` | Absolute API base URL, as a string or `URL`.                                                               |
| `fetch`      | No       | `globalThis.fetch`         | Injected Fetch-compatible implementation.                                                                  |
| `signal`     | No       | None                       | `AbortSignal` combined with the timeout.                                                                   |
| `timeoutMs`  | No       | `1500`                     | Per-call timeout in milliseconds.                                                                          |

```ts theme={null}
import { reportUsage } from "@zeroclickai/sellers";

const result = await reportUsage(
  {
    zcAgentId: "agt_x7f2kq93bh0d",
    idempotencyKey: "zcreq_8h2m4x0q9k1f_output_tokens",
    serviceSlug: "product-watch",
    meterSlug: "output_tokens",
    quantity: 4200,
  },
  { apiKey: process.env.ZEROCLICK_USAGE_WRITE_KEY!, timeoutMs: 5000 },
);
```

The standalone `guard`, `guardIdentity`, and `verifyRequest` exports take the options a client would otherwise hold; the [API reference](/sdks/typescript/api#standalone-exports) lists their shapes.
