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

# Account activation

> What enables live operations and why activation is tied to a real credit purchase

Live access on mintfax is gated on a single timestamp: `account.activated_at`. Until that field is set, live operational endpoints refuse to run. Sandbox is never gated.

## The activation gate

The activation gate runs on every authenticated request. When a request arrives with an `mfx_live_...` key on an operational route (`POST /faxes`, `GET /environment/*`, `GET /faxes/{id}`, and so on), mintfax checks the account's `activated_at`. If it is null, the request is rejected before it reaches your application handler:

```json theme={null}
{
  "error": "account_not_activated",
  "message": "Live operations require account activation. Activate via the dashboard.",
  "action": "activate_account",
  "docs": "https://mintfax.com/docs/errors/account-not-activated"
}
```

HTTP 403. See [`account_not_activated`](/docs/errors#account-not-activated) in the error catalog.

Sandbox routes have no activation check. As soon as registration completes, you can send sandbox faxes with the `mfx_test_...` key returned at signup, manage keys with the `mfx_acct_...` key, and explore the API freely.

## `activated_at` is set on first successful live credit purchase

`activated_at` is written atomically in the same database transaction as the first successful live credit purchase. Until that purchase succeeds, the account stays unactivated - even if the user has signed up, verified their email, and added a card.

The flow is:

1. User clicks **Activate Live** in the dashboard.
2. Stripe Checkout opens. The session collects the card AND charges for the first credit package.
3. On a successful charge, mintfax (in one transaction):
   * Records the payment.
   * Credits the live pool.
   * Sets `account.activated_at = now()`.
4. Once activation completes, subsequent live requests succeed immediately - there is no caching layer to invalidate.

If the Stripe Checkout fails (card declined, user abandons), nothing is set. The account remains unactivated and the user can retry.

## Why not `stripe_id`?

A naive design would treat the presence of `stripe_id` (a Stripe customer ID) as activation. mintfax does not, because a **setup-mode** Stripe Checkout writes `stripe_id` to the account record without funding it. That style of flow saves a card for later use but does not move any money.

If `stripe_id` were the gate, anyone could mark themselves "activated" by adding a card they had no intention of being charged on. We would then accept live fax submissions, place holds, attempt the carrier dispatch, and only discover at capture time that the card cannot actually be charged - too late for a clean failure.

Tying activation to a real credit purchase fixes that: the first live fax always succeeds, because we already know the card works (it just charged) and the pool has credits to draw from.

## Buy-as-activation: one UX step, four atomic effects

The dashboard's **Activate Live** CTA collapses four operations into a single Stripe Checkout session:

| Effect                | Result                                      |
| --------------------- | ------------------------------------------- |
| Card on file          | Stripe customer + payment method saved      |
| Initial charge        | Real money moves                            |
| Credits the live pool | `available` increases on the live wallet    |
| Sets `activated_at`   | Live operational endpoints become available |

All four happen in the same database transaction in mintfax's webhook handler when Stripe confirms the successful payment. There is no half-activated state - either every step succeeded and the account is live, or none did and the account is unchanged.

## The unactivated phase

Between registration and first live purchase, the account exists in an unactivated state. What works and what does not:

| Action                                              | State                                                                                      |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Register with `POST /account/register`              | Works                                                                                      |
| Receive `mfx_test_...` and `mfx_acct_...` at verify | Works                                                                                      |
| Send sandbox faxes with `mfx_test_...`              | Works                                                                                      |
| Read sandbox balance and transactions               | Works                                                                                      |
| Manage keys with `mfx_acct_...`                     | Works                                                                                      |
| Create a live environment                           | Works (the environment exists; the keys do not yet)                                        |
| Mint a `mfx_live_...` key                           | Works (the key exists, but every operational call rejects it with `account_not_activated`) |
| Send a live fax                                     | **Blocked** - `account_not_activated`                                                      |
| Read live balance via `/environment/balance`        | **Blocked** - `account_not_activated`                                                      |

A live key on an operational route never gets past authentication while the account is unactivated. `AuthenticateApiKey` raises `account_not_activated` during auth itself, so the request never reaches your handler. This is intentional: the system stays consistent (live operations stay strictly off until activation) and the error code is specific enough for a client to act on.

## Detecting and handling the gate

If you build automation that may run before activation completes, watch for the error code and show the dashboard URL to your operator:

```bash theme={null}
curl -X POST https://api.mintfax.com/v1/faxes \
  -H "Authorization: Bearer mfx_live_xyz789wvu654..." \
  -F "to=+15125550100" \
  -F "file=@invoice.pdf"
```

```json theme={null}
{
  "error": "account_not_activated",
  "message": "Live operations require account activation. Activate via the dashboard.",
  "action": "activate_account",
  "docs": "https://mintfax.com/docs/errors/account-not-activated"
}
```

The `action` field is stable - match on `activate_account` and prompt your user to complete the dashboard step. Once activation succeeds, the same request will go through without code changes.

## Reactivation

There is no deactivation gate in normal operation - once `activated_at` is set, it stays set. mintfax does not currently expose an environment-level suspension state; if you need to stop API access for an environment, revoke the keys attached to it.

## Related pages

* [Credits](/docs/credits) - the account-pool credit model
* [Keys](/docs/keys) - environment vs account keys and prefix routing
* [Errors](/docs/errors) - `account_not_activated` and adjacent codes
