Skip to Content
SDKsNode.js

Node.js SDK

A modern JavaScript/TypeScript client for the IdentityCall API with full async/await support.

Installation

npm install node-fetch form-data # With TypeScript npm install node-fetch form-data @types/node-fetch

Quick Start

import { IdentityCallClient } from './identitycall.js'; const client = new IdentityCallClient(); // List recordings const result = await client.recordings.list(); result.data.forEach(recording => { console.log(`${recording.id}: ${recording.name}`); });

Complete Client Implementation

Create a file called identitycall.js (or identitycall.ts for TypeScript):

identitycall.js
import fetch from 'node-fetch'; import FormData from 'form-data'; import fs from 'fs'; // Error classes export class IdentityCallError extends Error { constructor(message, statusCode = null, response = null) { super(message); this.name = 'IdentityCallError'; this.statusCode = statusCode; this.response = response; } } export class AuthenticationError extends IdentityCallError { constructor(message, ...args) { super(message, ...args); this.name = 'AuthenticationError'; } } export class PermissionError extends IdentityCallError { constructor(message, ...args) { super(message, ...args); this.name = 'PermissionError'; } } export class NotFoundError extends IdentityCallError { constructor(message, ...args) { super(message, ...args); this.name = 'NotFoundError'; } } export class ValidationError extends IdentityCallError { constructor(message, ...args) { super(message, ...args); this.name = 'ValidationError'; } } export class RateLimitError extends IdentityCallError { constructor(message, retryAfter = 60, ...args) { super(message, ...args); this.name = 'RateLimitError'; this.retryAfter = retryAfter; } } // Recordings resource class RecordingsResource { constructor(client) { this._client = client; } async list(page = 1, perPage = 20) { return this._client._request('GET', '/recordings', { params: { page, per_page: perPage } }); } async create(file, options = {}) { const formData = new FormData(); if (typeof file === 'string') { formData.append('file', fs.createReadStream(file)); } else { formData.append('file', file, options.filename || 'recording.mp3'); } formData.append('language', options.language || 'en'); if (options.name) { formData.append('name', options.name); } return this._client._request('POST', '/recordings', { body: formData, headers: formData.getHeaders() }); } async get(recordingId) { return this._client._request('GET', `/recordings/${recordingId}`); } async update(recordingId, data) { return this._client._request('PATCH', `/recordings/${recordingId}`, { json: data }); } async delete(recordingId) { return this._client._request('DELETE', `/recordings/${recordingId}`); } async transcription(recordingId) { return this._client._request('GET', `/recordings/${recordingId}/transcription`); } async results(recordingId) { return this._client._request('GET', `/recordings/${recordingId}/results`); } async summary(recordingId) { return this._client._request('GET', `/recordings/${recordingId}/summary`); } async waitForCompletion(recordingId, timeout = 600000, pollInterval = 5000) { const startTime = Date.now(); while (true) { const result = await this.get(recordingId); const recording = result.data; if (recording.status === 'completed') { return recording; } if (recording.status === 'failed') { throw new IdentityCallError('Transcription failed'); } if (Date.now() - startTime > timeout) { throw new Error('Transcription timed out'); } await new Promise(r => setTimeout(r, pollInterval)); } } } // Main client export class IdentityCallClient { static DEFAULT_BASE_URL = 'https://api.identitycall.com/api/v1/public'; static MAX_RETRIES = 3; constructor(options = {}) { this.apiKey = options.apiKey || process.env.IDENTITYCALL_API_KEY; if (!this.apiKey) { throw new Error('API key is required. Set IDENTITYCALL_API_KEY or pass apiKey option.'); } this.baseUrl = options.baseUrl || IdentityCallClient.DEFAULT_BASE_URL; this.timeout = options.timeout || 30000; // Resources this.recordings = new RecordingsResource(this); } async _request(method, path, options = {}) { const url = new URL(path, this.baseUrl); if (options.params) { Object.entries(options.params).forEach(([key, value]) => { url.searchParams.append(key, value); }); } const headers = { 'Authorization': `Bearer ${this.apiKey}`, 'Accept': 'application/json', ...options.headers }; let body = options.body; if (options.json) { headers['Content-Type'] = 'application/json'; body = JSON.stringify(options.json); } for (let attempt = 0; attempt < IdentityCallClient.MAX_RETRIES; attempt++) { try { const response = await fetch(url.toString(), { method, headers, body, timeout: this.timeout }); return await this._handleResponse(response); } catch (error) { if (error instanceof RateLimitError && attempt < IdentityCallClient.MAX_RETRIES - 1) { await new Promise(r => setTimeout(r, error.retryAfter * 1000)); continue; } if (error instanceof IdentityCallError && error.statusCode >= 500 && attempt < IdentityCallClient.MAX_RETRIES - 1) { await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000)); continue; } throw error; } } } async _handleResponse(response) { if (response.status === 204) { return null; } let data; try { data = await response.json(); } catch { data = {}; } if (response.ok) { return data; } const errorMessage = data.error || 'Unknown error'; switch (response.status) { case 401: throw new AuthenticationError(errorMessage, response.status, data); case 403: throw new PermissionError(errorMessage, response.status, data); case 404: throw new NotFoundError(errorMessage, response.status, data); case 422: throw new ValidationError(errorMessage, response.status, data); case 429: throw new RateLimitError(errorMessage, data.retry_after || 60, response.status, data); default: throw new IdentityCallError(errorMessage, response.status, data); } } } export default IdentityCallClient;

