Skip to content

oztenbot/agent-meter

Repository files navigation

agent-meter

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());

Why

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.

Install

npm install agent-meter

Zero runtime dependencies for the core SDK. Only uses Node.js built-in crypto.

For persistent storage, optionally install:

npm install better-sqlite3

Quick Start

import 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/widgets

Usage Records

Every 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
}

Configuration

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,
});

Per-Route Options

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 }));

Transports

MemoryTransport

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 records

SQLiteTransport

Persistent 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 down

Requires better-sqlite3 as a peer dependency. Uses WAL mode for concurrent reads.

HttpTransport

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);
  },
});

Request Signing

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 dropped

Architecture

Agent (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.

Design Principles

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

SKILL Install (Agents)

If you're an agent (Claude Code, OpenClaw, or any framework supporting agentskills.io skills), this is the fastest path.

Claude Code

cd ~/.claude && npx clawhub install agent-meter

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

OpenClaw

cd ~/.claude && npx clawhub install agent-meter

Add the bundled session parser to cron for continuous coverage:

*/30 * * * * ~/.claude/skills/agent-meter/meter-parse-sessions.sh

The parser reads OpenClaw's session JSONL files directly — 100% coverage, no behavioral compliance needed.

How It Works

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.

Roadmap

  • 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

Documentation

  • 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

License

MIT

About

Usage metering for the agent economy

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors