Skip to content

osm-API/osmtalk-examples

Repository files navigation

OsmTalk Examples

Idiot-proof, runnable examples for building voice AI agents with OsmTalk and the @osmapi/osmtalk-sdk.

Each example is a standalone Node + TypeScript project. Clone, fill in a .env, run two commands. Real code, real APIs, no fake/mocked anything.

CI npm


🚀 60-second speedrun

Get a real voice agent calling your phone in under a minute.

# 1. Clone + cd
git clone https://github.com/osm-API/osmtalk-examples.git
cd osmtalk-examples/01-personalized-call

# 2. Config — get OSMTALK_API_KEY from app.osmtalk.com → Settings → API Keys
cp .env.example .env
$EDITOR .env

# 3. Install + verify everything before paying for a call
npm install
npm run check          # validates env + auth + IDs + provider health

# 4. Place a real call (your phone rings ~3s later)
npm start

If anything errors, npm run check tells you exactly what to fix. It's designed so you never burn credits or ring phones because of a typo.


🎯 Which example do I want?

If you want to… Use Cost
Iterate on your agent's prompt without spending money 04-simulate-before-going-live/ Free
Place one outbound call from your code 01-personalized-call/ ~₹3-10 per call
Run an outbound campaign from a CSV of leads 02-bulk-campaign/ ~₹3-10 × leads
Receive call.completed events into your own server 03-webhook-receiver/ Free (just receives events)

Recommended order for first-time users: 04 (simulate) → 01 (real call) → 02 (scale up) → 03 (close the loop).


🧠 Understanding agent config (VAD, LLM, STT, TTS, all the knobs)

If you want the deep dive into "what does this setting actually do?":

📖 AGENT_CONFIG.md — the full reference.

Covers:

  1. The pipeline (how a call flows through STT → LLM → TTS)
  2. VAD settings (when is the user talking?) — section 6
  3. Smart Turn (when is the user DONE talking?) — section 7
  4. LLM tuning (model choice, reasoning, tokens)
  5. STT tuning (provider, language, boosted keywords)
  6. TTS tuning (voice, speed, SSML)
  7. Interruptions, muting, idle detection
  8. Recording, transcripts, analysis
  9. Tools (HTTP, client, MCP)
  10. Tuning recipes for common scenarios (fastest, most natural, Indic, noisy line, elderly callers, etc.)

Examples link to specific sections of this file where relevant.


📦 The 4 examples

01 — Personalized outbound call

01-personalized-call/

Place one phone call. The agent addresses the recipient by name and references their account, using dynamicVariables to substitute placeholders in the agent's prompt at call time.

await client.calls.outbound({
  agentId, phoneNumberId, destination,
  dynamicVariables: { first_name: "Arjun", company: "Acme" },
});

02 — Bulk campaign from CSV

02-bulk-campaign/

Scale to thousands of leads via CSV. Demonstrates client.campaigns.* with dialing-window enforcement, retry policy, concurrency limit, and auto-pause on Ctrl+C.

03 — Verified webhook receiver

03-webhook-receiver/

Express server that verifies HMAC signatures using verifyWebhookSignature(rawBody, header, secret) from the SDK. Includes in-memory replay-dedup so duplicate retries don't double-process events.

04 — Simulate before going live

04-simulate-before-going-live/

Run scripted conversations through client.eval.simulate(). No phone rings, no credits charged. The fastest way to iterate on system prompts.


🛠️ The mental model

┌─────────────────────┐     ┌─────────────────────┐     ┌─────────────────────┐
│ 04 Simulate         │     │ 01 Personalized     │     │ 02 Bulk Campaign    │
│ (free iteration)    │     │ Call                │     │ From CSV            │
│                     │     │                     │     │                     │
│ client.eval         │ ──► │ client.calls        │ ──► │ client.campaigns    │
│ .simulate()         │     │ .outbound()         │     │ .create() + start() │
│                     │     │                     │     │                     │
│ → text only         │     │ → 1 real call       │     │ → many real calls   │
└─────────────────────┘     └──────────┬──────────┘     └──────────┬──────────┘
   (prompt iteration)                  │                            │
                                       └──── results back via ──────┘
                                             webhook (Example 03)
                                                    │
                                                    ▼
                                          ┌─────────────────────┐
                                          │ 03 Webhook          │
                                          │ Receiver            │
                                          │                     │
                                          │ verifyWebhookSig    │
                                          │ ← call.completed    │
                                          │ ← call.failed       │
                                          └─────────────────────┘

✅ Prerequisites (for all examples)

  • Node.js 20+ (every example enforces this via engines.node)
  • An OsmTalk account → https://app.osmtalk.com (free signup)
  • An API key — Settings → API Keys (the calls scope is enough for Examples 01/02/04; Example 03 needs only a webhook secret)
  • At least ₹50 of credit if you want to run Examples 01/02 (test calls are cheap — ~₹3-10 each). Example 04 simulation is free.

🏃 Run any example

Every example has the same 3-step recipe:

cd osmtalk-examples/<example-folder>
cp .env.example .env          # then edit it with real values
npm install
npm run check                 # PREFLIGHT — verify before running anything real
npm start                     # the actual thing

The npm run check preflight is the killer feature. It catches:

  • Missing/placeholder env vars
  • Wrong-shape values (e.g. agent ID not starting with agent_)
  • API key auth failures
  • Non-existent agent / phone number IDs (uses SDK to verify they exist in your org)
  • Down/degraded provider warnings
  • (Example 02) CSV format issues + phone-number E.164 validation
  • (Example 03) Weak webhook secret warnings

So npm start only fails for actual runtime issues, not config typos.


❓ FAQ