TypeScript Version

identitycall.ts
import fetch, { Response } from 'node-fetch'; import FormData from 'form-data'; import fs from 'fs'; // Types interface Recording { id: number; name: string | null; status: 'pending' | 'processing' | 'completed' | 'failed'; language: string; duration_ms: number | null; created_at: string; updated_at: string; } interface Transcription { full_text: string; dialogues: Dialogue[]; } interface Dialogue { position: number; start_ms: number; end_ms: number; speaker: string; text: string; emotion: Record<string, number>; } interface PaginatedResponse<T> { data: T[]; meta: { current_page: number; per_page: number; total_pages: number; total_count: number; }; } interface SingleResponse<T> { data: T; } interface ClientOptions { apiKey?: string; baseUrl?: string; timeout?: number; } interface CreateOptions { filename?: string; language?: string; name?: string; } // Client implementation with types export class IdentityCallClient { private apiKey: string; private baseUrl: string; private timeout: number; public recordings: RecordingsResource; constructor(options: ClientOptions = {}) { this.apiKey = options.apiKey || process.env.IDENTITYCALL_API_KEY || ''; if (!this.apiKey) { throw new Error('API key is required'); } this.baseUrl = options.baseUrl || 'https://api.identitycall.com/api/v1/public'; this.timeout = options.timeout || 30000; this.recordings = new RecordingsResource(this); } async _request<T>(method: string, path: string, options: any = {}): Promise<T> { // Implementation same as JS version // Returns typed response } } class RecordingsResource { constructor(private client: IdentityCallClient) {} async list(page = 1, perPage = 20): Promise<PaginatedResponse<Recording>> { return this.client._request('GET', '/recordings', { params: { page, per_page: perPage } }); } async get(id: number): Promise<SingleResponse<Recording>> { return this.client._request('GET', `/recordings/${id}`); } async transcription(id: number): Promise<SingleResponse<Transcription>> { return this.client._request('GET', `/recordings/${id}/transcription`); } }

Usage Examples

List Recordings

import { IdentityCallClient } from './identitycall.js'; const client = new IdentityCallClient(); // Get first page const result = await client.recordings.list(); result.data.forEach(recording => { console.log(`[${recording.id}] ${recording.name} - ${recording.status}`); }); // With pagination const page2 = await client.recordings.list(2, 50); console.log(`Page ${page2.meta.current_page} of ${page2.meta.total_pages}`);

Upload Recording

// From file path const result = await client.recordings.create('./call.mp3', { language: 'en', name: 'Customer Support Call' }); console.log(`Uploaded! ID: ${result.data.id}`); // From stream import fs from 'fs'; const stream = fs.createReadStream('./call.mp3'); const result2 = await client.recordings.create(stream, { filename: 'call.mp3', language: 'en' });

Wait for Transcription

// Upload and wait const uploadResult = await client.recordings.create('./call.mp3'); const recordingId = uploadResult.data.id; console.log('Waiting for transcription...'); const recording = await client.recordings.waitForCompletion(recordingId); console.log(`Completed in ${recording.duration_ms}ms`); // Get transcription const transcription = await client.recordings.transcription(recordingId); console.log(transcription.data.full_text);

Get Analysis Results

const results = await client.recordings.results(recordingId); // Goals console.log('Goals:'); results.data.goals.forEach(goal => { const status = goal.met ? '✓' : '✗'; console.log(` ${status} ${goal.goal_name}: ${goal.score.toFixed(2)}`); }); // Keywords console.log('\nKeywords:'); results.data.keywords.forEach(kw => { console.log(` "${kw.keyword_name}" - ${kw.count} mentions`); });

Error Handling

import { IdentityCallClient, AuthenticationError, PermissionError, NotFoundError, RateLimitError } from './identitycall.js'; const client = new IdentityCallClient(); try { const recording = await client.recordings.get(999999); } catch (error) { if (error instanceof NotFoundError) { console.log('Recording not found'); } else if (error instanceof PermissionError) { console.log('Permission denied'); } else if (error instanceof AuthenticationError) { console.log('Invalid API key'); } else if (error instanceof RateLimitError) { console.log(`Rate limited. Retry in ${error.retryAfter}s`); } else { throw error; } }

Iterate All Pages

async function* iterateRecordings(perPage = 100) { let page = 1; while (true) { const result = await client.recordings.list(page, perPage); for (const recording of result.data) { yield recording; } if (result.meta.current_page >= result.meta.total_pages) { break; } page++; } } // Usage for await (const recording of iterateRecordings()) { console.log(recording.name); }

Browser Usage

For browser environments, use the native fetch API:

// Browser-compatible client class BrowserIdentityCallClient { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://api.identitycall.com/api/v1/public'; } async request(method, path, options = {}) { const url = new URL(path, this.baseUrl); if (options.params) { Object.entries(options.params).forEach(([k, v]) => { url.searchParams.set(k, v); }); } const response = await fetch(url, { method, headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Accept': 'application/json', ...options.headers }, body: options.body }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Request failed'); } return response.status === 204 ? null : response.json(); } async listRecordings(page = 1, perPage = 20) { return this.request('GET', '/recordings', { params: { page, per_page: perPage } }); } }

Next Steps