API Spotlight: Call Recording

Call recording function is an important part of voice communication services. Call recording is critical for customer services as recorded calls could be used for quality assurance, training purpose etcetera, or for evidence when required.

A recorded call is an audio recording of a telephone conversation between a caller and a callee. There are telephone recording laws. Depend on the law of a country or a state, call recording may require one-party consent or two-party consent. In the United States, federal law requires that at least one party taking part in the call must be notified of the recording. That is the reason why we might have heard notification messages every time when making a phone call to our bank, insurance, governmental services etc.

“This call may be monitored or recorded for quality assurance purposes.”
“This call may be recorded for quality and training purposes.”
“This call is being recorded, if you do not wish to be recorded please disconnect at this time.”

Often, the notification message itself is also included in the recorded call as an evidence of the consent.

Under RingCentral voice services, call recording enabling and notification messages can be managed via call recording settings from the admin console:

Automatic call recording: An account administrator can easily enable automatic recordings for inbound and outbound phone calls for users under that account.

On-demand call recording: When the on-demand option is enabled, during a call, we can simply press *9 to begin recording, then press *9 again whenever we want to stop recording. It’s even more convenient on the RingCentral mobile phone app or on the computer soft-phone app, which lets us simply click the Record button to start recording, then click the Stop button whenever we want to stop recording.

The notification messages for starting and stopping a call recording can be customized to meet a company’s policy.

Recorded calls from RingCentral voice service can be accessed via the RingCentral phone for mobile or from the RingCentral soft-phone on a computer. We can easily playback or pause a recording, scrub the progress bar to fast forward or rewind while listening to the recorded conversation. The tools bar below each recorded call allows us to call back or reply with a text message. We can also save a recorded call to a local folder or block the number just with a click on an appropriated icon.

Also, recorded calls can be accessed via the RingCentral account service dashboard under the call-log tab.

Alternatively, recorded calls can be accessed programmatically using the call-log API. We can take advantage of the API capability to access recorded calls more effectively. For instance, we can access the audio content of a recoded call, transcribe the recording and analyze the conversation to find critical subject and make alert accordingly. Or we can index all recorded calls’ transcripts and make it searchable.

How can I access RingCentral recorded calls programmatically?

Call recording metadata is part or the generic call log data. Thus, they are stored under a RingCentral account’s call log database.

There are two access levels to read a call log dataset from the database:

1. The account level allows an account administrator to access the entire company’s call log database. Below is the endpoint to read call log at the account level (Try it yourself)

https://platform.devtest.ringcentral.com/restapi/v1.0/account/{accountId}/call-log
The API endpoint above lets an admin user access the entire company’s call log metadata logged for the past 24 hours.

2. The extension level lets a user access his/her own call log database. Below is the endpoint to read call log at extension (user) level (Try it yourself)

https://platform.devtest.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId}/call-log
The API endpoint above lets a user, identified by the extensionId, access his/her own call log metadata logged for the past 24 hours.

To list only call logs with call recording that was created within a period of time, we can specify a few more useful query parameters to the API call:

“recordingType” specify the type of call recordings as Automatic or On-demand call recordings. Or as All to read both Automatic and On-Demand call recordings.

“dateFrom” and “dateTo”: specify the start and the end date and time for fetching call history which were logged within that period of time.

For example, making a GET request to the API endpoint below lets a user, identified by the extensionId to access his/her own recorded calls’ metadata logged for the whole month of March 2018.

https://platform.devtest.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId}/call-log?recordingType=All&dateFrom=2018-03-01T00:00:00.000Z&dateTo=2018-03-31T23:59:59.999Z

What information can I get from the call recording API?

The API response is a JSON object which contains an array of records. Each record item in the array contains metadata of a call including the essential information for call recording as listed below:

“direction” is the direction as Inbound or Outbound of a voice call.

“duration” is the call duration in seconds of an entire call. This is not the duration of a call recording!

“from” contains the caller’s phone number and name (if existed). And other information such as the location (city, state) if it can be determined from the phone number.

“to” contains the callee’s phone number and name (if existed). And other information such as the location (city, state) if it can be determined from the phone number.

“startTime” show the date and time when a call/fax transaction has been made.

“recording” this information exists if the call was recorded. It contains metadata of a recorded call and the link to the recording binary content:

“uri” the URI pointing to a call recording metadata resource.

“type” identified if a recording was recorded automatically or by on-demand.

“contentUri” the URI pointing to a recorded call binary content.

How can I access a recorded call binary content?

We can use the canonical contentUri returned in the recording with an accessToken:

https://media.ringcentral.com/restapi/v1.0/account//{accountId}/recording/{recordingId}/content?access_token={accessToken}

Once we have a recordingId of a call, we can also use the Call Recording API to get the metadata which includes the contentType and the duration of that particular recorded call.

https://media.ringcentral.com/restapi/v1.0/account//{accountId}/recording/{recordingId}

Let’s build a demo app

Now, let’s use one of our favorite SDKs, the JavaScript SDK to make API calls to RingCentral platform.

Assumed that we already had an app created under our RingCentral developer account and we‘ve got the app credentials in place (if not here is the 3 steps to create one). Our app’s platform type is a “Server-only (No UI)” and it must have the ReadCallLog permission so we can access the call log.

In this demo project, we read an account’s recorded calls, extract some metadata of the call such as the from recipient and to recipient phone numbers, the recording URI, the duration of the call and save them into a local database (using sqlite3). We will implement a Web app to display the call recording metadata and an audio player to playback the recording. We will use IBM speech-to-text service to transcribe the recording and add the transcript to the call metadata so we can search for spoken words. Get source code from Github: Node.JS — PHP