Q: I get HTTP 401 "Invalid or revoked API key" — what now? A: Regenerate at Settings → API Keys, tick the calls scope, paste the new value into .env. Older keys may have been issued before the API accepted osm_ tokens on main routes — anything you generate today works.

Q: My agent's prompt has {{Client Name}} (with a space) and the SDK errors with Invalid dynamicVariables A: Old API rejected spaces in keys. Updated May 2026 — keys accept letters, digits, underscores, hyphens, dots, and single inline spaces. If you still see this error, your API server is on an older commit; tell ops to redeploy.

Q: How do I find my agent ID / phone number ID without clicking around the dashboard? A: Use the SDK:

console.log(await client.agents.list());        // agent IDs + names
console.log(await client.phoneNumbers.list());  // number IDs + E.164

There's a copy-paste version in Example 01's README under "Discover IDs via the SDK".

Q: My agent uses gpt-5.4-nano. Will it work? A: Yes — the bot auto-routes GPT-5 / o-series models to OpenAI's /v1/responses API since chat/completions rejects them with function tools. You don't have to configure anything. See AGENT_CONFIG.md section 3.

Q: My phone rings but the agent never speaks — what happened? A: Almost always a TTS provider outage. Run client.platform.getModelHealth() — if ElevenLabs or Deepgram is degraded/down, swap the agent's TTS provider in the dashboard temporarily. The preflight in npm run check warns about this before you place the call.

Q: I want the lowest possible latency. What should I configure? A: Groq Llama-3.3-70b + Deepgram nova-3 + Deepgram aura-2 TTS. ~700ms TTFB total. See AGENT_CONFIG.md → Tuning recipes.

Q: How do I tune VAD so the bot stops interrupting users mid-sentence? A: Raise vadStopSecs to 0.4-0.6. Full VAD guide: AGENT_CONFIG.md → VAD settings.

Q: Can I test without spending money? A: Yes — Example 04. client.eval.simulate() runs the LLM portion of the conversation only. No audio, no STT/TTS, no phone, ₹0 spent.

Q: My webhook signature check always fails. A: Almost always: you're using express.json() instead of express.raw() on the webhook route. The signature is over the raw bytes; re-serialized JSON won't match. Example 03 has this set up correctly — copy the pattern.

Q: Can I publish these examples as my own packages? A: They're licensed MIT, so yes. But they're marked "private": true in package.json to prevent accidental npm publish — flip that off if you actually want to publish.

Q: How do I add a "press 1 for sales, 2 for support" menu? A: Configure DTMF routes in the dashboard under your agent → Advanced → Keypad. The bot routes the digit to a sub-agent or human phone number without involving the LLM (no extra latency). Set enableDtmf: true in advanced settings.


🔌 SDK features the examples lean on

Current pin: @osmapi/osmtalk-sdk@^0.5.0

  • dynamicVariables — per-call prompt personalization (Example 01)
  • assistantOverride — per-call agent setting overrides (Example 01)
  • Idempotency-Key — safe re-runs without double-dialing (Example 01)
  • Auto-retry on 5xx/429 — built in since 0.3.0, with Retry-After honored
  • client.calls.waitUntilEnded() — one-line poll-to-completion (Example 01)
  • client.calls.list({ filters, limit }) — paginated envelope (0.4.0); now also supports campaignId + failureReason filters (0.5.0) for triage
  • client.phoneNumbers.list() — discover org-owned numbers (0.4.0)
  • client.eval.simulate() — test prompts without spending money (Example 04)
  • client.campaigns.* lifecycle — start/pause/resume/stop (Example 02)
  • describeFailureReason(reason) — human-readable failure explainer with title / cause / likelyBlame / whatToTry / retryable (Examples 02 + 03)
  • isRetryableFailure(reason) — closed-set check for the kinds of failures a campaign worker should retry automatically (Examples 02 + 03)
  • callConnected(record) — heuristic for "was this a real conversation" vs phantom call. Server-side equivalent is CallRecord.didConnect.
  • verifyWebhookSignature(rawBody, header, secret) — HMAC verification (Example 03)
  • AbortSignal — cancellation on every method
  • OsmtalkError.isRetryable / .isClientError — typed error branching

📊 Understanding failure reasons

A call that ends without a real conversation gets status: "failed" plus a machine-readable failureReason — one of:

Reason What it means Likely blame Retryable?
no_audio_output Bot couldn't speak (TTS connection failed) Platform
no_audio_either_direction Nobody produced audio Environment
idle_timeout Pipeline hit the configured idle window Caller
provider_circuit_open Bot tripped its 3-errors-in-60s breaker Platform
sip_no_answer Carrier reported no answer / busy Carrier
sip_rejected Carrier rejected the call Carrier
bot_startup_failed Bot didn't ready within startup window Platform
caller_hung_up_silently Caller answered + hung up immediately Caller
stale_sweep Bot died mid-call, sweeper cleaned up Platform
unknown Could not classify Unknown

describeFailureReason() returns the same {title, cause, whatToTry} strings the dashboard banner uses and the docs site lists. Use it in your own logs / Slack alerts so testers see consistent wording everywhere.


CI

Every push and PR runs tsc --noEmit on each example against the live published SDK. Catches the next class of bugs where an SDK upgrade silently breaks an example.

Help

  • AGENT_CONFIG.md — every knob explained
  • CONTRIBUTING.md — how to add a 5th example or fix a bug
  • CHANGELOG.md — what changed when
  • Open an issue for anything unclear

License

MIT — copy, modify, ship.

About

Idiot-proof, runnable examples for the @osmapi/osmtalk-sdk — personalized calls, bulk campaigns, verified webhooks.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors