> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Check allowances

> Guard before work: declare what a request will use, let ZeroClick decide whether the buyer's plan covers it, and handle denials and allowance-API outages.

After a signature verifies, and before your API does any work, ask ZeroClick whether the buyer's plan covers what this request will use. That is the allowance check: `POST /v1/usage/check` with the request's `zcRequestId` and the declared usage. Checking records nothing and burns no credit; it is a pre-gate, not a charge. For the model behind it, see [usage and allowances](/docs/concepts/usage-and-allowances.md).

The SDK `guard` runs the check for you, immediately after verification:

```ts TypeScript theme={null}
const decision = await zeroClick.guard(request, {
  serviceSlug: "product-watch",
  usage: [{ meterSlug: "requests", quantity: 1 }],
});
if (decision.action === "deny") return decision.response;

// decision.context.zcRequestId, decision.context.zcAgentId
// decision.allowance.status === "allowed" | "unavailable"
```

```python Python theme={null}
decision = await zeroclick.guard(
    zc_request,
    service_slug="product-watch",
    usage=[UsageItem(meter_slug="requests", quantity=1)],
)
if decision.action == "deny":
    return to_fastapi(decision.response)

# decision.context.zc_request_id, decision.context.zc_agent_id
# decision.allowance == "allowed" | "unavailable"
```

```go Go theme={null}
result, err := seller.Guard(r.Context(), sellers.FromHTTP(r, body),
	"product-watch", []sellers.UsageItem{{MeterSlug: "requests", Quantity: 1}}, "")
if err != nil {
	// The usage list is malformed, or the outage policy is Throw.
	http.Error(w, `{"error":"internal_error"}`, http.StatusInternalServerError)
	return
}
if !result.OK {
	result.Response.WriteTo(w)
	return
}
// result.Context.RequestID, result.Context.AgentID
// result.Outcome == sellers.OutcomeAllowed | sellers.OutcomeUnavailable
```

In Go, the [`Meter` middleware](/docs/sdks/go/middleware) wraps `Guard` and the usage settlement in one. When you need the decision in your own handler, call `Guard` directly.

## Declare what the request will use

Each usage item names a meter and takes one of three forms:

| Form | Meaning |
| - | - |
| `quantity` | An exact amount, known up front. |
| `maxQuantity` | A ceiling for work you can't size yet; the payment settles at what you [report](/docs/integrate/settle-usage.md). See [charge up to a maximum](/docs/integrate/charge-up-to-a-maximum.md). |
| Neither | Defer to the meter's configured **Max usage per request** (`defaultMaxQuantity`). If the meter has none, the check denies with `meter_not_priced`. |

Before any network call, the SDK rejects an item that declares both `quantity` and `maxQuantity`. A check takes 1 to 20 items, one per meter. The SDK rejects duplicate meters too.

## Read the decision

`guard` returns a decision, not an exception. A buyer who can't pay is an expected event:

* **Deny** carries a ready-to-return response: the `401` for a failed signature, the exact `402 payment_required` body for a business denial, or the `503` under a fail-closed outage policy. Return it unchanged (`decision.response` in TypeScript, Python, and Ruby, `result.Response` in Go). Do no work. The `reason` field carries one of the seven denial reasons, listed at [errors](/docs/resources/errors.md).
* **Allow** carries the verified context (`zcRequestId`, `zcAgentId`) and an allowance status:

| Status | Meaning |
| - | - |
| `allowed` | The buyer's plan covers the declared usage. |
| `unavailable` | The allowance API gave no answer and your policy chose to serve anyway: possibly unbilled work. |
| `not_required` | Identity was proven and no allowance was needed. Only `guardIdentity` produces this. See [free and identity endpoints](/docs/integrate/free-and-identity-endpoints.md). |

## When the allowance API gives no answer

The check sits in front of every billable request, so its timeout is short: 1.5 seconds by default (`checkTimeoutMs` / `check_timeout_seconds` / `CheckTimeout`). When it expires, or the API is unreachable or failing, your configured policy decides:

| Policy | Behavior |
| - | - |
| `allow` (default) | Serve the request with allowance status `unavailable`. A ZeroClick outage should not take your API down with it. |
| `deny` | Refuse with the `503`. When unbilled work costs more than a failed request, choose this policy. |
| `throw` | Surface the typed error and decide in application code. |

The policy applies only after a signature has verified: a fail-open allowance policy never becomes a fail-open signature policy. The Go SDK further restricts the policy to genuine no-answers:

| Condition | Go SDK behavior |
| - | - |
| Timeout, DNS failure, connection refused, 5xx, 429, 408 | The policy applies. |
| Any other 4xx (revoked or wrong API key, unknown service) | Always an error, never fail-open: it is permanent and would give your product away indefinitely. |
| `allowed: false` with a reason the SDK does not recognize | Always a `402`. The SDK honors the denial and passes the unfamiliar reason through for you to log. |
| The caller's context was cancelled | Not an outage. The check runs anyway on its own timeout, and its real answer stands. |

> [!NOTE]
>
> The TypeScript and Python SDKs route every allowance API error through the policy, including 4xx responses and unrecognized denial reasons. Under the default `allow` policy, monitor `onAllowanceUnavailable` for repeated incidents: a persistent 4xx such as a revoked key means you are serving every request unbilled. Each SDK's errors page documents its exact classification.

Set `onAllowanceUnavailable` (and, in Go, `Logger`) to record every incident the policy absorbs. A fail-open that is also silent is invisible unbilled traffic:

```ts TypeScript theme={null}
const zeroClick = createSeller({
  signingSecrets: { [kid]: secret },
  apiKey: process.env.ZEROCLICK_API_KEY!,
  allowanceUnavailable: "allow",
  onAllowanceUnavailable: (error) => logger.warn("allowance outage", error.context),
});
```

```python Python theme={null}
zeroclick = create_async_seller(
    signing_secrets={kid: secret},
    api_key=os.environ["ZEROCLICK_API_KEY"],
    allowance_unavailable_policy="allow",
    on_allowance_unavailable=lambda error: logger.warning("allowance outage: %s", error),
)
```

```go Go theme={null}
seller, err := sellers.New(sellers.Config{
	APIKey:                 os.Getenv("ZEROCLICK_API_KEY"),
	ServiceSlug:            "product-watch",
	SigningSecrets:         secrets,
	Policy:                 sellers.PolicyAllow,
	Logger:                 log.Default(),
	OnAllowanceUnavailable: func(err error) { alert("allowance outage", err) },
})
```

## Call the allowance API directly

For flows where verification and the check live in different layers, call the check yourself with a `zcRequestId` from an already-verified context. The response is `{ "allowed": boolean, "reason": <denial reason or null> }`. Building the `402` from a denial is then on you (`paymentRequired` / `payment_required` / `PaymentRequired` construct the exact body).

```ts TypeScript theme={null}
const decision = await zeroClick.checkAllowance({
  zcRequestId: context.zcRequestId,
  serviceSlug: "product-watch",
  usage: [{ meterSlug: "output_tokens", maxQuantity: 100_000 }],
});
```

```python Python theme={null}
decision = await zeroclick.check_allowance(
    zc_request_id=context.zc_request_id,
    service_slug="product-watch",
    usage=[UsageItem(meter_slug="output_tokens", max_quantity=100_000)],
)
```

```go Go theme={null}
decision, err := seller.CheckAllowance(r.Context(), zc.RequestID,
	"product-watch", []sellers.UsageItem{{MeterSlug: "output_tokens", MaxQuantity: 100_000}})
```

Unlike `guard`, the direct check surfaces outages as errors instead of applying the policy. It runs under your own cancellation (an optional `{ signal }` in TypeScript, the caller's context in Go). Once a request is allowed, serve it. Then [settle the usage](/docs/integrate/settle-usage.md).


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