Help Center / Developer / APIs / Pivot

APIs

Pivot in the API

Pivot is the API that lets a developer control inbound call handling. Dash sends the call details to the developer's web server over HTTP. The server answers with JSON that describes the callflow, and Pivot runs that callflow for the caller. Use it to send a specific call to a destination your application chooses. The examples on this page show a callflow response and how to debug a Pivot request.

On this page

About Pivot

The Pivot gives developers control over Inbound Call Handling. Pivot send call details via HTTP to the developer’s web server. Pivot expects a response with appropriate JSON, and will execute the callflow returned on behalf of the developer.

A Pivot can be used in callflows in anywhere you want the external application to determine call routing and manage the call experience.

A Pivot can build complex call handling using a wide range of callflow elements. Complex call control may requiere muliple pivots.

To configure Pivots you need to have Advance Call Flows feature enabled on your account.

Use Cases

  • Collecting input from callers to determine routing

  • Automatically assigning inbound calls to the last agent they spoke with

  • Dynamically routing calls based on phone number

  • Automatically routing calls when a location is closed

Example Callflow

The most basic callflow for Pivot:

JSON

{
 "flow":{
     "module":"pivot"
     ,"data":{
         "voice_url":"http://your.pivot.server/path/to/script.php"
         ,"req_format":"kazoo"
         ,"method":"get"
         ,"debug":false
     }
 }
}

Debugging

You can set the debug flag to “true” to log the requests and responses Pivot receives from your Pivot Callflows.

Summary

Get a list of recent Pivot attempts:

Shell

$ curl -H "X-Auth-Token: {AUTH_TOKEN} \
     -H "Content-Type: application/json" \
     'http://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/pivot/debug'

Response:

JSON

{
   "data":{
      "name":"Missed Call",
      "hook":"notifications",
      "include_subaccounts":false,
      "http_verb":"get",
      "retries":4,
      "uri":"https://hooks.zapier.com/hooks/catch/591668/xlwslm/",
      "type":"account",
      "action":"doc_created",
      "custom_data":{
         "type":"missed_call"
      },
      "enabled":true,
      "include_internal_legs":true,
      "id":"51840ffc8bde832a1f5477def32cd665"
   }
}

Specific Call

Get details of a specific Pivot attempt:

Shell

$curl -H "X-Auth-Token: {AUTH_TOKEN} \
    -H "Content-Type: application/json" \
    'http://api.virtualpbx.net/v2/accounts/{ACCOUNT_ID}/pivot/debug/{CALL_ID}'

Response:

JSON

{
    "auth_token": "{AUTH_TOKEN}",
        "data": [
        {
            "call_id": "{CALL_ID}",
            "id": "{DEBUG_ID}",
            "method": "get",
            "req_body": "",
            "req_headers": {},
            "uri": "http://your.pivot.server/script.php?Caller-ID-Number=XXXXXXXXXX&Caller-ID-Name=JoeFromIT&Direction=inbound&ApiVersion=2013-05-01&ToRealm=your.pivot.server&To=3004&FromRealm=your.pivot.server&From=user_sov2kt&Account-ID={ACCOUNT_ID}&Call-ID={CALL_ID}"
        },
        {
            "call_id": "{CALL_ID}",
            "id": "{DEBUG_ID}",
            "resp_body": "\n    {\"module\":\"say\"\n     ,\"data\":{\"text\":\"Please leave your message after the beep\"}\n     ,\"children\":{\n         \"_\":{\n           \"module\":\"record_caller\"\n           ,\"data\":{\n               \"format\":\"mp3\"\n               ,\"url\":\"http://your.pivot.server/recordings\"\n               ,\"time_limit\":360\n           }\n         }\n        }\n    }\n"
        },
        {
        "call_id": "{CALL_ID}",
        "id": "{DEBUG_ID}",
        "resp_headers": {
            "content-length": "342",
            "content-type": "application/json",
            "date": "wed, 01 oct 2014 23:30:24 gmt",
            "server": "apache/2.4.7 (ubuntu)",
            "x-powered-by": "php/5.5.9-1ubuntu4.4"
        },
        "resp_status_code": "200"
    }
    ],
    "request_id": "{REQUEST_ID}",
    "revision": "{REVISION}",
    "status": "success"
    }

