Documentation Index
Fetch the complete documentation index at: https://mintfax.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Outcome
When you finish this guide, your integration sends to https://api.mintfax.com/v1 with a Bearer key, reads snake_case fields, signs an Idempotency-Key on every send, verifies Standard Webhooks signatures on every webhook, and has cleared a full fax round-trip in the sandbox. Code samples are curl and Node. Marketing positioning is at /compare/sinch; this page is the dev-execution path.
Prerequisites
- A mintfax environment with a sandbox API key (
mfx_test_...). Live keys (mfx_live_...) come later, after the sandbox round-trip.
- A webhook endpoint configured in the mintfax dashboard. The signing secret is shown once; store it as
MINTFAX_WEBHOOK_SECRET.
- Your existing Sinch v3 (or Phaxio v2.1) integration in source control. The cutover diff is small, but you want a clean revert.
- Node 18+ for the Node samples, or any HTTP client that speaks
multipart/form-data for the curl samples.
- Read Verify webhook signatures, Idempotency, Errors, Event types, and Sandbox before cutover. The migration leans on each one.
Step 1: Swap the base URL and authentication
Sinch v3 uses HTTP Basic with a project-scoped key and secret, and the project ID lives in the URL path. mintfax uses a Bearer token, infers the environment from the key prefix (mfx_test_ for sandbox, mfx_live_ for live), and has no project ID in the URL.
| Concern | Sinch Fax v3 | mintfax |
|---|
| Base URL | https://fax.api.sinch.com/v3/projects/{projectId} | https://api.mintfax.com/v1 |
| Auth | HTTP Basic (-u {KEY_ID}:{KEY_SECRET}) | Authorization: Bearer mfx_test_... |
| Env switch | Different project IDs | Different key prefixes |
# Sinch v3
curl -X POST 'https://fax.api.sinch.com/v3/projects/{PROJECT_ID}/faxes' \
-u {KEY_ID}:{KEY_SECRET} \
-F 'to=+15551235656' \
-F 'file=@document.pdf'
# mintfax
curl -X POST https://api.mintfax.com/v1/faxes \
-H "Authorization: Bearer mfx_test_4eC39HqLyjWDarjtT1zdp7dc" \
-F "to=+15005550001" \
-F "file=@document.pdf"
Verify A GET https://api.mintfax.com/v1/account with your sandbox key returns 200 and your account ID. A 401 means the header is missing or malformed; see unauthenticated and api_key_invalid.
Step 2: Rename fields from camelCase to snake_case
Phaxio v2.1 used snake_case. Sinch v3 broke that convention. mintfax restores it. Most send-path fields map directly; a few drop or rename.
| Sinch v3 (camelCase) | mintfax (snake_case) | Notes |
|---|
id | id | mintfax IDs are fax_-prefixed |
to | to | E.164 string, single recipient |
from | (not in send schema) | Caller ID is environment-level, not per-fax |
numberOfPages | pages | Null until the document has been rasterized |
status | status | Different enum; see Step 4 |
createTime | created_at | ISO 8601 UTC |
completedTime | completed_at | ISO 8601 UTC |
callbackUrl | webhook_url | Per-fax override |
maxRetries | retries | 0-10 in mintfax, 0-5 in Sinch v3 |
retryDelaySeconds | (not exposed) | mintfax controls retry timing |
errorCode | error_code | Free-form string in mintfax |
direction | (not in public API) | mintfax v1 is outbound-only |
projectId | (not applicable) | Environment is inferred from the key |
serviceId | (not applicable) | mintfax has no Services concept |
price.amount | (not on fax payload) | Pricing is on the environment, not the fax record |
coverPageId | (not in v1) | Cover-page templating is roadmap, not v1 |
labels | tags | Up to 10 keys, each up to 64 chars |
The mintfax Fax resource also adds fields Sinch v3 does not have: csid, page_size (letter/legal/a4/b4), resolution (standard/fine), optimize_for (text/photo), and idempotency_key. All are optional and fall back to your environment fax settings.
Verify Submit one fax, then GET /v1/faxes/{id}. Confirm every field your old code reads is present under its snake_case name.
Step 3: Add an idempotency key
Sinch v3 has no documented idempotency feature. A retried POST /faxes against Sinch can double-bill silently. mintfax accepts an Idempotency-Key header on POST /v1/faxes and POST /v1/faxes/{id}/resend; duplicate keys return the original response without re-processing, and keys expire after 24 hours.
Generate a UUID v4 per submission. Reuse the same key on retry.
curl -X POST https://api.mintfax.com/v1/faxes \
-H "Authorization: Bearer mfx_test_4eC39HqLyjWDarjtT1zdp7dc" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-F "to=+15005550001" \
-F "file=@document.pdf"
Verify Submit the same request twice with the same Idempotency-Key. Both responses carry the same id. Read the Idempotency guide for full semantics.
Step 4: Translate status and retry behavior
Sinch v3 status names are uppercase; mintfax statuses are lowercase, and add one extra value (submitted) to distinguish “in queue” from “handed to the carrier.”
| Sinch v3 status | mintfax status |
|---|
QUEUED | queued |
IN_PROGRESS | submitted, in_progress |
COMPLETED | delivered |
FAILURE | failed |
Retry caps differ. Sinch v3 caps at 5 retries (maxRetries 0-5); mintfax caps at 10 (retries 0-10), with a default of 3. Configure it per fax or as an environment default at PUT /v1/environment/fax-settings. Retry timing is controlled by mintfax, so there is no per-request retryDelaySeconds to map.
If you previously cancelled queued faxes, note that neither product exposes a cancel endpoint. A queued fax in mintfax runs to a terminal state (delivered or failed) before it can be deleted via DELETE /v1/faxes/{id}. A delete attempt against an in-flight fax returns fax_not_terminal.
Verify Submit a fax to +15005550004 (the transient-failure magic number) and watch the lifecycle: fax.queued, fax.sending (attempt 1), fax.sending (attempt 2 after the transient failure), then fax.delivered. fax.sending fires once per delivery attempt, so the repeat is how you observe mid-flight retries. See Sandbox.
Step 5: Replace the v3 webhook scheme with Standard Webhooks
This is the highest-value step in the migration. Sinch v3 dropped the Phaxio v2.1 X-Phaxio-Signature (HMAC-SHA1) header. The v2-to-v3 guide states verbatim: “Webhook signatures: Removed due to low usage, at the moment V3 does not support.” What replaced it is HTTP Basic credentials inside the callback URL. That mechanism leaks the credential to any reverse proxy in the path and to Sinch’s own request logs, and it does not authenticate the payload.
mintfax follows the Standard Webhooks specification and sends three headers on every delivery:
| Header | Value |
|---|
webhook-id | Event identifier (evt_-prefixed). Stable across retry attempts; use it to deduplicate. |
webhook-timestamp | Unix epoch seconds, re-stamped per delivery attempt. |
webhook-signature | One or more space-separated v1,<base64-hmac> tokens. |
The signature is HMAC-SHA256(secret, "{webhook-id}.{webhook-timestamp}.{body}"), base64-encoded, emitted as v1,<base64>. Use the Standard Webhooks reference library for your language - it handles constant-time comparison, the 5-minute timestamp tolerance, and parsing multi-secret rotation tokens.
import express from 'express';
import { Webhook } from 'standardwebhooks';
const app = express();
const wh = new Webhook(process.env.MINTFAX_WEBHOOK_SECRET);
app.post(
'/webhooks/mintfax',
express.raw({ type: 'application/json' }),
(req, res) => {
try {
const event = wh.verify(req.body, {
'webhook-id': req.headers['webhook-id'],
'webhook-timestamp': req.headers['webhook-timestamp'],
'webhook-signature': req.headers['webhook-signature'],
});
// event.type, event.id, event.data.object.id, event.data.object.status, ...
res.sendStatus(200);
} catch (err) {
res.status(401).send('Invalid signature');
}
},
);
Verify Send a fax to +15005550001 (success) and confirm your handler returns 200. Tamper with one byte of the body in a replayed request and confirm 401. Read the full verify webhook signatures page for the multi-language reference and rotation guidance before you ship.
Step 6: Adjust webhook event handling
Sinch v3 fires one FAX_COMPLETED event whose fax.status is either COMPLETED or FAILURE. mintfax fires distinct event types, plus events Sinch does not have: fax.queued, fax.sending, fax.delivered, fax.failed, balance.low, and balance.topup.
| Sinch v3 event | mintfax event |
|---|
FAX_COMPLETED (success) | fax.delivered |
FAX_COMPLETED (failure) | fax.failed |
| (no equivalent) | fax.queued |
inProgressNotifications | fax.sending (fires once per delivery attempt - this is also the mid-attempt signal) |
| (no equivalent) | balance.low, balance.topup |
INCOMING_FAX | (not supported; mintfax v1 is outbound-only) |
Two consequences for your handler. First, code that branched on fax.status inside one event now branches on event.type at the top of the payload; a switch over event.type is clearer. Second, if your Sinch v3 integration consumes INCOMING_FAX, mintfax has no equivalent today. Treat that as a known gap, and do not migrate inbound flows yet.
The mintfax envelope is the same across all event types: id (evt_-prefixed, also delivered as the webhook-id header; use it for deduplication), type (the event name), created (Unix timestamp in seconds), and data.object (event-specific payload). The full catalog is at Event types.
Verify Confirm your handler routes fax.delivered and fax.failed correctly. Replay protection plus id deduplication means you can replay any captured event safely while testing.
Step 7: Translate error codes
Sinch v3 returns an integer errorCode plus a type field on failure (one of DOCUMENT_CONVERSION_ERROR, CALL_ERROR, FAX_ERROR, FATAL_ERROR, GENERAL_ERROR), and uses HTTP statuses for top-level rejections. mintfax returns a stable string error field in a consistent envelope with message, action, and docs.
| Sinch v3 (HTTP / errorCode) | Meaning | mintfax error code |
|---|
| HTTP 401 | Authentication failure | unauthenticated, api_key_invalid, api_key_missing |
| HTTP 402 | Account balance at zero | insufficient_balance |
| HTTP 404 | Resource not found | not_found |
| HTTP 422 | Invalid parameters | validation_failed, fax_data_purged |
| HTTP 429 | Rate limit | rate_limit_exceeded |
| HTTP 500 | Temporary server error | internal_server_error |
| HTTP 800 | URL fetch failure (contentUrl) | (no equivalent; mintfax accepts multipart only) |
128-130 DOCUMENT_CONVERSION | Bad / unsupported MIME | validation_failed at submit time |
17 CALL_ERROR | Destination busy | Surfaces as fax.failed with error_code: line_busy |
16 CALL_ERROR | Ring timeout / no answer | Surfaces as fax.failed with carrier-supplied error_code |
4 / 54 / 133 (conversion) | Document conversion failures | Surfaces as fax.failed |
The per-failure-reason error catalog for the fax record itself (the error_code field on fax.failed) is intentionally compact in v1; treat unfamiliar values as opaque-but-logged. Top-level API errors (the envelope above) are stable, documented at Errors, and machine-matchable on the error field.
Verify Submit to +15005550005 (the permanent-failure magic number) and confirm your fax.failed handler logs data.error_code and routes to your alerting path.
Step 8: Adapt sandbox tests
Sinch v3 ships a single magic number for sandbox testing. mintfax ships per-failure-mode magic numbers so you can exercise each branch deterministically.
| Scenario | mintfax magic number |
|---|
| Always succeeds | +15005550001 |
| Always busy | +15005550002 |
| Rings out, no pickup | +15005550003 |
| Fails once, then OK | +15005550004 |
| Always fails | +15005550005 |
| Insufficient balance | +15005550006 |
| Validation failure | +15005550099 |
Existing test fixtures built around Sinch’s single number expand here: one fixture per failure mode, deterministic, no carrier dependency. The full list and behavior are on Sandbox.
Verify Run your full integration suite against the sandbox using these numbers before any traffic moves to live keys.
Verify
End-to-end check before you flip live traffic:
- Send a fax to
+15005550001 with a sandbox key and an Idempotency-Key. The response carries status: "queued" and a fax_-prefixed id.
- Your webhook endpoint receives
fax.queued, then fax.sending, then fax.delivered. Each request passes Standard Webhooks verification via the reference library.
- Repeat the same
POST /v1/faxes with the same idempotency key. The response is identical; no second fax is created.
- Send to
+15005550005 and confirm a fax.failed event with a populated error_code.
- Replace
mfx_test_ with mfx_live_ in your config, send one real fax to a known-good destination, and watch the same event sequence land.
What to do next
- Verify webhook signatures - Standard Webhooks library snippets in five languages, plus key rotation guidance.
- Idempotency - retry semantics, key lifetime, and what happens on replay.
- Errors - the stable error catalog with HTTP statuses, causes, and next actions.
- Event types - every event type, payload shape, and delivery behavior.
- Sandbox - magic numbers and simulated failure scenarios.