# Callers

Everything your account knows about one phone number, in one answer. Built for an AI agent or a screen pop that needs context before it answers.

## Look up a caller

`GET /v1/callers/{number}`

Everything your account knows about one phone number: the contacts it belongs to (up to 5), recent calls with who handled them and the AI summary, recent texts and WhatsApp messages, and the last call each way. Built for an AI agent or a screen pop that needs context before it answers.

`number` can be written any way: punctuation and a leading 1 are ignored. It needs at least 7 digits.

Calls and messages look back `days` days and return up to `limit` of each, newest first. Transcripts are left out unless `include` asks for them; each is cut at 6,000 characters. Message text is cut at 400 characters. A cut ends in ` [cut]`.

Keys with the `agents` scope can call this route. Each request is written to your account's HIPAA access log.

### Request

**curl**

```bash
curl -G https://api.vocatech.com/v1/callers/7185550142 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "days=90" \
  --data-urlencode "limit=10"
```

**Node**

```javascript
const params = new URLSearchParams({
  days: "90",
  limit: "10",
});

const res = await fetch(`https://api.vocatech.com/v1/callers/7185550142?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.get(
    "https://api.vocatech.com/v1/callers/7185550142",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={
        "days": 90,
        "limit": 10,
    },
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->get('/v1/callers/7185550142', [
    'query' => [
        'days' => 90,
        'limit' => 10,
    ],
]);

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `number` | path | string | yes | The phone number, any format, at least 7 digits. For example `7185550142`. |
| `days` | query | integer |  | How far back to look, 1 to 365. A value outside that range is brought inside it. Default `90`. From 1 to 365. |
| `limit` | query | integer |  | Most calls, and most messages, to return: 1 to 50. A value outside that range is brought inside it. Default `10`. From 1 to 50. |
| `include` | query | string |  | What to include, separated by commas: `contacts`, `calls`, `messages`, `transcripts`. `transcripts` brings the calls with it. Unknown words are ignored. Default `contacts,calls,messages`. For example `contacts,calls,messages,transcripts`. |

### Answers

#### 200