Remember to URL-encode the {CALL_ID} before sending the request.

Failback

You can add a children to your pivot callflow in case your server is unreachable or send back an error.

JSON

"flow": {
    "data": {
        "method": "GET",
        "req_timeout": "5",
        "req_format": "kazoo",
        "voice_url": "{SERVER_URL}"
    },
    "module": "pivot",
    "children": {
        "_": {
            "module": "play",
            "data": {
                "id": "{MEDIA_ID}"
            },
            "children": {}
        }
    }
}

The Request Payload

You can configure whether you receive the data as a query string in a GET or as a URL-encoded body in a POST.

Payload

Some of the fields may not be included in every request.

FieldDescription
Call-IDThe unique call identifier
Account-IDThe account id receiving the call
FromThe SIP From username
From-RealmThe SIP From realm
ToThe SIP To username
To-RealmThe SIP To realm
RequestThe SIP Request username
Request-RealmThe SIP Request realm
Call-StatusStatus of the call
Api-VersionThe version of the API
DirectionThe direction of the call, relative to VirtualPBX
Caller-ID-NameCaller ID Name
Caller-ID-NumberCaller ID Number
User-IDThe VirtualPBX User identifier(s) of the caller
LanguageThe caller's language preference
Recording-UrlWhere the recording will be stored
Recording-DurationHow long the recording is
Recording-IDThe recording id
DigitsAny DTMF (or collection of DTMFs) pressed

JSON Overview

Sometimes VirtualPBX’s callflow builder doesn’t match your needs, integrate with your applications, or provide the experience for your caller that you desire. Fortunately, the building blocks are available for you to play with as you see fit!

By routing a call to a Pivot callflow action, Vi will make an HTTP request to your web server asking for the callflow to process for that specific call (versus the one-size-fits-all approach in the normal callflow builder).

This could be best illustrated with an example, no?

Building Time-based routing

We want to add time-based rules when deciding how to route our office’s main number (this exists with the temporal_route callflow action already, but we’re trying to make a point!). Following a “typical” office’s hours, we are open 9am-5pm (9:00 to 17:00). During that time, we’d like to ring the front desk’s phone. Outside of those hours, we’d like calls to go directly to the company’s voicemail box.

Example of Building on Pivot

Based on the above goals, we need to build:

  • Front Desk's device

  • Company Voicemail box

  • Main number callflow

Once you’ve made those, note the IDs for the device and voicemail box (using the developer tool is a good way to find those).

The main number callflow should be:

Plain Text

[NUMBER] -> Pivot
            Url: http://your.webserver.com/path/to/main_number_tod.php

Now, whenever this number is called, VirtualPBX will query your URL for callflow to execute.

Build main_number_tod.php

The first thing needed is to set the content-type to application/json.

PHP

<?php
    header('content-type:application/json');

Now we need to know what time it is and determine what to do:

PHP

$now = time();
$hour = date("G", $now);
if ( $hour >= 9 && $hour < 17 ) {
  business_hours();
} else {
  after_hours();
}

