Node.js SDK
The official @rymi/node package provides convenient, strongly-typed access to the Rymi REST API from any server-side JavaScript or TypeScript application.
Installation
Install the package using your preferred package manager:
npm install @rymi/node
# or
yarn add @rymi/node
# or
pnpm add @rymi/nodeLatest release: @rymi/node on npm. The package ships with full TypeScript types — request/response shapes come from @rymi/sdk-types, a dependency that's installed automatically. You only need to depend on @rymi/sdk-types directly if you're sharing Rymi types across your own packages without pulling in the full client.
Initialization
Import the Rymi class and initialize it with your API key. If you omit the apiKey configuration, the SDK will automatically look for a RYMI_API_KEY environment variable.
import { Rymi } from '@rymi/node';
// Initialize with an explicit API Key:
const rymi = new Rymi({ apiKey: 'rymi_your_secret_key' });
// OR rely on process.env.RYMI_API_KEY:
const rymiEnv = new Rymi();Agents
Create, list, and manage your AI voice personas.
// Create a new conversational Agent
const agent = await rymi.agents.create({
name: 'Customer Support',
system_prompt: 'You are a helpful customer support assistant for Acme Corp.',
voice: 'Aoede',
language: 'en-US'
});
console.log(agent.id);
// List all configured agents
const { agents } = await rymi.agents.list();Multi-language agents & model stack
create and update accept the full configuration surface. Set supported_languages for a multi-language agent, and optionally pin the STT/LLM/TTS stack, per-channel fallbacks, and Enterprise self-hosted endpoints. provider_config is server-derived and is not accepted as input.
const bilingual = await rymi.agents.create({
name: 'Ganga Sahayak',
language: 'hi-IN',
supported_languages: ['hi-IN', 'en-US'],
llm_provider: 'gemini',
llm_model: 'gemini-2.5-flash',
// optional fallbacks + explicit STT/TTS
tts_provider: 'sarvam',
llm_fallback_provider: 'openai',
llm_fallback_model: 'gpt-4o-mini',
});Preview the model stack
Resolve the per-language stack (STT/LLM/TTS), blockers, warnings, and any required role upgrades before saving. Note: the concierge (realtime) role is not supported by this endpoint.
const preview = await rymi.agents.previewStack({
supported_languages: ['hi-IN', 'en-US'],
agent_role: 'operator',
});
console.log(preview.blockers, preview.warnings, preview.stacks);Knowledge sources (RAG)
Attach searchable context to an agent from raw text or a URL.
await rymi.agents.addKnowledgeSource(agent.id, {
kind: 'text',
title: 'Refund policy',
text: 'Refunds are processed within 5 business days...',
});
const { sources } = await rymi.agents.listKnowledgeSources(agent.id);
await rymi.agents.deleteKnowledgeSource(agent.id, sources[0].id);Configuration history
Audit recorded config changes and revert any single one — a reversible undo, not a full rollback.
const { changes } = await rymi.agents.listChanges(agent.id);
await rymi.agents.undoChange(agent.id, changes[0].change_id);Evaluations
Run the agent's evaluation suite and read per-scenario scores.
const run = await rymi.agents.runEvals(agent.id, { mode: 'synthetic' });
const { runs } = await rymi.agents.listEvalRuns(agent.id) as any;
const detail = await rymi.agents.getEvalRun(agent.id, run.id ?? runs[0].id);Clone an agent
const clone = await rymi.agents.clone(agent.id);
console.log(clone.id); // new agent ID, name gets " (Copy)" appendedList calls for an agent
const { calls, total } = await rymi.agents.listCalls(agent.id, { limit: 20 });Validate before publishing
Check whether an agent's config is ready to go live. Returns a validation report without changing anything.
const report = await rymi.agents.validatePublish({ agent_id: agent.id });
if (!report.valid) {
console.error(report.errors);
}Capability discovery
Before showing an editor or writing automation, query what the connected tenant is actually allowed to do. These are read-only calls and safe to chain at startup:
// Available models and voices for this tenant
const { models, voices } = await rymi.agents.llmOptions();
// Full config for an agent, including runtime-affecting fields
const detail = await rymi.agents.retrieve(agent.id);
// Dry-run a change-set against the field registry — does not save
const dryRun = await rymi.agents.applyChanges({
currentConfig: detail,
changes: [{ key: 'voice', value: 'Charon' }],
mode: 'edit',
});
// Whether the agent passes every publish requirement
const report = await rymi.agents.validatePublish({ agent_id: agent.id });When a call returns errors, the code field is a machine identifier you can branch on. Use the codes for routing logic; render the human-readable message for display.
Apply a structured change-set
Validate and resolve a flat key/value diff against the field registry. Does not persist — follow up with update().
const result = await rymi.agents.applyChanges({
currentConfig: { name: 'Support', voice: 'Aoede' },
changes: [{ key: 'voice', value: 'Charon' }],
mode: 'edit',
});
// result.config has the merged config; result.applied lists what changed
await rymi.agents.update(agent.id, result.config);Enrich company from website
Generate a company description for the agent's persona using AI + Google Search grounding.
const info = await rymi.agents.enrichCompany({
companyName: 'Acme Corp',
websiteUrl: 'https://acme.example.com',
});
if (info.enriched) {
console.log(info.companyDescription);
}Calls
Create voice call sessions with one or more participants. Each call maps to a single room that can include WebRTC (browser) and PSTN (phone) participants.
// Start an outbound PSTN call
const call = await rymi.calls.create({
agent_id: agent.id,
participants: [
{
transport: 'pstn',
identity: '+15551234567',
from_number: '+15559876543'
}
],
metadata: {
customer_name: 'Alice',
order_status: 'shipped'
}
});
console.log(`Call queued with ID: ${call.id}`);
console.log(`Room: ${call.room_name}`);
// Start a WebRTC call (returns a LiveKit token)
const webCall = await rymi.calls.create({
agent_id: agent.id,
participants: [
{ transport: 'webrtc', identity: 'browser-user-1' }
]
});
// Access the participant's LiveKit credentials
const participant = webCall.participants[0];
console.log(`LiveKit URL: ${participant.access?.url}`);
console.log(`Token: ${participant.access?.token}`);
// Retrieve transcript and metadata after the call ends
const details = await rymi.calls.retrieve(call.id);
const transcript = await rymi.calls.transcript(call.id);Observe calls
Read-only helpers for browsing and analysing calls — none of these place a call.
const { calls } = await rymi.calls.list({ limit: 20, status: 'completed' });
const live = await rymi.calls.active();
const summary = await rymi.calls.summary(call.id);
const recording = await rymi.calls.recording(call.id); // playback metadata
const stats = await rymi.calls.queueStats();
await rymi.calls.reprocess(call.id); // re-run post-call intelligenceControl a live call
Mutate an in-progress call — end it, or add participants for a warm transfer / conference.
// Add one or more participants to a call already in progress
await rymi.calls.addParticipants(call.id, {
participants: [{ transport: 'pstn', identity: '+15557654321', from_number: '+15559876543' }]
});
// End the call
await rymi.calls.end(call.id);Batch Calls
Queue up to 500 outbound PSTN recipients in one request.
const batch = await rymi.calls.batch({
agent_id: agent.id,
to: ['+15551234567', '+15557654321'],
from_number: '+15559876543',
variables: { batch_label: 'renewal-q2' }
});
console.log(batch.batch_id, batch.queued);Do-Not-Call (DNC)
Manage the Do-Not-Call registry. Numbers are normalized to E.164 server-side on both read and write, so any input format is accepted. Outbound calls to blocked numbers are refused.
// Add a single number
await rymi.dnc.add({ phone_number: '+15551234567', reason: 'customer opt-out' });
// Add up to 1000 at once — invalid numbers are skipped and returned
const result = await rymi.dnc.addBatch({ phone_numbers: ['+15551234567', '+15557654321'] });
console.log(result.count, result.invalid_count, result.invalid);
// Check up to 500 numbers without adding them (read-only)
const { results, blocked_count } = await rymi.dnc.check({ phone_numbers: ['+15551234567'] });
// List and remove
const { dnc_entries, total } = await rymi.dnc.list({ limit: 50 });
await rymi.dnc.remove('+15551234567'); // re-enables outbound to itSee the Compliance API for the underlying REST endpoints.
Phone Numbers
Register carrier-owned BYOC numbers, list registered numbers, and attach them to agents. Rymi does not purchase phone numbers; provision numbers in your carrier account first.
await rymi.numbers.register('+15559876543');
const { numbers } = await rymi.numbers.list();
await rymi.numbers.attach(numbers[0].number, agent.id);
// Detach + remove a number from the account
await rymi.numbers.remove(numbers[0].number);Telephony
Inspect — and, with carrier credentials, manage — the connected carrier. Most teams connect a carrier once from the dashboard; the SDK methods exist for programmatic provisioning.
const status = await rymi.telephony.status(); // { connected, provider, account_name, account_country }
const { numbers } = await rymi.telephony.numbers();
// Connect / disconnect a carrier (requires carrier credentials)
await rymi.telephony.connect({ provider: 'twilio', auth_id: 'AC...', auth_token: '...' });
await rymi.telephony.disconnect();Publishable keys
Manage browser-safe publishable keys for WebRTC calls. list returns prefixes and scoping only — never full secrets. create returns the full key once, at creation time; store it then.
const { keys } = await rymi.keys.listPublishable();
// Create a key scoped to a single agent. The full `key` is only returned here.
const created = await rymi.keys.createPublishable({
agent_id: agent.id,
label: 'Marketing site widget',
allowed_channels: ['web'],
default_from_number: '+15559876543',
});
console.log(created.key); // sb_publishable_... — store now, not retrievable later
await rymi.keys.revokePublishable(created.id);Server-side only
Key creation and revocation require your secret key, so do them from a backend — never ship secret keys to the browser. (For the same reason, these aren't exposed over MCP.)
Usage
Get a lane-aware usage summary. Voice balance is reported in minutes (the customer-facing unit), not dollars.
const usage = await rymi.billing.usageSummary();
console.log(usage.voice_runtime.remaining_minutes, usage.voice_runtime.status);
console.log(usage.studio_ai?.used_units, usage.post_call_intelligence?.status);Cost estimate & spend controls
Estimate a call's cost before placing it, and configure auto-recharge and spend alerts. These are spend-management settings, so they are denominated in USD.
// Estimate cost for a role/duration before dialing
const est = await rymi.billing.estimate({ tier: 'operator', duration_seconds: 300 });
// Top up automatically when the balance drops below a threshold
await rymi.billing.setAutoRecharge({ enabled: true, pack_usd: 50, threshold_usd: 10 });
// Get notified at spend thresholds or low balance
await rymi.billing.setAlerts({ thresholds_usd: [25, 50], low_balance_pct: 15, email_enabled: true });Templates
List published agent templates and use a template's defaults to seed a new agent.
const { templates } = await rymi.templates.list();
const starter = templates.find(t => t.label === 'Healthcare Receptionist');
if (starter) {
await rymi.agents.create({ name: 'Front Desk', ...starter.defaults });
}Managing webhook endpoints
Register and maintain the endpoints Rymi delivers events to. Each endpoint has its own signing secret — keep it to verify deliveries (see below).
const created = await rymi.webhooks.create({
url: 'https://api.example.com/rymi-webhook',
events: ['call.completed', 'call.intelligence.ready'],
secret: process.env.RYMI_WEBHOOK_SECRET!,
alert_email: 'oncall@example.com',
});
const { webhooks } = await rymi.webhooks.list();
await rymi.webhooks.update(webhooks[0].id, { events: ['call.intelligence.ready'] });
await rymi.webhooks.delete(webhooks[0].id);See the Webhooks API for the full event catalog.
Verifying Webhooks
When your application receives asynchronous events (like call.intelligence.ready), you must cryptographically verify the webhook signature to protect against replay attacks and spoofing.
The Node SDK includes a built-in helper to securely validate the X-Rymi-Signature HMAC.
import express from 'express';
import { Rymi } from '@rymi/node';
const app = express();
const rymi = new Rymi();
// Ensure you parse the body as raw TEXT or BUFFER for accurate HMAC verification
app.post('/webhook', express.text({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-rymi-signature'] as string;
const timestamp = req.headers['x-rymi-timestamp'] as string;
const payload = req.body; // Raw JSON string
try {
rymi.webhooks.verifySignature(
payload,
signature,
timestamp,
process.env.RYMI_WEBHOOK_SECRET!
);
console.log('Webhook authenticated successfully!');
// Process webhook event safely...
const event = JSON.parse(payload);
if (event.type === 'call.intelligence.ready') {
console.log('Call intelligence:', event.data.call_id, event.data.summary);
}
res.status(200).send('OK');
} catch (error) {
console.error('Invalid webhook signature!', error);
res.status(401).send('Unauthorized');
}
});
