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.

bash
npm install @avanor/sdk @avanor/sdk-express @avanor/sdk-openai

2. Set your environment

AVANOR_API_KEY binds events to your tenant server-side. Mint one at /dashboard/settings/api-keys on your Avanor workspace.

bash
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.tsts
// 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.

worker.tsts
// 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.

app.tsts
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

bash
# 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_sdk
  • sdk_version = 0.1.4
  • sdk_language = typescript
  • ingestion_path = sdk
  • gen_ai.* and avanor.* attributes populated from the wrapped chat.completions.create call

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