Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

Examples

Code examples for Anlyon's governed execution path: define an action, invoke it by name, wait for and decide approvals, read outcomes, and verify webhooks.

Short, copy-ready examples for the governed path. All examples use the official SDK and REST API.

Installation

npm install @anlyonhq/sdk

Create a .env file in your project. Two keys, because the key that defines where a credential may go should not be the key a model drives:

ANLYON_OPERATOR_KEY=your_operator_key
ANLYON_AGENT_KEY=your_agent_key
ANLYON_SIGNING_SECRET=your_signing_secret

Define an action, invoke it by name

import { Client } from '@anlyonhq/sdk';

const ops = new Client({ apiKey: process.env.ANLYON_OPERATOR_KEY! });

await ops.secrets.put('STRIPE_KEY', { value: process.env.STRIPE_KEY!, allowedHosts: ['api.stripe.com'] });

await ops.actions.create({
  name: 'refund-order',
  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,
});

const anlyon = new Client({ apiKey: process.env.ANLYON_AGENT_KEY! });

const { data } = await anlyon.actions.invoke('refund-order', { charge: 'ch_3P9x', amount: 12000 });
console.log(data!.pendingApproval ? `Parked: ${data!.approvalId}` : `Replied ${data!.responseStatus}`);
# Operator key: define the action once
curl -X POST https://api.anlyon.com/api/v2/actions \
  -H "Authorization: Bearer $ANLYON_OPERATOR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "refund-order",
    "method": "POST",
    "urlTemplate": "https://api.stripe.com/v1/refunds",
    "headers": { "Authorization": "Bearer {{secret:STRIPE_KEY}}" },
    "requiresApproval": true
  }'

# Agent key: invoke it by name
curl -X POST https://api.anlyon.com/api/v2/actions/refund-order/invoke \
  -H "Authorization: Bearer $ANLYON_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "input": { "charge": "ch_3P9x", "amount": 12000 } }'

Wait for the decision

The invocation came back pendingApproval: true. Wait for the human instead of polling:

const decided = await anlyon.approvals.waitForDecision(data!.approvalId!, { timeoutMs: 10 * 60_000 });

if (decided.status === 'approved') {
  console.log('Anlyon made the call. Invocation:', decided.payload.invocationId);
} else if (decided.status === 'pending') {
  console.log('Still waiting. Come back later; the approval stays decidable.');
} else {
  console.log('Not sent:', decided.status);
}

Decide from code

Deciding needs approvals:decide on a different key from the one that requested. Approving a gated action also runs the parked invocation:

const operator = new Client({ apiKey: process.env.ANLYON_OPERATOR_KEY! });

const pending = await operator.approvals.list({ status: 'pending' });

for (const approval of pending.data ?? []) {
  const { data } = await operator.approvals.approve(approval.id, { note: 'Checked the order.' });
  console.log(approval.id, '->', data!.invocation?.status);
}
curl -X POST https://api.anlyon.com/api/v2/approvals/$APPROVAL_ID/approve \
  -H "Authorization: Bearer $ANLYON_OPERATOR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Checked the order." }'

Replay safely with an idempotency key

Anlyon does not retry an invocation on its own. If your code retries, reuse the key. The retry is answered from the recorded invocation, and the action does not run again:

await anlyon.actions.invoke(
  'refund-order',
  { charge: 'ch_3P9x', amount: 12000 },
  { idempotencyKey: `refund-${orderId}` },
);

Read outcomes and reconcile unknown

// What the destination replied, and why the request was allowed or refused
const { data: invocation } = await anlyon.actions.invocation('inv_...');
console.log(invocation!.status, invocation!.responseStatus, invocation!.decision);

// Calls whose outcome Anlyon could not confirm
const unknown = await operator.actions.invocations({ status: 'unknown' });

// Check the destination, then record what you found (needs actions:resolve)
await operator.actions.resolveInvocation('inv_...', {
  outcome: 'succeeded',
  evidence: 'Refund re_1 is listed on the charge in Stripe.',
  externalReference: 're_1',
});

Attach invocations to a run

const { data: run } = await anlyon.runs.start({ name: 'refund-triage', agentId: 'agt_123' });

await anlyon.actions.invoke('refund-order', { charge: 'ch_3P9x', amount: 12000 }, { runId: run!.id });

await anlyon.runs.end(run!.id, { status: 'succeeded' });

The invocation, the approval wait and the decision all land on the same trace, exportable as OpenTelemetry Protocol JSON with runs.exportTrace(run.id).

Local approval gate (fallback)

When the call cannot move into Anlyon, gate your own function. Your process still runs it with your credential, so this gives you the decision and the record, not credential isolation:

const guardedRefund = anlyon.approvals.gate(
  { title: 'Refund a customer?', timeoutMs: 10 * 60_000 },
  async (orderId: string, amount: number) => { /* refund logic */ },
);

await guardedRefund('ord_42', 129.99);

Webhook verification

Verify incoming webhook signatures.

Next.js App Router

// app/api/webhook/route.ts
import { serve } from '@anlyonhq/sdk/nextjs';

export const { POST } = serve(async (request) => {
  const event = await request.json();

  switch (event.type) {
    case 'approval.requested':
      await notifyReviewers(event);
      break;
    case 'action.unknown':
      await openReconciliationTicket(event);
      break;
  }

  return { success: true };
});

Express

import express from 'express';
import { Receiver } from '@anlyonhq/sdk';

const app = express();
const receiver = new Receiver({
  signingSecret: process.env.ANLYON_SIGNING_SECRET!,
});

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['anlyon-signature'] as string;
  const body = req.body.toString();

  if (!receiver.verify({ signature, body })) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const data = JSON.parse(body);
  // Process webhook...

  res.json({ success: true });
});

Node.js HTTP

import { createServer } from 'http';
import { Receiver } from '@anlyonhq/sdk';

const receiver = new Receiver({
  signingSecret: process.env.ANLYON_SIGNING_SECRET!,
});

createServer(async (req, res) => {
  if (req.method === 'POST' && req.url === '/webhook') {
    const chunks: Buffer[] = [];
    for await (const chunk of req) chunks.push(chunk);
    const body = Buffer.concat(chunks).toString();

    const signature = req.headers['anlyon-signature'] as string;

    try {
      receiver.verifyOrThrow({ signature, body });
      const data = JSON.parse(body);
      // Process webhook...

      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ success: true }));
    } catch (error) {
      res.writeHead(401, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Invalid signature' }));
    }
  }
}).listen(3000);

Resources

Was this page helpful?