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

> Install @zeroclickai/sellers and guard one route end to end: verify the signature, check the allowance, serve, and settle usage.

[`@zeroclickai/sellers`](https://www.npmjs.com/package/@zeroclickai/sellers) implements the ZeroClick billing guard for web-native TypeScript backends. It verifies that ZeroClick signed each forwarded request, checks the buyer's allowance before you do the work, builds the refusal responses ZeroClick expects, and settles what was used. This page guards one route with it. The [quickstart](/quickstart) covers the same flow across all three SDKs, and the [integration contract](/integrate/overview) defines what the guard implements.

<Info>
  You need the keys from the [quickstart](/quickstart): a signing secret (`zcsec_…`) with its key id (`hsec_…`), and an API key (`zc_…`) with the `usage:read` and `usage:write` scopes. You can pass a scoped key per direction instead; see [configuration](/sdks/typescript/configuration). This page uses a service `product-watch` with a meter `requests`; substitute your own slugs.
</Info>

## Install

```sh theme={null}
pnpm add @zeroclickai/sellers
```

The package is ESM and uses the standard `Request`, `Response`, `fetch`, `AbortSignal`, and Web Crypto APIs. It works in any runtime that provides those, including current Node.js and compatible edge runtimes. Nothing in the SDK is framework-specific; you adapt at the boundary, covered [below](#framework-integration).

## Guard a route

Configure the client once, then use `guard` before doing work and `withUsage` on the successful response:

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

const signingSecretKid = process.env.ZEROCLICK_SIGNING_SECRET_KID;
const signingSecret = process.env.ZEROCLICK_SIGNING_SECRET;
const apiKey = process.env.ZEROCLICK_API_KEY;

if (!signingSecretKid || !signingSecret || !apiKey) {
  throw new Error("ZeroClick seller credentials are not configured");
}

const zeroClick = createSeller({
  signingSecrets: {
    [signingSecretKid]: signingSecret,
  },
  apiKey,
});

export async function handle(request: Request): Promise<Response> {
  const decision = await zeroClick.guard(request, {
    serviceSlug: "product-watch",
    usage: [{ meterSlug: "requests", quantity: 1 }],
  });

  if (decision.action === "deny") return decision.response;

  const response = Response.json({
    message: "Hello from the seller",
    zcRequestId: decision.context.zcRequestId,
  });

  return zeroClick.withUsage(response, [
    {
      serviceSlug: "product-watch",
      meterSlug: "requests",
      quantity: 1,
    },
  ]);
}
```

`withUsage` stamps the validated `zc-usage` header on the response so ZeroClick settles the usage synchronously; ZeroClick strips the header before the agent sees the response. Set it on successful (`2xx`) responses only. [Settle usage](/integrate/settle-usage) covers the asynchronous alternative for work that outlives the request.

## Decisions, not exceptions

`guard` always verifies the signature before it calls the allowance API. Its result is an explicit decision:

* `action: "allow"` carries the verified context (`zcRequestId`, `zcAgentId`, the signature timestamp, and the `kid` that verified) and an allowance status of `"allowed"` or `"unavailable"`.
* `action: "deny"` carries a `reason` and the exact `Response` to return. Invalid signatures produce a `401`, business denials produce the seller `402 payment_required` body, and fail-closed allowance outages produce a `503`.

Return `decision.response` unchanged: each body is exactly what ZeroClick expects back from your API. The [API reference](/sdks/typescript/api) lists every deny reason, and [errors](/sdks/typescript/errors) draws the line between decisions and thrown `ZCError`s.

## Anonymous probes

Signed anonymous probes are valid requests. To price a pay-as-you-go challenge, ZeroClick forwards the agent's unpaid request upstream with no agent id: the signature verifies, and `decision.context.zcAgentId` is `null`. Only the paid retry carries the agent id (`agt_…`). Treat probes like any other request: run the same guard and return the same responses. The `402` refusal a probe earns is exactly what ZeroClick re-prices for the agent.

## Framework integration

Adapt your framework's request object to a web-native `Request` at the boundary. Preserve the exact method, URL, headers, and body bytes. The SDK reads a clone, so the original request body remains readable in your handler. Return the SDK-provided `Response` directly, or adapt it back to the framework's response type.

A framework that already speaks web standards needs no glue. Hono hands you the untouched request as `c.req.raw`:

```ts theme={null}
import { Hono } from "hono";

const app = new Hono();

app.post("/v1/product-watch", async (c) => {
  // zeroClick is the client from createSeller above.
  const decision = await zeroClick.guard(c.req.raw, {
    serviceSlug: "product-watch",
    usage: [{ meterSlug: "requests", quantity: 1 }],
  });
  if (decision.action === "deny") return decision.response;

  // guard read a clone, so the body is still readable here.
  const { productId } = await c.req.json();
  const response = Response.json({ watching: productId });

  return zeroClick.withUsage(response, [
    { serviceSlug: "product-watch", meterSlug: "requests", quantity: 1 },
  ]);
});

export default app;
```

A Next.js route handler already receives a web-native `Request` and returns a `Response`. The `handle` function above drops in as `export async function POST(request: Request)` in a `route.ts` file.

<Warning>
  The signature covers the raw bytes ZeroClick sent: the percent-encoded path and query exactly as requested, and the unmodified body. An adapter that re-parses and re-serializes the body, or normalizes the URL, breaks verification. Hand the original request through untouched.
</Warning>

## Next steps

<Columns cols={2}>
  <Card title="Configuration" icon="settings" href="/sdks/typescript/configuration">
    Every `createSeller` option: rotation, split keys, timeouts, outage policy.
  </Card>

  <Card title="API reference" icon="code" href="/sdks/typescript/api">
    Every export, decision shape, and deny reason.
  </Card>

  <Card title="Errors" icon="triangle-alert" href="/sdks/typescript/errors">
    `ZCError` codes and what throws where.
  </Card>

  <Card title="Charge up to a maximum" icon="gauge" href="/integrate/charge-up-to-a-maximum">
    Bill work you can't size up front, like output tokens.
  </Card>
</Columns>
