convoso-js
v0.3.0
Published
Unofficial TypeScript SDK for the Convoso API
Downloads
753
Maintainers
Readme
Community project — not affiliated with or endorsed by Convoso.
Why convoso-js?
Building on the Convoso API means wrestling with URL-encoded POST endpoints, manual pagination offsets, and undocumented error codes. convoso-js wraps all of that into a clean, typed interface so you can focus on your integration — not the plumbing.
import { Convoso } from 'convoso-js';
const client = new Convoso({ authToken: process.env.CONVOSO_TOKEN! });
// That's it. Start making calls.
const { results } = await client.leads.search({ list_id: '333' });Feature Overview
Zero Dependencies
Uses native fetch — no bloated dependency tree. Ships at ~3 kB gzipped. Nothing to audit, nothing to conflict.
Fully Typed
Every parameter and response has strict TypeScript types with JSDoc documentation. Full autocomplete in any editor.
Auto-Pagination
Async generators iterate through all results across pages. No manual offset/limit management.
Smart Retry
Exponential backoff with jitter for 429 and 5xx errors. Respects Retry-After headers automatically.
Request & Response Hooks
Plug in logging, metrics, or middleware with onRequest / onResponse callbacks. Async hooks are fully supported.
Complete Coverage
37 endpoints across 16 resources — agents, leads, DNC, campaigns, call logs, SMS opt-out, and more.
Dual Format
Ships ESM + CJS with full .d.ts declarations and source maps. Works everywhere — Node.js, Bun, and Deno.
44 Error Codes
Built-in lookup table for all known Convoso error codes with human-readable descriptions. No more guessing.
Install
npm install convoso-jsyarn add convoso-js
pnpm add convoso-js
bun add convoso-jsRequires Node.js 18+ (native
fetch). Works with Bun and Deno out of the box.
Quick Start
import { Convoso } from 'convoso-js';
const client = new Convoso({
authToken: process.env.CONVOSO_TOKEN!,
});
// Search leads
const { results } = await client.leads.search({ list_id: '333', limit: 100 });
// Insert a lead
await client.leads.insert({
list_id: '333',
phone_number: '5551234567',
first_name: 'John',
last_name: 'Doe',
});
// Iterate all DNC entries with auto-pagination
for await (const entry of client.dnc.searchAll({ campaign_id: '500' })) {
console.log(entry.phone_number);
}
// Monitor agents in real time
const monitor = await client.agentMonitor.search({ campaign_id: '111' });
console.log(`${monitor.agents_ready} agents ready`);Configuration
Every option is optional except authToken:
const client = new Convoso({
// Required — your Convoso API token (injected into every request automatically)
authToken: 'your-api-token',
// Override the base URL (default: https://api.convoso.com/v1)
baseUrl: 'https://api.convoso.com/v1',
// Provide a custom fetch implementation (useful for proxies, testing, or polyfills)
fetch: customFetch,
// Retry 429/5xx responses with exponential backoff + jitter (default: 0 — no retries)
maxRetries: 3,
// Hook called before every request — great for logging or metrics
onRequest(path, params) {
console.log(`→ POST ${path}`);
},
// Hook called after every response — inspect status, body, or inject side effects
onResponse(path, response, data) {
console.log(`← ${path} ${response.status}`);
},
});| Option | Type | Default | Description |
|--------|------|---------|-------------|
| authToken | string | — | Required. API token injected into every request |
| baseUrl | string | https://api.convoso.com/v1 | API base URL |
| fetch | typeof fetch | globalThis.fetch | Custom fetch implementation |
| maxRetries | number | 0 | Max retry attempts for 429/5xx responses |
| onRequest | RequestHook | — | Callback before each request |
| onResponse | ResponseHook | — | Callback after each response |
Resources
All 37 Convoso API endpoints, organized into 16 typed resource classes:
Agent APIs
| Resource | Methods | Paginated |
|----------|---------|:---------:|
| client.agentMonitor | search() logout() | |
| client.agentPerformance | search() | |
| client.agentProductivity | search() | |
Lead Management
| Resource | Methods | Paginated |
|----------|---------|:---------:|
| client.leads | search() insert() update() delete() getRecordings() | searchAll() getRecordingsAll() |
| client.leadPost | insert() | |
| client.leadValidation | search() | |
| client.lists | search() insert() update() delete() | |
Call Operations
| Resource | Methods | Paginated |
|----------|---------|:---------:|
| client.callbacks | search() insert() update() delete() | searchAll() |
| client.callLogs | retrieve() update() | retrieveAll() |
| client.campaigns | search() status() | |
Compliance
| Resource | Methods | Paginated |
|----------|---------|:---------:|
| client.dnc | search() insert() update() delete() | searchAll() |
| client.smsOptOut | search() insert() update() | searchAll() |
Other
| Resource | Methods | Paginated |
|----------|---------|:---------:|
| client.status | search() insert() update() | |
| client.revenue | update() | |
| client.users | search() recordings() | |
| client.userActivity | search() | |
Auto-Pagination
Six endpoints support automatic pagination through async generators. Just iterate — the SDK handles offset management, page fetching, and termination for you.
// Iterate through every lead — pagination handled automatically
for await (const lead of client.leads.searchAll({ list_id: '333', pageSize: 200 })) {
console.log(lead.first_name, lead.phone_number);
}
// Early termination — stops fetching after you break
let count = 0;
for await (const entry of client.dnc.searchAll({ campaign_id: '500' })) {
if (++count >= 50) break;
}
// Collect into an array
const allCallbacks: CallbackRecord[] = [];
for await (const cb of client.callbacks.searchAll()) {
allCallbacks.push(cb);
}| Method | Yields | API Path |
|--------|--------|----------|
| leads.searchAll() | LeadRecord | /leads/search |
| leads.getRecordingsAll() | Recording entry | /leads/get-recordings |
| callbacks.searchAll() | CallbackRecord | /callbacks/search |
| callLogs.retrieveAll() | CallLogRecord | /log/retrieve |
| dnc.searchAll() | DncRecord | /dnc/search |
| smsOptOut.searchAll() | SmsOptOutRecord | /sms-opt-out/search |
Retry Logic
Enable automatic retries for transient failures with exponential backoff and jitter:
const client = new Convoso({
authToken: process.env.CONVOSO_TOKEN!,
maxRetries: 3,
});How it works:
- Retries on status codes:
429,500,502,503,504 - Backoff formula:
min(1000ms × 2^attempt, 30s) + random jitter (0–1s) - Respects
Retry-Afterheaders from429responses (overrides exponential delay) - Non-retryable errors (4xx) are thrown immediately
Request & Response Hooks
Hooks let you observe or log every API call without modifying resource code. Both sync and async hooks are supported.
const client = new Convoso({
authToken: process.env.CONVOSO_TOKEN!,
// Log every outbound request
onRequest(path, params) {
console.log(`→ POST ${path}`, Object.fromEntries(params));
},
// Track response times and status codes
async onResponse(path, response, data) {
await metrics.record({
endpoint: path,
status: response.status,
timestamp: Date.now(),
});
},
});Error Handling
Two typed error classes for distinct failure modes, plus a built-in lookup for 44 Convoso error codes:
import { ConvosoApiError, ConvosoHttpError, getErrorDescription } from 'convoso-js';
try {
await client.leads.insert({ list_id: '999', phone_number: '5551234567' });
} catch (err) {
if (err instanceof ConvosoApiError) {
// Convoso returned { success: false } with an error code
console.error(`Code ${err.code}: ${err.description}`);
//=> "Code 6002: No such list"
} else if (err instanceof ConvosoHttpError) {
// Non-2xx HTTP status (server error, network issue, etc.)
console.error(`HTTP ${err.status}: ${err.statusText}`);
}
}
// Standalone error code lookup
getErrorDescription(6002); //=> "No such list"Error Class Hierarchy
ConvosoError (base)
├── ConvosoApiError — API returned success: false
│ ├── .code — Numeric error code (e.g. 6002)
│ ├── .message — Error message from API
│ ├── .description — Human-readable description from built-in lookup
│ └── .body — Full raw response body
└── ConvosoHttpError — Non-2xx HTTP response
├── .status — HTTP status code (e.g. 500)
├── .statusText — HTTP status text
└── .body — Raw response bodyConvoso Connect
Convoso Connect is the platform's webhook engine — it fires HTTP requests to external systems when call events occur (dispositions, status changes, callbacks, etc.). The SDK provides typed interfaces and a parse utility for building webhook receivers.
import { parseConnectPayload } from 'convoso-js';
import type { ConnectPayload } from 'convoso-js';
// Parse and type-narrow an incoming Convoso Connect webhook
app.post('/webhook', (req, res) => {
const payload = parseConnectPayload(req.body);
// Full autocomplete — all 76+ fields are typed with JSDoc
console.log(payload.phone_number); // Lead's primary phone
console.log(payload.status); // Disposition code (abbreviation)
console.log(payload.call_log_id); // Call event ID
console.log(payload.length_in_sec); // Talk time in seconds
console.log(payload.agent_full_name);// Handling agent
console.log(payload.campaign_name); // Campaign name
res.json({ success: true });
});What's included:
| Export | Description |
|--------|-------------|
| ConnectPayload | Complete webhook payload type (lead + call log + extra fields) |
| ConnectLeadFields | 43 lead-level fields (name, phone, address, etc.) |
| ConnectCallLogFields | 24 call-level fields (duration, disposition, recording URL, etc.) |
| ConnectExtraFields | 12 extra fields (agent info + 10 custom fields) |
| parseConnectPayload() | Type-narrow unknown → ConnectPayload with validation |
| CONNECT_WORKFLOW_EVENTS | 11 workflow trigger types with descriptions |
| CONNECT_WORKFLOW_ACTIONS | 12 workflow action types with descriptions |
Full Convoso Connect documentation: thornebridge.github.io/convoso-js/connect/overview
Exports
Everything you need is exported from the top-level package:
// Client
import { Convoso } from 'convoso-js';
import type { ConvosoOptions } from 'convoso-js';
// Errors
import { ConvosoError, ConvosoApiError, ConvosoHttpError } from 'convoso-js';
import { CONVOSO_ERROR_CODES, getErrorDescription } from 'convoso-js';
import type { ConvosoErrorCode } from 'convoso-js';
// Convoso Connect (webhook types + utility)
import { parseConnectPayload, CONNECT_WORKFLOW_EVENTS, CONNECT_WORKFLOW_ACTIONS } from 'convoso-js';
import type { ConnectPayload, ConnectLeadFields, ConnectCallLogFields } from 'convoso-js';
import type { ConnectWorkflowEvent, ConnectWorkflowAction } from 'convoso-js';
// Request/response types (every resource has full type coverage)
import type { LeadSearchParams, LeadSearchResponse, LeadRecord } from 'convoso-js';
import type { DncInsertParams, CallbackSearchParams, CallLogRetrieveParams } from 'convoso-js';
// ... and every other resource typeArchitecture
convoso-js/
├── src/
│ ├── client.ts Convoso class — composes all 16 resources
│ ├── http.ts HttpClient — fetch wrapper, auth injection, retry, hooks
│ ├── errors.ts ConvosoError → ConvosoApiError / ConvosoHttpError
│ ├── error-codes.ts 44 known Convoso error codes with descriptions
│ ├── connect.ts parseConnectPayload() — webhook payload type narrowing
│ ├── connect-events.ts Workflow event triggers + action type constants
│ ├── index.ts Public API barrel export
│ ├── resources/
│ │ ├── base.ts BaseResource — holds HttpClient reference
│ │ ├── leads.ts LeadsResource (search, insert, update, delete, recordings)
│ │ ├── lists.ts ListsResource (search, insert, update, delete)
│ │ ├── dnc.ts DncResource (search, insert, update, delete)
│ │ ├── callbacks.ts CallbacksResource (search, insert, update, delete)
│ │ ├── call-logs.ts CallLogsResource (retrieve, update)
│ │ ├── campaigns.ts CampaignsResource (search, status)
│ │ ├── agent-monitor.ts AgentMonitorResource (search, logout)
│ │ ├── agent-*.ts AgentPerformance, AgentProductivity
│ │ ├── status.ts StatusResource (search, insert, update)
│ │ ├── revenue.ts RevenueResource (update)
│ │ ├── users.ts UsersResource (search, recordings)
│ │ ├── user-activity.ts UserActivityResource (search)
│ │ ├── lead-post.ts LeadPostResource (insert)
│ │ ├── lead-validation.ts LeadValidationResource (search)
│ │ └── sms-opt-out.ts SmsOptOutResource (search, insert, update)
│ └── types/ Per-resource param/response interfaces with JSDoc
├── tests/ 111 tests, 99% coverage (vitest + v8)
├── examples/ 3 runnable integration examples
├── docs/ 17 API reference markdown files (source of truth)
└── docs-site/ VitePress documentation siteDesign Decisions
- No response normalization — responses are returned exactly as Convoso sends them
auth_tokeninjected automatically — never appears in method parameters- Null/undefined stripping — cleaned from params before URL encoding
- Dual format — ESM + CJS with full
.d.tstype declarations and source maps - All POST — every Convoso endpoint uses POST with
application/x-www-form-urlencoded
Documentation
Full documentation at thornebridge.github.io/convoso-js:
| Guide | Description | |-------|-------------| | Getting Started | Install, first API call, resource overview | | Configuration | All client options explained | | Error Handling | Error classes + full 44-code reference table | | Retry & Hooks | Backoff strategy, Retry-After, middleware patterns | | Auto-Pagination | Async generators for bulk data operations | | Examples | Lead import, agent dashboard, DNC sync | | Convoso Connect | Webhook engine — adaptors, workflows, integrations (7 pages) | | API Reference | Complete endpoint documentation (17 pages) |
Examples
The examples/ directory includes three runnable integration patterns:
Lead Import — Bulk insertion with error handling
import { Convoso, ConvosoApiError } from 'convoso-js';
const client = new Convoso({
authToken: process.env.CONVOSO_TOKEN!,
maxRetries: 3,
});
const leads = [
{ list_id: '333', phone_number: '5551234567', first_name: 'Alice' },
{ list_id: '333', phone_number: '5559876543', first_name: 'Bob' },
];
for (const lead of leads) {
try {
await client.leads.insert(lead);
} catch (err) {
if (err instanceof ConvosoApiError) {
console.error(`Failed: ${lead.phone_number} — ${err.description}`);
}
}
}Agent Dashboard — Real-time monitoring
const monitor = await client.agentMonitor.search({ campaign_id: '111' });
const performance = await client.agentPerformance.search();
console.log(`Agents ready: ${monitor.agents_ready}`);
console.log(`Agents on call: ${monitor.agents_on_call}`);DNC Sync — Auto-paginated compliance
const dncNumbers: string[] = [];
for await (const entry of client.dnc.searchAll({ campaign_id: '500' })) {
dncNumbers.push(entry.phone_number);
}
console.log(`Synced ${dncNumbers.length} DNC entries`);Testing
111 tests with 99% code coverage, tested across Node.js 18, 20, and 22:
npm test # Run all tests
npm run test:watch # Watch mode
npm run coverage # Generate v8 coverage reportTest suite covers:
- HTTP client behavior (auth injection, parameter encoding, error detection)
- Retry logic (exponential backoff, jitter, Retry-After headers)
- Auto-pagination (all 6 async generators)
- Request/response hooks (sync and async)
- Error classes and 44 error code lookups
- All 16 resource classes
CI/CD
Automated workflows via GitHub Actions:
| Workflow | Trigger | What it does |
|----------|---------|--------------|
| CI | Push / PR to main | Lint, format check, typecheck, build, and test on Node 18, 20, 22 |
| Docs | Push to main | Builds VitePress site and deploys to GitHub Pages |
| Release | Tag v* | Full CI suite → publish to npm with provenance → create GitHub Release |
Contributing
git clone https://github.com/thornebridge/convoso-js.git
cd convoso-js
npm installnpm run typecheck # tsc --noEmit
npm run build # tsup → ESM + CJS + .d.ts
npm test # vitest (111 tests)
npm run lint # ESLint
npm run format # Prettier
npm run docs:dev # VitePress dev serverContributions welcome — please open an issue first to discuss larger changes. See CONTRIBUTING.md for details.
Thornebridge Open Source Policy
Thornebridge does not publish open source projects with the intention of maintaining them indefinitely. Our primary focus is building software for our clients and our own products.
However — if a project we've released is still being used by the community, we will continue to keep it updated. As our team makes additions, improvements, or updates internally, those changes will be reflected in the open source release. We believe that if people depend on our work, they deserve a working, up-to-date package.
In short: we don't promise long-term maintenance roadmaps, but we don't abandon working software either.
