Skip to main content

Overview

The @key0ai/key0/fastify subpath exports two pieces:
ExportTypePurpose
key0PluginFastify pluginRegisters agent-card and x402 payment routes on your Fastify server
fastifyValidateAccessTokenonRequest hookValidates Bearer JWTs and attaches the decoded token

Installation

npm install @key0ai/key0 fastify

Routes

When you register key0Plugin, the following routes are added to your Fastify instance:
MethodPathDescription
GET/.well-known/agent.jsonReturns the auto-generated A2A agent card
GET/discoverPlan catalog (all plans and pricing)
POST/x402/accessUnified x402 HTTP payment endpoint

Setup

1

Configure SellerConfig and storage

Create your SellerConfig, payment adapter, challenge store, and seen-tx store. These are framework-agnostic and shared across all integrations.
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 });
2

Register the Key0 plugin

Pass the config objects as plugin options to fastify.register.
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.
3

Protect routes with access-token validation

Use fastifyValidateAccessToken as an onRequest hook on any route that requires a paid access token.
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.
4

Start the server

await fastify.listen({ port: 3000 });

Full working example

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:
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.
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 for the full proxy configuration reference.

Next Steps

SellerConfig Reference

Complete reference for every configuration option.

Paying for Access

How clients discover, pay, and call your protected endpoints.

Hono Integration

Key0 as a Hono sub-app.

x402 HTTP Flow

Full protocol reference for the payment flow.