Developer Guide
Everything you need to integrate with the Vocatech API for call reporting, messaging, faxing, contact management, and real-time webhooks.
Overview
The Vocatech API is a REST API that gives you access to:
- Call history — Search and filter call records with full journey details.
- Message history — Search SMS and WhatsApp message records.
- Messaging — Send SMS messages through your Vocatech numbers.
- Faxing — Send faxes and view fax history with document retrieval.
- Contacts — Manage integration contacts: pull lists, push updates, and sync from external systems.
- Webhooks — Subscribe to real-time call, message, and fax events delivered to your endpoint.
Base URL
https://api.vocatech.com/v1
All API endpoints are relative to this base URL. For example, to list calls:
GET https://api.vocatech.com/v1/calls
Authentication
All requests require a Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEY
Your API key is scoped to a single company. All queries are automatically filtered to your company's data.
Example Request
curl -X GET "https://api.vocatech.com/v1/calls" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
Calls
| Method | Endpoint | Description |
|---|---|---|
| GET | /calls |
Search call history with filters |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
start_date | date | No | Start date (YYYY-MM-DD). Defaults to today. |
end_date | date | No | End date (YYYY-MM-DD). Defaults to today. |
direction | string | No | incoming, outgoing, or any (default). The direction of the call that was placed: a call an employee placed through the dial-out portal is outgoing even though it reached us on an inbound leg. |
status | string | No | answered, missed, or any (default). missed never returns an outgoing call. |
search | string | No | Search by call ID, name, or extension |
page | integer | No | Page number |
limit | integer | No | Results per page (max 500) |
Example
curl "https://api.vocatech.com/v1/calls?start_date=2026-03-01&end_date=2026-03-10&direction=incoming" \
-H "Authorization: Bearer YOUR_API_KEY"
Call Journey
Each call record includes a journey array showing every segment of the call in chronological order:
| Field | Type | Description |
|---|---|---|
type | string | AutoAttendant, HuntGroup, CallCenter, or User |
name | string | Name of the entity (e.g., "Main Menu", "Sales Queue", "John Doe") |
extension | string | Extension number |
start_time | datetime | When this segment started |
duration | integer | Segment duration in seconds |
answered | boolean | Whether the call was answered in this segment |
Messages
| Method | Endpoint | Description |
|---|---|---|
| POST | /messages |
Send an SMS message |
| GET | /messages |
Search message history |
Send a Message
curl -X POST "https://api.vocatech.com/v1/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "text",
"from": "5551234567",
"to": "5559876543",
"message": "Hello from Vocatech!"
}'
"test": true to the request body to validate without actually sending. Returns a 200 with validation results instead of creating any resources.
Message History Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
start_date | date | No | Start date (YYYY-MM-DD) |
end_date | date | No | End date (YYYY-MM-DD) |
direction | string | No | incoming, outgoing, or any |
channel | string | No | text, whatsapp, or any |
status | string | No | delivered, failed, read, or any |
Faxes
| Method | Endpoint | Description |
|---|---|---|
| POST | /faxes |
Send a fax |
| GET | /faxes |
Search fax history |
Send a Fax
The file must be base64-encoded. PDF is recommended. The from number must be an active fax line assigned to your company.
curl -X POST "https://api.vocatech.com/v1/faxes" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "7183951550",
"to": "2125551234",
"file": "JVBERi0xLjQK...",
"filename": "invoice.pdf",
"recipient_name": "John Doe",
"subject": "Invoice #1042"
}'
Response (201):
{
"status": "queued",
"fax": {
"fax_id": "fax_12345",
"faxage_job_id": "98765",
"from": "7183951550",
"to": "2125551234",
"filename": "invoice.pdf",
"subject": "Invoice #1042",
"created_at": "2026-06-01T15:30:00-04:00"
}
}
"test": true to validate the request without sending. Returns a 200 with validation results.
Fax History Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
start_date | date | No | Start date (YYYY-MM-DD) |
end_date | date | No | End date (YYYY-MM-DD) |
direction | string | No | incoming, outgoing, or any |
status | string | No | in_progress, completed, failed, restricted, or any |
search | string | No | Search by phone number or subject |
Fax Documents
When a fax has has_document: true, retrieve the PDF via the media endpoint:
curl "https://api.vocatech.com/v1/media/fax_12345" \
-H "Authorization: Bearer YOUR_API_KEY"
Returns a 30-minute signed URL to the PDF file, same as call recordings.
Media
The unified GET /v1/media/{id} endpoint provides authenticated access to all media files — SMS/MMS attachments, WhatsApp media, call recordings, and fax documents. The ID prefix determines the media type.
| Method | Endpoint | Description |
|---|---|---|
| GET | /media/{id} |
Get media metadata and download URL |
ID Prefixes
| Prefix | Type | Example | Source |
|---|---|---|---|
att_ | Message attachment | /v1/media/att_456 | SMS/MMS images, WhatsApp photos, documents, voice notes |
rec_ | Call recording | /v1/media/rec_84729 | Call recording audio from call journey segments |
fax_ | Fax document | /v1/media/fax_1234 | Fax PDF documents (sent and received) |
Message Attachments (att_)
When a message includes media, the attachments array in the message response or webhook payload contains attachment objects:
{
"message_id": "sms_12345",
"direction": "incoming",
"channel": "text",
"body": "Check out this photo!",
"attachments": [
{
"id": "att_456",
"content_type": "image/jpeg",
"filename": "photo1.jpg",
"size": 245760
}
]
}
Download the attachment:
curl "https://api.vocatech.com/v1/media/att_456" \
-H "Authorization: Bearer YOUR_API_KEY"
Supported Media Types
| Channel | Supported Types |
|---|---|
| SMS/MMS | Images (JPEG, PNG, GIF), contacts, and other MMS-supported formats |
| Images, video (MP4), audio (MP3, OGG, AMR), documents (PDF, DOCX, XLSX), contacts (VCF) |
attachments field is always present. For text-only messages it will be [].
Call Recordings (rec_)
Call recordings are accessed through the same /v1/media endpoint using the rec_ prefix with the segment ID from the call journey.
How It Works
- Call
GET /v1/callsto retrieve call reports — each journey segment includes arecording_urlfield (null if no recording exists). - The
recording_urlpoints to/v1/media/rec_{segment_id}— call it to get a 30-minute signed download URL. - Download the MP3 file directly using the signed URL (no auth header needed for the download itself).
curl "https://api.vocatech.com/v1/media/rec_84729" \
-H "Authorization: Bearer YOUR_API_KEY"
Recording Response
{
"id": "rec_84729",
"type": "recording",
"content_type": "audio/mp3",
"format": "mp3",
"duration": 185,
"url": "https://storage.googleapis.com/...signed...",
"expires_at": "2026-03-20T19:30:00.000Z"
}
Notes
- Signed URLs expire after 30 minutes. Request a new URL by calling the endpoint again.
- Only media belonging to your company is accessible — ownership is verified automatically.
- Not all call segments have recordings. Check the
recording_urlfield in the call journey — if null, no recording is available.
Support
Open a ticket with Vocatech support from your own system. A help desk sync, an automation step, or an AI voice agent's "After a message" action posts here; the ticket belongs to your account, lands in our support inbox like an email or a text does, and is worked the same way.
| Method | Endpoint | Description |
|---|---|---|
| POST | /support/tickets |
Open a ticket (or add to one by reference) |
Open a Ticket
curl -X POST "https://api.vocatech.com/v1/support/tickets" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "My fax line has been down since this morning. Please call me back.",
"contact_name": "David Green",
"contact_phone": "7185550142",
"reply_by": "call",
"reference": "your-own-id-for-this-matter",
"source": "AI front desk"
}'
Response (201):
{
"ticket": {
"key": "case-20260908-121500-5be629593d2e9aa564f78a2d7fcb1f67",
"subject": "Message from David Green, Sep 8, 8:15 AM",
"status": "open",
"created_at": "2026-09-08T12:15:00+00:00",
"appended": false
}
}
Fields
| Field | Required | Description |
|---|---|---|
message | Yes | What the person needs, in their own words. Up to 4,000 characters. |
subject | No | One line. Defaults to the contact and the time. |
contact_name, contact_phone, contact_email | No | Who to get back to. Phones are 10 digit US numbers; formatting is stripped. A nested contact object with name, phone, email is accepted too. |
reply_by | No | call, text or email: how the person asked to be reached back. |
caller_id | No | The number the person called from, when it differs from contact_phone. |
reference | No | Your own id for the matter (letters, digits, . _ : -, up to 80). The same reference within 7 days adds the message to the ticket it opened (200, appended: true). A ticket key you were given reports back on that ticket. |
source | No | The system that opened it, shown on the ticket. |
POST https://api.vocatech.com/v1/support/tickets, header {"Authorization": "Bearer {{cred}}"} with your key stored as the credential, and a body template such as
{"message":"{{message}}","contact_name":"{{caller_name}}","contact_phone":"{{callback_number}}","contact_email":"{{email}}","reply_by":"{{reply_by}}","caller_id":"{{caller_number}}","reference":"{{call_sid}}","source":"{{agent_name}}"}.
Then pick it under Routing, Messages, "After a message", and choose "An action only" as the delivery when you want no copy by email. The same shape reaches any other ticketing system: change the URL, the header and the body to its API.
Contacts
Manage your integration contacts programmatically. Pull existing contact lists, push new contacts, or sync from external systems like CRMs and property management software.
| Method | Endpoint | Description |
|---|---|---|
| GET | /contacts/fields |
List your contact field schema |
| GET | /contacts |
List contacts (paginated, searchable) |
| POST | /contacts |
Create or update contacts (single or batch) |
| DELETE | /contacts |
Delete contacts (single or batch) |
Step 1: Get Your Field Schema
Before pushing contacts, call GET /contacts/fields to see what fields are configured for your company:
curl "https://api.vocatech.com/v1/contacts/fields" \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
{
"fields": [
{ "id": 12, "name": "Company", "is_phone": false, "is_match": true, "is_integration": false },
{ "id": 18, "name": "Phone", "is_phone": true, "is_match": false, "is_integration": false },
{
"id": 20, "name": "Portal", "is_phone": false, "is_match": false,
"is_integration": true,
"integration": {
"id": 1, "initials": "AP",
"fields": [
{ "key": "contact_url", "display_name": "Contact URL", "value": "https://admin.vocatech.com/company/?company_id={CONTACT_ID}" },
{ "key": "default_url", "display_name": "Default URL", "value": "https://admin.vocatech.com" }
]
}
}
]
}
- Match fields (
is_match: true) are used for deduplication. When you push a contact, if ALL match fields match an existing contact, it updates instead of creating a duplicate. - Phone fields (
is_phone: true) auto-format values to digits. They support multiple numbers separated by semicolons (e.g.,"5551234567;5559876543"). - Integration fields (
is_integration: true) are clickable connectors with initials and URLs. The field value acts as a reference ID for generating links.
Step 2: List Existing Contacts
curl "https://api.vocatech.com/v1/contacts?page=1&limit=100" \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
{
"query": { "page": 1, "limit": 100, "search": null },
"contacts": [
{
"id": 181119,
"fields": {
"Company": "ABC Mortgage",
"Contact": "John Smith",
"Phone": "2125551234;2125559999;2125550100",
"Portal": "9105222634"
}
}
],
"meta": {
"page": 1,
"limit": 100,
"total_pages": 19,
"total_contacts": 1858
}
}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Results per page, max 500 (default: 100) |
search | string | No | Search across all field values |
Step 3: Push Contacts
Send a single contact:
curl -X POST "https://api.vocatech.com/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"Company": "ABC Mortgage",
"Contact": "John Smith",
"Phone": "2125551234;2125559999;2125550100",
"Portal": "9105222634"
}
}'
Response (201 if created, 200 if updated):
{
"summary": { "total": 1, "created": 1, "updated": 0, "errors": 0 },
"contacts": [
{
"contact": {
"id": 181119,
"fields": {
"Company": "ABC Mortgage",
"Contact": "John Smith",
"Phone": "2125551234;2125559999;2125550100",
"Portal": "9105222634"
}
},
"action": "created"
}
],
"errors": []
}
"5551234567;5559876543;5554441111". Each number is formatted individually and duplicates are removed.
Batch Push
Send up to 500 contacts at once by using the contacts array:
curl -X POST "https://api.vocatech.com/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "fields": { "Company": "ABC Mortgage", "Phone": "2125551234;2125559999", "Portal": "9105222634" } },
{ "fields": { "Company": "XYZ Corp", "Phone": "7185558888", "Portal": "9105333745" } }
]
}'
Response:
{
"summary": { "total": 2, "created": 1, "updated": 1, "errors": 0 },
"contacts": [ ... ],
"errors": []
}
Delete Contacts
Delete a single contact by ID:
curl -X DELETE "https://api.vocatech.com/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "id": 12345 }'
Delete multiple contacts:
curl -X DELETE "https://api.vocatech.com/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "ids": [12345, 12346, 12347] }'
Webhooks
Webhooks let you receive real-time notifications when call, message, or fax events occur. Create an endpoint, subscribe to events, and we'll send HTTP POST requests to your URL.
| Method | Endpoint | Description |
|---|---|---|
| GET | /webhooks |
List all webhook endpoints |
| POST | /webhooks |
Create a webhook endpoint |
| GET | /webhooks/{id} |
View a webhook endpoint |
| PATCH | /webhooks/{id} |
Update a webhook endpoint |
| DELETE | /webhooks/{id} |
Delete a webhook endpoint |
| POST | /webhooks/{id}/test |
Send a test delivery |
| GET | /webhooks/failures |
List failed deliveries |
Create a Webhook
curl -X POST "https://api.vocatech.com/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My CRM Webhook",
"url": "https://yourapp.com/webhook",
"event_filters": ["call.started", "call.ended", "message.received", "message.sent"]
}'
secret_key is only returned when you create the webhook. Store it securely — you'll need it to verify delivery signatures.
Event Types
| Event | When it fires |
|---|---|
call.started | A call leg begins (dialled or received) |
call.answered | A call leg is answered |
call.ended | A call leg finishes |
call.transcription | AI processing completes (5-30 min after call ends) |
| Message Events | |
message.sent | An outgoing SMS or WhatsApp message is sent |
message.received | An inbound SMS or WhatsApp message arrives |
message.status_updated | Delivery status changes (sent → delivered → read) |
| Fax Events | |
fax.sent | An outgoing fax is queued for delivery |
fax.received | An inbound fax arrives |
fax.delivered | An outgoing fax is confirmed delivered |
fax.failed | A fax transmission failed |
Subscribe to one or more events via the event_filters array. You can mix call, message, and fax events in a single webhook.
You can also filter by extension using extension_filters to only receive call events for specific extensions.
Webhook Payloads
When an event fires, your endpoint receives a JSON POST with this structure:
{
"id": "evt_67890abcdef",
"event_type": "call.ended",
"timestamp": "2026-03-10T14:30:45+00:00",
"company_id": 123,
"data": {
"call_id": "c045ac31-abbe-45dc-9722-efbe4044cc73",
"direction": "incoming",
"extension": "101",
"extension_name": "John Doe",
"remote_name": "Jane Smith",
"remote_number": "5551234567",
"group_number": "5559876543",
"start_time": "2026-03-10T14:30:00.000Z",
"end_time": "2026-03-10T14:31:10.000Z",
"duration": 70
}
}
For call.started and call.answered events, the end_time and duration fields are not included.
Message Event Payload
Message events use the same envelope with a different data shape. The attachments array is always present (empty for text-only messages):
{
"id": "evt_msg_abc12345",
"event_type": "message.received",
"timestamp": "2026-03-15T14:30:45+00:00",
"company_id": 123,
"data": {
"message_id": "sms_789012",
"direction": "incoming",
"channel": "text",
"from": "+14155551234",
"to": "+17185550000",
"body": "Check out this photo!",
"status": "delivered",
"sent_at": "2026-03-15T14:30:45.000Z",
"attachments": [
{
"url": "https://api.vocatech.com/v1/media/att_456",
"content_type": "image/jpeg",
"filename": "photo1.jpg",
"size": 245760
}
]
}
}
| Field | Type | Description |
|---|---|---|
message_id | string | Unique message identifier |
direction | string | incoming or outgoing |
channel | string | text (SMS) or whatsapp |
from | string | Sender phone number (E.164) |
to | string | Recipient phone number (E.164) |
body | string | Message text content |
status | string | sent, delivered, read, or failed |
sent_at | datetime | When the message was sent (UTC) |
attachments | array | Media attachments (empty array [] for text-only messages). Each object has url, content_type, filename, and size. |
For message.status_updated events, the data object includes the new status value and an updated_at timestamp. The read status is only available for WhatsApp messages.
Fax Event Payload
Fax events follow the same envelope. The fax_id can be used with GET /media/fax_{id} to download the PDF document.
{
"id": "evt_fax_abc12345",
"event_type": "fax.received",
"timestamp": "2026-06-01T14:30:45+00:00",
"company_id": 123,
"data": {
"fax_id": "fax_1234",
"direction": "incoming",
"from": "+12125551234",
"to": "+17183951550",
"pages": 3,
"subject": null,
"status": "completed",
"faxage_job_id": "98765",
"sent_at": "2026-06-01T14:30:45.000Z"
}
}
| Field | Type | Description |
|---|---|---|
fax_id | string | Unique fax identifier (use with /media/{fax_id}) |
direction | string | incoming or outgoing |
from | string | Sender fax number (E.164) |
to | string | Recipient fax number (E.164) |
pages | integer | Page count (null if not yet known) |
subject | string | Subject line (null if none) |
status | string | in_progress, completed, failed, or restricted |
faxage_job_id | string | Faxage provider job ID for tracking |
sent_at | datetime | When the fax was sent or received (UTC) |
For call.transcription events, the data object also includes:
| Field | Type | Description |
|---|---|---|
summary | string | AI-generated call summary |
transcription | string | Full transcript text |
Signature Verification
Every delivery includes an X-Vocatech-Signature header for security:
X-Vocatech-Signature: t=1710000000,v1=5257a8...b4c2e9
How to Verify
1. Extract the timestamp (t) and signature (v1) from the header.
2. Compute the expected signature:
// PHP
$expected = hash_hmac('sha256', "t={$timestamp}.{$rawBody}", $secretKey);
// Node.js
const expected = crypto
.createHmac('sha256', secretKey)
.update(`t=${timestamp}.${rawBody}`)
.digest('hex');
// Python
import hmac, hashlib
expected = hmac.new(secret_key.encode(), f"t={timestamp}.{raw_body}".encode(), hashlib.sha256).hexdigest()
3. Compare: if v1 matches your computed value, the delivery is authentic.
4. Replay protection: reject if abs(current_time - t) > 300 (5 minutes).
Retry Policy
If your endpoint returns a non-2xx status code or times out, we retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
After 5 total attempts, the delivery is marked as failed. View failures at GET /webhooks/failures.
Webhook Testing
Two ways to test your webhooks:
1. Test Tool (no server needed)
Use our interactive test tool at api.vocatech.com/test. It creates a temporary receiver URL so you can see exactly what payloads look like.
2. Send a Test Delivery
Send a test event to your actual endpoint:
curl -X POST "https://api.vocatech.com/v1/webhooks/{id}/test" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
This sends a webhook.test event to the endpoint's URL with a valid signature.
Rate Limits
Rate limits are applied per API key:
| Tier | Endpoints | Limits |
|---|---|---|
| Default | Most endpoints | 600/min • 3,600/hr • 100,000/day |
| Reports | GET /calls, GET /messages |
300/min • 3,000/hr • 100,000/day |
| Messaging | POST /messages |
20/min • 200/day |
When exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait.
Need more messaging volume?
The Messaging limit above is the standard key's included allowance, meant for regular day-to-day sending. For higher volume there is API Messaging: a separate, messaging-only API key with the same endpoints and authentication, higher limits (60 messages per minute and 5,000 per day as standard), and per-message billing. Batches beyond the per-minute rate are accepted and queued for you (202) instead of rejected. It is enabled per company in the Portal under Textdock → API Messaging, which also shows usage and terms.
Errors
The API uses standard HTTP status codes. Error responses include a JSON body:
{
"error": {
"code": 404,
"type": "RESOURCE_NOT_FOUND",
"message": "Webhook endpoint not found."
}
}
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 204 | Deleted (no content) |
| 400 | Bad request (missing or invalid fields) |
| 401 | Unauthorized (invalid or missing API key) |
| 403 | Forbidden (no access to this resource) |
| 404 | Not found |
| 409 | Conflict (deprecated — existing resources are now reused) |
| 422 | Validation error |
| 429 | Rate limit exceeded |
Pagination
List endpoints support pagination with page and limit query parameters:
GET /v1/calls?page=2&limit=50
Responses include a meta object:
{
"meta": {
"page": 2,
"limit": 50,
"total_pages": 10,
"total_calls": 487
}
}
Need help? Contact support@vocatech.com