By area
Faxes
Read from the API’s own reference file every hour.
Send faxes from your fax lines and read your fax history.
Send a fax
POST/v1/
Sends one fax from one of your fax lines. Send the document as a base64-encoded PDF of at most 25 pages, up to 20 MB before encoding. Another file type, a longer PDF, or a PDF whose pages cannot be read is refused with 400.
Limits. Per account: 3 requests a minute and 50 faxes sent a day (Eastern calendar day). The fax API is for sending your own documents, not for marketing or bulk faxing.
from must be an active fax line on your account. to must be a valid 10-digit US or Canadian number. Premium numbers (900, 976), service codes and area codes that are other countries are refused.
The answer is 201 with a fax_id once the fax carrier has the job. Sending takes minutes. Read the outcome in GET /v1/faxes (status), and fetch the document with GET /v1/media/{fax_id}.
Dry run. test: true runs every check (the numbers, the file and its pages, your line, the monthly limit) and sends nothing. It counts toward the 3 a minute, never toward the 50 a day.
Monthly limit. Each account may send 1,000 fax pages a month for each fax line on it (unless Vocatech set another figure), pooled across its lines, counted by the Eastern calendar month across the API, the portal and email to fax. Past it, the answer is 429. Another fax line brings another 1,000 pages a month; contact Vocatech support for a line or a different figure.
Request
curl -X POST https://api.vocatech.com/v1/faxes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "7185550100",
"to": "7185550142",
"filename": "intake-form.pdf",
"subject": "Intake form",
"file": "'"$(base64 < intake-form.pdf | tr -d '\n')"'"
}'import { readFile } from "node:fs/promises";
const res = await fetch("https://api.vocatech.com/v1/faxes", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "7185550100",
to: "7185550142",
filename: "intake-form.pdf",
subject: "Intake form",
file: (await readFile("intake-form.pdf")).toString("base64"),
}),
});
console.log(res.status, await res.json());import base64
import requests
with open("intake-form.pdf", "rb") as f:
document = base64.b64encode(f.read()).decode()
r = requests.post(
"https://api.vocatech.com/v1/faxes",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"from": "7185550100",
"to": "7185550142",
"filename": "intake-form.pdf",
"subject": "Intake form",
"file": document,
},
timeout=60,
)
print(r.status_code, r.json())<?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->post('/v1/faxes', [
'json' => [
'from' => '7185550100',
'to' => '7185550142',
'filename' => 'intake-form.pdf',
'subject' => 'Intake form',
'file' => base64_encode(file_get_contents('intake-form.pdf')),
],
]);
echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;Body
| Field | Type | Description |
|---|---|---|
from required | string | One of your active fax lines, 10 digits. Punctuation and a leading 1 are ignored. For example 7185550100. |
to required | string | The recipient's fax number: a valid 10-digit US or Canadian number. Premium numbers (900, 976) and area codes that are other countries are refused. For example 7185550142. |
file required | string (base64) | The document as base64. Send a PDF. Up to 20 MB before encoding. |
filename | string | The document's file name. Default document.pdf. |
subject | string | A subject for your records. It shows in GET /v1/faxes. |
recipient_name | string | The recipient's name, passed to the fax carrier. |
test | boolean | A dry run: check everything and send nothing. Default false. |
{
"from": "7185550100",
"to": "7185550142",
"filename": "intake-form.pdf",
"subject": "Intake form",
"recipient_name": "Jordan Reyes",
"file": "JVBERi0xLjQKJcfsj6IKMSAwIG9iago8PC9UeXBlL0NhdGFsb2c+PgplbmRvYmoK"
}Answers
| Field | Type | Description |
|---|---|---|
status required | string | Always test. |
fax required | object | |
fax. required | string | |
fax. required | string | |
fax. required | string | |
fax. required | string | The fax line's name in the portal. |
fax. required | boolean | Always true. |
{
"status": "test",
"fax": {
"from": "7185550100",
"to": "7185550142",
"filename": "intake-form.pdf",
"fax_line": "Front Desk Fax",
"valid": true
}
}| Field | Type | Description |
|---|---|---|
status required | string | Always queued. |
fax required | object | |
fax. required | string | The fax's id. Use it with GET /v1/media/{id}. Matches ^fax_[0-9]+$. |
fax. required | string or null | The fax carrier's job id. |
fax. required | string | |
fax. required | string | |
fax. required | string | |
fax. required | string or null | |
fax. required | string | When we took it, ISO 8601 with the Eastern offset. |
{
"status": "queued",
"fax": {
"fax_id": "fax_77120",
"faxage_job_id": "93125501",
"from": "7185550100",
"to": "7185550142",
"filename": "intake-form.pdf",
"subject": "Intake form",
"created_at": "2026-09-24T10:05:09-04:00"
}
}Example bodies
{
"error": {
"code": 400,
"type": "BAD_REQUEST",
"message": "Missing required fields: from, to, and file are required."
}
}{
"error": {
"code": 400,
"type": "BAD_REQUEST",
"message": "Invalid to number format. Expected 10 digits."
}
}{
"error": {
"code": 400,
"type": "BAD_REQUEST",
"message": "to may not be a premium-rate number (900)."
}
}{
"error": {
"code": 400,
"type": "BAD_REQUEST",
"message": "to is outside the US and Canada (area code 876). International numbers are not dialed through the API."
}
}{
"error": {
"code": 400,
"type": "BAD_REQUEST",
"message": "File must be base64-encoded."
}
}from is not an active fax line of yours, the key's scopes, or the address.Example bodies
{
"error": {
"code": 403,
"type": "FORBIDDEN",
"message": "Faxing is not enabled for this company."
}
}{
"error": {
"code": 403,
"type": "FORBIDDEN",
"message": "Invalid from number. Must be an active fax line assigned to your company."
}
}Example body
{
"error": {
"code": 413,
"type": "ERROR",
"message": "The document is too large. The limit is 20 MB."
}
}faxes counter is full (3 a minute per account), the account has sent its 50 faxes for the day, or the account's monthly page limit is used. The day and monthly limits answer with an action error and no Retry-After.| Header | Type | Description |
|---|---|---|
Retry-After | integer | Seconds to wait before you try again. |
Example body
{
"error": {
"code": 429,
"type": "RATE_LIMIT_EXCEEDED",
"message": "This account has used its 3,000 outbound fax pages for the month, 1,000 pages for each of its 3 fax lines. Contact Vocatech support to raise it."
}
}Example body
{
"error": {
"code": 500,
"type": "SERVER_ERROR",
"message": "cURL error 28: Operation timed out after 60000 milliseconds with 0 bytes received"
}
}Retry-After: 5), or a service this route needs could not be reached.| Header | Type | Description |
|---|---|---|
Retry-After | integer | Seconds to wait before you try again. |
List faxes
GET/v1/
Your sent and received faxes in a date window, newest first.
Dates work as in GET /v1/calls: start_date and end_date together as local days in timezone, or neither for today, at most 366 days. The response echoes the local days you asked for.
Paging. limit is 500 by default and at most 500. Faxes that tie on time keep a stable order.
has_document says whether GET /v1/media/{fax_id} can return the PDF. Answers 403 when faxing is not on for your account.
Known issue. Fax times are stored in Eastern time. sent_at shows that Eastern clock time with a Z, so it reads 4 or 5 hours early, and the date window is compared the same way: a fax from midnight to about 4 AM Eastern lands in the previous day's window.
Each request is written to your account's HIPAA access log.
Request
curl -G https://api.vocatech.com/v1/faxes \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "start_date=2026-09-24" \
--data-urlencode "end_date=2026-09-24"const params = new URLSearchParams({
start_date: "2026-09-24",
end_date: "2026-09-24",
});
const res = await fetch(`https://api.vocatech.com/v1/faxes?${params}`, {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
console.log(res.status, await res.json());import requests
r = requests.get(
"https://api.vocatech.com/v1/faxes",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={
"start_date": "2026-09-24",
"end_date": "2026-09-24",
},
timeout=30,
)
print(r.status_code, r.json())<?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/faxes', [
'query' => [
'start_date' => '2026-09-24',
'end_date' => '2026-09-24',
],
]);
echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;Parameters
| Name | In | Type | Description |
|---|---|---|---|
start_date | query | string (date) | First local day of the window, YYYY-MM-DD, in timezone. Send it with end_date. Send neither for today. A single date is ignored and you get today. For example 2026-09-01. |
end_date | query | string (date) | Last local day of the window, YYYY-MM-DD, included in full. At most 366 days after start_date. For example 2026-09-07. |
timezone | query | string | IANA time zone for the dates, for example America/Chicago. An unknown zone falls back to America/New_York. The response echoes the zone used. Default America/New_York. |
direction | query | string | incoming (received) or outgoing (sent). One of any, incoming or outgoing. Default any. |
status | query | string | Only faxes in this status. One of any, in_progress, completed, failed or restricted. Default any. |
search | query | string | Text to find in the other party's number or the subject. |
sort | query | string | Order by time. Rows that tie keep a stable order, so paging is safe. One of day_desc or day_asc. Default day_desc. |
page | query | integer | Page number, from 1. Default 1. At least 1. |
limit | query | integer | Rows per page, 1 to 500. A value outside that range is brought inside it. Default 500. From 1 to 500. |
Answers
| Field | Type | Description |
|---|---|---|
query required | object | The filters that were applied, echoed back. |
query. | integer | |
query. | string (date) | |
query. | string (date) | |
query. | string | |
query. | string | |
query. | string | |
query. | string | |
faxes required | array of objects | |
faxes[]. required | string | Use it with GET /v1/media/{id} to get the document. Matches ^fax_[0-9]+$. |
faxes[]. required | string | One of incoming or outgoing. |
faxes[]. required | string | One of in_progress, completed, failed, restricted or unknown. |
faxes[]. required | string or null | Digits without the leading 1. |
faxes[]. required | string or null | Digits without the leading 1. |
faxes[]. required | integer or null | |
faxes[]. required | string or null | |
faxes[]. required | string or null | The fax carrier's job id. |
faxes[]. required | boolean | True when GET /v1/media/{fax_id} can return the PDF. |
faxes[]. required | string (date-time) or null | When the fax was logged. Known issue: this is Eastern time written with a Z, so it reads 4 or 5 hours early. |
meta required | object | Paging for list routes. Each list adds its own total. |
meta. required | integer | This page, from 1. |
meta. required | integer | Rows per page, after limits were applied. |
meta. required | integer | How many pages there are. |
meta. required | integer | Faxes that match, across all pages. |
{
"query": {
"company_id": 1234567890,
"start_date": "2026-09-24",
"end_date": "2026-09-24",
"direction": "any",
"status": "any",
"search": "",
"timezone": "America/New_York"
},
"faxes": [
{
"fax_id": "fax_77135",
"direction": "incoming",
"status": "completed",
"from": "2125550187",
"to": "7185550100",
"pages": 2,
"subject": null,
"faxage_job_id": "93125877",
"has_document": true,
"sent_at": "2026-09-24T11:40:00.000Z"
},
{
"fax_id": "fax_77120",
"direction": "outgoing",
"status": "completed",
"from": "7185550100",
"to": "7185550142",
"pages": 3,
"subject": "Intake form",
"faxage_job_id": "93125501",
"has_document": true,
"sent_at": "2026-09-24T10:05:09.000Z"
}
],
"meta": {
"page": 1,
"limit": 500,
"total_pages": 1,
"total_faxes": 2
}
}YYYY-MM-DD, an end before the start, or a window over 366 days.Example body
{
"error": {
"code": 400,
"type": "BAD_REQUEST",
"message": "Invalid date format or timezone."
}
}Example body
{
"error": {
"code": 403,
"type": "FORBIDDEN",
"message": "Faxing is not enabled for this company."
}
}Retry-After seconds. The body names the counter, the window and the limit.| Header | Type | Description |
|---|---|---|
Retry-After | integer | Seconds to wait before you try again. |
Retry-After: 5), or a service this route needs could not be reached.| Header | Type | Description |
|---|---|---|
Retry-After | integer | Seconds to wait before you try again. |
