Skip to content

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:

bash
npm install @rymi/node
# or
yarn add @rymi/node
# or
pnpm add @rymi/node

Latest 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.

typescript
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.

typescript
// 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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
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

typescript
const clone = await rymi.agents.clone(agent.id);
console.log(clone.id); // new agent ID, name gets " (Copy)" appended

List calls for an agent

typescript
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.

typescript
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:

typescript
// 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().

typescript
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.

typescript
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.

typescript
// 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.

typescript
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 intelligence

Control a live call

Mutate an in-progress call — end it, or add participants for a warm transfer / conference.

typescript
// 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.

typescript
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.

typescript
// 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 it

See 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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
// 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.

typescript
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).

typescript
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.

typescript
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');
    }
});