Usage metering for the agent economy.
Drop-in metering for APIs that serve AI agents. Track every request, attribute it to an agent, and emit structured usage records — in three lines of code.
import { AgentMeter } from "agent-meter";
const meter = new AgentMeter({ serviceId: "my-api" });
app.use(meter.express());APIs are getting a new class of customer: autonomous agents. Agents call your API thousands of times, on behalf of humans you've never met, with usage patterns nothing like a browser session.
Subscriptions don't work when the buyer is a machine that can meter its own consumption. Usage-based pricing does — but only if you can measure it.
agent-meter gives you the measurement layer. Zero dependencies, framework-agnostic, and designed for the standards that are emerging around agent-to-service commerce.
npm install agent-meterZero runtime dependencies for the core SDK. Only uses Node.js built-in crypto.
For persistent storage, optionally install:
npm install better-sqlite3import express from "express";
import { AgentMeter, MemoryTransport } from "agent-meter";
const app = express();
const transport = new MemoryTransport();
const meter = new AgentMeter({ serviceId: "my-api", transport });
app.use(meter.express());
app.get("/api/widgets", (req, res) => {
res.json({ widgets: ["a", "b", "c"] });
});
app.listen(3000);Agents identify themselves with the X-Agent-Id header. Every request from an identified agent produces a UsageRecord.
curl -H "X-Agent-Id: bot-123" http://localhost:3000/api/widgetsEvery metered request produces a structured record:
{
"id": "a1b2c3d4e5f6...",
"timestamp": "2026-02-15T00:00:00.000Z",
"serviceId": "my-api",
"agent": {
"agentId": "bot-123",
"name": "WidgetBot"
},
"operation": "GET /api/widgets",
"units": 1,
"unitType": "request",
"pricingModel": "per-call",
"method": "GET",
"path": "/api/widgets",
"statusCode": 200,
"durationMs": 12
}const meter = new AgentMeter({
// Required: identifies your service
serviceId: "my-api",
// Where records go (default: MemoryTransport)
transport: new HttpTransport({ url: "https://billing.example.com/ingest" }),
// Default pricing model for all routes
defaultPricing: "per-call",
// Custom agent identification (default: X-Agent-Id header)
identifyAgent: (req) => ({
agentId: req.headers["authorization"],
tier: req.headers["x-agent-tier"],
}),
// HMAC-SHA256 signature verification
signingSecret: process.env.SIGNING_SECRET,
// Transform or filter records before they're sent
beforeEmit: (record) => {
if (record.path === "/health") return undefined; // drop
return { ...record, metadata: { region: "us-east-1" } };
},
// Whether to meter 4xx/5xx responses (default: false)
meterErrors: false,
});app.post("/api/generate", meter.express({
operation: "generate-text",
units: (req) => req.body.tokens,
unitType: "token",
pricing: "per-unit",
}));
app.get("/health", meter.express({ skip: true }));Stores records in-memory. Useful for testing and development.
import { MemoryTransport } from "agent-meter";
const transport = new MemoryTransport();
// After requests...
console.log(transport.records);
// Query interface (same as SQLiteTransport)
const records = transport.query({ agentId: "bot-123" });
const count = transport.count({ operation: "GET /widgets" });
const stats = transport.summary();
transport.flush(); // clear all recordsPersistent local storage with zero infrastructure. Records survive restarts and are queryable.
import { SQLiteTransport } from "agent-meter/sqlite";
const transport = new SQLiteTransport({
filename: "./usage.db", // or :memory: for testing
tableName: "usage_records", // optional, default shown
});
// Records are persisted immediately on send
// Query them later:
const records = transport.query({ agentId: "bot-123", limit: 100 });
const total = transport.count({ from: "2026-02-15T00:00:00.000Z" });
const stats = transport.summary({ serviceId: "my-api" });
// stats.totalRecords, stats.totalUnits, stats.uniqueAgents
// stats.byOperation, stats.byAgent
transport.close(); // when shutting downRequires better-sqlite3 as a peer dependency. Uses WAL mode for concurrent reads.
Batches and POSTs records to a backend. Retries failed requests with exponential backoff.
import { HttpTransport } from "agent-meter";
const transport = new HttpTransport({
url: "https://billing.example.com/ingest",
headers: { Authorization: "Bearer sk-..." },
batchSize: 10, // flush every N records
flushIntervalMs: 5000, // or every 5 seconds
maxRetries: 3, // retry failed requests (default: 3)
onError: (err, batch) => {
console.error(`Failed to send ${batch.length} records:`, err.message);
},
});Agents can sign requests with HMAC-SHA256 so your service can verify authenticity:
// Agent side
import { signPayload } from "agent-meter";
const signature = signPayload(JSON.stringify(body), sharedSecret);
// Send as X-Agent-Signature header
// Service side
const meter = new AgentMeter({
serviceId: "my-api",
signingSecret: sharedSecret,
});
// Unsigned or invalid requests are silently droppedAgent (X-Agent-Id header)
│
▼
┌─────────────────────────┐
│ Your Express API │
│ ┌───────────────────┐ │
│ │ agent-meter │ │
│ │ middleware │ │
│ └────────┬──────────┘ │
│ │ UsageRecord │
└───────────┼──────────────┘
▼
┌─────────────────────────┐
│ Transport │
│ (Memory, SQLite, HTTP, custom) │
└─────────────────────────┘
The SDK is framework-agnostic at its core. AgentMeter.record() works with any request/response pair. The .express() method is a thin adapter (~10 lines). Adapters for Fastify, Hono, and others are coming.
- Zero runtime dependencies. Only
node:crypto. Install pulls nothing. - Measure first, bill later. This SDK captures usage. Billing is a separate concern.
- Agent-native. Built for machine clients, not browser sessions. Agent identity is a first-class concept.
- Non-blocking. Metering never delays your API response.
- Extensible. Custom transports, identification, pricing models, and hooks.
If you're an agent (Claude Code, OpenClaw, or any framework supporting agentskills.io skills), this is the fastest path.
cd ~/.claude && npx clawhub install agent-meterSkills must live under ~/.claude/skills/ for Claude Code to find them. clawhub install writes to ./skills/<slug> relative to your working directory, so run it from ~/.claude.
Then run /meter — it auto-installs hooks on first invocation.
cd ~/.claude && npx clawhub install agent-meterAdd the bundled session parser to cron for continuous coverage:
*/30 * * * * ~/.claude/skills/agent-meter/meter-parse-sessions.shThe parser reads OpenClaw's session JSONL files directly — 100% coverage, no behavioral compliance needed.
All capture methods write to ~/.agent-meter/spend.jsonl — one JSON record per API call. The SKILL's /meter command reads this file and produces spend summaries by project, model, and purpose.
See Spend Schema for the full field reference.
- SQLite transport (persistent local storage)
- Query interface (query, count, summary)
- HTTP retry with exponential backoff
- SKILL-based agent install (Claude Code + OpenClaw)
- Automatic spend capture via hooks
- Fastify adapter
- Hono adapter
- Rate-limit awareness (meter + enforce)
- Crabacus dashboard (spend visualization)
- ClawHub publishing
- Quickstart — Install, 3-line setup, verify it works
- Overview — Architecture, design decisions, attestation, roadmap
- Security Deep Dive — Threat models, trust analysis, attack scenarios
- Spend Schema — JSONL schema for spend capture
MIT