What we know about the number.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `number` | string | yes | The number you asked about, digits only, without a leading 1. |
| `days` | integer | yes | How many days back the calls and messages go. |
| `contacts` | array of objects | yes | Contacts with this number, up to 5. Up to 5 items. |
| `contacts[].id` | integer | yes | The contact's id. |
| `contacts[].fields` | object | yes | Field name to value. The names are your contact fields (`GET /v1/contacts/fields`). Phone fields hold digits; several numbers are separated by `;`. |
| `contacts[].fields.{field}` | string or null |  | One value for each field, by its name. |
| `calls` | array of objects | yes | Newest first. |
| `calls[].call_id` | string | yes |  |
| `calls[].direction` | string | yes | `incoming` or `outgoing`. |
| `calls[].status` | string | yes | The first leg's status, as in `GET /v1/calls`. |
| `calls[].start_time` | string (date-time) or null | yes |  |
| `calls[].end_time` | string (date-time) or null | yes |  |
| `calls[].duration` | integer | yes | Seconds. |
| `calls[].remote_name` | string | yes |  |
| `calls[].remote_number` | string | yes |  |
| `calls[].our_number` | string | yes | Your number on the call. |
| `calls[].handled_by` | object or null | yes | The person on your side: the last extension that held an incoming call, or the one that placed an outgoing call. |
| `calls[].handled_by.extension` | string | yes |  |
| `calls[].handled_by.name` | string | yes |  |
| `calls[].summary` | string or null | yes | The first AI summary on the call. |
| `calls[].journey` | array of objects | yes |  |
| `calls[].journey[].order` | integer | yes |  |
| `calls[].journey[].type` | string | yes |  |
| `calls[].journey[].extension` | string or null | yes |  |
| `calls[].journey[].extension_name` | string or null | yes |  |
| `calls[].journey[].start_time` | string (date-time) or null | yes |  |
| `calls[].journey[].duration` | integer | yes | Seconds. |
| `calls[].journey[].summary` | string or null | yes | The AI summary of this leg, when recorded. |
| `calls[].journey[].transcription` | string or null |  | Only with `include=transcripts`. Cut at 6,000 characters, ending in ` [cut]`. |
| `messages` | array of objects | yes | Newest first. |
| `messages[].channel` | string | yes | One of `text` or `whatsapp`. |
| `messages[].direction` | string | yes | `incoming` or `outgoing`. |
| `messages[].body` | string | yes | Cut at 400 characters, ending in ` [cut]`. |
| `messages[].sent_time` | string | yes | UTC, ISO 8601 without milliseconds. |
| `messages[].our_number` | string | yes |  |
| `messages[].remote_number` | string | yes |  |
| `last_call` | object or null | yes | The newest call either way. |
| `last_call.call_id` | string | yes |  |
| `last_call.direction` | string | yes | `incoming` or `outgoing`. |
| `last_call.status` | string | yes | The first leg's status, as in `GET /v1/calls`. |
| `last_call.start_time` | string (date-time) or null | yes |  |
| `last_call.end_time` | string (date-time) or null | yes |  |
| `last_call.duration` | integer | yes | Seconds. |
| `last_call.remote_name` | string | yes |  |
| `last_call.remote_number` | string | yes |  |
| `last_call.our_number` | string | yes | Your number on the call. |
| `last_call.handled_by` | object or null | yes | The person on your side: the last extension that held an incoming call, or the one that placed an outgoing call. |
| `last_call.handled_by.extension` | string | yes |  |
| `last_call.handled_by.name` | string | yes |  |
| `last_call.summary` | string or null | yes | The first AI summary on the call. |
| `last_call.journey` | array of objects | yes |  |
| `last_call.journey[].order` | integer | yes |  |
| `last_call.journey[].type` | string | yes |  |
| `last_call.journey[].extension` | string or null | yes |  |
| `last_call.journey[].extension_name` | string or null | yes |  |
| `last_call.journey[].start_time` | string (date-time) or null | yes |  |
| `last_call.journey[].duration` | integer | yes | Seconds. |
| `last_call.journey[].summary` | string or null | yes | The AI summary of this leg, when recorded. |
| `last_call.journey[].transcription` | string or null |  | Only with `include=transcripts`. Cut at 6,000 characters, ending in ` [cut]`. |
| `last_incoming` | object or null | yes | The newest incoming call. |
| `last_incoming.call_id` | string | yes |  |
| `last_incoming.direction` | string | yes | `incoming` or `outgoing`. |
| `last_incoming.status` | string | yes | The first leg's status, as in `GET /v1/calls`. |
| `last_incoming.start_time` | string (date-time) or null | yes |  |
| `last_incoming.end_time` | string (date-time) or null | yes |  |
| `last_incoming.duration` | integer | yes | Seconds. |
| `last_incoming.remote_name` | string | yes |  |
| `last_incoming.remote_number` | string | yes |  |
| `last_incoming.our_number` | string | yes | Your number on the call. |
| `last_incoming.handled_by` | object or null | yes | The person on your side: the last extension that held an incoming call, or the one that placed an outgoing call. |
| `last_incoming.handled_by.extension` | string | yes |  |
| `last_incoming.handled_by.name` | string | yes |  |
| `last_incoming.summary` | string or null | yes | The first AI summary on the call. |
| `last_incoming.journey` | array of objects | yes |  |
| `last_incoming.journey[].order` | integer | yes |  |
| `last_incoming.journey[].type` | string | yes |  |
| `last_incoming.journey[].extension` | string or null | yes |  |
| `last_incoming.journey[].extension_name` | string or null | yes |  |
| `last_incoming.journey[].start_time` | string (date-time) or null | yes |  |
| `last_incoming.journey[].duration` | integer | yes | Seconds. |
| `last_incoming.journey[].summary` | string or null | yes | The AI summary of this leg, when recorded. |
| `last_incoming.journey[].transcription` | string or null |  | Only with `include=transcripts`. Cut at 6,000 characters, ending in ` [cut]`. |
| `last_outgoing` | object or null | yes | The newest outgoing call. |
| `last_outgoing.call_id` | string | yes |  |
| `last_outgoing.direction` | string | yes | `incoming` or `outgoing`. |
| `last_outgoing.status` | string | yes | The first leg's status, as in `GET /v1/calls`. |
| `last_outgoing.start_time` | string (date-time) or null | yes |  |
| `last_outgoing.end_time` | string (date-time) or null | yes |  |
| `last_outgoing.duration` | integer | yes | Seconds. |
| `last_outgoing.remote_name` | string | yes |  |
| `last_outgoing.remote_number` | string | yes |  |
| `last_outgoing.our_number` | string | yes | Your number on the call. |
| `last_outgoing.handled_by` | object or null | yes | The person on your side: the last extension that held an incoming call, or the one that placed an outgoing call. |
| `last_outgoing.handled_by.extension` | string | yes |  |
| `last_outgoing.handled_by.name` | string | yes |  |
| `last_outgoing.summary` | string or null | yes | The first AI summary on the call. |
| `last_outgoing.journey` | array of objects | yes |  |
| `last_outgoing.journey[].order` | integer | yes |  |
| `last_outgoing.journey[].type` | string | yes |  |
| `last_outgoing.journey[].extension` | string or null | yes |  |
| `last_outgoing.journey[].extension_name` | string or null | yes |  |
| `last_outgoing.journey[].start_time` | string (date-time) or null | yes |  |
| `last_outgoing.journey[].duration` | integer | yes | Seconds. |
| `last_outgoing.journey[].summary` | string or null | yes | The AI summary of this leg, when recorded. |
| `last_outgoing.journey[].transcription` | string or null |  | Only with `include=transcripts`. Cut at 6,000 characters, ending in ` [cut]`. |
| `counts` | object | yes |  |
| `counts.contacts` | integer | yes |  |
| `counts.calls` | integer | yes |  |
| `counts.messages` | integer | yes |  |

**Example**