Let’s first import the RingCentral SDK and other dependencies including the IBM Watson library. Then get the platform instant so we can use it to call RingCentral APIs.

var RC = require('ringcentral')
var fs = require('fs')
var async = require("async");
const sqlite3 = require('sqlite3').verbose();
var CALLS_DATABASE = './db/calllogs.db';
var watson = require('./watson');
require('dotenv').load()
var rcsdk = null
if (process.env.PROD == "production"){
rcsdk = new RC({
server:RC.server.production,
appKey: process.env.CLIENT_ID_PROD,
appSecret:process.env.CLIENT_SECRET_PROD
})
}else{
rcsdk = new RC({
server:RC.server.sandbox,
appKey: process.env.CLIENT_ID_SB,
appSecret:process.env.CLIENT_SECRET_SB
})
}
var platform = rcsdk.platform()

Then we create a sqlite table for saving call recording metadata later.

createTable()
function createTable() {
let db = new sqlite3.Database(CALLS_DATABASE);
var query = 'CREATE TABLE if not exists calls (id DOUBLE PRIMARY KEY, fromRecipient VARCHAR(12) NOT NULL, toRecipient VARCHAR(12) NOT NULL, recordingUrl VARCHAR(256) NOT NULL, duration INT DEFAULT 0, transcript TEXT NOT NULL)'
db.run(query);
}

Now let’s implement an engine which helps us login to RingCentral platform and make APIs call to read call recordings, parse the response and save some metadata to our local database. The engine also includes a function transcriptCallRecording which invokes the watson.js module and call IBM speech-to-text API to transcribe a recorded call. Once we get the transcript of a recorded call, we will update the recorded call metadata from our local database with the text and that would make it possible to search for spoken words from a recorded call.

var engine = module.exports = {
login: function(req, res){
var un = ""
var pwd = ""
if (process.env.PROD == "production"){
un= process.env.USERNAME_PROD,
pwd= process.env.PASSWORD_PROD
}else{
un= process.env.USERNAME_SB,
pwd= process.env.PASSWORD_SB
}
platform.login({ username:un, password:pwd })
.then(function(resp){
res.render('index')
})
.catch(function(e){
throw e
})
},
transcriptCallRecording(req, res){
var audioSrc = "./recordings/" + req.body.audioSrc + ".mp3"
watson.transcribe(res, req.body.audioSrc, audioSrc)
},
readCallLogsAsync(req, res){
var endpoint = ""
if (req.query.access == "account")
endpoint = '/account/~/call-log'
else
endpoint = '/account/~/extension/~/call-log'
var recordArr = []
platform.get(endpoint, req.body)
.then(function(resp){
var json = resp.json()
let db = new sqlite3.Database(CALLS_DATABASE);
async.each(json.records,
function(record, callback){
if (record.hasOwnProperty("recording")){
var item = {}
if (record.from.hasOwnProperty('phoneNumber'))
item['fromRecipient'] = record.from.phoneNumber
else if (record.from.hasOwnProperty('name'))
item['fromRecipient'] = record.from.name
if (record.to.hasOwnProperty('phoneNumber'))
item['toRecipient'] = record.to.phoneNumber
else if (record.to.hasOwnProperty('name'))
item['toRecipient'] = record.to.name
item['duration'] = record.duration
item['id'] = record.recording.id
item['recordingUrl'] = record.recording.contentUri
recordArr.push(item)
var query = "INSERT or IGNORE into calls VALUES (" + item['id'] + ",'" + item['fromRecipient'] + "','" + item['toRecipient'] + "','" + item['recordingUrl'] + "'," + item['duration'] + ",'')";
db.run(query, function(err, result) {
if (err){
console.error(err.message);
}else{
callback()
}
});
}
}
);
saveAudioFile(recordArr, res)
})
.catch(function(e){
var errorRes = {}
var err = e.toString();
if (err.includes("ReadCompanyCallLog"){
errorRes['calllog_error'] = "You do not have admin role to access account level. You can choose the extension access level."
res.send(JSON.stringify(errorRes))
}else{
errorRes['calllog_error'] = "Cannot access call log."
res.send(JSON.stringify(errorRes))
}
console.log(err)
})
},
searchCallsFromDB(req, res){
let db = new sqlite3.Database(CALLS_DATABASE);
var query = "SELECT * FROM calls WHERE transcript LIKE '%" + req.body.search + "%'";
db.all(query, function(err, result) {
if (err){
return console.error(err.message);
}
if (result != undefined && result.length > 0){
res.render('recordedcalls', {
calls: result
})
}else{
res.render('recordedcalls', {
calls: []
})
}
});
},
loadCallsFromDB: function(req, res){
let db = new sqlite3.Database(CALLS_DATABASE);
var query = "SELECT * FROM calls";
db.all(query, function (err, result) {
if (err){
return console.error(err.message);
}
for (var i=0; i<result.length; i++){
result[i].recordingUrl = platform.createUrl(result[i].recordingUrl, {addToken: true});
}
res.render('recordedcalls', {
calls: result
})
});
}
}
function saveAudioFile(recordArr, resObj){
async.each(recordArr,
function(record, callback){
var recordingId = record.id
platform.get(record.recordingUrl)
.then(function(res) {
return res.response().buffer();
})
.then(function(buffer) {
var audioSrc = "./recordings/" + record.id + '.mp3'
fs.writeFileSync(audioSrc, buffer);
callback()
})
.catch(function(e){
console.log(e)
throw e
})
},
function(err){
resObj.send('{"result":"ok"}')
}
);
}

Hope you enjoy your reading and find the Call Recording API useful for your business. Meanwhile, stay tuned for more up coming API spotlight blogs.

Learn more about our Developer Program here: https://developer.ringcentral.com/