> ## 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.

# Migrate from the Dropbox Fax API to mintfax

> Endpoint, auth, webhook, idempotency, retry, and rate-limit mapping for engineers porting an integration off the Dropbox Fax API v3 to mintfax. curl and Node samples.

## Outcome

You finish this guide with a mintfax sandbox integration that does what your Dropbox Fax API v3 client does today: send a fax, list and inspect records, fetch the file, and delete on the outbound side. You also add three capabilities the Dropbox Fax API v3 does not document: an `Idempotency-Key` header on every send, a per-fax `max_attempts` retry policy, and a defined `rate_limit_exceeded` error you can back off on. Webhook verification moves from the custom `event_hash` scheme to Standard Webhooks HMAC-SHA256. Samples are in curl and Node.

## Audience and preflight

You are on the Dropbox Fax API v3, calling `https://api.hellosign.com/v3/fax/send`, authenticating with an API key over HTTP Basic or with an OAuth-issued Bearer access token, and receiving webhook deliveries at a URL you registered on the Dropbox Sign API settings page. If you are still on the v1 endpoints, the endpoints below still target the same mintfax surface; you skip the internal v1-to-v3 migration entirely.

> [!NOTE]
>
> Three preflight differences to know before you write any code:
>
> * **No monthly minimum.** mintfax has no monthly minimum, no per-line fee, and no setup fee. The Dropbox Fax API's published pricing includes a $100 monthly minimum. Below roughly 1,430 pages per month a mintfax bill comes in under the Dropbox $100 minimum, and it scales with actual usage.
> * **One public per-page price.** mintfax charges \$0.07 per page to every destination, with no volume tiers. The price is on [mintfax.com/pricing](https://mintfax.com/pricing); high-volume inquiries go to `sales@mintfax.com`. The Dropbox Fax API's per-page price lives on a Dropbox help article and is not linked from the Dropbox Sign API product page.
> * **Single stable API version.** mintfax has one version, `/v1`. There is no v2, no in-flight migration, and no plan tier that gates a subset of endpoints.

## Prerequisites

* A mintfax sandbox API key (prefix `mfx_test_`). Two calls provision one: `POST /account/register` then `POST /account/register/verify` with the 6-digit code. Sample: `mfx_test_4eC39HqLyjWDarjtT1zdp7dc`.
* Your existing Dropbox Fax API v3 code and a working call to `POST https://api.hellosign.com/v3/fax/send`.
* An HTTPS receiver URL for webhook deliveries. The signing secret (prefix `whsec_`) is shown once at endpoint creation in the mintfax dashboard; store it as `MINTFAX_WEBHOOK_SECRET`.
* Node 18+ for the Node samples.

## Endpoint mapping

Every Dropbox Fax API v3 operation maps to a mintfax endpoint on the outbound side. Inbound has no mintfax equivalent; see [step 8](#step-8-inbound-gap) below.

| Operation | Dropbox Fax API v3 | mintfax |
| - | - | - |
| Send a fax | `POST /v3/fax/send` | `POST /v1/faxes` |
| List faxes | `GET /v3/fax/list` | `GET /v1/faxes` (cursor-paginated) |
| Get one fax | `GET /v3/fax/{fax_id}` | `GET /v1/faxes/{id}` |
| Download the file | `GET /v3/fax/files/{fax_id}` | `GET /v1/faxes/{id}/image` (TIFF) |
| Delete a fax | `DELETE /v3/fax/{fax_id}` | `DELETE /v1/faxes/{id}` (terminal only) |

Two differences carry through every row: the base URL changes from `https://api.hellosign.com/v3` to `https://api.mintfax.com/v1`, and the auth header changes shape. Step 1 covers auth; step 2 covers the send payload.

`GET /v1/faxes` is cursor-paginated: pass `meta.next_cursor` back as `?cursor=...`. Default window is the last seven days; widen with `created_after` and `created_before`. `DELETE /v1/faxes/{id}` works only on terminal faxes (`delivered` or `failed`); in-flight calls return `409 fax_not_terminal`.

## Step 1: Swap authentication

The Dropbox Fax API v3 accepts either HTTP Basic with the API key (`-u "${API_KEY}:"`) or Bearer with an OAuth access token. A `test_mode` boolean on the request marks a send as a test, not a separate key.

mintfax accepts one scheme: Bearer with a prefixed key. The prefix names the environment. `mfx_test_` routes to sandbox, `mfx_live_` routes to live, and `mfx_acct_` is an account-scoped administrative key that cannot send faxes. A leaked test key cannot reach a live destination; there is no `test_mode` request field.

```bash curl theme={null}
# Dropbox Fax API v3 (before) - HTTP Basic
curl -X POST 'https://api.hellosign.com/v3/fax/send' \
  -u "${DROPBOX_SIGN_API_KEY}:" \
  -F 'recipient=+12125551234' \
  -F 'files[]=@document.pdf' \
  -F 'test_mode=1'

# mintfax (after)
curl -X POST 'https://api.mintfax.com/v1/faxes' \
  -H 'Authorization: Bearer mfx_test_4eC39HqLyjWDarjtT1zdp7dc' \
  -F 'to=+12125551234' \
  -F 'file=@document.pdf'
```

```javascript Node theme={null}
// Dropbox Fax API v3 (before)
await fetch('https://api.hellosign.com/v3/fax/send', {
  method: 'POST',
  headers: {
    Authorization:
      'Basic ' + Buffer.from(`${process.env.DROPBOX_SIGN_API_KEY}:`).toString('base64'),
  },
  body: form, // includes test_mode=1
});

// mintfax (after)
await fetch('https://api.mintfax.com/v1/faxes', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.MINTFAX_API_KEY}` },
  body: form,
});
```

**Verify** `GET https://api.mintfax.com/v1/account` with the new header returns 200. A 401 means the header value is malformed or the key prefix does not match the environment you expect; see [`unauthenticated`](https://mintfax.com/docs/errors#unauthenticated) and [`api_key_invalid`](https://mintfax.com/docs/errors#api-key-invalid).

