@relay-agent/sdk
v0.3.0
Published
TypeScript SDK for Relay Agent — developer-first voice AI engine
Downloads
770
Maintainers
Readme
@relay-agent/sdk
TypeScript SDK for Relay Agent — a developer-first voice AI engine for building phone and web call agents.
Building a browser voice integration? See the complete copy-paste guide: INTEGRATION.md — backend route + drop-in browser client (zero dependencies) + React example. Designed to be readable end-to-end by AI coding agents.
Status: Relay Agent is currently invite-only. You'll be issued an API key (
ra_...) by the team — public sign-up is closed.
Installation
npm install @relay-agent/sdkQuick Start
import { RelayClient } from '@relay-agent/sdk';
const client = new RelayClient({ apiKey: 'ra_...' });
// Create a voice agent
const agent = await client.agents.create({
name: 'Support Agent',
model: {
provider: 'openai',
model: 'gpt-4.1-mini',
systemPrompt: 'You are a helpful customer support agent.',
},
voice: {
provider: 'elevenlabs',
voiceId: 'rachel',
},
});
// Publish the agent
await client.agents.publish(agent.id);
// Make an outbound phone call
const call = await client.calls.createPhone({
agentId: agent.id,
from: '+15551234567',
to: '+15559876543',
});
console.log(`Call started: ${call.id}`);Features
- Agents — Create, update, publish, version, and rollback voice agents
- Calls — Outbound phone calls, web calls, call control (whisper, interject, terminate)
- Tools — Function tools, transfer calls, agent transfers with typed configuration
- Phone Numbers — Search, provision, sync, and manage phone numbers
- Campaigns — Batch outbound calling with scheduling and retry policies
- Knowledge Bases — RAG-powered document Q&A for agents
- A/B Testing — Compare agent variants with traffic splitting
- Composed Agents — Multi-agent workflows with transitions
- SIP Trunks — BYOC telephony with credential and IP ACL management
- Webhooks — Typed event payloads with HMAC-SHA256 signature verification
- Usage — Cost tracking by agent, daily breakdown
- Provisioning — Multi-tenant user/org provisioning with magic links, plus upgrade-to-platform for partner accounts
- Analytics — Latency percentiles (overall + STT/LLM/TTS breakdown) over a date range
- Guardrails — Per-agent behavioral rules, plus AI extraction from poorly-rated calls
- Test Suites — Scenario-based agent testing with scored runs
- Transfer Directory — Managed transfer destinations with bulk import
- Heartbeat Tasks — Triggered background agents that monitor your platform
- Workflows — List composed agents instantiated from workflow templates
What's new in 0.2.0
client.provisioning.upgradeToPlatform({ orgId, apiKeyName? })— promote a child org to platform tier; returns a new platform API key.client.workflows.list()— list composed agents created from workflow templates.RelayClient.verify()/sign()now use the webhook signing secret as their second argument, not the API key. Functionally the same string-string-string call, but the secret semantics are clarified (and the SDK docs no longer mislead). Migration: changeRelayClient.verify(body, apiKey, sig)→RelayClient.verify(body, WEBHOOK_SIGNING_SECRET, sig). Existing code passing the API key never actually worked — webhooks are signed withWEBHOOK_SIGNING_SECRET.
See the full integration guide for a migration table from raw fetch.
Quality & Operations
// Latency analytics
const latency = await client.analytics.getLatency({ period: '7d' });
console.log(`P95 turn latency: ${latency.overall.p95}ms`);
// Guardrails — add a rule, or extract them from a bad call
await client.guardrails.create(agent.id, {
type: 'avoid_phrase',
rule: 'Never quote a price without confirming the plan.',
severity: 'critical',
});
await client.guardrails.extractFromCall(call.id);
// Test suites — define scenarios and run them
const suite = await client.testSuites.create({ name: 'Regression', agentId: agent.id });
await client.testSuites.addScenario(suite.id, {
name: 'Refund request',
callerPersona: 'A frustrated customer wanting a refund',
initialMessage: 'I want my money back.',
expectedBehaviors: [{ description: 'Stays polite and offers help', type: 'must_do' }],
});
const run = await client.testSuites.run(suite.id);
// Transfer directory — managed destinations + bulk import
await client.transferDirectory.create({ name: 'Billing', destination: '+15551234567' });
await client.transferDirectory.import([
{ name: 'Sales', destination: '+15550000001' },
{ name: 'Support', destination: 'sip:[email protected]', destinationType: 'sip' },
]);
// Heartbeat tasks — background monitoring agents
const task = await client.heartbeatTasks.create({
name: 'Error watcher',
prompt: 'Summarize any spike in call errors and suggest a cause.',
contextType: 'recent_errors',
trigger: { type: 'error_rate', threshold: 0.1, window: '15m' },
});
await client.heartbeatTasks.trigger(task.id);Web Calls (Browser)
For browser-based voice calls, use the web entry point:
import { WebCallSession } from '@relay-agent/sdk/web';
// First, create a web call via your backend
const response = await fetch('/api/call', { method: 'POST' });
const { accessToken, websocketUrl } = await response.json();
// Then connect from the browser
const session = new WebCallSession({
accessToken,
websocketUrl,
transport: 'auto', // 'webrtc' | 'websocket' | 'auto'
});
session.on('connected', () => console.log('Call connected'));
session.on('transcript', (msg) => console.log(`${msg.role}: ${msg.text}`));
session.on('ended', () => console.log('Call ended'));
await session.connect();Webhook Verification
Relay POSTs signed JSON webhooks with these headers:
| Header | Value |
|---|---|
| X-Relay-Signature | HMAC-SHA256 of the raw request body, hex-encoded |
| X-Relay-Event | Event type (e.g. call.ended) |
| X-Relay-Timestamp | ISO 8601 dispatch timestamp |
Verify with your WEBHOOK_SIGNING_SECRET — NOT your API key. These are different values on the server; ask whoever issued your API key for the matching webhook secret.
import { RelayClient } from '@relay-agent/sdk';
import type { WebhookEvent } from '@relay-agent/sdk';
// In Express, you need the RAW body for signature verification. Mount
// express.raw() (or a similar raw-body parser) on this route specifically.
app.post('/webhooks/relay', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
const signature = req.headers['x-relay-signature'] as string;
if (!RelayClient.verify(raw, process.env.RELAY_WEBHOOK_SECRET!, signature)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(raw) as WebhookEvent;
switch (event.type) {
case 'call.ended':
console.log(`Call ended: ${event.data.reason}`);
console.log(`Cost: ${event.data.cost.totalCents} cents`);
break;
case 'tool.invoked':
console.log(`Tool called: ${event.data.name}`);
break;
}
res.status(200).send('OK');
});Error Handling
The SDK throws typed errors for different HTTP status codes:
import {
BadRequestError, // 400
AuthenticationError, // 401
ForbiddenError, // 403
NotFoundError, // 404
ConflictError, // 409
RateLimitError, // 429
InternalServerError, // 500
} from '@relay-agent/sdk';
try {
await client.agents.get('nonexistent');
} catch (err) {
if (err instanceof NotFoundError) {
console.log(`Agent not found: ${err.message}`);
} else if (err instanceof RateLimitError) {
console.log('Rate limited, retrying...');
}
}Configuration
const client = new RelayClient({
apiKey: 'ra_...', // or set RELAY_AGENT_API_KEY env var
baseUrl: 'https://api.relay-agent.com', // default
timeout: 30000, // request timeout in ms
maxRetries: 2, // retries for 5xx, 429, 408
});License
MIT
