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
- Install
- 30-Second Quick Start
- How It Works
- Walkthrough: Web Scraper to Paid Agent
- Walkthrough: Add to Existing Express App
- Walkthrough: Paid Agent with Payment Verification
- Configuration Reference
- Handler API
- Context Object
- Middleware
- Auto-Generated Endpoints
- Response Format
- Payment & x402
- Connecting to the Synapse Network
- FAQ
npm install @synapse-network/agent-kitRequires Node.js 18+ (uses native fetch).
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:
node my-agent.jsTest it:
# 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/manifestThat's it. Your agent is running with health checks, metrics, rate limiting, and x402 pricing — all automatic.
┌─────────────────────────────────┐
│ @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.
Let's say you have a scraper that pulls product data. Here's how to wrap it.
Before (plain scraper):
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):
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.
Already have an Express server? Don't create a new one — use the middleware.
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/healthPOST /synapse/queryGET /synapse/manifestGET /synapse/metrics
Custom prefix:
app.use(synapseMiddleware({ prefix: "/v1/agent", /* ... */ }));
// Routes: /v1/agent/health, /v1/agent/query, etc.For agents that charge real money, add a payment verifier:
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:
{
"status": "payment_required",
"price_usd": 0.25,
"token": "USDC",
"network": "hedera",
"message": "No payment provided"
}HTTP status: 402 Payment Required.
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: "..." }
},
});The handler is the function that does your actual work.
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",
},
};
});The second argument to your handler contains everything about the request:
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:
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"
}'Add functions that run before your handler. Great for logging, validation, auth, or enrichment.
// 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.
Every agent gets these automatically:
{ "status": "ok", "agent": "agent-abc123", "name": "My Agent", "domain": "general", "version": "1.0.0", "uptime_ms": 45000 }Main endpoint. Send { "query": "...", "filters": {}, "limit": 10, "sort_by": "..." }.
Returns your handler's results wrapped in a standard format:
{
"status": "ok",
"agent": "My Agent",
"query_parsed": { "original": "...", "filters": {}, "limit": 10 },
"response_time_ms": 142,
"results": [ ... ],
"results_count": 5,
"market_context": { ... }
}Agent metadata for discovery. Returned to Synapse server during registration.
{
"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", ... }
}Payment negotiation. Clients check this before paying.
{ "price_usd": 0.02, "token": "USDC", "network": "base", "payment_required": true }Operational stats.
{ "totalQueries": 142, "totalErrors": 3, "totalRevenue": 2.84, "uptime_ms": 3600000 }Your handler should return:
{
// 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.
Set price: 0 (default). No payment verification. Anyone can query.
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.
Set both price and verifyPayment. The agent enforces payment itself:
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" };
}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.
Start your agent, then register it with the Synapse server yourself:
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"
}'Don't connect to Synapse at all. Your agent works as an independent API that any HTTP client can call directly.
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 and 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():
const app = express();
agent1.attach(app, "/agent1");
agent2.attach(app, "/agent2");
app.listen(4102);MIT — ETHGlobal Cannes 2026