Fax-IQ API
Base URL
https://api.fax-iq.comGetting started
Fax-IQ is a JSON API with one multipart endpoint. Every request goes to https://api.fax-iq.com, carries an API key, and answers JSON. An account admin creates keys in Admin-IQ, under Fax-IQ → API → Keys (not a customer yet? Request access). With one, send your first fax from the command line:
curl -X POST 'https://api.fax-iq.com/api/v1/faxes' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-F to=+12695550123 \
-F quality=fine \
-F file=@invoice.pdfAuthentication
Put your key in the Authorization header as a bearer token. It identifies your account, so every request is scoped to you automatically. There is no account parameter to pass, and none that would let you read anyone else's faxes.
curl 'https://api.fax-iq.com/api/v1/faxes?limit=5' \
-H 'Authorization: Bearer YOUR_API_KEY' Each key carries one or both scopes, chosen when you create it. A request to an endpoint your key does not have the scope for answers 403. A key also carries a source (keys made in Admin-IQ are api), which is stamped on every fax it sends, so your reports can tell your integration's faxes from ones sent through Admin-IQ or by email.
| Scope | Grants |
|---|---|
| faxes:send | Send faxes and cancel ones still in progress. |
| faxes:read | List and fetch your faxes, their event trail, their documents and your stats. |
Sending, and what happens next
POST /api/v1/faxes answers 202: the fax is accepted and queued, not sent. Transmission takes from thirty seconds to several minutes, and a busy or unanswered line is retried. There are two ways to learn the outcome. Ask TAS Technologies Group to register a callback endpoint for you and you will be sent a signed fax.delivered or fax.failed event; or poll GET /api/v1/faxes/:id every few seconds, backing off after the first minute, until the status is terminal.
| Status | Meaning |
|---|---|
| queued | Waiting for the send worker. This is also the state between retry attempts. |
| processing | Being submitted to the carrier. |
| sending | The carrier accepted it; transmission in progress. |
| delivered | Sent successfully. Terminal, and definitive: page count and duration are filled in. |
| failed | Gave up after all attempts. Terminal; errorCode and errorMessage say why. |
| canceled | Canceled by you. Terminal. |
| receiving | An inbound fax in progress. |
| received | An inbound fax stored and available. Terminal. |
Inbound faxes appear in the same list with direction: inbound. They are also emailed to the addresses configured on the receiving number, which you can see on Fax-IQ → Numbers & settings in Admin-IQ.
Responses
A successful response is wrapped: { "success": true, "data": … }. On list endpoints, pagination sits beside data rather than inside it. GET /api/v1/health is the one exception and is not wrapped at all.
Every response carries an x-correlation-id header. Send us that value when something looks wrong, because it is how we find your request in our logs. You can set it yourself on the way in and we will echo it back.
Errors
A failure answers a JSON body with an error object. On a 400, data.fieldErrors names the field:
{
"error": {
"message": "Validation failed",
"code": "VALIDATION_ERROR",
"correlationId": "7c1f0b6e-2a94-4c3d-8f51-0d9e6a2b4c88",
"data": { "formErrors": [], "fieldErrors": { "to": ["Invalid phone number"] } }
}
}| Status | Meaning |
|---|---|
| 202 | A send was accepted and queued. It has not been sent yet, so watch the status. |
| 400 | Validation failed (data.fieldErrors names the field), or the document is unusable because it is encrypted or over 100 pages. |
| 401 | The key is missing, wrong, revoked or expired. All four look the same from outside. |
| 403 | The key is valid but does not carry the scope this endpoint needs, or your account is inactive at the fax service. |
| 404 | No such fax. A fax that belongs to another account also answers 404. |
| 409 | The action conflicts with the fax's state, such as canceling one already delivered. |
| 413 | The document is over 20 MB. |
| 429 | Rate limited. Wait for the window in `x-ratelimit-reset` and retry. |
Rate limits
Every response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset. The last is the number of seconds until the window resets, not a timestamp. The ceiling is roughly 100 requests a minute per source address, so poll gently and prefer callbacks.
Endpoints
/api/v1/faxesfaxes:sendSend a fax
Queues a document for transmission from one of your fax numbers. Send it as multipart/form-data with the document in a part named file, or as JSON with either a public https mediaUrl or base64 content. PDF is used as-is; PNG, JPEG and plain text are converted to PDF.
Body fields
| Name | Type | Notes |
|---|---|---|
| to* | string | The destination fax number. Any common format; stored as E.164 |
| from | string | Which number to send from. Required only when you have more than one. One of your active fax numbers |
| file | file | The document, as a multipart part. Exactly one of file, mediaUrl or content. PDF, PNG, JPEG or text; 20 MB; 100 pages |
| mediaUrl | string | Where to fetch the document from, in a JSON body. Public https URL |
| content | string | The document inline, in a JSON body, with contentType and filename. base64 |
| contentType | string | The type of the inline document. JSON with content only. MIME type, e.g. application/pdf |
| quality | string | Transmission resolution. standard, fine, superfine · defaults to |
| maxAttempts | integer | How many attempts to make; overrides your account default, which is 3 unless one has been set. A busy or unanswered line, or any carrier failure other than a document or resolution problem, is retried until this is reached. 1–10 · defaults to |
| retryDelays | integer[] | Seconds to wait before each retry, e.g. [60, 300, 900]; the last entry repeats. Overrides your account default. In multipart, pass it as a string: "60,300,900". 1–10 entries, each 10–86400 seconds |
| statusCallbackUrl | string | An extra callback URL for this fax only, in addition to your registered endpoints. https URL only |
| metadata | object | Your own key/value pairs, echoed on the resource and in every callback. In multipart, pass it as a JSON string. Flat, up to 20 keys of up to 64 characters; values a string (up to 512 characters), number, boolean or null |
| filename | string | Display name for the document. Up to 255 characters |
| sourceSystem | string | The system behind the send, for your own reporting. Runs of whitespace are collapsed to one space. Up to 64 characters: letters, digits, spaces, dots, underscores and dashes, starting with a letter or digit |
| sourceReference | string | Your own id for this fax, such as a job or document number. Up to 255 characters |
| account | string | The account number in your own system. When omitted, metadata.DATA5 is used if present. Up to 255 characters |
Request body
{
"to": "+12695550123",
"mediaUrl": "https://example.com/invoice.pdf",
"quality": "fine",
"metadata": { "invoice": "INV-1001" }
}Example
curl -X POST 'https://api.fax-iq.com/api/v1/faxes' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "to": "+12695550123", "mediaUrl": "https://example.com/invoice.pdf", "quality": "fine", "metadata": { "invoice": "INV-1001" } }'Response
{
"success": true,
"data": {
"id": "0c0b5f2c-8d1e-4b7a-9f3c-2a6d5e8b1c04",
"direction": "outbound",
"status": "queued",
"source": "api",
"from": "+12695550100",
"to": "+12695550123",
"quality": "fine",
"numPages": null,
"attemptCount": 0,
"maxAttempts": 3,
"retryDelays": null,
"originalFilename": "invoice.pdf",
"mediaUrl": "https://api.fax-iq.com/api/v1/faxes/0c0b5f2c-8d1e-4b7a-9f3c-2a6d5e8b1c04/media",
"createdAt": "2026-09-22T20:00:00.000Z",
"sentAt": null,
"completedAt": null,
"updatedAt": "2026-09-22T20:00:00.000Z",
"…": "the remaining fields, as on the list"
}
}- The response is 202 Accepted, and it means accepted, not sent. A fax takes from thirty seconds to several minutes. Register a callback endpoint or poll GET /api/v1/faxes/:id until the status is delivered, failed or canceled.
- A key carries its own source, so omit that field. Sending a different value is a 400.
- Retry spacing is resolved per fax when it is created: retryDelays on the request, else your account default, else the server default (2, 4, 6 minutes…). Omit it to inherit. The value used is echoed on the resource; null means the server default.
- A document over 20 MB answers 413, and one over 100 pages answers 400. Encrypted PDFs are rejected, and from and to must differ.
/api/v1/faxesfaxes:readList faxes
Returns your faxes, newest first unless you pass sort. Every filter is optional and they combine. Results are always scoped to your account. There is no parameter that widens them.
Query parameters
| Name | Type | Notes |
|---|---|---|
| limit | integer | How many faxes to return. 1–100 · defaults to |
| offset | integer | How many to skip, for paging. 0 or more · defaults to |
| direction | string | Filter by direction. inbound, outbound |
| status | string | Filter by one or more statuses: status=failed,canceled, or repeat the parameter. queued, processing, sending, delivered, failed, canceled, receiving, received |
| from | string | Exact match on the sending number, after normalising it. Any common format |
| to | string | Exact match on the destination number, after normalising it. Any common format |
| source | string | Who submitted the fax. api, portal, windows_client, email |
| sourceSystem | string | Exact match on the system named on the fax when it was sent. |
| sourceReference | string | Exact match on your own id for the fax. |
| account | string | Exact match on the account number. |
| q | string | Case-insensitive search within account, from, to and sourceReference. Up to 100 characters |
| createdAfter | string | Only faxes created at or after this moment. Must not be later than createdBefore. ISO 8601 datetime |
| createdBefore | string | Only faxes created at or before this moment. ISO 8601 datetime |
| sort | string | Which column to sort on. Faxes without a completedAt or account sort last either way. createdAt, completedAt, status, to, from, account · defaults to |
| order | string | Sort direction. asc, desc · defaults to |
Example
curl -X GET 'https://api.fax-iq.com/api/v1/faxes' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": [
{
"id": "0c0b5f2c-8d1e-4b7a-9f3c-2a6d5e8b1c04",
"direction": "outbound",
"status": "delivered",
"source": "api",
"sourceSystem": "Billing",
"sourceReference": "INV-1001",
"account": "ACCT-4471",
"from": "+12695550100",
"to": "+12695550123",
"quality": "fine",
"numPages": 3,
"attemptCount": 1,
"maxAttempts": 3,
"retryDelays": null,
"errorCode": null,
"errorMessage": null,
"remoteStationId": null,
"durationSeconds": 212,
"metadata": { "invoice": "INV-1001" },
"statusCallbackUrl": null,
"originalFilename": "invoice.pdf",
"mediaUrl": "https://api.fax-iq.com/api/v1/faxes/0c0b5f2c-8d1e-4b7a-9f3c-2a6d5e8b1c04/media",
"createdAt": "2026-09-22T20:00:00.000Z",
"sentAt": "2026-09-22T20:00:02.000Z",
"completedAt": "2026-09-22T20:03:41.000Z",
"updatedAt": "2026-09-22T20:03:41.000Z"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0 },
"clientId": "3c9a77e2-0b41-4f8d-9a16-2d5e8c4b1f07"
}- pagination and clientId sit beside data, not inside it. clientId is always your own account.
- Ties in the sort order are broken by createdAt and then id, so pages never overlap.
/api/v1/faxes/:idfaxes:readFetch one fax
One fax by id, with its current status and an attempts array holding one entry per carrier attempt. This is how you follow up on something you sent.
Path parameters
| Name | Type | Notes |
|---|---|---|
| id* | string | The fax id returned when it was accepted. |
Example
curl -X GET 'https://api.fax-iq.com/api/v1/faxes/:id' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": {
"id": "0c0b5f2c-8d1e-4b7a-9f3c-2a6d5e8b1c04",
"direction": "outbound",
"status": "failed",
"from": "+12695550100",
"to": "+12695550123",
"attemptCount": 3,
"maxAttempts": 3,
"retryDelays": [60, 300],
"errorCode": "34004",
"errorMessage": "No answer",
"attempts": [
{ "attemptNo": 1, "providerStatus": "no-answer", "errorCode": "34004", "errorMessage": "No answer", "numPages": null, "durationSeconds": 48, "startedAt": "2026-09-22T20:00:02.000Z", "completedAt": "2026-09-22T20:00:50.000Z" }
],
"…": "the remaining fields, as on the list"
}
}- A fax belonging to another account answers 404, the same as one that does not exist.
- failed carries errorCode (a carrier code such as 34004, or PROVIDER_REJECTED when the request was refused outright) and errorMessage.
/api/v1/faxes/:id/eventsfaxes:readRead the event trail
The audit trail for one fax, newest first: created, submitted, status_webhook, retry_scheduled, delivered, callback_delivered and so on. The first place to look when a callback seems to be missing.
Path parameters
| Name | Type | Notes |
|---|---|---|
| id* | string | The fax id. |
Example
curl -X GET 'https://api.fax-iq.com/api/v1/faxes/:id/events' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": [
{ "id": "…", "event": "delivered", "level": "info", "details": null, "createdAt": "2026-09-22T20:03:41.000Z" },
{ "id": "…", "event": "submitted", "level": "info", "details": null, "createdAt": "2026-09-22T20:00:02.000Z" },
{ "id": "…", "event": "created", "level": "info", "details": null, "createdAt": "2026-09-22T20:00:00.000Z" }
]
}/api/v1/faxes/:id/mediafaxes:readDownload the document
The stored document. By default a 302 to a signed URL valid for five minutes, which any client that follows redirects turns into the file. Two query flags change that.
Path parameters
| Name | Type | Notes |
|---|---|---|
| id* | string | The fax id. |
Query parameters
| Name | Type | Notes |
|---|---|---|
| redirect | boolean | false returns { url, expiresAt, contentType, sizeBytes, filename } as JSON instead of redirecting. defaults to |
| inline | boolean | true streams the bytes through the API with Content-Disposition: inline, for a server that must not expose storage URLs. defaults to |
Example
curl -X GET 'https://api.fax-iq.com/api/v1/faxes/:id/media' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"url": "https://….amazonaws.com/…?X-Amz-Expires=300&…",
"expiresAt": "2026-09-22T20:10:00.000Z",
"contentType": "application/pdf",
"sizeBytes": 184320,
"filename": "fax-0c0b5f2c-8d1e-4b7a-9f3c-2a6d5e8b1c04.pdf"
}- Outbound documents are always application/pdf. An inbound fax may be image/tiff with a .tiff filename, so read contentType or the response headers rather than assuming PDF.
- A fax with no stored document answers 404.
/api/v1/faxes/:id/cancelfaxes:sendCancel a fax
Cancels a queued fax immediately. One that is processing or sending gets a best-effort cancel at the carrier and is marked canceled. Anything else (delivered, failed, already canceled, or an inbound fax) answers 409.
Path parameters
| Name | Type | Notes |
|---|---|---|
| id* | string | The fax id. |
Example
curl -X POST 'https://api.fax-iq.com/api/v1/faxes/:id/cancel' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": { "id": "0c0b5f2c-8d1e-4b7a-9f3c-2a6d5e8b1c04", "status": "canceled", "…": "the remaining fields" }
}/api/v1/fax-numbersfaxes:readYour fax numbers
The active fax numbers on your account, with each one's label and inbound forwarding addresses. Any of these is a valid from on a send.
Example
curl -X GET 'https://api.fax-iq.com/api/v1/fax-numbers' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": [
{ "id": "…", "phoneNumber": "+12695550100", "label": "Main office", "forwardToEmails": ["records@example.com"], "isActive": true }
]
}- Any scope may call this.
/api/v1/mefaxes:readYour account
Everything about your account in one call and with no secrets: the client, the key you called with, your fax numbers, approved email senders, callback endpoints, whether the carrier is configured, the installs that send heartbeats with whether each is online, and a warnings list worth showing to whoever runs your integration.
Example
curl -X GET 'https://api.fax-iq.com/api/v1/me' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": {
"client": { "id": "…", "cltId": 1234, "name": "Acme Medical", "isActive": true, "retryDelays": null, "maxAttempts": null, "createdAt": "…" },
"token": { "tokenPrefix": "a1b2c3d4", "scopes": ["faxes:send", "faxes:read"], "source": "api", "expiresAt": null },
"faxNumbers": [ { "phoneNumber": "+12695550100", "label": "Main office", "forwardToEmails": [], "isActive": true } ],
"emailSenders": [],
"webhookEndpoints": [],
"provider": { "configured": true, "isActive": true },
"agents": [ { "id": "…", "source": "windows_client", "sourceSystem": "UltraComm", "hostname": "FAX-PC", "version": "2.3.1", "status": "offline", "lastSeenAt": "…", "firstSeenAt": "…", "secondsSinceSeen": 412, "details": { "queued": 3 } } ],
"warnings": [ { "code": "agent_offline", "message": "A client-side install has stopped sending heartbeats" } ]
}
}- Any scope may call this.
- agent_offline in warnings names the installs that have stopped sending heartbeats.
/api/v1/heartbeatfaxes:readReport an install is running
Tells the fax service that a client-side install, such as the Windows client, is running. Each sourceSystem is one install; GET /me lists them with whether each is online.
Body fields
| Name | Type | Notes |
|---|---|---|
| sourceSystem* | string | Identifies the install within your account. Send the same value you put on its faxes. Up to 64 characters: letters, digits, spaces, dots, underscores and dashes, starting with a letter or digit |
| version | string | The install's software version. Up to 64 characters |
| hostname | string | The computer it runs on. Up to 255 characters |
| details | object | Anything else worth showing beside the install, such as a queue length. Up to 20 flat keys; string, number, boolean or null values |
Request body
{
"sourceSystem": "UltraComm",
"version": "2.3.1",
"hostname": "FAX-PC",
"details": { "queued": 3 }
}Example
curl -X POST 'https://api.fax-iq.com/api/v1/heartbeat' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "sourceSystem": "UltraComm", "version": "2.3.1", "hostname": "FAX-PC", "details": { "queued": 3 } }'Response
{
"success": true,
"data": { "serverTime": "2026-10-09T20:00:00.000Z", "heartbeatIntervalS": 60 }
}- Any scope may call this.
- Send one on startup and then every heartbeatIntervalS seconds from the response, even when idle. An install with no heartbeat for three minutes shows as offline.
- Omitted fields keep their last reported value. Two computers reporting the same sourceSystem share one install, and the latest heartbeat wins.
/api/v1/faxes/statsfaxes:readRead delivery stats
Counts by status for outbound and inbound faxes over a window, with pages, the delivery rate, a breakdown by source and a per-day series.
Query parameters
| Name | Type | Notes |
|---|---|---|
| window | string | How far back to count. 24h, 7d, 30d · defaults to |
Example
curl -X GET 'https://api.fax-iq.com/api/v1/faxes/stats' \
-H 'Authorization: Bearer YOUR_API_KEY'Response
{
"success": true,
"data": {
"window": "24h",
"since": "2026-09-23T20:00:00.000Z",
"generatedAt": "2026-09-24T20:00:00.000Z",
"clientId": "3c9a77e2-0b41-4f8d-9a16-2d5e8c4b1f07",
"outbound": { "total": 4, "queued": 1, "processing": 0, "sending": 0, "delivered": 2, "failed": 1, "canceled": 0, "pages": 5, "deliveryRate": 0.6667 },
"inbound": { "total": 1, "receiving": 0, "received": 1, "failed": 0, "pages": 3 },
"bySource": [ { "source": "api", "sourceSystem": "Billing", "total": 2, "delivered": 2, "failed": 0 } ],
"byDay": [ { "date": "2026-09-24", "outbound": 4, "inbound": 1, "delivered": 2, "failed": 1 } ]
}
}- deliveryRate is delivered over delivered plus failed, and null until something has settled.
- Cached for 15 seconds per window. Polling faster than that returns the same answer.
/api/v1/healthno token neededService health
Whether the fax service is up, with its database and storage checks. The only endpoint that needs no token, so it is safe to use from an uptime monitor.
Example
curl -X GET 'https://api.fax-iq.com/api/v1/health'Response
{
"status": "ok",
"version": "0.9.0",
"server": "fax-prod-1",
"environment": "production",
"checks": { "database": true, "storage": true },
"uptime": 864213,
"timestamp": "2026-09-24T20:00:00.000Z"
}- This one is not wrapped in the success/data envelope, and it answers 200 even when degraded, so read status.