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
signingSecretsorresolveSigningSecret: not both, not neither. - Both usage directions covered. Allowance checks (
guard,checkAllowance) useusageReadKey. Usage reporting (reportUsage) usesusageWriteKey. A singleapiKeywith both scopes backfills either side.createSellerrejects a config that leaves a direction without a key;context.issuesnames the missing side.
Options
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:apiKey backfills the other. See keys and secrets for minting scoped keys.
Signing-secret rotation
ZeroClick identifies each signature with akid: 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:
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 bykid instead of holding secrets in memory:
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 withallowance.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:
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 boundcheckAllowance and reportUsage methods accept an optional { signal }:
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
ThecheckAllowance and reportUsage named exports hold no client state. Each call carries its own options, for middleware layers or workers that never construct a client:
guard, guardIdentity, and verifyRequest exports take the options a client would otherwise hold; the API reference lists their shapes.