Fax API
Create a guest fax over HTTP. No API key, and no subscription. The person who opens the status link pays on that page, at the same price as the website.
Call it from your server. A successful response is wrapped in data.
An error is code and message, with no data wrapper.
The API does not send CORS headers, so a page on another site cannot call it from the browser.
This site, https://www.faxette.com, faxes to the US and Canada.
For the EU, the UK, Switzerland, Norway, Iceland, Liechtenstein, Monaco, Montenegro, Ukraine and the Isle of Man, use https://eu.faxette.com/api.
Errors
message is one sentence. Branch on code, which stays stable.
{
"code": "validation_failed",
"message": "Fax number is in a country we can't fax to yet."
}
| code | Status | When |
|---|---|---|
| validation_failed | 422 | A field is missing or refused. message is one sentence. |
| unauthorized | 401 | A request that needs a token was sent without one. |
| not_found | 404 | Unknown fax, or a token that does not match. Both look the same. |
| rate_limited | 429 | Too many upload slots or fax creates. The message says to wait a minute. |
Upload a document
POST /api/v1/documents
Ask for an upload slot, then PUT the file bytes to upload_url.
The bytes go straight to storage. This request only checks what you say the file is.
A fax is created afterwards with the slot's id.
There is no request to download a document, or to list them.
checksum is the file's MD5, base64-encoded, the same value as a Content-MD5 header.
byte_size is the exact size in bytes.
content_type is one of application/pdf, image/jpeg, image/png, image/webp, image/heic, and image/heif.
The slot's id works for 1 hour. The upload URL works for 5 minutes.
Storage refuses a file whose size or MD5 does not match the slot.
openssl dgst -md5 -binary referral.pdf | openssl base64
wc -c referral.pdf | awk '{print $1}'
curl -s https://www.faxette.com/api/v1/documents \
-H "Content-Type: application/json" \
-d '{"filename":"referral.pdf","byte_size":248113,"checksum":"gbgEgwqIGW9JMTqqgYW+xw==","content_type":"application/pdf"}'
201, with the slot.
{
"data": {
"type": "Document",
"id": "SIGNED_ID",
"upload_url": "https://example.r2.cloudflarestorage.com/faxette/...",
"upload_headers": {
"Content-Type": "application/pdf",
"Content-MD5": "gbgEgwqIGW9JMTqqgYW+xw==",
"Content-Disposition": "inline; filename=\"referral.pdf\"; filename*=UTF-8''referral.pdf"
}
}
}
PUT the raw bytes to upload_url, with every header in upload_headers.
Copy the values from the response. $UPLOAD_URL below stands in for upload_url.
curl -X PUT --data-binary @referral.pdf \
-H "Content-Type: application/pdf" \
-H "Content-MD5: gbgEgwqIGW9JMTqqgYW+xw==" \
-H "Content-Disposition: inline; filename=\"referral.pdf\"; filename*=UTF-8''referral.pdf" \
"$UPLOAD_URL"
Create a fax
POST /api/v1/faxes
Creating a fax does not charge a card. The response includes token once. Store it. It is not returned again.
Poll GET /api/v1/faxes/:id until status is documents_ready and checkout_available is true, then send the person to status_url.
They check the fax number, documents and price, and pay there.
Payment starts from the Pay button on that page. A link that started checkout would be opened by chat apps' link previews, so there is no checkout endpoint.
Sending this request twice creates two faxes. There is no idempotency key.
With document ids
documents is the slot ids, in the order they should be faxed.
An id already attached to a fax cannot be used on another one. Upload the file again.
curl -s https://www.faxette.com/api/v1/faxes \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","fax_number":"+14155552671","documents":["SIGNED_ID"]}'
With the files in one request
Send documents[] as multipart files, in fax order.
The bytes come through this server, so a large file is better sent with an upload slot.
The file's content type has to be one of the types above. type= sets it when the filename would not.
curl -s https://www.faxette.com/api/v1/faxes \
-F "[email protected]" \
-F "fax_number=+14155552671" \
-F "documents[][email protected];type=application/pdf" \
-F "documents[][email protected];type=image/jpeg"
An empty draft
Leave documents out. The response's status is draft, and the person adds the files on the status page.
curl -s https://www.faxette.com/api/v1/faxes \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","fax_number":"+14155552671"}'
Fields
| Field | What to send |
|---|---|
| Required. Where we email the receipt and updates. The address has to be able to receive mail. Disposable inboxes are refused. | |
| fax_number |
Required.
A number without a country code is read as a US number. E.164 is +14155552671.
Premium-rate numbers are refused. Numbers outside the countries this site faxes to are refused.
|
| documents | Optional. Slot ids, or multipart files. Up to 10, each up to 20 MB (20,971,520 bytes). |
201, with the fax and its token. Other fields are null until the fax moves on.
{
"data": {
"type": "Fax",
"id": "k3n8q0w1x4y7",
"status": "processing_documents",
"fax_number": "+14155552671",
"page_count": null,
"retries_remaining": 2,
"next_attempt_at": null,
"resend_by": null,
"updated_at": "2026-10-27T16:04:00.000Z",
"status_url": "https://www.faxette.com/f/k3n8q0w1x4y7?token=exampletoken",
"checkout_available": false,
"price": null,
"payment": null,
"attempt": 1,
"max_attempts": 3,
"document_errors": [],
"failure_reason": null,
"token": "exampletoken"
}
}
Once the pages are counted, those fields look like this. currency on this site is usd.
{
"status": "documents_ready",
"page_count": 3,
"checkout_available": true,
"price": { "amount_cents": 500, "currency": "usd" }
}
Read a fax
GET /api/v1/faxes/:id?token=
Returns the same object as create, without token.
The response never includes the documents or their filenames.
Poll it while status is processing_documents.
document_errors says which file to fix, by its position in the fax, starting at 1.
curl -s "https://www.faxette.com/api/v1/faxes/k3n8q0w1x4y7?token=exampletoken"
{
"document_errors": [
{
"reason": "password_protected",
"position": 2,
"message": "Document 2 is locked with a password. Open it, save or print it as a new PDF without the password, then upload that copy."
}
]
}
Replace the documents
PATCH /api/v1/faxes/:id/documents?token=
documents is the full list, in fax order.
To keep a file, send its slot id again. To drop one, leave it out. Dropped files are deleted, and the pages are counted again.
This works while status is draft, processing_documents or documents_ready.
Once checkout has started, the files are locked. Start a new fax to change them.
Slot ids work for 1 hour, and the fax response never includes them, so keep the ids you uploaded.
curl -s -X PATCH "https://www.faxette.com/api/v1/faxes/k3n8q0w1x4y7/documents?token=exampletoken" \
-H "Content-Type: application/json" \
-d '{"documents":["SIGNED_ID","ANOTHER_ID"]}'
200, with the fax. status goes back to processing_documents.
Send it again
POST /api/v1/faxes/:id/resend?token=
A failed fax can be sent again, free, while the card is still held.
That window is 48 hours, and resend_by is when it ends.
retries_remaining has to be above 0. It starts at 2.
failure_reason of our_side cannot be resent. The hold is released straight away.
fax_number is optional. Leave it out, or send it blank, to use the same number.
A number without a country code is read in the country of the fax's current number.
curl -s -X POST "https://www.faxette.com/api/v1/faxes/k3n8q0w1x4y7/resend?token=exampletoken" \
-H "Content-Type: application/json" \
-d '{"fax_number":"+14155550199"}'
200, with the fax. status is scheduled.
{
"status": "failed",
"failure_reason": "temporary",
"retries_remaining": 2,
"resend_by": "2026-10-29T16:04:00.000Z",
"payment": { "status": "authorized", "amount_cents": 500, "tax_cents": 0, "currency": "usd" }
}
The fax object
| Field | Type | Meaning |
|---|---|---|
| type | string | Always "Fax". |
| id | string | 12 lowercase letters or digits. |
| status | string | Where the fax is. See the status table. |
| fax_number | string | E.164, like +14155552671. |
| page_count | integer or null | Null until the pages are counted. |
| retries_remaining | integer | Customer resends left. Starts at 2. Busy-line retries do not use this. |
| next_attempt_at | time or null | When a busy line will be dialed again. |
| resend_by | time or null | When a failed fax can no longer be resent, and the hold is released. |
| updated_at | time | When the fax last changed. |
| status_url | string | The page where the sender pays. It carries the token. |
| checkout_available | boolean | True when that page can take payment. |
| price | object or null | Before tax: amount_cents and currency. Null until the pages are counted. Extra pages are already included. |
| payment | object or null | Null until the card is held. Then status, amount_cents, tax_cents and currency. |
| attempt | integer | Which dial of this send, from 1 to max_attempts. |
| max_attempts | integer | Always 3. |
| document_errors | array | What to fix before the fax can be sent. Empty when there is nothing to fix. |
| failure_reason | string or null | Set when status is failed. |
| token | string | Only on create. An opaque string. Store it. |
status
| status | Meaning |
|---|---|
| draft | Files are missing, or a file needs fixing. See document_errors. |
| processing_documents | Counting pages. |
| documents_ready | Priced. The status page can take payment. |
| awaiting_payment | Checkout has started. |
| scheduled | Paid, and waiting to send. On a busy-line retry, next_attempt_at is set. |
| sending | On the line. |
| delivered | The receiving machine confirmed it. The card is charged. |
| failed | See failure_reason. The card stays held when the fax can be resent. |
| canceled | Unpaid for 24 hours. The files are deleted. |
A busy line is dialed again after 5 minutes, then 15 minutes. Those dials are attempt of max_attempts.
They are separate from a customer resend.
payment
payment.status is authorized while the card is held,
paid once the fax is delivered and the hold is charged,
canceled when the hold is released,
or refunded if the charge was refunded.
amount_cents is what the card was held or charged for, tax included.
This site does not add tax, so tax_cents is 0.
price stays the amount before tax.
failure_reason
Null unless status is failed.
| temporary | The line was busy, did not answer, or dropped the call, after 3 attempts. The fax can be resent. |
| wrong_number | The number did not reach a fax machine. Resend it to a corrected number. |
| our_side | Something failed on our side. The hold is released, and the fax cannot be resent. |
document_errors
Each item has reason, position and message.
position is the document's number in the fax, and null when the problem is about the whole fax.
message is the sentence to show a person.
The same problems can also come back as validation_failed on create, before a fax exists.
| password_protected | The PDF has a password. |
| unreadable_pdf | The PDF could not be opened. |
| unreadable_image | The image could not be opened. |
| unsupported_type | Not a PDF or a photo we can fax. |
| image_too_large | Over 50 megapixels. |
| multi_page_image | An image file with more than one page. Save it as a PDF. |
| too_many_pages | Over 200 pages. |
| too_many_documents | Over 10 files. |
| document_too_large | One file is over 20 MB. |
| no_documents | The fax has no files. |
| build_failed | We could not assemble the fax. Uploading the files again is the next step. |
Limits
- 60
POST /api/v1/documentsrequests per minute per IP address. - 20
POST /api/v1/faxesrequests per minute per IP address. - 10 documents, 20 MB each, 200 pages, 50 megapixels per image.
- An unpaid fax is canceled after 24 hours.
- Files are deleted 48 hours after the fax is delivered, fails for the last time, or is canceled. The status page can delete them sooner once it is delivered.
For Claude, ChatGPT, and other assistants, See the MCP server β