```json
{
  "number": "7185550142",
  "days": 90,
  "contacts": [
    {
      "id": 5012,
      "fields": {
        "First Name": "Jordan",
        "Last Name": "Reyes",
        "Phone": "7185550142",
        "Email": "jordan.reyes@example.com",
        "Account Number": "A-2291"
      }
    }
  ],
  "calls": [
    {
      "call_id": "8a1f3c2e-5b7d-4e9a-9c1b-2d4e6f8a0b1c",
      "direction": "incoming",
      "status": "answered",
      "start_time": "2026-09-21T14:03:12.000Z",
      "end_time": "2026-09-21T14:09:40.000Z",
      "duration": 388,
      "remote_name": "Jordan Reyes",
      "remote_number": "7185550142",
      "our_number": "7185550100",
      "handled_by": {
        "extension": "104",
        "name": "Maya Chen"
      },
      "summary": "Jordan Reyes asked to move the Thursday appointment to Friday at 10 AM. Maya Chen confirmed the new time and said a reminder text will follow.",
      "journey": [
        {
          "order": 1,
          "type": "auto_attendant",
          "extension": "100",
          "extension_name": "Main Menu",
          "start_time": "2026-09-21T14:03:12.000Z",
          "duration": 19,
          "summary": null
        },
        {
          "order": 2,
          "type": "user",
          "extension": "104",
          "extension_name": "Maya Chen",
          "start_time": "2026-09-21T14:03:31.000Z",
          "duration": 369,
          "summary": "Jordan Reyes asked to move the Thursday appointment to Friday at 10 AM. Maya Chen confirmed the new time and said a reminder text will follow."
        }
      ]
    }
  ],
  "messages": [
    {
      "channel": "text",
      "direction": "incoming",
      "body": "Running 10 minutes late, see you soon.",
      "sent_time": "2026-09-21T15:22:04Z",
      "our_number": "7185550100",
      "remote_number": "7185550142"
    }
  ],
  "last_call": {
    "call_id": "8a1f3c2e-5b7d-4e9a-9c1b-2d4e6f8a0b1c",
    "direction": "incoming",
    "status": "answered",
    "start_time": "2026-09-21T14:03:12.000Z",
    "end_time": "2026-09-21T14:09:40.000Z",
    "duration": 388,
    "remote_name": "Jordan Reyes",
    "remote_number": "7185550142",
    "our_number": "7185550100",
    "handled_by": {
      "extension": "104",
      "name": "Maya Chen"
    },
    "summary": "Jordan Reyes asked to move the Thursday appointment to Friday at 10 AM. Maya Chen confirmed the new time and said a reminder text will follow.",
    "journey": [
      {
        "order": 1,
        "type": "auto_attendant",
        "extension": "100",
        "extension_name": "Main Menu",
        "start_time": "2026-09-21T14:03:12.000Z",
        "duration": 19,
        "summary": null
      },
      {
        "order": 2,
        "type": "user",
        "extension": "104",
        "extension_name": "Maya Chen",
        "start_time": "2026-09-21T14:03:31.000Z",
        "duration": 369,
        "summary": "Jordan Reyes asked to move the Thursday appointment to Friday at 10 AM. Maya Chen confirmed the new time and said a reminder text will follow."
      }
    ]
  },
  "last_incoming": {
    "call_id": "8a1f3c2e-5b7d-4e9a-9c1b-2d4e6f8a0b1c",
    "direction": "incoming",
    "status": "answered",
    "start_time": "2026-09-21T14:03:12.000Z",
    "end_time": "2026-09-21T14:09:40.000Z",
    "duration": 388,
    "remote_name": "Jordan Reyes",
    "remote_number": "7185550142",
    "our_number": "7185550100",
    "handled_by": {
      "extension": "104",
      "name": "Maya Chen"
    },
    "summary": "Jordan Reyes asked to move the Thursday appointment to Friday at 10 AM. Maya Chen confirmed the new time and said a reminder text will follow.",
    "journey": [
      {
        "order": 1,
        "type": "auto_attendant",
        "extension": "100",
        "extension_name": "Main Menu",
        "start_time": "2026-09-21T14:03:12.000Z",
        "duration": 19,
        "summary": null
      },
      {
        "order": 2,
        "type": "user",
        "extension": "104",
        "extension_name": "Maya Chen",
        "start_time": "2026-09-21T14:03:31.000Z",
        "duration": 369,
        "summary": "Jordan Reyes asked to move the Thursday appointment to Friday at 10 AM. Maya Chen confirmed the new time and said a reminder text will follow."
      }
    ]
  },
  "last_outgoing": null,
  "counts": {
    "contacts": 1,
    "calls": 1,
    "messages": 1
  }
}
```

#### 400

Fewer than 7 digits.

**Example**

```json
{
  "error": {
    "code": 400,
    "type": "BAD_REQUEST",
    "message": "number must contain at least 7 digits."
  }
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 429

A rate limit was reached. Wait `Retry-After` seconds. The body names the counter, the window and the limit.

#### 503

Try again shortly. The key could not be checked just now (`Retry-After: 5`), or a service this route needs could not be reached.
