bland-voice
Version:
The official SDK for Bland AI phone calls.
325 lines (302 loc) • 11.7 kB
JavaScript
const fetch = require('node-fetch');
/**
* A Node.js client for the Bland AI API.
*/
class Bland {
/**
* Constructs a new Bland client instance.
* @param {string} apiKey - Your Bland AI API key.
*/
constructor(apiKey) {
if (!apiKey) {
throw new Error('API key is required');
}
this.apiKey = apiKey;
this.baseUrl = 'https://api.bland.ai';
}
/**
* Starts a phone call using the Bland AI API.
* @param {string} phoneNumber - The target phone number, including country code.
* @param {boolean} reduceLatency - If true, reduces response latency.
* @param {number} voiceId - The ID of the voice to be used.
* @param {Object} additionalParams - Additional parameters for the call.
* @returns {Promise<Object>} - The API response.
*/
async startCall(phoneNumber, reduceLatency, voiceId, additionalParams) {
if (!phoneNumber) {
throw new Error('Phone number is required');
}
const payload = {
phone_number: phoneNumber,
reduce_latency: reduceLatency,
voice_id: voiceId,
...additionalParams,
};
try {
const response = await fetch(this.baseUrl + "/call", {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error making the call:', error);
throw error;
}
}
/**
* Fetches the logs for a specific call. If 'poll' is true, it will poll the logs until the call is completed or an error occurs.
* @param {string} callId - The unique identifier for the call.
* @param {boolean} poll - If true, continually polls the logs.
* @returns {Promise<Object>} - The logs data or error.
*/
async fetchLogs(callId, poll = false) {
const fetchLogData = async () => {
try {
const response = await fetch(`${this.baseUrl}/logs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify({ call_id: callId }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error fetching logs:', error);
throw error;
}
};
if (poll) {
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
const data = await fetchLogData();
if (data.error) {
clearInterval(interval);
reject(data.error);
} else if (data.completed) {
clearInterval(interval);
resolve(data);
}
}, 2000); // Poll every 2 seconds as per API recommendation
});
} else {
return fetchLogData();
}
}
/**
* Ends an ongoing phone call using the Bland AI API.
* @param {string} callId - The unique identifier for the call to be ended.
* @returns {Promise<Object>} - The API response.
*/
async endCall(callId) {
if (!callId) {
throw new Error('Call ID is required');
}
try {
const response = await fetch(`${this.baseUrl}/end`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify({ call_id: callId }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error ending the call:', error);
throw error;
}
}
/**
* Initiates a batch of phone calls.
* @param {string} basePrompt - The base prompt used for all calls.
* @param {Array} callData - An array of objects defining the calls.
* @param {Object} options - Optional parameters for the batch call.
* @returns {Promise<Object>} - The API response.
*/
async batchCall(basePrompt, callData, options = {}) {
const payload = {
base_prompt: basePrompt,
call_data: callData,
...options,
};
try {
const response = await fetch(`${this.baseUrl}/batch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error initiating batch call:', error);
throw error;
}
}
/**
* Retrieves the recording of a completed call.
* @param {string} callId - The unique identifier for the call.
* @returns {Promise<Object>} - The API response with the recording URL.
*/
async getRecording(callId) {
if (!callId) {
throw new Error('Call ID is required');
}
try {
const response = await fetch(`${this.baseUrl}/recording`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify({ call_id: callId }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error retrieving the recording:', error);
throw error;
}
}
/**
* Updates the inbound prompt for a specific phone number.
* @param {string} phoneNumber - The phone number associated with the prompt.
* @param {string} prompt - The new prompt text.
* @param {number} [voiceId=0] - (Optional) The ID for the custom voice.
* @returns {Promise<Object>} - The API response.
*/
async updateInboundPrompt(phoneNumber, prompt, voiceId = 0) {
if (!phoneNumber || !prompt) {
throw new Error('Phone number and prompt are required');
}
const payload = {
phone_number: phoneNumber,
prompt: prompt,
voice_id: voiceId,
};
try {
const response = await fetch(`${this.baseUrl}/inbound/update`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error updating inbound prompt:', error);
throw error;
}
}
/**
* Makes an outbound call, waits on hold, and connects once a human answers.
* @param {string} phoneNumber - The phone number to call.
* @param {string} holdConnect - The phone number to connect to once hold ends.
* @param {string} [task=""] - (Optional) Additional instructions for the call.
* @returns {Promise<Object>} - The API response with status and call ID.
*/
async hold(phoneNumber, holdConnect, task = "") {
if (!phoneNumber || !holdConnect) {
throw new Error('Phone number and hold connect number are required');
}
const payload = {
phone_number: phoneNumber,
hold_connect: holdConnect,
task: task,
};
try {
const response = await fetch(`${this.baseUrl}/hold`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error executing hold for me:', error);
throw error;
}
}
/**
* Retrieves details for a specific batch of calls.
* @param {string} batchId - The unique identifier for the batch.
* @param {boolean} [includeCalls=false] - Whether to include individual call data.
* @returns {Promise<Object>} - The batch details.
*/
async getBatch(batchId, includeCalls = false) {
if (!batchId) {
throw new Error('Batch ID is required');
}
const queryParams = new URLSearchParams({ batch_id: batchId, include_calls: includeCalls }).toString();
try {
const response = await fetch(`${this.baseUrl}/batch?${queryParams}`, {
method: 'GET',
headers: { 'Authorization': this.apiKey },
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error retrieving batch:', error);
throw error;
}
}
/**
* Cancels all calls in a specific batch.
* @param {string} batchId - The unique identifier for the batch to be cancelled.
* @returns {Promise<Object>} - The API response with status and details.
*/
async cancelBatch(batchId) {
if (!batchId) {
throw new Error('Batch ID is required');
}
try {
const response = await fetch(`${this.baseUrl}/batch/stop`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify({ batch_id: batchId }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error cancelling batch:', error);
throw error;
}
}
/**
* Purchases and configures a new inbound phone number.
* @param {Object} options - The options for purchasing the number.
* @returns {Promise<Object>} - The API response with the new phone number.
*/
async purchaseInboundNumber(options) {
if (!options || !options.area_code) {
throw new Error('Area code is required');
}
try {
const response = await fetch(`${this.baseUrl}/purchasenumber`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': this.apiKey },
body: JSON.stringify(options),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Error purchasing inbound number:', error);
throw error;
}
}
}
module.exports = Bland;