Skip to content

Node.js SDK ​

@rymi/node is the official server-side SDK for Node. It gives you strongly-typed access to the Rymi REST API from JavaScript or TypeScript.

Installation ​

Install the package:

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 installs with it. Depend on @rymi/sdk-types directly only if you're sharing Rymi types across your own packages without pulling in the full client.

Initialization ​

Import the Rymi class and pass your API key. Omit apiKey and the SDK reads RYMI_API_KEY from the environment.

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, and warnings before saving.

typescript
const preview = await rymi.agents.previewStack({
    supported_languages: ['hi-IN', 'en-US'],
});
console.log(preview.blockers, preview.warnings, preview.provider_config);

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. It's 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 allowed to do. These are read-only calls, 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 or 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 the connected carrier, and with carrier credentials, manage it. 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 a custom stack's cost for a given duration before dialing
const est = await rymi.billing.estimate({
    stt_model: 'nova-2',
    llm_model: 'gemini-2.5-flash',
    tts_model: 'gemini-2.5-flash-tts',
    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 });

Campaigns and live leads ​

Create a campaign, launch it, then keep adding people while it runs. See Campaigns and the Campaigns API.

typescript
const { campaign } = await rymi.campaigns.create({
    agent_id: 'agent_123',
    type: 'outbound',
    name: 'Subscriber updates',
    schedule_policy: { start: '09:00', end: '20:00', timezone: 'Asia/Kolkata' },
});
await rymi.campaigns.launch(campaign.id);

// Push a lead from your backend; it's dialed on the next sweep inside the window.
await rymi.campaigns.members.import(campaign.id, {
    contacts: [{ phone: '+919876543210', name: 'Ramesh', consent: { voice: true } }],
});

// Or give a form tool a URL it can post to without an API key (shown once).
const { url } = await rymi.campaigns.intake.set(campaign.id, { assume_voice_consent: true, default_country: 'IN' });

Sharing and API-tool secrets ​

typescript
// Public share link: anyone with the URL can talk to the published agent.
const { link } = await rymi.agents.setShareLink('agent_123', { enabled: true, minutes_limit: 120 });

// Secret for an API tool header, used as {{secrets.RESERVATIONS_API_KEY}}.
await rymi.toolSecrets.set('RESERVATIONS_API_KEY', { value: 'sk_live_...', host: 'api.example.com' });

See Sharing and API Tools.

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