## Step 2: Translate the send-fax payload

Rename the send-request fields. The Dropbox Fax API's `recipient` becomes mintfax's `to` (E.164, leading `+` and country code required); the multipart file part is renamed from `files[]` to `file` (one file per request, max 10 MB); the `test_mode` boolean is dropped because the sandbox key already routes to sandbox.

```bash curl theme={null}
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=+12015550100' \
  -F 'file=@invoice.pdf' \
  -F 'cover_page=standard' \
  -F 'recipient_name=Acme Billing' \
  -F 'subject=April invoice' \
  -F 'message=See attached, thanks.'
```

**Verify** The response is `201` with `id` (prefix `fax_`), `status: "queued"`, the `to` value, and `created_at`. Bare-digit numbers return `422 validation_failed` with `context.errors.to`.

## Step 3: Replace the webhook signature check

The Dropbox Fax API v3 signs every webhook delivery with a homegrown HMAC-SHA256 scheme that hashes the concatenation `event_time + event_type`, keyed by the API key, and delivers the hex digest inside the payload as `event_hash`. The signed input does not include the request body: two different deliveries with the same `event_time` and `event_type` share a signature.

mintfax implements the [Standard Webhooks](https://www.standardwebhooks.com/) specification. Every delivery carries three headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`) and the signed input is `{webhook-id}.{webhook-timestamp}.{body}`. The signing secret is per endpoint, prefixed `whsec_`, and shown once at creation. Receivers reject signatures whose timestamp is more than 5 minutes off the receiver's clock.

Use the Standard Webhooks reference library for your language. It handles constant-time signature comparison, the timestamp window, and multi-secret rotation.

```javascript Node theme={null}
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'],
      });
      // dedupe on event.id (same value as the webhook-id header), then route by event.type
      res.status(200).send('ok');
    } catch {
      res.status(401).send('bad signature');
    }
  }
);
```

Event routing changes. On the Dropbox Fax API v3 the webhook payload carries `event_type` and the receiver branches on that value. mintfax fires `fax.queued`, `fax.sending`, `fax.delivered`, and `fax.failed`, with `fax.sending` firing once per delivery attempt. Route on `event.type`, dedupe on `event.id` (echoed in the `webhook-id` header). Full schema at [Event types](https://mintfax.com/docs/webhooks/events/overview.md); the complete verification recipe including key rotation is at [Verify webhook signatures](https://mintfax.com/docs/webhooks/verify-requests.md).

**Verify** Send a fax to `+15005550001` from the sandbox. Your endpoint receives `fax.queued` then `fax.delivered`, both with valid Standard Webhooks signatures.

## Step 4: Add idempotency to every send

The Dropbox Fax API v3 does not document an `Idempotency-Key` header on `POST /v3/fax/send`, an equivalent replay-safety token in the request body, or a duplicate-detection field on the response. A retry after a network timeout can double-send.

mintfax accepts `Idempotency-Key` on `POST /v1/faxes`. Duplicate keys return the original response without re-processing for 24 hours. A request without the header receives an `X-Idempotency-Key: not provided; recommended` nudge header on the response. Generate one key per logical send and reuse it on retry.

```javascript Node theme={null}
import crypto from 'node:crypto';

const idempotencyKey = crypto.randomUUID();

for (let attempt = 0; attempt < 3; attempt++) {
  const res = await fetch('https://api.mintfax.com/v1/faxes', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MINTFAX_API_KEY}`,
      'Idempotency-Key': idempotencyKey,
    },
    body: form,
  });
  if (res.ok) break;
  if (res.status >= 500) continue; // safe replay with same key
  throw new Error(`fax submit failed: ${res.status}`);
}
```

Two failure modes to handle:

* Same key with a different body returns `409 idempotency_key_reuse`. Either regenerate the key or replay the exact same body.
* Same key while the original is still in flight returns `409 idempotency_key_in_progress`. Wait and re-poll `GET /v1/faxes/{id}` instead of resubmitting.

Replayed responses carry `Idempotent-Replayed: true`. Full behaviour on [Idempotency keys](https://mintfax.com/docs/idempotency.md).

**Verify** Submit the same request twice with the same key. The second response matches the first, no second fax appears in `GET /v1/faxes`, and the response header echoes the key.

## Step 5: Set a per-fax retry policy

The Dropbox Fax API v3 does not document a per-fax retry policy: there is no `max_attempts`, `retry_count`, or equivalent field in the send-request body, and the reference does not state what happens when a transmission fails on the carrier side. A busy signal may be retried, or it may not; the docs do not say.

mintfax retries carrier failures automatically. The default is 3 attempts with backoff `1m / 3m / 5m / 5m` between attempts. Set `max_attempts` in the range 1-5 per fax to override.

```bash curl theme={null}
curl -X POST 'https://api.mintfax.com/v1/faxes' \
  -H 'Authorization: Bearer mfx_test_4eC39HqLyjWDarjtT1zdp7dc' \
  -F 'to=+12015550100' \
  -F 'file=@invoice.pdf' \
  -F 'max_attempts=4'
