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

# Go SDK configuration

> Every field of the Go seller SDK's Config: usage keys, signing secrets and rotation, body limits, the allowance outage policy, and timeouts.

`sellers.New(sellers.Config{…})` builds the client. The config requires a usage key (or a both-scopes `APIKey`) and exactly one of `SigningSecrets` or `Resolve`. Everything else has a working default.

`New` returns an error rather than defaulting past a bad config. A seller booting with no signing secret would accept nothing, and failing at startup is far cheaper than discovering that in traffic. `New` refuses a config that leaves either usage direction uncovered, sets both or neither of `SigningSecrets` and `Resolve`, contains an empty secret, or names an unknown `Policy`.

```go theme={null}
seller, err := sellers.New(sellers.Config{
	APIKey:      os.Getenv("ZEROCLICK_API_KEY"),
	ServiceSlug: "product-watch",
	SigningSecrets: map[string]string{
		"hsec_k5nq0v7m3d8p": os.Getenv("ZEROCLICK_SIGNING_SECRET"),
	},
	Logger: log.Default(),
})
if err != nil {
	log.Fatal(err)
}
```

The client is safe for concurrent use; build it once at startup.

## Usage keys

<ParamField path="APIKey" type="string">
  A single API key (`zc_…`) carrying both the `usage:read` and `usage:write` scopes. It backfills whichever scoped key below is not set.
</ParamField>

<ParamField path="UsageReadKey" type="string">
  A key with the `usage:read` scope, used for allowance checks (`Guard`, `CheckAllowance`). Falls back to `APIKey` when empty.
</ParamField>

<ParamField path="UsageWriteKey" type="string">
  A key with the `usage:write` scope, used for usage reporting (`ReportUsage`), which often runs in a separate process such as a worker. Falls back to `APIKey` when empty.
</ParamField>

Provide `APIKey` alone, or provide the scoped keys and omit it. If either direction ends up with no key, `New` returns an error naming the missing side.

```go theme={null}
// Least privilege: split keys, no both-scopes credential.
seller, err := sellers.New(sellers.Config{
	UsageReadKey:  os.Getenv("ZEROCLICK_USAGE_READ_KEY"),
	UsageWriteKey: os.Getenv("ZEROCLICK_USAGE_WRITE_KEY"),
	ServiceSlug:   "product-watch",
	SigningSecrets: map[string]string{
		"hsec_k5nq0v7m3d8p": os.Getenv("ZEROCLICK_SIGNING_SECRET"),
	},
})
```

See [keys and secrets](/integrate/keys-and-secrets) for how the scopes map to the ZeroClick API.

## Service and plan

<ParamField path="ServiceSlug" type="string">
  The service these endpoints sell, as configured in ZeroClick. Required by the `Meter` and `Identify` middleware, which read it from the client. `Guard` takes a service slug per call instead, so a multi-service backend can share one client.
</ParamField>

<ParamField path="PlanSlug" type="string">
  Optional. When set, the SDK offers this plan on the `402 payment_required` response, steering the buyer toward it.
</ParamField>

## Signing secrets

Provide exactly one of `SigningSecrets` or `Resolve`.

<ParamField path="SigningSecrets" type="map[string]string">
  A static map from key id (`kid`, format `hsec_…`) to secret value (`zcsec_…`). The kid appears in every request's `zc-signature` header, and the verifier uses it to select the secret. The map keys must match the key ids shown in your dashboard exactly. The client copies the map at construction; a later mutation of your map does not change which requests the client accepts.
</ParamField>

<ParamField path="Resolve" type="ResolveSigningSecret">
  A per-kid lookup for secrets that live in a vault or database rather than a static map:

  ```go theme={null}
  type ResolveSigningSecret func(kid string) (secret string, ok bool, err error)
  ```

  For a kid you do not recognize, return `ok == false`; the SDK refuses the request with a `401`. Return an error only when the lookup itself failed (the vault was unreachable). It surfaces as an error to your code, not as a refusal, so an infrastructure fault is never silently read as a forged request. Because the client consults `Resolve` on each request, rotated secrets take effect without a restart.
</ParamField>

### Loading from the environment

`SecretsFromEnv()` builds the map from the `ZEROCLICK_SIGNING_SECRETS` environment variable, which holds `<kid>:<secret>` pairs, comma-separated. The format carries the key id, so there is nothing to hardcode:

```sh theme={null}
ZEROCLICK_SIGNING_SECRETS="hsec_k5nq0v7m3d8p:zcsec_…"
```

```go theme={null}
secrets, err := sellers.SecretsFromEnv()
if err != nil {
	log.Fatal(err)
}
```

`ParseSigningSecrets(raw string)` parses the same format from any source, for sellers reading the value from a secret manager instead of the environment. Each entry splits on the first colon only, because secrets may contain colons. Error messages never echo the value, so a config mistake does not put a secret in your logs.

