Start
Quickstart
Written beside the API’s code, and read from it every hour.
Three steps take you from nothing to today's calls.
1Get a key
In the Vocatech portal, open your company and go to the Integrations page. Under Portal Public API, press Generate and copy the key. This is your main key, and it reaches every route. For texting only, make an API Messaging key instead.
Put the key in an environment variable. The Node, Python and PHP examples in this guide use the small vt() helper below: save it next to your code. curl needs nothing else.
# Put the key in the environment once. Every curl example reads it from there.
export VOCATECH_API_KEY="paste-your-key-here"
# That is all curl needs.// vocatech.mjs: a tiny client. Node 18 or newer, no packages.
const BASE = 'https://api.vocatech.com';
const KEY = process.env.VOCATECH_API_KEY;
export async function vt(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${KEY}`,
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
},
body: body === undefined ? undefined : JSON.stringify(body),
});
const text = await res.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch {
data = { raw: text };
}
if (!res.ok) {
const err = new Error(`${res.status} ${errorText(data)}`);
err.status = res.status;
err.retryAfter = Number(res.headers.get('retry-after')) || null;
err.body = data;
throw err;
}
return data;
}
// The API writes errors in a few shapes. This reads all of them.
export function errorText(body) {
if (!body) return '';
if (typeof body.error === 'string') return body.message || body.error;
if (body.error) return body.error.message || body.error.description || '';
return body.raw || '';
}# vocatech.py: a tiny client. Python 3.8 or newer, and: pip install requests
import os
import requests
BASE = "https://api.vocatech.com"
KEY = os.environ["VOCATECH_API_KEY"]
def error_text(body):
"""The API writes errors in a few shapes. This reads all of them."""
if not isinstance(body, dict):
return ""
err = body.get("error")
if isinstance(err, str):
return body.get("message") or err
if isinstance(err, dict):
return err.get("message") or err.get("description") or ""
return ""
class VocatechError(Exception):
def __init__(self, status, body, retry_after):
super().__init__(f"{status} {error_text(body)}")
self.status = status
self.body = body
self.retry_after = retry_after
def vt(method, path, body=None, params=None):
r = requests.request(
method,
BASE + path,
params=params,
json=body,
headers={"Authorization": f"Bearer {KEY}"},
timeout=60,
)
try:
data = r.json() if r.content else None
except ValueError:
data = {"raw": r.text}
if not r.ok:
retry = r.headers.get("Retry-After")
raise VocatechError(r.status_code, data, int(retry) if retry else None)
return data<?php
// vocatech.php: a tiny client. PHP 8.1 or newer, with the curl extension.
const VT_BASE = 'https://api.vocatech.com';
/** The API writes errors in a few shapes. This reads all of them. */
function vt_error_text(?array $body): string
{
$error = $body['error'] ?? null;
if (is_string($error)) {
return (string) ($body['message'] ?? $error);
}
if (is_array($error)) {
return (string) ($error['message'] ?? $error['description'] ?? '');
}
return '';
}
final class VocatechError extends RuntimeException
{
public function __construct(
public readonly int $status,
public readonly ?array $body,
public readonly ?int $retryAfter
) {
parent::__construct($status . ' ' . vt_error_text($body), $status);
}
}
function vt(string $method, string $path, ?array $body = null): ?array
{
$retryAfter = null;
$headers = ['Authorization: Bearer ' . getenv('VOCATECH_API_KEY')];
$ch = curl_init(VT_BASE . $path);
if ($body !== null) {
$headers[] = 'Content-Type: application/json';
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HEADERFUNCTION => function ($ch, string $line) use (&$retryAfter): int {
if (stripos($line, 'Retry-After:') === 0) {
$retryAfter = (int) trim(substr($line, 12));
}
return strlen($line);
},
]);
$raw = curl_exec($ch);
if ($raw === false) {
throw new RuntimeException('Network error: ' . curl_error($ch));
}
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$data = $raw === '' ? null : json_decode($raw, true);
if ($status >= 400) {
throw new VocatechError($status, is_array($data) ? $data : null, $retryAfter);
}
return is_array($data) ? $data : null;
}The Node examples are ES modules: save them as .mjs files and run them with Node 18 or newer.
2Check the connection
GET /v1/whoami answers with your company id and the address the API sees you calling from. Every key may call it.
curl https://api.vocatech.com/v1/whoami \
-H "Authorization: Bearer $VOCATECH_API_KEY"import { vt } from './vocatech.mjs';
const me = await vt('GET', '/v1/whoami');
console.log(me.company_id, me.client_ip, me.ip_restricted);from vocatech import vt
me = vt("GET", "/v1/whoami")
print(me["company_id"], me["client_ip"], me["ip_restricted"])<?php
require __DIR__ . '/vocatech.php';
$me = vt('GET', '/v1/whoami');
echo $me['company_id'], ' ', $me['client_ip'], PHP_EOL;{
"client_ip": "203.0.113.24",
"forwarded_for": ["203.0.113.24"],
"company_id": 1234567890,
"ip_restricted": false,
"allowed_ips": []
}If you plan to lock the key to your server's address, client_ip is the address to allow. See IP allow-list.
3Pull today's calls
GET /v1/calls with no dates returns today, in New York time. Each call carries its journey: every menu, group and person it reached, with the AI summary, the transcript and a recording link when there is one.
# No dates: today, in New York time
curl https://api.vocatech.com/v1/calls \
-H "Authorization: Bearer $VOCATECH_API_KEY"
# One day, in your own time zone
curl "https://api.vocatech.com/v1/calls?start_date=2026-09-23&end_date=2026-09-23&timezone=America/Chicago" \
-H "Authorization: Bearer $VOCATECH_API_KEY"import { vt } from './vocatech.mjs';
const today = await vt('GET', '/v1/calls');
console.log(`${today.meta.total_calls} calls today`);
for (const call of today.calls) {
console.log(call.start_time, call.direction, call.remote_number, call.extension_name);
}from vocatech import vt
today = vt("GET", "/v1/calls")
print(today["meta"]["total_calls"], "calls today")
for call in today["calls"]:
print(call["start_time"], call["direction"], call["remote_number"], call["extension_name"])<?php
require __DIR__ . '/vocatech.php';
$today = vt('GET', '/v1/calls');
echo $today['meta']['total_calls'], " calls today\n";
foreach ($today['calls'] as $call) {
echo "{$call['start_time']} {$call['direction']} {$call['remote_number']} {$call['extension_name']}\n";
}{
"query": {
"company_id": 1234567890,
"start_date": "2026-09-24",
"end_date": "2026-09-24",
"direction": "any",
"status": "any",
"search": "",
"timezone": "America/New_York"
},
"calls": [
{
"call_id": "8a1f3c2e-5b7d-4e9a-9c1b-2d4e6f8a0b1c",
"direction": "incoming",
"status": "answered",
"extension": "104",
"extension_name": "Maya Chen",
"remote_name": "Jordan Reyes",
"remote_number": "7185550142",
"group_number": "7185550100",
"start_time": "2026-09-24T13:02:11.000Z",
"end_time": "2026-09-24T13:06:49.000Z",
"duration": 278,
"journey": [
{
"order": 1,
"type": "auto_attendant",
"extension_name": "Main Menu",
"extension": "900",
"start_time": "2026-09-24T13:02:11.000Z",
"end_time": "2026-09-24T13:02:24.000Z",
"duration": 13,
"summary": null,
"transcription": null,
"recording_url": null,
"recordings": []
},
{
"order": 2,
"type": "user",
"extension_name": "Maya Chen",
"extension": "104",
"start_time": "2026-09-24T13:02:24.000Z",
"end_time": "2026-09-24T13:06:49.000Z",
"duration": 265,
"summary": "Jordan asked to move the Thursday visit to Friday morning. Maya booked Friday at 9 AM.",
"transcription": "Maya: Good morning, how can I help?\nJordan: Hi, I need to move my Thursday visit.\nMaya: Sure. Friday at 9 AM works.",
"recording_url": "https://api.vocatech.com/v1/media/rec_58213407",
"recordings": [
{
"part": 1,
"parts": 1,
"recording_id": 10592210,
"start_time": "2026-09-24T13:02:25.000Z",
"duration": 264,
"url": "https://api.vocatech.com/v1/media/rec_58213407?part=1"
}
]
}
]
}
],
"meta": {
"page": 1,
"limit": 500,
"total_pages": 1,
"total_calls": 1
}
}Next, walk every page and download the recordings in Pull today's calls, or have events pushed to you in Receive webhooks.
