npm package: @synapse-network/agent-kit
Description: Turn any service into a paid Synapse agent in 5 lines of code
Latest version: 1.0.0

# @synapse-network/agent-kit

**Turn any API, scraper, or data source into a paid AI agent in under 5 minutes.**

Synapse Agent Kit wraps your existing code and gives it a standard interface that the Synapse network (and any x402-compatible client) can discover, pay, and query. You keep your logic — we handle the plumbing.

```
Your code + Agent Kit = Paid agent on the Synapse network
```

---

## Table of Contents

- [Install](#install)
- [30-Second Quick Start](#30-second-quick-start)
- [How It Works](#how-it-works)
- [Walkthrough: Web Scraper to Paid Agent](#walkthrough-web-scraper-to-paid-agent)
- [Walkthrough: Add to Existing Express App](#walkthrough-add-to-existing-express-app)
- [Walkthrough: Paid Agent with Payment Verification](#walkthrough-paid-agent-with-payment-verification)
- [Configuration Reference](#configuration-reference)
- [Handler API](#handler-api)
- [Context Object](#context-object)
- [Middleware](#middleware)
- [Auto-Generated Endpoints](#auto-generated-endpoints)
- [Response Format](#response-format)
- [Payment & x402](#payment--x402)
- [Connecting to the Synapse Network](#connecting-to-the-synapse-network)
- [FAQ](#faq)

---

## Install

```bash
npm install @synapse-network/agent-kit
```

Requires Node.js 18+ (uses native `fetch`).

---

## 30-Second Quick Start

```js
import { SynapseAgent } from "@synapse-network/agent-kit";

const agent = new SynapseAgent({
  name: "My Agent",
  price: 0.02,       // $0.02 USD per query (set to 0 for free)
});

agent.handle(async (query) => {
  // Replace this with your actual logic
  return {
    results: [{ answer: `You asked: ${query}` }],
    results_count: 1,
  };
});

agent.start(4102);
```

Run it:

```bash
node my-agent.js
```

Test it:

```bash
# Health check
curl http://localhost:4102/health

# Send a query
curl -X POST http://localhost:4102/query \
  -H "Content-Type: application/json" \
  -d '{"query": "hello world"}'

# See pricing
curl http://localhost:4102/x402/price

# See agent metadata
curl http://localhost:4102/manifest
```

That's it. Your agent is running with health checks, metrics, rate limiting, and x402 pricing — all automatic.

---

## How It Works

```
                        ┌─────────────────────────────────┐
                        │  @synapse-network/agent-kit      │
 Incoming query ──────► │                                  │
                        │  1. Rate limiting                │
                        │  2. Payment verification (x402)  │
                        │  3. Your middleware (optional)    │
                        │  4. YOUR HANDLER ◄── your code   │
                        │  5. Standard response format     │
                        │                                  │
                        └──────────┬──────────────────────┘
                                   │
                    Auto-generated endpoints:
                    GET  /health    — is the agent alive?
                    POST /query     — send a query, get results
                    GET  /manifest  — agent metadata for discovery
                    GET  /x402/price — how much does it cost?
                    GET  /metrics   — queries served, errors, revenue
```

You only write the handler — the function that takes a query and returns results. Everything else is handled by the kit.

---

## Walkthrough: Web Scraper to Paid Agent

Let's say you have a scraper that pulls product data. Here's how to wrap it.

**Before (plain scraper):**

```js
import * as cheerio from "cheerio";

async function scrapeProducts(query) {
  const html = await fetch("https://example.com/search?q=" + query).then(r => r.text());
  const $ = cheerio.load(html);
  const products = [];
  $(".product").each((i, el) => {
    products.push({
      name: $(el).find(".title").text(),
      price: $(el).find(".price").text(),
      rating: $(el).find(".rating").text(),
    });
  });
  return products;
}
```

**After (Synapse agent):**

```js
import * as cheerio from "cheerio";
import { SynapseAgent } from "@synapse-network/agent-kit";

// Your existing scraper — unchanged
async function scrapeProducts(query) {
  const html = await fetch("https://example.com/search?q=" + query).then(r => r.text());
  const $ = cheerio.load(html);
  const products = [];
  $(".product").each((i, el) => {
    products.push({
      name: $(el).find(".title").text(),
      price: $(el).find(".price").text(),
      rating: $(el).find(".rating").text(),
    });
  });
  return products;
}

// +5 lines to make it a paid agent
const agent = new SynapseAgent({
  name: "Product Scraper",
  description: "Real-time product data with pricing and ratings",
  domain: "general",
  price: 0.02,
  capabilities: ["product_data", "pricing", "reviews"],
});

agent.handle(async (query) => {
  const products = await scrapeProducts(query);
  return { results: products, results_count: products.length };
});

agent.start(4102);
```

Your scraper code is **completely unchanged**. The agent kit just wraps it with a standard API.

---

## Walkthrough: Add to Existing Express App

Already have an Express server? Don't create a new one — use the middleware.

```js
import express from "express";
import { synapseMiddleware } from "@synapse-network/agent-kit/middleware";

const app = express();
app.use(express.json());

// ---- Your existing routes (unchanged) ----
app.get("/api/users", (req, res) => { /* ... */ });
app.post("/api/orders", (req, res) => { /* ... */ });

// ---- Add Synapse agent in one line ----
app.use(synapseMiddleware({
  name: "My Existing API",
  domain: "general",
  price: 0.05,
  handler: async (query, ctx) => {
    // Reuse any existing function from your codebase
    const data = await myDatabaseQuery(query);
    return { results: data, results_count: data.length };
  },
}));

app.listen(3000);
```

This adds the following routes alongside your existing ones:
- `GET /synapse/health`
- `POST /synapse/query`
- `GET /synapse/manifest`
- `GET /synapse/metrics`

Custom prefix:

```js
app.use(synapseMiddleware({ prefix: "/v1/agent", /* ... */ }));
// Routes: /v1/agent/health, /v1/agent/query, etc.
```

---

## Walkthrough: Paid Agent with Payment Verification

For agents that charge real money, add a payment verifier:

```js
const agent = new SynapseAgent({
  name: "Premium Data Agent",
  price: 0.25,
  token: "USDC",
  network: "hedera",

  // This function runs BEFORE your handler.
  // Return { valid: true } to proceed, { valid: false } to reject.
  verifyPayment: async (paymentHeader, price, ctx) => {
    if (!paymentHeader) {
      return { valid: false, reason: "No payment provided" };
    }

    // In production: verify the x402 payment proof on-chain
    // For now: accept any non-empty payment header
    const txHash = paymentHeader; // parse from x402 proof
    return { valid: true, tx_hash: txHash };
  },
});
```

When `verifyPayment` is set and the payment is missing/invalid, the kit auto-returns:

```json
{
  "status": "payment_required",
  "price_usd": 0.25,
  "token": "USDC",
  "network": "hedera",
  "message": "No payment provided"
}
```

HTTP status: `402 Payment Required`.

---

## Configuration Reference

```js
new SynapseAgent({
  // --- Identity ---
  name: "My Agent",                    // Required. Display name.
  description: "What this agent does", // Shown in discovery/manifest.
  id: "my-custom-id",                  // Optional. Auto-generated if omitted.
  version: "1.0.0",                    // Optional. Shown in health/manifest.

  // --- Pricing ---
  price: 0.05,                         // USD per query. Default: 0 (free).
  token: "USDC",                       // Payment token. "USDC" or "HBAR".
  network: "hedera",                   // Settlement network. "hedera" or "base".

  // --- Discovery ---
  domain: "finance",                   // Agent's data domain. Used for routing.
                                       // Options: real_estate, finance, legal,
                                       //          medical, travel, general
  capabilities: ["stock_quotes"],      // Tags for search/matching.
  ensName: "myagent.synapse.eth",      // ENS subname for identity.

  // --- Behavior ---
  rateLimit: { maxPerMinute: 60 },     // Per-IP rate limit. Default: 60/min.

  // --- Network ---
  synapseServer: "https://...",        // Auto-register with Synapse on startup.
                                       // Also reads SYNAPSE_SERVER_URL env var.

  // --- Payment ---
  verifyPayment: async (header, price, ctx) => {
    // Optional. If set, x402 payment is required.
    // Return { valid: true, tx_hash: "0x..." } or { valid: false, reason: "..." }
  },
});
```

---

## Handler API

The handler is the function that does your actual work.

```js
agent.handle(async (query, context) => {
  // query   — string, the user's natural language query
  // context — object with parsed request data (see below)

  // Do your thing: scrape, query a DB, call an API, compute...
  const data = await doSomething(query);

  // Return results in this format:
  return {
    results: data,              // Required. Array of result objects.
    results_count: data.length, // Required. Total number of results.
    market_context: {           // Optional. Summary or metadata.
      source: "my-database",
      freshness: "real-time",
    },
  };
});
```

---

## Context Object

The second argument to your handler contains everything about the request:

```js
agent.handle(async (query, ctx) => {
  ctx.query;      // "find cheap flights"  — same as the first argument
  ctx.filters;    // { min_price: 100 }    — from request body
  ctx.limit;      // 10                    — max results requested
  ctx.sort_by;    // "price_asc"           — from request body
  ctx.headers;    // { "authorization": "Bearer ..." } — raw HTTP headers

  ctx.payment.verified;  // true/false — was x402 payment verified?
  ctx.payment.method;    // "x402" or null
  ctx.payment.tx_hash;   // "0x..." or null

  ctx.agent.id;     // "agent-abc123"
  ctx.agent.name;   // "My Agent"
  ctx.agent.domain;  // "finance"
  ctx.agent.price;   // 0.05
});
```

Callers send queries like:

```bash
curl -X POST http://localhost:4102/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "find cheap flights to Paris",
    "filters": { "max_price": 500 },
    "limit": 5,
    "sort_by": "price_asc"
  }'
```

---

## Middleware

Add functions that run before your handler. Great for logging, validation, auth, or enrichment.

```js
// Logging
agent.use(async (ctx, next) => {
  console.log(`[${new Date().toISOString()}] Query: "${ctx.query}"`);
  await next();
});

// Input validation
agent.use(async (ctx, next) => {
  if (!ctx.query || !ctx.query.trim()) {
    throw new Error("Query cannot be empty");  // Returns 400 automatically
  }
  if (ctx.query.length > 1000) {
    throw new Error("Query too long (max 1000 chars)");
  }
  await next();
});

// Custom auth (in addition to x402)
agent.use(async (ctx, next) => {
  const apiKey = ctx.headers["x-api-key"];
  if (!apiKey || apiKey !== process.env.MY_API_KEY) {
    throw new Error("Invalid API key");
  }
  await next();
});

// Enrich context
agent.use(async (ctx, next) => {
  ctx.customData = { region: "eu", tier: "premium" };
  await next();
});
```

Middleware runs in order. Throw an error to stop the chain and return 400.

---

## Auto-Generated Endpoints

Every agent gets these automatically:

### `GET /health`
```json
{ "status": "ok", "agent": "agent-abc123", "name": "My Agent", "domain": "general", "version": "1.0.0", "uptime_ms": 45000 }
```

### `POST /query`
Main endpoint. Send `{ "query": "...", "filters": {}, "limit": 10, "sort_by": "..." }`.

Returns your handler's results wrapped in a standard format:
```json
{
  "status": "ok",
  "agent": "My Agent",
  "query_parsed": { "original": "...", "filters": {}, "limit": 10 },
  "response_time_ms": 142,
  "results": [ ... ],
  "results_count": 5,
  "market_context": { ... }
}
```

### `GET /manifest`
Agent metadata for discovery. Returned to Synapse server during registration.
```json
{
  "id": "agent-abc123",
  "name": "My Agent",
  "description": "...",
  "domain": "general",
  "price_per_query_usd": 0.02,
  "token": "USDC",
  "network": "base",
  "capabilities": ["..."],
  "ens_name": "myagent.synapse.eth",
  "x402": { "enabled": true, "price_usd": 0.02 },
  "endpoints": { "query": "/query", "health": "/health", ... }
}
```

### `GET /x402/price`
Payment negotiation. Clients check this before paying.
```json
{ "price_usd": 0.02, "token": "USDC", "network": "base", "payment_required": true }
```

### `GET /metrics`
Operational stats.
```json
{ "totalQueries": 142, "totalErrors": 3, "totalRevenue": 2.84, "uptime_ms": 3600000 }
```

---

## Response Format

Your handler should return:

```js
{
  // Required
  results: [
    { title: "...", price: 29.99, url: "..." },
    { title: "...", price: 49.99, url: "..." },
  ],
  results_count: 2,

  // Optional — summary data about the results
  market_context: {
    source: "my-database",
    freshness: "real-time",
    total_available: 1500,
  },
}
```

The kit wraps this with `status`, `agent`, `response_time_ms`, and `query_parsed` automatically. You don't need to add those.

---

## Payment & x402

### Free agents
Set `price: 0` (default). No payment verification. Anyone can query.

### Paid agents without verification
Set `price: 0.05` but don't set `verifyPayment`. The agent advertises its price (via `/x402/price` and `/manifest`) but doesn't enforce payment — the Synapse server handles payment on behalf of the caller.

### Paid agents with verification
Set both `price` and `verifyPayment`. The agent enforces payment itself:

```js
verifyPayment: async (paymentHeader, price, ctx) => {
  // paymentHeader: value of X-Payment or X-402-Payment HTTP header
  // price: your agent's price in USD
  // ctx: the full request context

  // Verify the payment proof on-chain, via Hedera SDK, etc.
  const isValid = await verifyOnChain(paymentHeader, price);

  return isValid
    ? { valid: true, tx_hash: "0x..." }
    : { valid: false, reason: "Payment proof invalid" };
}
```

---

## Connecting to the Synapse Network

### Option 1: Auto-registration on startup

```js
const agent = new SynapseAgent({
  name: "My Agent",
  synapseServer: "https://synapse.yourserver.com",  // or set SYNAPSE_SERVER_URL env
  // ...
});
```

On `agent.start()`, the kit POSTs to `/api/agents/register` on the Synapse server. Your agent appears in the registry and can be discovered by queries.

### Option 2: Manual registration

Start your agent, then register it with the Synapse server yourself:

```bash
curl -X POST https://synapse.yourserver.com/api/agents/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Agent",
    "description": "...",
    "domain": "general",
    "price_per_query_usd": 0.02,
    "x402_endpoint": "https://my-agent.example.com/query"
  }'
```

### Option 3: Standalone

Don't connect to Synapse at all. Your agent works as an independent API that any HTTP client can call directly.

---

## FAQ

**Do I need to rewrite my existing code?**
No. Your existing function goes inside `agent.handle()` as-is. The kit wraps it.

**Can I use this with Python/Go/Rust?**
Not directly — this is a Node.js SDK. But the protocol is just HTTP + JSON. You can implement the same endpoints (`/query`, `/health`, `/manifest`, `/x402/price`) in any language and it'll work with the Synapse network. See [Response Format](#response-format) and [Auto-Generated Endpoints](#auto-generated-endpoints) for the spec.

**What if my service is slow?**
The kit has no timeout on your handler. If your scraper takes 10 seconds, the response takes 10 seconds. Add your own timeout logic in the handler if needed.

**What if my service crashes?**
The kit catches errors from your handler and returns a clean `500 { "status": "error", "message": "..." }`. Your Express process stays alive.

**How does rate limiting work?**
Per-IP sliding window. Default: 60 requests/minute. Returns `429` when exceeded. Configure with `rateLimit: { maxPerMinute: 100 }`.

**Can I run multiple agents in one process?**
Yes — use `.attach()` instead of `.start()`:

```js
const app = express();
agent1.attach(app, "/agent1");
agent2.attach(app, "/agent2");
app.listen(4102);
```

---

## License

MIT — ETHGlobal Cannes 2026