Now we have two functions to build the JSON for the callflow to execute:

  • business_hours():

    PHP
      function business_hours() {
      ?>
        {"module":"device"
         ,"data":{"id":"{FRONT_DESK_DEVICE_ID}"}
         ,"children":{
           "_":{
             "module":"voicemail"
             ,"data":{"id":"{COMPANY_VM_BOX_ID}"}
           }
         }
      <?php
      }
  • after_hours():

    PHP
      function after_hours() {
      ?>
        {"module":"voicemail"
         ,"data":{"id":"{COMPANY_VM_BOX_ID}"}
        }
      <?php
      }

Bring it all together:

PHP

<?php
    header('content-type:application/json');
    $now = time();
    $hour = date("G", $now);
    if ( $hour > 8 && $hour < 17 ) {
      business_hours();
    } else {
      after_hours();
    }
    function business_hours() {
    ?>
      {"module":"device"
       ,"data":{"id":"{FRONT_DESK_DEVICE_ID}"}
       ,"children":{
         "_":{
           "module":"voicemail"
           ,"data":{"id":"{COMPANY_VM_BOX_ID}"}
         }
       }
    <?php
    }
    function after_hours() {
    ?>
      {"module":"voicemail"
       ,"data":{"id":"{COMPANY_VM_BOX_ID}"}
      }
    <?php
    }
?>

TwiML Format

Pivot supports a subset of TwiML to help ease you into Pivot with an existing TwiML-based application.

Core Supported

Default Request Data included

Request ParameterNameDescription
CallerNameCaller-ID-NameName of the caller, if any
DirectionDirectionDirection of the call (outbound if VirtualPBX originated the call, inbound otherwise)
ApiVersonN/AVersion string related to API changes
CallStatusN/AWhat state the call is currently in
ToTo-UserDialed number
FromFrom-UserCaller's number, if available
AccountSidAccount-IDAccount ID processing the call
CallSidCall-IDUnique identifier of the call leg

Optional/Conditional Request Data

Request ParameterNameDescription
RecordingUrlRecording-URLWhere a recording will be sent (via HTTP PUT request)
RecordingDurationRecording-DurationLength of the recording, if available
RecordingSidMedia-NameName of the recording file
DigitsDTMF-PressedThe DTMF(s) (touch tone) pressed by the caller
DialCallStatusN/ACall status of the b-leg
DialCallSidOther-Leg-Unique-IDCall-ID of the b-leg
DialCallDurationBilling-SecondsHow many billable seconds the call lasted

Other optional data includes user-defined key/value pairs stored using the verb below.

TwiML Verbs

VerbDescriptionNestable Verbs and Nouns
Connect the caller to other endpointsplain text DID, , , , ,
Record the caller
Collect DTMFs from the caller,
Play a media file (mp3, wav) to the caller
SayUse a TTS engine to say the supplied text
RedirectLike an HTTP Redirect, make another HTTP request
PausePause callflow execution for supplied number of seconds
HangupHangup the caller
RejectReject (and don't answer - won't start billing) the call

Custom Verbs

VerbDescriptionNestable Nouns
Key value pair(s) to store along-side the call

Core TwiML Nouns

NounDescription
Conference room endpoint for
Call queue to line callers up in
DID with extended attributes
ID of an existing User (works like the User callflow element
ID of an existing Device
SIP URI to dial

Custom Nouns

NounDescription
Includes 'key' and 'value' attributes; values will be put subsequent requests

Bridging

VirtualPBX JSON offers a plethora of ways to call out to various endpoints! Below are some minimalist examples to whet your appetite. Most take more options, are nestable as children of other callflow actions, and are generally quite useful in accomplishing most peoples’ needs.

Devices

Dial a single device

JSON

{
    "module":"device",
    "data":{"id":"device_id"}
}

Users

Dial a User (any devices owned by the user)

JSON

{
    "module": "user",
    "data": {
        "id": "user_id"
    }
}

Ring Group

Ring groups are ultra-flexible in what types of endpoints you can combine: devices, users, or groups! You need only include the IDs you want to ring and VirtualPBX will build the appropriate list of endpoints.

JSON

{
    "module":"ring_group",
    "data":{
        "endpoints": ["device_1_id",
                      "device_2_id",
                      "user_1_id",
                      "user_2_id",
                      "group_1_id",
                      "group_2_id"
                     ]
    }
}

You are free to mix/match devices, users, and groups based on the needs of this particular call.

Dialing outside the account

It is all well and good that dialing to known VirtualPBX endpoints is so easy, but what about contacting the outside world?

VirtualPBX supports two types of resources, global and per-account (or local, as VirtualPBX refers to them). You can optionally route to either, depending on how you’ve configured your account and whether you utilize the VirtualPBX cluster’s global resources.

The only real difference is the use_local_resources flag.

Using Global resources

JSON

{
    "module": "resources",
    "data": {
        "to_did": "+14155550000",
        "use_local_resources": false
    }
}

Using Local resources

JSON

{
    "module": "resources",
    "data": {
        "to_did": "+14155550000",
        "use_local_resources": true
    }
}

Collect

It is possible to collect DTMFs via VirtualPBX JSON using the cf_collect_dtmf callflow action. You can also store the collected DTMF in the custom-named keys to differentiate different collections.

Collecting DTMF

The initial VirtualPBX JSON to collect DTMF could look something like:

PHP

<?php
header('content-type:application/json');
?>
{
    "module":"tts",
    "data":{
        "text": "Please enter up to four digits."
    },
    "children": {
        "_": {
            "module": "collect_dtmf",
            "data": {
                "max_digits": 4,
                "collection_name": "custom_name"
            },
            "children": {
                "_": {
                    "module": "pivot",
                    "data": {
                        "voice_url": "http://pivot.your.company.com/collected.php"
                    },
                    "children": {}
                }
            }
        }
    }
}

First, VirtualPBX will use the TTS engine to say the text field. Next, it will wait for the user to press up to 4 DTMF (with # being a terminating DTMF that is not included in the collection). Finally, a second pivot request will be made the the collected.php script on your server.

This is a basic menu! Congrats, you can build custom IVRs!

Processing collected DTMF

Here is demonstrated speaking back the digits pressed to the caller; you could obviously key off the DTMF to do whatever further call processing.

PHP

<?php
header('content-type:application/json');
$dtmf = $_REQUEST['Digits'];
if ( empty($dtmf) ) {
?>
{
    "module": "tts",
    "data": {
        "text": "We didn't get that"
    },
    "children": {}
}
<?php } else if ( is_string($dtmf) ) { ?>
{
    "module": "tts",
    "data": {
        "text": "You typed <?= $dtmf ?>"
    },
    "children": {}
}
<?php } else { ?>
{
    "module": "tts",
    "data": {
        "text": "You typed <?= $dtmf['custom_name'] ?>"
    },
    "children": {}
}
<?php } ?>

The is_string($dtmf) check is to support the old way of returning DTMF in the Pivot request. Otherwise, you should receive an array of DTMF collections, indexed by the key name supplied (“default” if you didn’t specify one).

Conferencing

There are two main ways to add the caller to a conference:

  1. Use pre-built VirtualPBX conference rooms, configured via Crossbar

  2. Create ad-hoc conference rooms via Pivot

Pre-built example

JSON

{
    "module": "conference",
    "data": {
        "id": "conference_id"
    }
}

Ad-Hoc example

JSON

{
    "module": "conference",
    "data": {
        "config": {
            "name": "My Ad-hoc Conference"
        }
    }
}

This will create a minimalist conference bridge, named “My Ad-hoc Conference”. Use the same name to ensure callers end up in the same conference together.

Conference Per Area Code

PHP

<?php
header('content-type: application/json');
$caller_id = $_REQUEST['Caller-ID-Number'];
$areacode = ($caller_id, 0, 3);
?>
{
    "module": "tts",
    "data": {
        "text": "Welcome to the <?= $areacode ?> conference!"
    },
    "children": {
        "_": {
            "module": "conference",
            "data": {
                "config": {
                    "name": "Areacode <?= $areacode ?>"
                }
            },
            "children": {}
        }
    }
}

Obviously this doesn’t handle hidden or missing caller ID.

DTMF

Sending DTMF to the caller is sometimes necessary (automating IVR navigation, perhaps). Use the send_dtmf callflow to do so.

Example

JSON

{
    "module": "send_dtmf",
    "data": {
        "digits": "123ABC#",
        "duration_ms": 2000
    },
    "children": {}
}
  • digits is a string of DTMF to send.

  • duration_ms is how long of a tone to send each DTMF (and optional)

The above example would send “1”, “2”, “3”, “A”, “B”, “C”, and finally ”#”, each as 2 second tones.

Hang Ups

Sometimes it is necessary to respond to a call with a hangup cause and code. For extra fun, some phones will display the hangup cause on the display, which can be a source of amusement. For instance, for a while VirtualPBX would return a “403 Insert Coin” if the account was out of money.

Example

JSON

{
    "module": "response",
    "data": {
        "code": "486",
        "message": "User Busy",
        "media":"id_or_url"
    }
}

If you define media, the media will be played to the user before hanging up the call.

Play

It is pretty easy to play files, either hosted in your account or accessible via a URI.

Play sample

The initial VirtualPBX JSON to play a file could look something like:

PHP

<?php
header('content-type:application/json');
?>
{
    "module": "play",
    "data": {
        "id": "media_id"
    },
    "children": {
        "_": {
            "module": "play",
            "data":{
                "id": "http://some.file.server/some/file.mp3"
            },
            "children": {}
        }
    }
}

Here we see two play actions, one that uses a media file hosted by VirtualPBX and one that fetches the file from an HTTP server.

Presence

Pivot allows you to set custom presence updates (known as manual presence).

Example

JSON

{
    "module": "manual_presence",
    "data": {
        "presence_id": "username",
        "status": "ringing"
    }
}

Do note, presence_id without an @realm.com will be suffixed with the account’s realm.

status can be one of idle, ringing, or busy.

Recording

Recording the caller (or caller and callee in a bridged-call scenario) is straightforward to start. What’s trickier is how to store the recording.

Recording Caller/Callee situations

Starting recording

JSON

{
    "module": "record_call",
    "data": {
        "action": "start",
        "time_limit": 1200,
        "format": "mp3",
        "url": "http://your.server.com/recordings"
    }
}

This will start the call recording, limiting it to 1200 seconds, and will encode the audio into an MP3 file (alternatively, you can use “wav”). The url is where the resulting file will be sent via an HTTP PUT request. It is then up to the receiving server to properly handle the request and store the file for later use.

Stop recording

If you need to programmatically stop the current recording (vs implicitly when the call ends):

JSON

{
    "module": "record_call",
    "data": {
        "action": "stop"
    }
}

Sample Recording Per User

JSON

{
    "module": "record_call",
    "data": {
        "action": "start",
        "format": "mp3",
        "url": "http://my.recording.server/{ACCOUNT_ID}/{USER_ID}",
        "time_limit":360
    },
    "children": {
        "_": {
            "module": "user",
            "data": {
                "id": "{USER_ID}"
            },
            "children": {
                "_": {
                    "module": "record_call",
                    "data": {
                        "action": "stop"
                    },
                    "children": {
                        "module": "voicemail",
                        "data": {
                            "id": "{VMBOX_ID}"
                        },
                        "children": {}
                    }
                }
            }
        }
    }
}

Recording just the caller

This action is more appropriate for recording just the caller (think voicemail or recording menu prompts).

JSON

{
    "module": "say",
    "data": {
        "text": "Please leave your message after the beep"
    },
    "children": {
        "_": {
            "module": "record_caller",
            "data": {
                "format": "mp3",
                "url": "http://my.recording.server/voicemail/{ACCOUNT_ID}/{BOX_ID}",
                "time_limit":360
            }
        }
    }
}

Receiving a recording

Here is a simple PHP/.htaccess combo for receiving a recording.

  1. Assume url in our data object is "http://your.server.com/kzr"

  2. Create a .htaccess file in the DocumentRoot. This will direct the request to /kzr/index.php with the recordingquery string parameter set to CALL_ID. EXT.

Plain Text

        <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteBase /
            RewriteRule ^kzr/call_recording_(.+)\.(.+)$  kzr/index.php?recording=$1.$2 [QSA,L]
        </IfModule>
  1. Create /kzr/index.php to receive and store the recording.

PHP

<?php
/* PUT data comes in on the stdin stream */
$putdata = fopen("php://input", "r");
$r = $_REQUEST["recording"];
/* Open a file for writing */
$fp = fopen("/tmp/$r", "w");
/* Read the data 1 KB at a time and write to the file */
while ($data = fread($putdata, 1024))
    fwrite($fp, $data);
/* Close the streams */
fclose($fp);
fclose($putdata);
?>

A file should be created in /tmp/ named CALL_ID. EXT. You could, of course, store this in MySQL, Postgres, S3, feed it to a transcription service, etc.

Say

Text-to-speech is an easy way to read text to the caller

Play sample

The initial VirtualPBX JSON to

PHP

<?php
header('content-type:application/json');
?>
{
    "module": "tts",
    "data": {
        "text": "Pivot is pretty awesome. Have a great day."
    },
    "children": {
        "_": {
            "module": "response",
            "data": {}
        }
    }
}

Here the TTS engine will read the text to the caller and then hang up.