Documentation
AgentLedger tracks what your AI agents actually do — every API call, email, ticket, and charge. Get set up in under 5 minutes.
Quick Start
Set up the database
Create a Supabase project and run the migration in the SQL Editor.
-- Paste the contents "text-violet-400">of supabase/migrations/001_initial_schema.sql\n-- into Supabase Dashboard > SQL Editor > New Query > RunDeploy the dashboard
Deploy to Vercel with one click, or run locally:
git clone https://github.com/miken1988/agentledger.git
cd agentledger
cp .env.local.example .env.local
# Add your Supabase URL, anon key, and service role key
npm install
npm run devInstall the SDK and start tracking
npm install agentledger"text-violet-400">import { AgentLedger } "text-violet-400">from 'agentledger';
"text-violet-400">const ledger = "text-violet-400">new AgentLedger({
apiKey: process.env.AGENTLEDGER_KEY,
});
// Wrap any agent action
"text-violet-400">const result = "text-violet-400">await ledger.track({
agent: 'support-bot',
service: 'slack',
action: 'send_message',
}, "text-violet-400">async () => {
"text-violet-400">return "text-violet-400">await slack.chat.postMessage({
channel: '#support',
text: 'Issue resolved!'
});
});Installation
npm install agentledgerThe SDK has zero external dependencies and works in Node.js 18+. It also works in Bun and Deno.
Configuration
"text-violet-400">const ledger = "text-violet-400">new AgentLedger({
apiKey: 'al_...', // Required. Get "text-violet-400">from dashboard.
baseUrl: 'https://...', // Your AgentLedger instance URL
failOpen: true, // If AgentLedger is down, actions proceed (default: true)
timeout: 5000, // API timeout in ms (default: 5000)
onError: (err) => log(err), // Optional error callback
});| Option | Default | Description |
|---|---|---|
apiKey | required | Your API key (starts with al_) |
baseUrl | https://agentledger.co | Your AgentLedger API endpoint |
failOpen | true | If true, actions proceed when AgentLedger is unreachable |
timeout | 5000 | API call timeout in milliseconds |
onError | undefined | Callback for communication errors |
Fail-open by default. AgentLedger never blocks your agents from running unless you explicitly set failOpen: false. Even budget checks fail-open — if the API is unreachable, the action proceeds.
Core SDK
ledger.track(options, fn)
Wraps an async function with logging and pre-flight budget checks. This is the main method you'll use.
"text-violet-400">const { result, allowed, durationMs, actionId } = "text-violet-400">await ledger.track({
agent: 'support-bot', // Agent name
service: 'sendgrid', // Service being called
action: 'send_email', // Action being per"text-violet-400">formed
costCents: 1, // Optional: estimated cost
metadata: { to: 'user@' }, // Optional: custom metadata
}, "text-violet-400">async () => {
"text-violet-400">return "text-violet-400">await sendEmail(to, subject, body);
});ledger.check(options)
Pre-flight check without executing the action. Useful before expensive operations.
"text-violet-400">const { allowed, blockReason, remainingBudget } = "text-violet-400">await ledger.check({
agent: 'billing-agent',
service: 'stripe',
action: 'charge',
});
"text-violet-400">if (!allowed) {
console.log('Blocked:', blockReason);
}ledger.log(options)
Log an action manually when you want full control over timing.
"text-violet-400">await ledger.log({
agent: 'data-sync',
service: 'postgres',
action: 'bulk_insert',
status: 'success',
durationMs: 1523,
costCents: 0,
});Agent Controls
"text-violet-400">await ledger.pauseAgent('support-bot'); // Blocks all future actions
"text-violet-400">await ledger.resumeAgent('support-bot'); // Resumes the agent
"text-violet-400">await ledger.killAgent('rogue-bot'); // Permanently kills the agentLangChain Integration
Drop-in callback handler that auto-tracks tool calls, LLM completions, and chain runs.
npm install agentledger langchain @langchain/core"text-violet-400">import { AgentLedger } "text-violet-400">from 'agentledger';
"text-violet-400">import { AgentLedgerCallbackHandler } "text-violet-400">from 'agentledger/integrations/langchain';
"text-violet-400">const ledger = "text-violet-400">new AgentLedger({ apiKey: 'al_...' });
"text-violet-400">const handler = "text-violet-400">new AgentLedgerCallbackHandler(ledger, {
agent: 'research-bot',
trackLLM: true, // Track LLM calls with token usage (default: true)
trackTools: true, // Track tool invocations (default: true)
trackChains: false, // Track chain/agent runs (default: false)
serviceMap: {
'tavily_search': { service: 'tavily', action: 'search' },
'calculator': { service: 'math', action: 'calculate' },
'send_email': { service: 'sendgrid', action: 'send' },
},
});
// Use with any LangChain component
"text-violet-400">const agent = createReactAgent({
llm: "text-violet-400">new ChatOpenAI({ callbacks: [handler] }),
tools,
});
"text-violet-400">await agent.invoke(
{ input: 'Research the latest AI "text-violet-400">news and email me a summary' },
{ callbacks: [handler] }
);The serviceMap lets you control how LangChain tool names map to AgentLedger services. If a tool isn't in the map, its name is used as the service with "invoke" as the action.
OpenAI Agents Integration
Wrap tool handlers so every function call from OpenAI is tracked.
"text-violet-400">import { AgentLedger } "text-violet-400">from 'agentledger';
"text-violet-400">import { createToolExecutor } "text-violet-400">from 'agentledger/integrations/openai';
"text-violet-400">const ledger = "text-violet-400">new AgentLedger({ apiKey: 'al_...' });
// Define your tool handlers
"text-violet-400">const handlers = {
send_email: "text-violet-400">async (args) => sendEmail(args.to, args.body),
create_ticket: "text-violet-400">async (args) => createJiraTicket(args.title, args.desc),
charge_card: "text-violet-400">async (args) => stripe.charges.create(args),
};
// Map tool names to services
"text-violet-400">const serviceMap = {
send_email: { service: 'sendgrid', action: 'send' },
create_ticket: { service: 'jira', action: 'create_issue' },
charge_card: { service: 'stripe', action: 'charge' },
};
// Create the executor
"text-violet-400">const execute = createToolExecutor(ledger, 'my-agent', handlers, serviceMap);
// In your OpenAI agent loop
"text-violet-400">for ("text-violet-400">const toolCall "text-violet-400">of response.choices[0].message.tool_calls) {
"text-violet-400">const result = "text-violet-400">await execute(
toolCall."text-violet-400">function.name,
JSON.parse(toolCall."text-violet-400">function.arguments)
);
// ... send result back to OpenAI
}Wrap individual functions
"text-violet-400">import { withAgentLedger } "text-violet-400">from 'agentledger/integrations/openai';
// Wrap a single "text-violet-400">function — preserves original signature
"text-violet-400">const trackedSendEmail = withAgentLedger(ledger, {
agent: 'email-bot',
service: 'sendgrid',
action: 'send_email',
}, sendEmail);
// Use exactly like the original
"text-violet-400">await trackedSendEmail(to, subject, body);MCP Server Integration
One line to track every tool invocation in your MCP server.
"text-violet-400">import { McpServer } "text-violet-400">from '@modelcontextprotocol/sdk/server/mcp.js';
"text-violet-400">import { AgentLedger } "text-violet-400">from 'agentledger';
"text-violet-400">import { wrapMCPServer } "text-violet-400">from 'agentledger/integrations/mcp';
"text-violet-400">const ledger = "text-violet-400">new AgentLedger({ apiKey: 'al_...' });
"text-violet-400">const server = "text-violet-400">new McpServer({ name: 'my-tools', version: '1.0.0' });
// Register tools as normal
server.tool('send_email', { to: z.string(), body: z.string() }, "text-violet-400">async (args) => {
"text-violet-400">return "text-violet-400">await sendEmail(args.to, args.body);
});
server.tool('search_web', { query: z.string() }, "text-violet-400">async (args) => {
"text-violet-400">return "text-violet-400">await tavily.search(args.query);
});
// One line — all tool calls are now logged to AgentLedger
wrapMCPServer(ledger, server, {
agent: 'my-mcp-server',
serviceMap: {
send_email: { service: 'sendgrid' },
search_web: { service: 'tavily', action: 'search' },
},
});Wrap individual tools
"text-violet-400">import { wrapMCPTool } "text-violet-400">from 'agentledger/integrations/mcp';
server.tool('send_email', schema, wrapMCPTool(ledger, {
agent: 'my-server',
service: 'sendgrid',
action: 'send_email',
}, "text-violet-400">async (args) => {
"text-violet-400">return "text-violet-400">await sendEmail(args.to, args.body);
}));Express / Generic Integration
Express middleware
"text-violet-400">import { agentLedgerMiddleware } "text-violet-400">from 'agentledger/integrations/express';
// Track spec"text-violet-400">ific routes
app.post('/api/send-email', agentLedgerMiddleware(ledger, {
agent: 'email-bot',
service: 'sendgrid',
action: 'send_email',
}), emailHandler);
// Auto-detect "text-violet-400">from path
app.use('/api/agent', agentLedgerMiddleware(ledger, {
agent: 'my-agent',
autoDetect: true,
}));Wrap any function
Works with any framework — no Express required.
"text-violet-400">import { trackFunction } "text-violet-400">from 'agentledger/integrations/express';
"text-violet-400">const trackedSendEmail = trackFunction(ledger, {
agent: 'my-bot',
service: 'sendgrid',
action: 'send_email',
}, sendEmail);
// Same signature as the original "text-violet-400">function
"text-violet-400">await trackedSendEmail(to, subject, body);REST API
All endpoints require an Authorization: Bearer al_... header.
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/actions | Log an agent action |
GET | /api/v1/actions | List actions (paginated) |
POST | /api/v1/check | Pre-flight budget/status check |
GET | /api/v1/stats | Dashboard summary stats |
GET | /api/v1/agents/:name | Agent details + recent actions |
POST | /api/v1/agents/:name/pause | Pause an agent |
POST | /api/v1/agents/:name/resume | Resume a paused agent |
POST | /api/v1/agents/:name/kill | Permanently kill an agent |
POST | /api/v1/budgets | Create or update a budget |
GET | /api/v1/alerts | List anomaly alerts |
Example: Log an action
curl -X POST https://your-instance.vercel.app/api/v1/actions \
-H "Authorization: Bearer al_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"agent": "support-bot",
"service": "slack",
"action": "send_message",
"status": "success",
"cost_cents": 0,
"duration_ms": 150,
"metadata": { "channel": "#support" }
}'Example: Pre-flight check
curl -X POST https://your-instance.vercel.app/api/v1/check \
-H "Authorization: Bearer al_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"agent": "billing-agent",
"service": "stripe",
"action": "charge"
}'
// Response:
// { "allowed": true, "remainingBudget": {} }
// or
// { "allowed": false, "blockReason": "daily cost budget exceeded ($50.00/$50.00)" }Webhooks
Get real-time HTTP notifications when events occur. Webhooks are signed with HMAC-SHA256 so you can verify authenticity.
Events
| Event | Fired When |
|---|---|
action.logged | Any agent action is recorded |
agent.paused | An agent is paused |
agent.killed | An agent is permanently killed |
agent.resumed | A paused agent is resumed |
budget.exceeded | A budget limit is reached |
budget.warning | A budget crosses 75% usage |
alert.created | Any anomaly alert is created |
Create a webhook
curl -X POST https://your-instance.vercel.app/api/v1/webhooks \
-H "Authorization: Bearer al_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["budget.exceeded", "agent.killed"],
"description": "Slack alerts"
}'
// Response includes a signing secret (shown only once):
// { "id": "...", "secret": "whsec_...", "url": "...", "events": [...] }Verify signatures
// Every webhook request includes X-AgentLedger-Signature header
"text-violet-400">const crypto = require('crypto');
app.post('/webhook', (req, res) => {
"text-violet-400">const signature = req.headers['x-agentledger-signature'];
"text-violet-400">const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.string"text-violet-400">ify(req.body))
.digest('hex');
"text-violet-400">if (signature !== expected) {
"text-violet-400">return res.status(401).send('Invalid signature');
}
// Process the event
"text-violet-400">const { event, data, timestamp } = req.body;
console.log(`Received ${event}:`, data);
res.sendStatus(200);
});Auto-disable. Webhooks are automatically disabled after 10 consecutive delivery failures. Re-enable them from the dashboard or API.
API Key Management
Create up to 5 active API keys per organization. Rotate and revoke keys without downtime.
Create a new key
curl -X POST https://your-instance.vercel.app/api/v1/keys/create \
-H "Authorization: Bearer al_current_key" \
-H "Content-Type: application/json" \
-d '{ "name": "production", "description": "Main production key" }'
// Response includes the full key (shown only once):
// { "id": "...", "key": "al_...", "name": "production" }Rotate a key
Atomically revokes an old key and creates a new one with the same name.
curl -X POST https://your-instance.vercel.app/api/v1/keys/rotate \
-H "Authorization: Bearer al_current_key" \
-H "Content-Type: application/json" \
-d '{ "keyId": "key-uuid-to-rotate" }'Revoke a key
curl -X POST https://your-instance.vercel.app/api/v1/keys/revoke \
-H "Authorization: Bearer al_current_key" \
-H "Content-Type: application/json" \
-d '{ "keyId": "key-uuid-to-revoke" }'Safety. You cannot revoke the key you're currently using to authenticate. This prevents accidental lockouts.
Dashboard
The dashboard provides a real-time view of all your agent activity.
Overview — total actions, costs, active agents, error rate, 24h activity chart, and service breakdown.
Actions — searchable, filterable feed of every action with agent, service, status, duration, and cost.
Agents — all registered agents with status, action counts, costs, and pause/kill controls.
Budgets — create and manage daily/weekly/monthly budgets per agent.
Alerts — anomaly alerts for budget exceeded, unusual activity spikes, and agent kills.
Budgets & Alerts
Set spending and action limits per agent. When a budget is exceeded, all future actions are blocked until the budget resets.
// Create a budget via the API
curl -X POST https://your-instance.vercel.app/api/v1/budgets \
-H "Authorization: Bearer al_..." \
-H "Content-Type: application/json" \
-d '{
"agent": "billing-agent",
"period": "daily",
"max_actions": 1000,
"max_cost_cents": 5000
}'Budget counters reset automatically:
| Period | Resets At |
|---|---|
daily | Midnight UTC |
weekly | Monday midnight UTC |
monthly | 1st of the month midnight UTC |
Automatic budget resets require enabling pg_cron in your Supabase project. See the migration file for the cron schedule SQL.
Self-Hosting
Requirements
• Node.js 18+
• Supabase project (free tier works)
• Vercel, Railway, Fly.io, or any Node.js host
Environment Variables
| Variable | Description |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | Your Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Supabase anonymous/public key |
SUPABASE_SERVICE_ROLE_KEY | Supabase service role key (keep secret) |
Deploy to Vercel
The fastest way. Click the deploy button in the README, or:
vercel deploy --prodRun locally
git clone https://github.com/miken1988/agentledger.git
cd agentledger
cp .env.local.example .env.local
# Edit .env.local with your Supabase credentials
npm install
npm run dev
# Open http://localhost:3000