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

# Fastify

> Register Key0 as a Fastify plugin for agent discovery, x402 payments, and access token validation.

## Overview

The `@key0ai/key0/fastify` subpath exports two pieces:

| Export                       | Type             | Purpose                                                             |
| ---------------------------- | ---------------- | ------------------------------------------------------------------- |
| `key0Plugin`                 | Fastify plugin   | Registers agent-card and x402 payment routes on your Fastify server |
| `fastifyValidateAccessToken` | `onRequest` hook | Validates Bearer JWTs and attaches the decoded token                |

## Installation

```bash theme={null}
npm install @key0ai/key0 fastify
```

## Routes

When you register `key0Plugin`, the following routes are added to your Fastify instance:

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

## Setup

<Steps>
  <Step title="Configure SellerConfig and storage">
    Create your `SellerConfig`, payment adapter, challenge store, and seen-tx store. These are framework-agnostic and shared across all integrations.

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

    const redis = new Redis();

    const sellerConfig: SellerConfig = {
      agentName: "My API Agent",
      agentDescription: "Payment-gated API powered by Key0",
      agentUrl: "https://api.example.com",
      providerName: "Example Inc.",
      providerUrl: "https://example.com",
      walletAddress: "0xYourWalletAddress",
      network: "mainnet",
      plans: [
        { planId: "basic", unitAmount: "$0.01", description: "Single API call" },
      ],
      fetchResourceCredentials: async ({ requestId, challengeId, planId, txHash }) => {
        // Issue a credential after payment is verified
        return { token: "your-issued-token" };
      },
    };

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

  <Step title="Register the Key0 plugin">
    Pass the config objects as plugin options to `fastify.register`.

    ```typescript theme={null}
    import Fastify from "fastify";
    import { key0Plugin } from "@key0ai/key0/fastify";

    const fastify = Fastify({ logger: true });

    await fastify.register(key0Plugin, {
      config: sellerConfig,
      adapter,
      store,
      seenTxStore,
    });
    ```

    After registration, `GET /.well-known/agent.json` returns your agent card.
  </Step>

  <Step title="Protect routes with access-token validation">
    Use `fastifyValidateAccessToken` as an `onRequest` hook on any route that requires a paid access token.

    ```typescript theme={null}
    import { fastifyValidateAccessToken } from "@key0ai/key0/fastify";

    const validateToken = fastifyValidateAccessToken({
      secret: process.env.JWT_SECRET!,
    });

    fastify.get(
      "/protected/resource",
      { onRequest: validateToken },
      async (request, reply) => {
        const token = (request as typeof request & { key0Token?: unknown }).key0Token;
        return { message: "Access granted", token };
      },
    );
    ```

    The hook reads the `Authorization: Bearer <jwt>` header, verifies the signature, and attaches the decoded payload to `request.key0Token`. If verification fails, it short-circuits with a `401` or `500` JSON response.
  </Step>

  <Step title="Start the server">
    ```typescript theme={null}
    await fastify.listen({ port: 3000 });
    ```
  </Step>
</Steps>

## Full working example

```typescript theme={null}
import Fastify from "fastify";
import Redis from "ioredis";
import {
  X402Adapter,
  RedisChallengeStore,
  RedisSeenTxStore,
} from "@key0ai/key0";
import type { SellerConfig } from "@key0ai/key0";
import { key0Plugin, fastifyValidateAccessToken } from "@key0ai/key0/fastify";

const redis = new Redis();

const sellerConfig: SellerConfig = {
  agentName: "Photo API",
  agentDescription: "Pay-per-photo API",
  agentUrl: "https://api.example.com",
  providerName: "Example Inc.",
  providerUrl: "https://example.com",
  walletAddress: "0xYourWalletAddress",
  network: "mainnet",
  plans: [
    { planId: "single", unitAmount: "$0.05", description: "One photo" },
  ],
  fetchResourceCredentials: async ({ requestId }) => {
    return { token: `access-${requestId}` };
  },
};

const fastify = Fastify({ logger: true });

// Register Key0 plugin (agent card + x402 payment endpoint)
await fastify.register(key0Plugin, {
  config: sellerConfig,
  adapter: new X402Adapter({ network: "mainnet" }),
  store: new RedisChallengeStore({ redis }),
  seenTxStore: new RedisSeenTxStore({ redis }),
});

// Protected route
const validateToken = fastifyValidateAccessToken({
  secret: process.env.JWT_SECRET!,
});

fastify.get(
  "/photos/:id",
  { onRequest: validateToken },
  async (request, reply) => {
    const token = (request as typeof request & { key0Token?: unknown }).key0Token;
    return { photo: "data", token };
  },
);

await fastify.listen({ port: 3000 });
console.log("Fastify server running on http://localhost:3000");
```

## Accessing the decoded token

The `fastifyValidateAccessToken` hook attaches the decoded JWT payload to `request.key0Token`. Because Fastify's `FastifyRequest` type does not include this property by default, you need a type assertion to access it:

```typescript theme={null}
const token = (request as typeof request & { key0Token?: unknown }).key0Token;
```

The payload includes `sub` (request ID), `jti` (challenge ID), `resourceId`, `planId`, and `txHash`.

## Pay-Per-Request Routes

`key0Plugin` registers the plugin **and** exposes a `payPerRequest(routeId, opts?)` factory on the plugin instance. Use it as a Fastify `preHandler` hook to gate routes behind micro-payments — no JWT issued, payment settles inline.

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

// Retrieve the key0 instance from the Fastify decorator
const key0 = fastify.key0;

// Gate the route — every call needs a PAYMENT-SIGNATURE header
fastify.get(
  "/api/weather/:city",
  { preHandler: key0.payPerRequest("weather-query") },
  async (request, reply) => {
    const payment = request.key0Payment; // PaymentInfo
    reply.send({ city: request.params.city, temp: 72, txHash: payment?.txHash });
  },
);
```

Without a `PAYMENT-SIGNATURE` header the hook returns `402` with x402 payment requirements. After settlement, `request.key0Payment` contains the `PaymentInfo` object.

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="Hono Integration" icon="bolt" href="/integrations/hono">
    Key0 as a Hono sub-app.
  </Card>

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