@afini/twin-sdk
v0.1.0
Published
Official TypeScript SDK for the AfiniTwin B2B API.
Maintainers
Readme
@afini/twin-sdk
Official TypeScript SDK for the AfiniTwin B2B API.
The AfiniTwin is a portable cognitive profile (Big Five + 5 supplementary layers) built on the Afini.ai platform. This SDK gives you typed access to a user's snapshot from your own systems — CRMs, custom assistants, internal pipelines.
Installation
npm install @afini/twin-sdkRequires Node ≥ 18 (native fetch). For older Node, pass fetchImpl from node-fetch.
Get an API key
Active users with a Professional plan on Afini.ai can generate keys at afini.ai/dashboard/twin/api. The key is shown once — store it securely (Vault, Railway secrets, env vars).
Quick start
import { AfiniTwinClient } from '@afini/twin-sdk';
const client = new AfiniTwinClient({
apiKey: process.env.AFINITWIN_KEY!,
});
// Identity + plan + remaining quota
const me = await client.me();
console.log(`User has ${me.twins.ready} ready snapshots; quota ${me.quota.remaining}/${me.quota.monthlyLimit}`);
// All snapshots
const { snapshots } = await client.historic();
// Download the standard preset as Markdown for the latest snapshot
const md = await client.preset('estandar', { format: 'md', lang: 'es' });Sending data into the user's profile (twin:write scope)
If the API key has the twin:write scope, you can seed life-facts and annotations. They go to the user's review queue at /dashboard/discoveries; nothing is injected into the profile until the user approves.
const result = await client.lifeFacts.create([
{
category: 'professional',
value: 'Trabaja en una startup de IA en Bilbao desde 2023',
valence: 'positive',
consent: true, // explicit confirmation that you have user consent
externalRef: 'crm-12345',
},
]);
console.log(`${result.accepted} candidates queued, see ${result.inboxUrl}`);For free-form notes:
await client.annotations.create([
{ tag: 'observation', text: 'Mostró interés por escalar a Pro', consent: true },
]);Verifying webhook signatures
Every webhook POST carries an X-AfiniTwin-Signature: sha256=<hmac> header. Verify before trusting the payload:
import { verifyWebhookSignature, type WebhookPayload } from '@afini/twin-sdk';
import express from 'express';
const SECRET = process.env.AFINITWIN_WEBHOOK_SECRET!; // whsec_...
app.post('/webhooks/afinitwin', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.header('x-afinitwin-signature');
if (!verifyWebhookSignature(req.body, sig, SECRET)) {
return res.status(403).end();
}
const event = JSON.parse(req.body.toString()) as WebhookPayload;
switch (event.event) {
case 'twin.snapshot.ready':
// … pull the new snapshot
break;
case 'twin.quota.exceeded':
// … alert your billing/UX
break;
default:
console.log('event:', event.event);
}
res.status(200).end();
});Error handling
All API errors throw AfiniTwinApiError with a status and structured body:
import { AfiniTwinApiError } from '@afini/twin-sdk';
try {
await client.me();
} catch (err) {
if (err instanceof AfiniTwinApiError) {
if (err.status === 429 && err.body?.code === 'TIER_QUOTA_EXCEEDED') {
// upgrade your B2B tier
}
}
throw err;
}Rate limits
| Endpoint group | Per minute | Per month |
|----------------|------------|-----------|
| /health | 120 | unlimited |
| /me, /historic, /snapshots/*, /preset/* | 60 | per tier |
| /life-facts, /annotations | 30 | per tier |
The monthly cap is enforced per user across all keys based on the B2B tier (Included = 10k, Starter = 100k, Pro = 1M, Enterprise = custom). Hitting the cap returns 429 TIER_QUOTA_EXCEEDED with resetsAt and upgradeUrl.
License
MIT © Bilbao AI S.L.
