Editor's note, 3 October 2026. This post covers an earlier namespace. It still runs on the same API. Anlyon's product today is shared limits and receipts for AI agent actions.
The Anlyon TypeScript SDK is how your agent asks for something to happen in production. The agent invokes a named action. Anlyon holds the credential, waits for a human when the action needs one, and makes the call to Stripe, GitHub or your own API itself. The same client also carries the earlier namespaces: memory, schedules and message delivery.
Installation
Install the SDK using your preferred package manager:
npm install @anlyonhq/sdk
# or
pnpm add @anlyonhq/sdk
# or
yarn add @anlyonhq/sdk
Quick Start
1. Initialize the Client
import { Client } from '@anlyonhq/sdk';
const client = new Client({
apiKey: process.env.ANLYON_API_KEY!,
});
2. Define an action (operator key, once)
Define the production call from an operator key, not the key your agent runs with. This is the only place the credential appears.
const ops = new Client({ apiKey: process.env.ANLYON_OPERATOR_KEY! });
await ops.secrets.put('STRIPE_KEY', { value: process.env.STRIPE_KEY! });
await ops.actions.create({
name: 'refund-order',
description: 'Refund a Stripe charge.',
method: 'POST',
urlTemplate: 'https://api.stripe.com/v1/refunds',
headers: { Authorization: 'Bearer {{secret:STRIPE_KEY}}', 'Content-Type': 'application/x-www-form-urlencoded' },
inputSchema: {
type: 'object',
properties: { charge: { type: 'string' }, amount: { type: 'integer' } },
required: ['charge', 'amount'],
},
requiresApproval: true,
});
3. Invoke it from the agent
const anlyon = new Client({ apiKey: process.env.ANLYON_AGENT_KEY! });
const { data } = await anlyon.actions.invoke(
'refund-order',
{ charge: 'ch_3P9x', amount: 12000 },
{ idempotencyKey: 'refund:ord_42' },
);
if (data!.pendingApproval) {
console.log('Parked for a human:', data!.approvalId);
} else {
console.log('Stripe replied', data!.responseStatus);
}
The agent's half carries no URL, header or key. With the idempotency key, a same-key, same-request invoke is answered from the recorded invocation. It is not executed again.
4. The local gate, for calls that can't move
When a call cannot become an HTTP action (an ORM write, a native SDK), wrap your own function instead:
import { ApprovalDeniedError } from '@anlyonhq/sdk';
const guardedRefund = client.approvals.gate(
{ title: 'Refund a customer?', timeoutMs: 10 * 60_000 },
refund,
);
try {
await guardedRefund('ord_42', 129.99); // blocks until a human approves
} catch (err) {
if (err instanceof ApprovalDeniedError) {
console.log('Denied:', err.approval.decisionNote);
}
}
This is the fallback path. Your process still runs your function with your credential, so you get the human decision and the record, not credential isolation.
5. Publish a message (durable delivery)
// Send a message immediately
const result = await client.messages.publish({
url: 'https://api.example.com/webhook',
method: 'POST',
body: { event: 'user.created', userId: '123' },
});
console.log(`Message published: ${result.data.messageId}`);
6. Schedule a delayed message
// Send a message 60 seconds from now
const result = await client.messages.publish({
url: 'https://api.example.com/webhook',
method: 'POST',
body: { event: 'reminder', message: 'Don\'t forget!' },
executeAt: Math.floor(Date.now() / 1000) + 60,
});
Using the Fluent Builder API
The SDK provides a fluent builder API for a more intuitive experience:
// Using the fluent builder
await client.messages
.createMessage()
.to('https://api.example.com/webhook')
.body({ event: 'test', data: { value: 42 } })
.in(60) // 60 seconds from now
.maxRetries(5)
.send();
Next.js Integration
The SDK ships a Next.js App Router handler that verifies the webhook signature, so a delivery callback is verified before your handler processes it.
Using serve() (Recommended)
The simplest way to handle Anlyon webhooks in Next.js:
// app/api/scheduler/route.ts
import { serve } from '@anlyonhq/sdk/nextjs';
export const { POST } = serve(async (request) => {
const data = await request.json();
// Process the verified webhook
await processJob(data);
return { success: true };
});
Using verifySignature() Middleware
For more control over your handler:
// app/api/webhook/route.ts
import { verifySignature } from '@anlyonhq/sdk/nextjs';
export const POST = verifySignature(async (request) => {
const data = await request.json();
// Signature is already verified
await handleWebhook(data);
return Response.json({ ok: true });
});
Set your signing secret in your environment:
ANLYON_SIGNING_SECRET=whsec_your_signing_secret_here
Advanced Features
CRON Scheduling
Create recurring tasks with CRON expressions:
// Create a daily schedule
await client.schedules.create({
name: 'Daily Report',
cronExpression: '0 9 * * *', // 9 AM every day
timezone: 'America/New_York',
url: 'https://api.example.com/report',
method: 'POST',
body: { type: 'daily_report' },
});
// Using the fluent builder
await client.schedules
.createSchedule()
.name('Hourly Ping')
.hourly()
.to('https://api.example.com/ping')
.body({ type: 'ping' })
.create();
URL Groups (Fan-Out Messaging)
Broadcast messages to multiple endpoints:
// Create a URL group
const group = await client.urlGroups.create({
name: 'Production Webhooks',
endpoints: [
{ url: 'https://service1.example.com/webhook' },
{ url: 'https://service2.example.com/webhook' },
],
});
// Broadcast to all endpoints
await client.urlGroups.publish(group.data.id, {
body: { event: 'broadcast', data: { userId: 123 } },
});
Dead Letter Queue Management
Handle failed messages:
// List failed messages
const failed = await client.dlq.list();
// Retry a specific message
await client.dlq.retry('msg_id');
// Retry all failed messages
await client.dlq.retryAll();
// Get DLQ statistics
const stats = await client.dlq.getStats();
Error Handling
The SDK provides typed error classes for different scenarios:
import {
AnlyonError,
AuthenticationError,
QuotaExceededError,
ValidationError
} from '@anlyonhq/sdk';
try {
await client.messages.publish({ /* ... */ });
} catch (error) {
if (error instanceof QuotaExceededError) {
console.error('Quota exceeded:', error.message);
// Handle quota exceeded
} else if (error instanceof ValidationError) {
console.error('Validation error:', error.message);
// Handle validation error
} else if (error instanceof AuthenticationError) {
console.error('Authentication failed');
// Handle auth error
}
}
Pagination
The SDK provides helpers for iterating over paginated results:
// List all messages (automatically handles pagination)
for await (const message of client.messages.listAll()) {
console.log(message.messageId);
}
// Or manually paginate
const result = await client.messages.list({ limit: 50 });
// result.data contains messages
// result.pagination contains pagination info
Best Practices
- Store API keys securely: Use environment variables, never commit them to version control
- Handle errors gracefully: Always wrap SDK calls in try-catch blocks
- Use TypeScript: The SDK is fully typed for better developer experience
- Set appropriate retries on deliveries: Messages, schedules and workflows retry. Action invocations deliberately do not. An invocation whose outcome is
unknownneeds reconciling, not retrying - Monitor delivery: Use the analytics API to track message delivery
Next Steps
- Explore our CRON scheduling guide
- Learn about URL Groups and fan-out messaging
- Check out our Dead Letter Queue guide
- Read the full API documentation
- Read how to add human-in-the-loop approval to AI agent tool calls
- Read Actions in the docs
