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

> Deny decisions vs ZCError exceptions, the full error-code list, narrowing with isZCError, and which operations throw which codes.

The SDK separates expected protocol outcomes from failures. Missing, stale, malformed, or invalid ZeroClick signatures are expected protocol outcomes, not exceptions. `guard` and `guardIdentity` return a deny decision that carries the exact `Response` to return. The lower-level `verifyRequest` returns `{ ok: false, reason, response }`. Business denials (`allowed: false` from the allowance API) are deny decisions too. Malformed inputs, ZeroClick API failures, signing-secret resolution failures, and encryption failures throw `ZCError`.

## ZCError

`ZCError` extends `Error` with a machine-readable `code` and a sanitized `context`:

```ts theme={null}
class ZCError extends Error {
  readonly name: "ZCError";
  readonly code: ZCErrorCode;
  readonly context: {
    operation: string; // the SDK operation that failed
    issues?: { code: string; message: string; path: (string | number)[] }[];
    kid?: string;
    reason?: string;
    status?: number;
  };
}
```

`context` contains only sanitized operational fields: the operation, status, reason, key id, and validation issue paths. It never includes API keys, signing secrets, or request body bytes, so it is safe to log and is what `onAllowanceUnavailable` receives.

## Narrowing with isZCError

Use `isZCError` to narrow an unknown error, optionally to one error code:

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

try {
  await zeroClick.reportUsage({
    zcAgentId: "agt_x7f2kq93bh0d",
    idempotencyKey: "zcreq_8h2m4x0q9k1f_output_tokens",
    serviceSlug: "product-watch",
    meterSlug: "output_tokens",
    quantity: 4200,
  });
} catch (error) {
  if (isZCError(error, "api_status_error")) {
    console.error(error.code, error.context.status, error.context.reason);
  }
  throw error;
}
```

`isZCError(error)` narrows to `ZCError`; passing a code narrows further, at runtime and in types: inside the branch, `error` is `ZCError & { code: "api_status_error" }`.

## Error codes

Sixteen codes cover the SDK. The first seven come from the core package; only the `@zeroclickai/sellers/encryption` subpath throws the nine JWE codes.

### Core codes

| Code                               | Meaning                                                                                                                                                               |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `malformed_input`                  | An SDK input failed validation: configuration, guard input, usage items, or call options. `context.issues` lists each violation's path.                               |
| `malformed_request`                | The value passed as the request is not a usable web-native `Request`, or the SDK could not clone and read its body.                                                   |
| `crypto_unavailable`               | The runtime provides no Web Crypto (`globalThis.crypto.subtle`), so the SDK cannot verify signatures.                                                                 |
| `signing_secret_resolution_failed` | The signing-secret resolver threw or returned an unusable value. Returning `null` for an unknown `kid` is not an error: the SDK denies that request as `unknown_kid`. |
| `api_transport_error`              | The call to the ZeroClick API failed or timed out before any response arrived.                                                                                        |
| `api_status_error`                 | The ZeroClick API answered with an unsuccessful status. `context.status` carries it; `reportUsage` denials add `context.reason`.                                      |
| `api_response_invalid`             | The ZeroClick API answered, but with a body the SDK could not parse or validate.                                                                                      |

### JWE codes

| Code                            | Meaning                                                                 |
| ------------------------------- | ----------------------------------------------------------------------- |
| `invalid_compact_jwe`           | The request body is not a valid Compact JWE.                            |
| `unsupported_jwe_suite`         | The JWE does not use the fixed `ECDH-ES+A256KW` / `A256GCM` suite.      |
| `jwe_kid_required`              | The JWE protected header carries no key id to resolve a private key by. |
| `invalid_reply_jwk`             | The buyer's reply JWK is not a public P-256 key.                        |
| `private_reply_jwk`             | The reply JWK contains private-key material, so the SDK rejects it.     |
| `private_key_not_found`         | The resolver returned no private key for the JWE's key id.              |
| `private_key_resolution_failed` | The private-key resolver threw or returned an unusable value.           |
| `decryption_failed`             | The SDK could not decrypt the Compact JWE with the resolved key.        |
| `encryption_failed`             | The SDK could not encrypt the response body for the buyer's reply key.  |

## What throws where

| Operation                                                     | Codes it can throw                                                                                                                                                                                                                                                                                                  |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `createSeller`                                                | `malformed_input`, when the config violates the one-secret-source rule or leaves a usage direction without a key.                                                                                                                                                                                                   |
| `verifyRequest`, `guard`, `guardIdentity` (verification step) | `malformed_input`, `malformed_request`, `crypto_unavailable`, `signing_secret_resolution_failed`. Signature outcomes never throw.                                                                                                                                                                                   |
| `guard` (allowance step)                                      | `api_transport_error`, `api_status_error`, `api_response_invalid`, routed through the outage policy. The default `"allow"` converts them into an allow with `allowance.status: "unavailable"`, surfacing them only to `onAllowanceUnavailable`. `"deny"` converts them into the `503`, and `"throw"` rethrows them. |
| `checkAllowance`                                              | `malformed_input`, `api_transport_error`, `api_status_error`, `api_response_invalid`.                                                                                                                                                                                                                               |
| `reportUsage`                                                 | Same as `checkAllowance`. On a `402`, `404`, or `409` denial, `context.reason` carries the machine reason, such as `usage_exhausted` or `meter_not_priced`.                                                                                                                                                         |
| `paymentRequired`, `withUsage`                                | `malformed_input`, including a `withUsage` call on an already-read `Response`.                                                                                                                                                                                                                                      |
| `decryptRequest`                                              | `malformed_input`, `malformed_request`, and every JWE code except `encryption_failed`.                                                                                                                                                                                                                              |
| `encryptResponse`                                             | `malformed_input`, `encryption_failed`.                                                                                                                                                                                                                                                                             |

<Note>
  A duplicate usage report is not an error: `reportUsage` resolves with `duplicate: true` and replays the stored event. Only denials and infrastructure failures throw.
</Note>

The [error reference](/resources/errors) documents the wire-level error envelope behind `api_status_error`.
