# Quickstart

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](https://vocatech.com/developers/guide/keys#keys) 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.

**curl**

```bash
# 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.
```

**Node**

```javascript
// 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 || '';
}
```

**Python**

```python
# 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**

```php
<?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**

```bash
curl https://api.vocatech.com/v1/whoami \
  -H "Authorization: Bearer $VOCATECH_API_KEY"
```

**Node**

```javascript
import { vt } from './vocatech.mjs';

const me = await vt('GET', '/v1/whoami');
console.log(me.company_id, me.client_ip, me.ip_restricted);
```

**Python**

```python
from vocatech import vt

me = vt("GET", "/v1/whoami")
print(me["company_id"], me["client_ip"], me["ip_restricted"])
```

**PHP**

```php
<?php
require __DIR__ . '/vocatech.php';

$me = vt('GET', '/v1/whoami');
echo $me['company_id'], ' ', $me['client_ip'], PHP_EOL;
```

**Response200 OKCopy**

```json
{
  "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](https://vocatech.com/developers/guide/keys#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.

**curl**

```bash
# 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"
```

**Node**

```javascript
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);
}
```

**Python**

```python
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**

```php
<?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";
}
```

**Response200 OKCopy**

```json
{
  "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](https://vocatech.com/developers/guide/pull-calls), or have events pushed to you in [Receive webhooks](https://vocatech.com/developers/guide/receive-webhooks).
