Quickstart
Install + paste + run. First agent_events row in under 5 minutes.
1. Install
The core SDK plus the two adapters you need for the Express + OpenAI example.
npm install @avanor/sdk @avanor/sdk-express @avanor/sdk-openai2. Set your environment
AVANOR_API_KEY binds events to your tenant server-side. Mint one at /dashboard/settings/api-keys on your Avanor workspace.
export AVANOR_API_KEY=avs_...
export OPENAI_API_KEY=sk-...3. Initialize the client
One file at the top of your app. The init call is idempotent — safe to call multiple times from any entry point.
// avanor.ts (one file, top of customer's app)
import { Avanor } from '@avanor/sdk';
export const avanor = Avanor.init({
apiKey: process.env.AVANOR_API_KEY!, // tenant binding derived server-side
environment: process.env.NODE_ENV ?? 'dev', // 'dev' | 'staging' | 'prod'
service: 'broker-portal', // becomes service.name on every span
});Multiple tenants in one process
Avanor.init() is a process singleton, the right default for a single-tenant app. When one process must emit to several Avanor tenants (a background worker servicing multiple customers, or an agency hosting multiple end-customers), use createClient() instead. Each call returns a fully isolated client with its own API key, queue, and lifecycle. The init() singleton is never touched.
// Multi-tenant worker: one process, many Avanor tenants.
import { createClient } from '@avanor/sdk';
const tenantA = createClient({
apiKey: process.env.AVANOR_KEY_A!,
environment: 'prod',
service: 'tenant-a-worker',
});
const tenantB = createClient({
apiKey: process.env.AVANOR_KEY_B!,
environment: 'prod',
service: 'tenant-b-worker',
});
tenantA.track('job_processed', { jobId });
tenantB.track('job_processed', { jobId });
// Drain both queues before the worker exits.
await Promise.all([tenantA.flush(), tenantB.flush()]);4. Paste the example
A complete Express + OpenAI handler that gates on allow(), makes an auto-instrumented LLM call, and writes a hash-chained attest() row when it finishes.
import express from 'express';
import { Avanor } from '@avanor/sdk';
import { instrumentOpenAI } from '@avanor/sdk-openai';
import OpenAI from 'openai';
const avanor = Avanor.init({
apiKey: process.env.AVANOR_API_KEY!,
environment: process.env.NODE_ENV ?? 'dev',
service: 'broker-portal',
});
const openai = instrumentOpenAI(new OpenAI()); // monkey-patches in place
const app = express();
app.use(express.json());
app.use(avanor.middleware()); // tracks every request
app.post('/send-email', async (req, res) => {
const { to, subject, body } = req.body;
const { allow, reason, approvalId } = await avanor.allow('email_send', {
'recipient.domain': to.split('@')[1],
'subject.length': subject.length,
'has_pii': /\b\d{3}-\d{2}-\d{4}\b/.test(body),
});
if (!allow) return res.status(403).json({ reason, approvalId });
const draft = await openai.chat.completions.create({ // auto-instrumented
model: 'gpt-4o',
messages: [{ role: 'user', content: `Rewrite professionally: ${body}` }],
});
// ... send via Resend/SES ...
const att = await avanor.attest('email_send', {
input: { to, subject }, output: { messageId: 'abc123' }, status: 'ok',
});
res.json({ ok: true, attestId: att.attestId });
});
app.listen(3000);5. Run it
# in one shell
node --experimental-strip-types app.ts
# in another
curl -X POST localhost:3000/send-email \
-H 'content-type: application/json' \
-d '{"to":"alice@example.com","subject":"hi","body":"hello"}'6. See your first event
Open /dashboard/events. Within a few seconds you should see a new row with:
platform=customer_sdksdk_version=0.1.4sdk_language=typescriptingestion_path=sdkgen_ai.*andavanor.*attributes populated from the wrappedchat.completions.createcall
What this gives you, in one sentence
Every outbound LLM call and every guarded action your code makes lands in the Avanor audit log with a server-issued, hash-chained record. The evidence is yours to export, the policy decisions are yours to author, and the kill switch is yours (and Avanor support) to flip without a customer redeploy.
Next
- Express + OpenAI recipe — same code with line-by-line commentary.
- API Reference — every public type and method.