```

Retryable codes (`busy`, `no_answer`, `line_quality`, `carrier_busy`, `network_error`, `timeout`, `internal_error`) consume an attempt. Terminal codes (`invalid_number`, `bad_destination`, `no_route`) short-circuit to `fax.failed` regardless of attempts remaining. Full lifecycle on the fax resource's `attempts[]` array; a `fax.sending` event fires once per attempt.

**Verify** Send to `+15005550004` (the `DelayedDelivery` sandbox number). The first attempt fails with a transient error, then attempt 2 succeeds. You receive two `fax.sending` events and one `fax.delivered`.

## Step 6: Handle 429 rate-limit responses

The Dropbox Fax API v3 enforces a hard cap of 200 pending faxes per user per day. Exceeding the cap returns HTTP 429 with the body *"Please wait. You already have at least 200 transmissions that are currently pending or transmitting."* The Dropbox help article for the rate limit does not document a `Retry-After` header or a programmatic wait-time hint on that response, so the client picks its own backoff.

mintfax returns HTTP 429 with `rate_limit_exceeded` in the error envelope. Back off before retrying and reuse the same `Idempotency-Key` on the resend so the retry is safe.

```javascript Node theme={null}
async function send(form, idempotencyKey) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch('https://api.mintfax.com/v1/faxes', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.MINTFAX_API_KEY}`,
        'Idempotency-Key': idempotencyKey,
      },
      body: form,
    });
    if (res.status !== 429) return res;
    const wait = Math.min(60, 2 ** attempt) * 1000; // exponential backoff, capped at 60s
    await new Promise((r) => setTimeout(r, wait));
  }
  throw new Error('rate_limit_exceeded: gave up after 5 attempts');
}
```

