How to choose a fax API
How to choose a fax API
You have a Jira ticket about faxing. Maybe it is patient records, authorization requests, or legal documents that need to move between systems that do not share a protocol. You have looked at a few vendors. They all have the same product page: send, receive, retries, error handling, webhooks. At that level they look identical.
They are not. What separates them is what is included at which tier, and how much failure handling your code has to do. This note names the six things a modern fax API integration bundle should include, and turns each one into a concrete test you can run against any vendor on your shortlist before you commit.
Why the product page is not enough
Every fax API vendor publishes the same capability list. Send a fax. Receive a fax. Get webhook events. Retry on failure. Handle errors. The capability list describes what the API does when everything goes right. What distinguishes vendors is what happens when something goes wrong, and how much of that handling falls to your code rather than theirs.
A fax is not a REST request. It is a dial-up negotiation with an aging PSTN device at the other end. The carrier might return a busy signal. The recipient line might drop mid-handshake. The modem negotiation might fail because of line noise. The fax might time out at 29 seconds and then succeed on the next attempt. Handling this gracefully in your integration requires that the API vendor has made deliberate choices about idempotency, retries, and error taxonomy. Those choices are not visible on the capability list.
The six tests below are how you read past the capability list.
The six tests
1. Idempotency keys
A fax API is not stateless from your application’s perspective. When you submit a send request, you want to know the fax was submitted exactly once, not that the HTTP call completed. Network interruptions, timeouts, and retry logic in your HTTP client can all result in duplicate submissions if the API does not support idempotency.
An idempotency key is a caller-generated token you include with each POST /faxes request. The API stores the key and the response. If the same key arrives again within an expiry window, the API returns the stored response without queuing a second fax. Your application can retry safely on any transport error without risk of sending duplicates.
A well-specified implementation also returns a header (such as Idempotent-Replayed: true) so your application can tell when it received a cached response versus a fresh one. It should also return a distinct error code when you reuse a key with a different payload, so you know you have a key-collision bug in your own code rather than a vendor-side problem.
What to check: Does the API have an Idempotency-Key header or equivalent? What is the key expiry window? Does the API distinguish between key reuse with the same body (safe replay) and key reuse with a different body (collision error)?
2. Signed webhooks
Fax APIs deliver status events to a webhook endpoint you register. When a fax delivers, fails, or retries, the vendor sends a POST request to your handler. Most APIs support webhooks. Not all sign the payloads.
Without signing, any request that looks like a fax event and reaches your endpoint could be treated as legitimate. A signed webhook includes a cryptographic signature computed with a shared secret. Your handler verifies the signature before acting on the payload, so a spoofed or replayed request is rejected before your code does anything with it.
HMAC-SHA256 is the standard scheme. Your endpoint computes the expected signature from the raw request body and a known secret, compares it to the value in the request header, and rejects the request if they do not match. For healthcare integrations where webhook events trigger downstream PHI handling, this is not optional security hardening. It is a basic integrity check.
What to check: Does the API sign webhook payloads? Which algorithm? Does the vendor follow a documented, portable specification so the verification logic is not proprietary?
One thing to watch for: some APIs have included signing in one version and then removed it in a subsequent one, sometimes without a prominent changelog entry. Check the documentation for the API version you are integrating today, not the version that came up in a forum post.
3. Real retries
Fax delivery fails for transient reasons. The recipient line is busy. The modem negotiation drops partway through. A carrier node is congested. These are all conditions that tend to resolve on a second or third attempt, minutes later.
A fax API that includes automatic retries handles this for you. It requeues a failed attempt according to a documented schedule, with backoff between attempts. Your integration receives a single final status event when the fax either delivers or exhausts its configured attempts. An API without automatic retries fires a failure event and expects you to re-submit.
The behavior on terminal errors is equally important. Some failure codes will not succeed on retry: a disconnected number, a line that is not fax-capable, a number that routes to nothing. A well-designed retry implementation distinguishes between retryable codes (busy, timeout, line quality) and terminal ones (invalid number, bad destination, no route), and short-circuits on terminal codes rather than wasting attempts that cannot succeed.
What to check: Does the API retry automatically? What is the retry schedule? Can you configure the attempt count per fax? Does it distinguish retryable failures from terminal ones? Can you inspect per-attempt detail (attempt number, status, error code) on the fax resource or via the events API?
4. Structured errors
When a fax fails, you need to know why. A stable, vendor-owned error code lets your integration branch on failure type: log a busy failure differently from an invalid-number failure, suppress retries for no-route, show a readable explanation to a user when a destination is unreachable.
Most of the complexity here is invisible on the capability list. Carrier error codes are not standardized. The same physical failure (a busy tone at the destination) can return different values from different carriers in different countries. An API that passes carrier error codes through directly forces your code to maintain a mapping against a non-standard, evolving set of values. An API with a vendor-owned error taxonomy abstracts that mapping: you write code against stable documented values, and the vendor handles the carrier translation.
What to check: Does the API publish a stable, documented error code taxonomy? Are error codes vendor-owned and stable, or carrier-specific and passed through? Does the documentation describe which codes are retryable versus terminal?
A useful signal: are the error codes exercisable in the sandbox? If you can trigger each error type against a test number in a non-production environment, you can write and verify your error-handling branches before you go live.
5. Self-serve BAA
If your integration handles protected health information (patient records, lab results, referrals, prior authorization forms), you need a HIPAA Business Associate Agreement with your fax vendor. A BAA records the vendor’s obligations around PHI handling and is a required instrument for any covered entity or business associate sending PHI over a third-party service.
What varies between vendors is the process for getting one. Some vendors issue a BAA automatically at every paid tier via a self-serve click-through in the dashboard, backed by a downloadable PDF receipt. Others gate the BAA behind a higher-priced tier. Some require a sales conversation. Some do not sign one at all.
The procurement difference is not trivial. A vendor with a self-serve BAA lets you reach production-eligible status on the day you activate. A vendor that gates the BAA behind a separate plan tier or a sales call adds lead time. In some procurement environments, that lead time stretches to weeks.
What to check: Is a BAA available? Is it gated behind a plan tier? Is it self-serve (dashboard click-through) or request-based? Does signing create a downloadable PDF record with the signer name, date, and template version?
“HIPAA compliant” as a vendor self-attribution is not a useful verification signal. Compliance is a property of the specific workflow you run under a specific BAA, not a certification you verify on a vendor’s landing page. The practical question is whether the vendor operates under a BAA with your organization, and whether you can execute that BAA without a procurement gate.
6. Public per-page pricing
Fax APIs price per page, not per fax. A single-page fax and a twenty-page fax cost differently. Pricing tables that publish per-fax rates are not comparable to ones that publish per-page rates.
Public pricing matters for two reasons. First, you can build a cost model at the design stage rather than after a sales call. Second, pricing transparency signals how the vendor treats self-serve customers: a vendor whose base tier has a visible rate treats API-only buyers as real customers; a vendor whose pricing starts with “contact us” is set up for a different kind of deal.
Some vendors price by seat or by monthly subscription rather than by page. Seat-based pricing is misaligned with API-only buyers. Your volume determines your cost, not your headcount. A seat pricing model designed for a team of users in a web portal adds a pricing variable your integration does not need.
What to check: Is pricing published? Is it per-page? Are volume tiers and any included features documented? Are HIPAA-eligible features (the BAA) available at all tiers, or gated behind a tier that requires negotiation?
Using these tests
Run each test as a pre-integration check, not a post-integration review. The tests that take the most time to discover after you have started building (missing BAA, no webhook signing, seat-based pricing buried in the enterprise tier) are exactly the ones where finding out early saves the most calendar time.
Most can be answered by reading the API documentation and the pricing page before you sign up. The retry behavior and error taxonomy deserve a second look in the sandbox: send to a magic test number that forces a transient failure and watch what the API does next. If the vendor has no sandbox, that absence tells you something.
The six criteria together describe an integration contract that is safe to build against. Your submissions are idempotent. Your event handlers verify the source. Delivery retries handle themselves. Failure paths are structured and testable. Compliance contracts are available immediately. You can price your usage before you commit.
If you want to see how mintfax stacks up on these six tests, the compare page shows how it sits against the APIs likely on your shortlist, and the pricing page covers the per-page rates and what is included at each tier. The HIPAA compliance page has the BAA details, and the error catalogue covers the full error taxonomy.