npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mtdsdev/ai-telecom

v1.0.1

Published

Node.js SDK for the AI Telecom voice API — place AI phone calls with a phone number and a prompt.

Readme


npm install @mtdsdev/ai-telecom
import { Client } from '@mtdsdev/ai-telecom';

const client = new Client({ apiKey: 'sk_live_...' });

const call = await client.calls.create({
  to: '+84901234567',
  prompt: `
    Bạn là nhân viên tư vấn của MTDS.
    Chào khách, hỏi xem anh chị có quan tâm gói internet không.
  `,
});

console.log(call.id, call.status); // call_Ku3n8pQ7... queued

That is the whole getting-started. No campaign, no contact list, no workflow to build first — a phone number and a prompt is the complete API surface you need.

baseUrl defaults to https://telecom.mtds.vn; pass it only when developing against a local deployment. Requires Node.js 18+ (native fetch).

The TypeScript API uses camelCase (agentId, fromNumber); the HTTP wire format remains snake_case as in the OpenAPI spec.


Calls

// Prompt inline — omit fromNumber → platform uses your first active SIP line
await client.calls.create({ to: '+84901234567', prompt: '...' });

// Pick which number to call from (display number, SIP extension, or trunk name)
await client.calls.create({
  to: '+84901234567',
  prompt: '...',
  fromNumber: '0592014235',
});

await client.calls.create({ to: '+84901234567', agentId: 'agent_9fQz...' });

// Per-call script; keep the agent's voice / KB / config
await client.calls.create({
  to: '+84901234567',
  agentId: 'agent_9fQz...',
  prompt: 'Hôm nay chỉ nói về gói mùa hè…',
});

// Skip scoring / detach the agent's knowledge base for this call
await client.calls.create({
  to: '+84901234567',
  agentId: 'agent_9fQz...',
  evaluation: '',
  knowledgeBaseId: '',
});

// Many numbers at once → returns an array
await client.calls.create({
  to: ['+84901234567', '+84912345678'],
  agentId: 'agent_9fQz...',
});

// Attach your own IDs (schedule in your own system — call create when ready)
await client.calls.create({
  to: '+84901234567',
  prompt: '...',
  metadata: { crm_id: 'C-42' },
});

Prompt resolution order: inline prompt → inline agent → agentId → your default agent.

Empty string "" on evaluation, knowledgeBase, or knowledgeBaseId means explicitly off. Omit the field to inherit from the agent.

Reading results

const call = await client.calls.get('call_Ku3n8pQ7...');

call.status;           // completed | no_answer | failed | …
call.duration;
call.transcript;
call.score;
call.outcome;
call.usage?.costUsd;   // null = pricing not configured, NOT free
call.usage?.latencyMs;

(await client.calls.recordingUrl(call.id)).url;

Listing and filtering

await client.calls.list({ status: 'completed', scoreMin: 70 });
await client.calls.list({ from: '2026-07-01', until: '2026-07-31' });

for await (const call of client.calls.iter({ outcome: 'potential' })) {
  console.log(call.to, call.score);
}

Cancel / wait

await client.calls.cancel('call_Ku3n8pQ7...');
const done = await client.calls.wait('call_Ku3n8pQ7...', { timeout: 600 });

Prefer the call.completed webhook in production.


Agents (optional)

const agent = await client.agents.create({
  name: 'Fibre internet outreach',
  prompt: 'Bạn là nhân viên tư vấn của MTDS...',
  evaluation: 'Chấm 0–100 theo mức độ quan tâm.',
  isDefault: true,
});

await client.calls.create({ to: '+84901234567', agentId: agent.id });
await client.agents.list();
await client.agents.update(agent.id, { prompt: '...' });
await client.agents.delete(agent.id);

Knowledge bases (optional)

const kb = await client.knowledgeBases.create({ name: 'Bảng giá 2026' });
await client.knowledgeBases.documents.create(kb.id, {
  name: 'Gói cước',
  content: 'Gói Cơ bản: hai trăm nghìn đồng một tháng...',
});

await client.knowledgeBases.documents.upload(kb.id, 'bang-gia.pdf');
await client.knowledgeBases.wait(kb.id);

// Shortcut: create + upload + wait
const kb2 = await client.knowledgeBases.createWithUpload('bang-gia.pdf', {
  name: 'Bảng giá 2026',
});

Attach with knowledgeBaseId on an agent or call; pass knowledgeBaseId: '' to detach for one call.


Phone numbers

Read-only; provisioned by an administrator.

for (const number of await client.phoneNumbers.list()) {
  console.log(number.id, number.number, number.trunk, number.quotaRemaining);
}

await client.calls.create({
  to: '+84901234567',
  prompt: '...',
  fromNumber: '0592014235', // or extension "1001" or trunk name
});

Usage

const usage = await client.usage.get();
const usage2 = await client.usage.get({ from: '2026-07-01' });

console.log(usage.total?.calls, usage.total?.minutes);

minutes are actual talk time: duration_sec / 60 per answered call, not rounded up.


Webhooks

import { webhooks, WebhookVerificationError } from '@mtdsdev/ai-telecom';
import express from 'express';

const app = express();
app.post(
  '/hooks/ai-telecom',
  express.raw({ type: '*/*' }),
  (req, res) => {
    try {
      const event = webhooks.verify(
        req.body as Buffer,
        String(req.headers['x-signature'] ?? ''),
        process.env.WEBHOOK_SECRET!,
      );
      console.log(event.event, event.data.id, event.data.score);
      res.sendStatus(200);
    } catch (e) {
      if (e instanceof WebhookVerificationError) return res.sendStatus(400);
      throw e;
    }
  },
);

Always verify the raw body before trusting the payload.


Errors

import {
  AITelecomError,
  AuthenticationError,
  PermissionError,
  NotFoundError,
  ValidationError,
  RateLimitError,
  ServerError,
  APIConnectionError,
} from '@mtdsdev/ai-telecom';

try {
  await client.calls.create({ to: 'not-a-number', prompt: '...' });
} catch (e) {
  if (e instanceof ValidationError) console.log(e.code, e.message);
}

Network errors and 429/5xx are retried with backoff. 401/403/404/400/422 are not.


Configuration

const client = new Client({
  apiKey: 'sk_live_...',
  baseUrl: 'http://localhost:3000',
  timeoutMs: 30_000,
  maxRetries: 2,
});

Versioning

Pinned to /api/v1. Unknown response fields are ignored (forward-compatible).

License

MIT © MTDS