> ## 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 API reference

> Every public identifier in the Go seller SDK: Client methods, package helpers, response builders, result types, and the jwe subpackage.

Everything in `cdn.zeroclick.io/sdks/sellers-go`, importable as:

```go theme={null}
import sellers "cdn.zeroclick.io/sdks/sellers-go"
```

The `Client` is safe for concurrent use: build it once with [`New`](/sdks/go/configuration) and share it. Every decision flows through the framework-neutral [`Request`](#request) and [`Response`](#response) types, so the SDK works outside `net/http` too. `FromHTTP` is only the `net/http` adapter, and the [`Meter` and `Identify` middleware](/sdks/go/middleware) are packaged versions of the same calls.

Guards return decisions, not errors: an unsigned or unpayable request is an expected event on a public endpoint, so it comes back as a result carrying the exact `Response` to return. The SDK reserves errors for your own misconfiguration and for ZeroClick API failures. See [Go SDK errors](/sdks/go/errors).

## Client methods

### Guard

```go theme={null}
func (c *Client) Guard(ctx context.Context, request *Request, serviceSlug string, usage []UsageItem, planSlug string) (GuardResult, error)
```

The one call a billable endpoint needs: it verifies the signature, then checks that the buyer's allowance covers `usage`, before any work happens. Verification always runs first: a request that fails it never reaches the allowance API. `planSlug` may be empty.

```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 was malformed, or Policy is PolicyThrow.
	http.Error(w, `{"error":"internal_error"}`, http.StatusInternalServerError)
	return
}
if !result.OK {
	result.Response.WriteTo(w)
	return
}
// Serve the request. result.Context.AgentID is the buyer,
// result.Context.RequestID correlates with ZeroClick's logs.
```

`body` must be the raw request bytes, read in full before the call. The middleware bounds that read for you. When you call `Guard` directly, bound the read yourself (for example with `http.MaxBytesReader`).

Declaring an exact `Quantity` lets ZeroClick offer the `exact` x402 payment scheme. A `MaxQuantity` forces `upto`, which fewer clients can pay. Prefer a fixed per-request charge, and report variable work afterwards with [`ReportUsage`](#reportusage). See [charge up to a maximum](/integrate/charge-up-to-a-maximum).

<Note>
  `Guard` deliberately severs the caller's cancellation for the allowance check: it wraps `ctx` with `context.WithoutCancel` and applies its own `CheckTimeout`. Inheriting cancellation meant a client disconnect, or any proxy-side timeout, short-circuited the check before it was even sent. Under the `allow` policy the request was served anyway: a buyer who would have been denied got served, and the API was never asked. The check's own timeout still bounds it. [`CheckAllowance`](#checkallowance) keeps the caller's context, because it surfaces the error instead of applying a policy to it.
</Note>

The SDK still honors a denial with a reason it does not recognize: `allowed` is legible on its own, and the unfamiliar reason passes through in `GuardResult.Reason` for you to log. Rejecting the whole response on an unfamiliar reason would turn a denial into an allow the day ZeroClick adds a new one. An additive server change must stay additive.

### GuardIdentity

```go theme={null}
func (c *Client) GuardIdentity(request *Request, serviceSlug string) (GuardResult, error)
```

Guards a free endpoint that must still know which buyer is calling, such as a limits or account route. It makes no network call.

A signed request with no `zc-agent-id` is an anonymous probe: valid, but it has not established a buyer. `GuardIdentity` answers it with a `402` carrying an empty usage list (`Reason` is `identity_required`). That makes ZeroClick issue a \$0 identity challenge and retry the request with the identity attached. A signed request that does carry a buyer passes with `Outcome` set to `OutcomeNotRequired`. See [free and identity endpoints](/integrate/free-and-identity-endpoints).

### Verify

```go theme={null}
func (c *Client) Verify(request *Request) (VerifyResult, error)
```

Verifies the request's signature using the client's secrets, tolerance, and clock; it does nothing else. Use it directly only for endpoints that bill nothing. Anything billable should go through [`Guard`](#guard), which also checks that the buyer can pay. The package-level [`Verify`](#verify-package-function) does the same without a `Client`.

### CheckAllowance

```go theme={null}
func (c *Client) CheckAllowance(ctx context.Context, requestID, serviceSlug string, usage []UsageItem) (AllowanceDecision, error)
```

Asks the allowance API directly, without verifying a request and without applying the outage policy. Errors surface for you to handle, classified with [`IsAllowanceUnavailable`](#isallowanceunavailable). It keeps the caller's context, bounded by `CheckTimeout`. Checking records nothing and burns no credit. [`Guard`](#guard) is what an endpoint should use; this is for sellers driving the check themselves.

```go theme={null}
decision, err := seller.CheckAllowance(ctx, "zcreq_8h2m4x0q9k1f", "product-watch",
	[]sellers.UsageItem{{MeterSlug: "output_tokens", MaxQuantity: 4096}})
if err != nil {
	// No usable answer; classify with sellers.IsAllowanceUnavailable(err).
}
if !decision.Allowed {
	// decision.Reason, e.g. "usage_exhausted"
}
```

### ReportUsage

```go theme={null}
func (c *Client) ReportUsage(ctx context.Context, input ReportUsageInput) (ReportUsageResult, error)
```

Records usage out of band, after the response has gone out. This is how a seller bills for work whose amount is only known once it is done (pages extracted, tokens produced). The seller still declares a fixed per-request charge to the guard, so the `exact` scheme stays available. `CheckTimeout` bounds the call, and the write-scoped key authenticates it.

```go theme={null}
_, err := seller.ReportUsage(sellers.BackgroundContext(r), sellers.ReportUsageInput{
	AgentID:        zc.AgentID,
	ServiceSlug:    "product-watch",
	MeterSlug:      "output_tokens",
	Quantity:       tokens,
	IdempotencyKey: zc.RequestID + "_output_tokens", // derived, never random
})
```

Reports are idempotent on `IdempotencyKey`: a retried report replays the stored event with `Duplicate: true`, which is a success, not a failure. Derive the key from the request id and the meter: a random key double-bills on retry. A failure here means work was delivered and not billed. Log it with the reason; do not fail the request the buyer already received.

### Meter

```go theme={null}
func (c *Client) Meter(usage ...UsageItem) func(http.Handler) http.Handler
```

Middleware that guards a billable endpoint: it verifies the signature, checks that the buyer can pay, and, if the handler responds 2xx, settles the declared usage via the `zc-usage` response header. It takes fixed quantities only. If handed an [`UpTo`](#perrequest-and-upto) item or a non-positive quantity, it panics at wire-up. Covered in depth in [Go SDK middleware](/sdks/go/middleware).

### Identify

```go theme={null}
func (c *Client) Identify() func(http.Handler) http.Handler
```

Middleware form of [`GuardIdentity`](#guardidentity), for free identity-scoped endpoints. Also covered in [Go SDK middleware](/sdks/go/middleware).

## Request helpers

### FromHTTP

```go theme={null}
func FromHTTP(r *http.Request, body []byte) *Request
```

Builds a [`Request`](#request) from a server-side `*http.Request` and an already-read body. It reads `r.RequestURI`, which is the raw percent-encoded request target exactly as it arrived. `r.URL.Path` is decoded and would break verification for any target containing an encoded character. On a hand-built request (where `RequestURI` is unset), it falls back to the escaped path plus raw query, which is exact unless the path held an encoded slash.

### FromContext

```go theme={null}
func FromContext(ctx context.Context) (Context, bool)
```

Returns what a guarded request proved about its caller; `ok` is false on an unguarded route.

```go theme={null}
zc, ok := sellers.FromContext(r.Context())
// zc.AgentID: the buyer (agt_…)
// zc.RequestID: correlates with ZeroClick's logs; use it for idempotency keys
```

### BackgroundContext

```go theme={null}
func BackgroundContext(r *http.Request) context.Context
```

Returns a context for work that outlives the response, such as reporting usage after the buyer has their result. It is `context.WithoutCancel(r.Context())`: context values survive, cancellation does not. Deriving from `r.Context()` would not work: the server cancels that context the moment the handler returns. The report would be dropped, and the work would go unbilled with nothing in the logs to say so.

## Usage constructors

### PerRequest and UpTo

```go theme={null}
func PerRequest(meterSlug string, quantity int) UsageItem
func UpTo(meterSlug string, maxQuantity int) UsageItem
```

`PerRequest` declares a fixed charge, which is what lets ZeroClick offer the `exact` x402 scheme. `UpTo` declares a ceiling for work whose cost is unknown until it is done. It settles at the amount actually reported, so a ceiling never overcharges. But it forces the `upto` scheme, which fewer clients can pay, and [`Meter`](#meter) rejects it because a ceiling has no settled quantity. Prefer `PerRequest` plus [`ReportUsage`](#reportusage) for the variable part.

## Signing secret helpers

### SecretsFromEnv and ParseSigningSecrets

```go theme={null}
func SecretsFromEnv() (map[string]string, error)
func ParseSigningSecrets(raw string) (map[string]string, error)
```

`SecretsFromEnv` parses the `ZEROCLICK_SIGNING_SECRETS` environment variable (the constant `SigningSecretsEnv`), which holds `<kid>:<secret>` pairs, comma-separated. Listing more than one is how a rotation survives. `ParseSigningSecrets` parses the same format from any source, such as a secret manager. Entries split on the first colon only, so secrets containing colons are safe, and parse errors never echo the value. See [configuration](/sdks/go/configuration) for the format and rotation examples.

### SigningSecrets

```go theme={null}
func SigningSecrets(secrets map[string]string) ResolveSigningSecret
```

Adapts a static map to the `ResolveSigningSecret` resolver signature, for use with the package-level [`Verify`](#verify-package-function). `Config.SigningSecrets` applies this adapter for you.

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

`ok == false` means the key id is unknown (a refusal); a non-nil error means the lookup itself failed, such as a vault being unreachable (a fault).

## Response builders

`Guard` and the middleware build these responses for you; use them directly when you drive the flow yourself.

### PaymentRequired

```go theme={null}
func PaymentRequired(serviceSlug string, usage []UsageItem, planSlug string) (*Response, error)
```

The `402` that tells ZeroClick what this request would cost: `{"error":"payment_required","serviceSlug":…,"usage":[…]}`, with `planSlug` included when non-empty. ZeroClick reads the usage list, prices it, and issues the buyer an x402 or MPP challenge; the buyer never sees this body. Return it **before** doing the work: a `402` after the fact means the work is done and unpaid. An empty usage list is meaningful, not a mistake: it is the free identity-scoped refusal, answered with a \$0 identity challenge.

```go theme={null}
response, err := sellers.PaymentRequired("product-watch",
	[]sellers.UsageItem{{MeterSlug: "requests", Quantity: 1}}, "")
```

### MustPaymentRequired

```go theme={null}
func MustPaymentRequired(serviceSlug string, usage []UsageItem, planSlug string) *Response
```

[`PaymentRequired`](#paymentrequired) for a usage list built from constants, where a validation failure is a bug rather than a runtime condition. It panics instead of returning an error.

### InvalidSignature

```go theme={null}
func InvalidSignature() *Response
```

The `401 {"error":"invalid_zeroclick_signature"}` for a request that did not prove it came from ZeroClick. The body says nothing about why, deliberately. Your logs carry the [`FailureReason`](/sdks/go/errors).

### AllowanceUnavailable

```go theme={null}
func AllowanceUnavailable() *Response
```

The `503 {"error":"allowance_unavailable"}` for "we could not reach the allowance API", as distinct from "the allowance API said no". The SDK returns it only under `PolicyDeny`.

### WithUsage and UsageHeader

```go theme={null}
func WithUsage(response *Response, usage []SyncUsageItem) (*Response, error)
func UsageHeader(usage []SyncUsageItem) (string, error)
```

`UsageHeader` renders the `zc-usage` header value: what this response actually consumed, settled against whatever was authorized. `WithUsage` attaches it to a response. Set it only on a response that delivered: a `4xx` for input you rejected must not carry it, because that bills the buyer for a refusal. The [`Meter`](#meter) middleware does this for you on 2xx responses.

```go theme={null}
response, err := sellers.WithUsage(sellers.JSON(200, result), []sellers.SyncUsageItem{
	{ServiceSlug: "product-watch", MeterSlug: "requests", Quantity: 1},
})
```

### JSON

```go theme={null}
func JSON(status int, payload any) *Response
```

Builds a JSON [`Response`](#response) with the `content-type` header set. It panics only on a payload that cannot be marshalled, which is a programming error rather than a runtime condition.

## Verification primitives

### Verify (package function)

```go theme={null}
func Verify(req *Request, opts VerifyOptions) (VerifyResult, error)

type VerifyOptions struct {
	Resolve          ResolveSigningSecret // required
	ToleranceSeconds int                  // 0 means 300
	Now              func() time.Time     // nil means time.Now
}
```

Verifies a ZeroClick signature over an already-read body, without a `Client`; no API key is needed. The body must be the raw bytes as received: re-serialized JSON will not match, because the digest covers bytes, not meaning. The verifier parses the signature header additively: it ignores unknown members (ZeroClick appends fields such as the sandbox marker over time) and rejects duplicate members. The returned error is non-nil only for a resolver fault (wrapped in `ErrSecretResolution`) or a missing resolver; every authentication failure is a `VerifyResult` decision instead.

### CanonicalString

```go theme={null}
func CanonicalString(timestamp, method, pathAndQuery string, body []byte, requestID, agentID string) string
```

Builds the exact bytes that get signed, as six newline-joined fields: the timestamp, the uppercased method, the raw percent-encoded path and query, the lowercase hex SHA-256 of the body bytes, the `zc-request-id` value, and the `zc-agent-id` value or empty string. Useful for debugging a verification mismatch or building your own verifier; the full wire format is in the [signature spec](/integrate/signature-spec).

## Error helpers

### IsAllowanceUnavailable

```go theme={null}
func IsAllowanceUnavailable(err error) bool
```

Reports whether an error means "the allowance API did not give us an answer", as opposed to "the API said no" or "we are misconfigured". Only that condition is subject to the configured outage policy. The full classification is on the [errors page](/sdks/go/errors).

## Types

### Request

```go theme={null}
type Request struct {
	Method       string
	PathAndQuery string              // raw, percent-encoded, exactly as it arrived
	Headers      map[string][]string // an http.Header or any map
	Body         []byte              // raw bytes as received, before any parsing
}

func (r *Request) Header(name string) string
```

The framework-neutral view of an inbound request. `PathAndQuery` must be the raw percent-encoded target: the signature covers those bytes, and the decoded path most frameworks hand you hashes differently. [`FromHTTP`](#fromhttp) takes care of this for `net/http`. `Header` looks up the first value for a name, case-insensitively, whether `Headers` is an `http.Header` or a plain map.

### Response

```go theme={null}
type Response struct {
	Status  int
	Body    []byte
	Headers map[string]string
}

func (r *Response) WriteTo(w http.ResponseWriter) error
```

The framework-neutral response you return to ZeroClick. `WriteTo` writes it to a `net/http` response writer; outside `net/http`, map the three fields onto your framework's response type.

### Context

```go theme={null}
type Context struct {
	RequestID   string // zcreq_…, the correlation id across ZeroClick and your logs
	AgentID     string // agt_…; empty for a signed anonymous probe
	AnonymousID string // the same value as AgentID, under the name that will replace it
	BuyerID     string // byr_…; the owner behind the agent, empty for an anonymous agent
	Timestamp   int64  // the signature's unix-seconds timestamp
	KID         string // which signing secret verified this request
}
```

`AnonymousID` repeats `AgentID` under the name that will eventually replace `zc-agent-id`. `BuyerID` is the buyer that agent belongs to; unlike the fields above it is not covered by the signature. See [agents and access](/concepts/agents-and-access).

What a verified request proved. `AgentID` is empty for a signed anonymous probe, which is valid: ZeroClick uses probes to price pay-as-you-go `402`s. Test it with `== ""`, never against a sentinel: an absent header and a present-but-empty one both mean "no buyer identity" and both sign identically.

### GuardResult and GuardOutcome

```go theme={null}
type GuardResult struct {
	OK       bool
	Context  Context
	Outcome  GuardOutcome
	Reason   string
	Response *Response // never nil when OK is false
}

const (
	OutcomeAllowed     GuardOutcome = "allowed"     // the buyer's allowance covers this request
	OutcomeUnavailable GuardOutcome = "unavailable" // no answer; the policy chose to serve, possibly unbilled
	OutcomeNotRequired GuardOutcome = "not_required" // identity proven; no allowance was needed
)
```

The guard's decision. Read `OK` first; when it is false, return `Response` verbatim and do no work. `Reason` is the denial code: a verification [`FailureReason`](/sdks/go/errors), an allowance denial such as `usage_exhausted`, `identity_required` from [`GuardIdentity`](#guardidentity), or `allowance_unavailable` under `PolicyDeny`. Watch for `Outcome == OutcomeUnavailable` in logs and metrics: it means work is being done that may not be billed.

### VerifyResult

```go theme={null}
type VerifyResult struct {
	OK       bool
	Context  Context
	Reason   FailureReason // why the request was refused, for your logs
	Response *Response     // the 401 to return, ready-built
}
```

A decision, not an error. The errors page lists the [`FailureReason`](/sdks/go/errors) values and explains why they are values rather than errors.

### UsageItem

```go theme={null}
type UsageItem struct {
	MeterSlug   string
	Quantity    int
	MaxQuantity int
}
```

What a request will be charged for. Set exactly one of `Quantity` or `MaxQuantity`, or neither to defer to the meter's configured per-request ceiling. A usage list must name at least one meter and may not repeat one. See [usage and allowances](/concepts/usage-and-allowances) for how the three forms are priced.

### SyncUsageItem

```go theme={null}
type SyncUsageItem struct {
	ServiceSlug string
	MeterSlug   string
	Quantity    int
}
```

Actual usage reported alongside a successful response, in the `zc-usage` header.

### AllowanceDecision

```go theme={null}
type AllowanceDecision struct {
	Allowed bool
	Reason  string // a denial code, or "" when allowed
}
```

ZeroClick's answer to an allowance check. The SDK exports the denial codes as constants:

```go theme={null}
const (
	DenialServiceNotFound = "service_not_found"
	DenialAccessNotFound  = "access_not_found"
	DenialAccessInactive  = "access_inactive"
	DenialPlanExpired     = "plan_expired"
	DenialMeterNotFound   = "meter_not_found"
	DenialMeterNotPriced  = "meter_not_priced"
	DenialUsageExhausted  = "usage_exhausted"
)
```

A reason outside this set passes through unchanged, and the decision still stands.

### ReportUsageInput and ReportUsageResult

```go theme={null}
type ReportUsageInput struct {
	AgentID        string // the buyer, from Context.AgentID
	ServiceSlug    string
	MeterSlug      string
	Quantity       int    // must be positive
	IdempotencyKey string // derive from the request id + meter, never random
	OccurredAt     string // RFC 3339; empty means "now", decided server-side
}

type ReportUsageResult struct {
	Recorded   bool
	Duplicate  bool           // the idempotency key had already landed: a success
	UsageEvent map[string]any // the recorded event, as returned by the API
}
```

Input and outcome of [`ReportUsage`](#reportusage).

## Encrypted request bodies

ZeroClick can encrypt a buyer's request body to your public key. Support lives in a separate subpackage, so a seller who does not use it never pulls in a JOSE library. The core package imports only the standard library, and Go's module graph pruning keeps `go-jose` out of your `go.sum` and your binary unless you import this:

```go theme={null}
import "cdn.zeroclick.io/sdks/sellers-go/jwe"
```

```go theme={null}
envelope, err := jwe.DecryptRequest(body, jwe.PrivateKeys(keys))
// envelope.Plaintext is the original request body; envelope.ReplyJWK is
// non-nil when the buyer asked for an encrypted reply.

reply, err := jwe.EncryptResponse(response, envelope)
// Returns the response unchanged when no reply key was sent.
```

Verify the signature **before** decrypting: the signature covers the ciphertext as it arrived on the wire, not the plaintext. The cipher suite is pinned (ECDH-ES+A256KW key management, A256GCM content encryption), so a sender cannot negotiate something weaker. The [errors page](/sdks/go/errors) lists the subpackage's error codes.

<Warning>
  Known limitation: `jwe.DecryptRequest` returns `decryption_failed` for an encrypted request whose plaintext is **empty**; every non-empty body works. This is a limitation of Go's JOSE libraries, not of the platform: ZeroClick permits empty-plaintext encrypted requests, and the TypeScript and Python SDKs decrypt them. If your endpoint accepts encrypted requests and a zero-length body is meaningful to it, handle that case before decrypting.
</Warning>
