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

# Hono

> Mount Key0 as a Hono sub-app for agent discovery and x402 payments.

The Hono integration provides two exports from `@key0ai/key0/hono`:

* **`key0App(opts)`** -- a Hono sub-app that serves agent discovery and the x402 payment endpoint.
* **`honoValidateAccessToken({ secret })`** -- middleware that validates Bearer JWTs issued by Key0.

## Installation

```bash theme={null}
bun add @key0ai/key0 hono
```

## Routes

When you mount `key0App`, the following routes are registered:

| Method | Path                      | Description                          |
| ------ | ------------------------- | ------------------------------------ |
| GET    | `/.well-known/agent.json` | A2A agent card (discovery)           |
| GET    | `/discover`               | Plan catalog (all plans and pricing) |
| POST   | `/x402/access`            | Unified x402 HTTP payment endpoint   |

## Setup

<Steps>
  <Step title="Configure the seller">
    Define your `SellerConfig` with wallet address, network, agent identity, and pricing plans.

    ```typescript theme={null}
    import { AccessTokenIssuer } from "@key0ai/key0";
    import type { SellerConfig } from "@key0ai/key0";

    const SECRET = process.env.KEY0_ACCESS_TOKEN_SECRET!;
    const tokenIssuer = new AccessTokenIssuer(SECRET);

    const sellerConfig: SellerConfig = {
      agentName: "My API Agent",
      agentDescription: "Pay-per-use API access via USDC on Base",
      agentUrl: "https://api.example.com",
      providerName: "Example Corp",
      providerUrl: "https://example.com",
      walletAddress: "0xYourWalletAddress" as `0x${string}`,
      network: "testnet",
      plans: [
        {
          planId: "basic",
          unitAmount: "$0.10",
          description: "Basic API access",
        },
      ],
      fetchResourceCredentials: async (params) => {
        return tokenIssuer.sign(
          {
            sub: params.requestId,
            jti: params.challengeId,
            resourceId: params.resourceId,
            planId: params.planId,
            txHash: params.txHash,
          },
          3600,
        );
      },
    };
    ```
  </Step>

  <Step title="Create stores and adapter">
    Provide the required `IChallengeStore`, `ISeenTxStore`, and `IPaymentAdapter` instances.

    ```typescript theme={null}
    import {
      RedisChallengeStore,
      RedisSeenTxStore,
      X402Adapter,
    } from "@key0ai/key0";
    import Redis from "ioredis";

    const redis = new Redis();
    const store = new RedisChallengeStore({ redis });
    const seenTxStore = new RedisSeenTxStore({ redis });
    const adapter = new X402Adapter({ network: "testnet" });
    ```
  </Step>

  <Step title="Mount the sub-app">
    Import `key0App` from `@key0ai/key0/hono` and mount it on your main Hono app.

    ```typescript theme={null}
    import { Hono } from "hono";
    import { key0App } from "@key0ai/key0/hono";

    const app = new Hono();

    app.route(
      "/",
      key0App({
        config: sellerConfig,
        adapter,
        store,
        seenTxStore,
      }),
    );
    ```

    Your agent card is now available at `GET /.well-known/agent.json` and the payment endpoint at `POST /x402/access`.
  </Step>

  <Step title="Protect routes with access token validation">
    Use `honoValidateAccessToken` as middleware on any route that requires a valid Key0 JWT.

    ```typescript theme={null}
    import { honoValidateAccessToken } from "@key0ai/key0/hono";

    const guard = honoValidateAccessToken({
      secret: SECRET,
    });

    app.get("/api/resource", guard, (c) => {
      const token = c.get("key0Token");
      return c.json({
        message: "Authenticated",
        challengeId: token.jti,
        planId: token.planId,
        txHash: token.txHash,
      });
    });
    ```
  </Step>
</Steps>

## Accessing the decoded token

After `honoValidateAccessToken` succeeds, the decoded JWT payload is available via `c.get("key0Token")`. The payload contains:

| Field        | Type     | Description                    |
| ------------ | -------- | ------------------------------ |
| `sub`        | `string` | The original request ID        |
| `jti`        | `string` | The challenge ID               |
| `resourceId` | `string` | The resource that was paid for |
| `planId`     | `string` | The plan the client purchased  |
| `txHash`     | `string` | The on-chain transaction hash  |

On failure, the middleware returns a JSON error response:

| Scenario                 | HTTP Status | Error Code          |
| ------------------------ | ----------- | ------------------- |
| Missing/malformed header | 401         | `INVALID_REQUEST`   |
| Expired token            | 401         | `CHALLENGE_EXPIRED` |
| Invalid signature        | 401         | `INVALID_REQUEST`   |
| Internal error           | 500         | `INTERNAL_ERROR`    |

## Full example

