---
title: "A leaked API key"
description: "Contain an exposed Mailfully key: rotate it, understand the grace window on the old secret, and audit what it could reach."
---

> **For AI agents:** the complete documentation index is at [llms.txt](/docs/llms.txt). Append `.md` to any page URL for its markdown version.

Decide whether to rotate or revoke before you touch the key. Rotation starts a 24-hour grace window that revoking afterward cannot shorten, so doing the two in the wrong order leaves a hostile exposure live for the full 24 hours regardless. Then audit what the key could reach.

## Decide: rotate or revoke

| Situation | Action |
|---|---|
| Accidental commit in a private repo you control | Rotate: the 24-hour overlap lets you redeploy without an outage |
| Public repo, published client bundle, or any exposure you cannot bound | Revoke first, and don't rotate first: revoking an active key ends it immediately, but revoking a key that already carries a rotation's future-dated `revoked_at` just keeps that existing timestamp |

Rotation stamps the old key with a future-dated `revoked_at` to start its grace window. Revoke on a key that already has a `revoked_at` for any reason, including that grace window, and revoke keeps the existing timestamp rather than moving it earlier: revoke never shortens or extends a rotation grace window. So for a public or hostile exposure, revoke first.

## Rotate immediately, for a bounded exposure

Rotation mints a successor that copies the old key's name, scopes, domain, and environment, and returns the new plaintext once. Rotate from the dashboard's API keys page:

```bash
curl -X POST https://api.mailfully.com/v1/api-keys/key_01J1F8Z3N9WXYQ5T2V4B6C8D0E/rotate \
  -H "Authorization: Bearer <session token>"
```

Rotation requires `manage:keys`, a session-only scope: every `mf_` API key receives `403` here, so the request has to carry a dashboard session token, never `mf_live_`. Only a key with no `revoked_at` can be rotated; a revoked or already-mid-grace key returns `409`. See [Rotate an API key](/api-reference/api-keys/rotate).

## Understand the grace window

Rotation doesn't kill the old secret instantly. It keeps authenticating for 24 hours so a deploy can overlap, then fails with `403 invalid_api_key` ("This API key has been revoked."). For a hostile exposure, that 24 hours is 24 hours of someone else sending as you, which is exactly why the decision above has to happen first.

## Revoke for a public or hostile exposure

Revocation ends the old secret immediately, at the cost of breaking anything still using it, provided the key is still active when you revoke it. That's the right call for a public repo, a published client bundle, or any exposure you can't bound.

```bash
curl -X POST https://api.mailfully.com/v1/api-keys/key_01J1F8Z3N9WXYQ5T2V4B6C8D0E/revoke \
  -H "Authorization: Bearer <session token>"
```

Also `manage:keys`, so also a dashboard-session action. See [Revoke an API key](/api-reference/api-keys/revoke).

## Audit what it could do

Read the key's scopes and its `domain_id`. A `domain_id` restricts the key to that one domain; without one, a `send` key can send from any of your verified domains. Check for sends you don't recognize and for a volume spike, both reachable with an API key since they need only `read:emails` and `read:analytics`:

```bash
curl "https://api.mailfully.com/v1/emails?limit=100" \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx"
```

See [`GET /v1/emails`](/api-reference/emails/list) and [Analytics](/guides/analytics) for the daily rollup.

## Then clean up the exposure

Rotating or revoking the key doesn't remove the secret from git history or from a log aggregator. Treat those as separate cleanup.

## Prevent the next one

Keep keys in environment variables rather than in source, use separate test and live keys, and scope each key as narrowly as the task needs. [API key security](/concepts/api-key-security) covers where to store a key's secret and how to keep scopes narrow.