**Verify** The response is HTTP 429 and the JSON envelope carries `error: "rate_limit_exceeded"` and `request_id: "req_..."`. See [Errors](https://mintfax.com/docs/errors.md) for the envelope.

## Step 7: Move from `test_mode` to the sandbox environment

The Dropbox Fax API v3 marks a send as a test by setting `test_mode: true` on the request. Live and test traffic share the same key and the same endpoint; the boolean decides which is which.

mintfax separates environments by key prefix. `mfx_test_` keys route to the sandbox and cannot reach a live destination. Nine test numbers force nine outcomes:

| Number | Scenario | Behaviour |
| - | - | - |
| `+15005550001` | Delivered | Succeeds on first attempt, 1 page |
| `+15005550002` | Busy | Fails per attempt with `busy`; terminates failed after retries |
| `+15005550003` | NoAnswer | Fails per attempt with `no_answer`; terminates after retries |
| `+15005550004` | DelayedDelivery | Fails first attempt with a transient error, delivers on attempt 2+ |
| `+15005550005` | LineQualityFailure | Fails per attempt with `line_quality`; terminates after retries |
| `+15005550006` | InsufficientBalance | HTTP 402 `insufficient_balance`; no fax record created |
| `+15005550007` | DeliveredMultiPage | Succeeds on first attempt, 3 pages |
| `+15005550008` | InvalidNumber | Fails on attempt 1 with `invalid_number`; no retries |
| `+15005550099` | ValidationFailure | HTTP 422 validation error; no fax record created |

Any non-magic sandbox number defaults to the Delivered outcome. Sandbox and live share the same API surface; three behaviours differ (credit top-ups, PSTN routing, and the live-activation gate). See [Sandbox testing](https://mintfax.com/docs/sandbox.md) for the full matrix.

## Step 8: Inbound gap

mintfax is outbound only in v1. There is no inbound receive capability, no inbound-fax storage, and no inbound-fax API. If your workload depends on receiving faxes to a provisioned number, or on managing an inbox through a portal, the Dropbox Fax API is the right tool for now and mintfax is not a drop-in.

One operational note on the Dropbox side worth carrying with you either way: received faxes cannot be deleted through the Dropbox Fax API. To delete a received fax, a user has to log in to the web application. If a HIPAA-shaped workflow requires API-driven PHI deletion on inbound records, that constraint on the Dropbox Fax API is a live retention consideration independent of the choice to migrate.

For outbound-only workloads, continue to step 9.

## Step 9: Signup and agreements

The Dropbox Fax API's signup path routes through the Dropbox Sign Support team. To move from evaluation to a working API key, a would-be customer emails `support@hellosign.com` and sets up the API after that. Supporting HIPAA compliance on any Dropbox Sign product requires an annual Standard or Premium plan, a signed Business Associate Agreement, and a minimum contract value that Dropbox does not publish.

mintfax's signup is self-serve through the API. `POST /account/register` followed by `POST /account/register/verify` with a 6-digit code provisions a sandbox key immediately. No card, no BAA, no support ticket. Live activation happens on the first successful live credit purchase.

The BAA and DPA are separate click-through documents in the dashboard. Acceptance writes a durable evidence record (account, template version, signer name and title, IP, user agent, `accepted_at`) and the executed PDF is downloadable immediately. No sales contact, no annual plan gate.

## Migration checklist

Work through these in order. Each item corresponds to a step above.

* [ ] Sandbox key issued from `POST /account/register/verify`.
* [ ] Base URL updated to `https://api.mintfax.com/v1`.
* [ ] Auth header changed from HTTP Basic (or Bearer OAuth) to `Authorization: Bearer mfx_test_...`.
* [ ] `test_mode: true` removed from the send payload; sandbox is inferred from the key prefix.
* [ ] `recipient` renamed to `to`; numbers reformatted to E.164.
* [ ] `Idempotency-Key` header generated per logical send and reused on retry.
* [ ] `max_attempts` set per fax where the default of 3 attempts is not the right ceiling.
* [ ] Receiver code verifies Standard Webhooks signatures via the reference library; `event_hash` check removed.
* [ ] Event handler routes on `event.type` (values `fax.queued`, `fax.sending`, `fax.delivered`, `fax.failed`) and dedupes on `event.id`.
* [ ] 429 handler backs off and retries on `rate_limit_exceeded`, reusing the same `Idempotency-Key` across attempts.
* [ ] Sandbox round-trip complete: `+15005550001` returns a `fax_`-prefixed ID, transitions through `queued` and `submitted`, and ends in `delivered` with a valid webhook signature.
* [ ] `+15005550004` verifies the retry path; `+15005550008` verifies terminal short-circuit; `+15005550099` verifies validation rejection.
* [ ] BAA (and DPA if applicable) accepted in the dashboard before the first live key.
* [ ] Feature flag in place at the receiver so a single deploy flips between the Dropbox Fax API and mintfax per request.

## What to do next

* [Verify webhook signatures](https://mintfax.com/docs/webhooks/verify-requests.md) - Standard Webhooks contract and reference-library snippets beyond Node.
* [Idempotency keys](https://mintfax.com/docs/idempotency.md) - full 24-hour replay window and generation strategy.
* [Errors](https://mintfax.com/docs/errors.md) - error-code catalog with `action` hints.
* [Event types](https://mintfax.com/docs/webhooks/events/overview.md) - every event type, payload shape, and delivery guarantee.
* [Sandbox testing](https://mintfax.com/docs/sandbox.md) - the nine magic numbers and their outcomes.
* [Dropbox Fax API alternative](https://mintfax.com/docs/compare/dropbox-fax-api-alternative) - side-by-side comparison of the two products.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.