### Rotation

Hold both secrets while requests signed with either are still in flight, then drop the old one:

```go theme={null}
SigningSecrets: map[string]string{
	"hsec_k5nq0v7m3d8p": os.Getenv("ZEROCLICK_SIGNING_SECRET_CURRENT"),
	"hsec_w2j9r4t8b6mx": os.Getenv("ZEROCLICK_SIGNING_SECRET_PREVIOUS"),
},
```

Or, in the one-variable form:

```sh theme={null}
ZEROCLICK_SIGNING_SECRETS="hsec_k5nq0v7m3d8p:zcsec_…,hsec_w2j9r4t8b6mx:zcsec_…"
```

Each request names its kid, so the verifier always picks the right secret. Rotation requires no downtime.

## Request body limit

<ParamField path="MaxBodyBytes" type="int64" default="10 MiB">
  Caps what a guarded endpoint will buffer. Zero means `DefaultMaxBodyBytes` (10 MiB).

  The signature covers the whole body, so the SDK must hold the whole body in memory to verify it. An attacker does not need a valid signature to make you buffer. This is a memory bound against unauthenticated callers, not a politeness limit. The SDK refuses a request over the cap with `413 {"error":"request_body_too_large"}` before verification.
</ParamField>

## Allowance outage policy

If the allowance API gives **no answer** (a timeout, a transport failure, a 5xx), `Policy` decides what happens. This is separate from the API answering *no*, which is always a `402`, and from your own misconfiguration, which is always an error. See [Go SDK errors](/sdks/go/errors) for the exact classification.

<ParamField path="Policy" type="UnavailablePolicy" default="PolicyAllow">
  | Policy                  | Behavior                                                                                                              |
  | ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
  | `PolicyAllow` (default) | Serve the request. A ZeroClick outage should not take your API down with it.                                          |
  | `PolicyDeny`            | Refuse with `503 {"error":"allowance_unavailable"}`. Choose this when unbilled work costs more than a failed request. |
  | `PolicyThrow`           | Surface the error and decide yourself. In the middleware, this becomes a `500`.                                       |

  The guard consults the policy only after a signature verifies, so a fail-open allowance policy never becomes a fail-open signature policy. But every legitimate buyer request is signed, so `PolicyAllow` bounds free work to real traffic rather than to a small subset. Set `Logger` or `OnAllowanceUnavailable`, because a fail-open that is also silent is invisible unbilled traffic.
</ParamField>

<ParamField path="CheckTimeout" type="time.Duration" default="1500ms">
  Bounds each allowance check (and each `ReportUsage` call). It sits in front of every billable request, so it is deliberately short: under the outage policy, a slow answer is worse than no answer.
</ParamField>

<ParamField path="OnAllowanceUnavailable" type="func(error)">
  Called with the underlying error before the policy applies, so you can alarm on an outage you are choosing to absorb.
</ParamField>

## Logging

<ParamField path="Logger" type="interface{ Printf(string, ...any) }">
  Receives conditions the SDK absorbs rather than surfaces: an allowance outage served under the allow policy, a guard failure the middleware turns into a `500`, a usage report that failed after the response was delivered. `*log.Logger` satisfies it, so `Logger: log.Default()` works. Nil discards these conditions, which makes unbilled traffic invisible; set it.
</ParamField>

## Signature verification

<ParamField path="ToleranceSeconds" type="int" default="300">
  How far a signature's timestamp may drift from your clock, in either direction, before the SDK refuses the request as stale. See the [signature spec](/integrate/signature-spec) for what the timestamp protects against.
</ParamField>

## Plumbing

<ParamField path="APIBaseURL" type="string" default="https://api.zeroclick.io">
  Base URL for the allowance and usage endpoints. Leave it unset in production.
</ParamField>

<ParamField path="HTTPClient" type="*http.Client">
  The client used for ZeroClick API calls. Defaults to a plain `&http.Client{}`; per-call timeouts come from `CheckTimeout`.
</ParamField>

<ParamField path="Now" type="func() time.Time">
  Clock override so tests can pin time. Defaults to `time.Now`.
</ParamField>

## Defaults at a glance

| Constant                  | Value                       |
| ------------------------- | --------------------------- |
| `DefaultAPIBaseURL`       | `https://api.zeroclick.io`  |
| `DefaultToleranceSeconds` | `300`                       |
| `DefaultCheckTimeout`     | `1500 * time.Millisecond`   |
| `DefaultMaxBodyBytes`     | `10 << 20` (10 MiB)         |
| `SigningSecretsEnv`       | `ZEROCLICK_SIGNING_SECRETS` |
| Default `Policy`          | `PolicyAllow`               |