```typescript theme={null}
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import {
  AccessTokenIssuer,
  RedisChallengeStore,
  RedisSeenTxStore,
  X402Adapter,
} from "@key0ai/key0";
import { key0App, honoValidateAccessToken } from "@key0ai/key0/hono";
import Redis from "ioredis";

const redis = new Redis();
const SECRET = process.env.KEY0_ACCESS_TOKEN_SECRET!;
const tokenIssuer = new AccessTokenIssuer(SECRET);

const sellerConfig = {
  agentName: "My API Agent",
  agentDescription: "Pay-per-use API access via USDC on Base",
  agentUrl: "https://api.example.com",
  providerName: "Example Corp",
  providerUrl: "https://example.com",
  walletAddress: process.env.KEY0_WALLET_ADDRESS! as `0x${string}`,
  network: "testnet" as const,
  plans: [
    {
      planId: "basic",
      unitAmount: "$0.10",
      description: "Basic API access",
    },
  ],
  fetchResourceCredentials: async (params: {
    requestId: string;
    challengeId: string;
    resourceId: string;
    planId: string;
    txHash: string;
  }) => {
    return tokenIssuer.sign(
      {
        sub: params.requestId,
        jti: params.challengeId,
        resourceId: params.resourceId,
        planId: params.planId,
        txHash: params.txHash,
      },
      3600,
    );
  },
};

const app = new Hono();

// Mount Key0 sub-app (agent card + x402 payment endpoint)
app.route(
  "/",
  key0App({
    config: sellerConfig,
    adapter: new X402Adapter({ network: "testnet" }),
    store: new RedisChallengeStore({ redis }),
    seenTxStore: new RedisSeenTxStore({ redis }),
  }),
);

// Protected route
const guard = honoValidateAccessToken({ secret: SECRET });

app.get("/api/resource", guard, (c) => {
  const token = c.get("key0Token");
  return c.json({ resource: "data", plan: token.planId });
});

serve({ fetch: app.fetch, port: 3000 });
console.log("Hono server running on http://localhost:3000");
```

## API reference

### `key0App(opts)`

Creates a Hono sub-app with agent card discovery and the x402 payment endpoint.

```typescript theme={null}
function key0App(opts: Key0Config): Hono
```

**Parameters:**

| Field         | Type              | Description                           |
| ------------- | ----------------- | ------------------------------------- |
| `config`      | `SellerConfig`    | Seller configuration (wallet, plans)  |
| `adapter`     | `IPaymentAdapter` | On-chain payment verification adapter |
| `store`       | `IChallengeStore` | Challenge state storage               |
| `seenTxStore` | `ISeenTxStore`    | Double-spend prevention storage       |

### `honoValidateAccessToken(config)`

Returns Hono middleware that validates a `Bearer` token from the `Authorization` header.

```typescript theme={null}
function honoValidateAccessToken(config: ValidateAccessTokenConfig): MiddlewareHandler
```

**Parameters:**

| Field    | Type     | Description                        |
| -------- | -------- | ---------------------------------- |
| `secret` | `string` | The JWT secret used to sign tokens |

## Pay-Per-Request Routes

`key0App` returns the Hono app **and** a `payPerRequest(routeId, opts?)` factory. Use it as Hono middleware on individual routes to gate them behind micro-payments — no JWT issued, payment settles inline.

```typescript theme={null}
const key0 = key0App({
  config: {
    // ...
    routes: [
      {
        routeId: "weather-query",
        method: "GET",
        path: "/api/weather/:city",
        unitAmount: "$0.01",
      },
    ],
    fetchResourceCredentials: async (params) => tokenIssuer.sign(params, 3600),
  },
  adapter, store, seenTxStore,
});

const app = new Hono();
app.route("/", key0);

// Gate the route — every call needs a PAYMENT-SIGNATURE header
app.get("/api/weather/:city", key0.payPerRequest("weather-query"), (c) => {
  const payment = c.get("key0Payment"); // PaymentInfo
  return c.json({ city: c.req.param("city"), temp: 72, txHash: payment?.txHash });
});
```

Without a `PAYMENT-SIGNATURE` header the middleware returns `402` with x402 payment requirements. After settlement, the `PaymentInfo` is available via `c.get("key0Payment")`.

For standalone gateway mode, add `proxyTo` to `SellerConfig` — see the [Express integration](/integrations/express#pay-per-request-routes) for the full proxy configuration reference.

## Next Steps

<CardGroup cols={2}>
  <Card title="SellerConfig Reference" icon="sliders" href="/sdk-reference/seller-config">
    Complete reference for every configuration option.
  </Card>

  <Card title="Paying for Access" icon="credit-card" href="/guides/paying-for-access">
    How clients discover, pay, and call your protected endpoints.
  </Card>

  <Card title="Express Integration" icon="server" href="/integrations/express">
    Full A2A JSON-RPC support is available on Express only.
  </Card>

  <Card title="x402 HTTP Flow" icon="globe" href="/protocol/x402-http-flow">
    Full protocol reference for the payment flow.
  </Card>
</CardGroup>
