> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tallyforagents.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Bearer-token API keys, scoped to account and mode.

Every Tally API request is authenticated with a Bearer token. The token's prefix encodes its mode, so callers don't pass `mode` explicitly — it's derived from the key.

## The header

```
Authorization: Bearer tly_test_<rest_of_key>
```

Two prefixes, fixed at key creation:

| Prefix       | Mode | Network                                    |
| ------------ | ---- | ------------------------------------------ |
| `tly_test_…` | test | Base Sepolia (testnet USDC, no real money) |
| `tly_live_…` | live | Base mainnet (real USDC)                   |

A `tly_test_` key can only see test-mode resources; same for live. There's no cross-mode access path — see [Test mode and live mode](/test-mode) for the full partitioning model.

<Note>
  The same `Authorization: Bearer …` header also accepts short-lived OAuth access
  tokens (`tly_oat_…`) issued by Tally's [OAuth 2.1 flow](/oauth). MCP hosts use
  OAuth so users don't paste a static key — see
  [MCP server → Authentication](/sdk/mcp-server#authentication).
</Note>

## Creating a key

From the dashboard, **API keys → New key**. The plaintext is shown exactly once. Store it in your secret manager immediately.

Programmatic key creation is not currently exposed through the public API — by design. Keys are sensitive, and minting them is a privileged action you should do consciously through the dashboard.

## Authentication failure responses

A request that fails authentication gets one of these:

### 401 Unauthorized — missing or malformed

```json theme={null}
{ "error": { "type": "unauthenticated", "message": "Missing or malformed Authorization header." } }
```

The `Authorization` header is missing, doesn't start with `Bearer `, or the token doesn't have a recognized prefix.

### 401 Unauthorized — invalid or revoked

```json theme={null}
{ "error": { "type": "unauthenticated", "message": "Invalid or revoked API key." } }
```

The key doesn't exist (never minted, mistyped) **or** has been revoked, **or** is past its 24-hour rotation grace window. The server doesn't distinguish these in the response — treat it as a generic auth failure and check the dashboard for the key's status.

### 401 Unauthorized — mode mismatch

```json theme={null}
{ "error": { "type": "unauthenticated", "message": "API key mode mismatch." } }
```

Defense in depth: the prefix says one mode but the database row says another. Should never happen in normal use; if you see this, treat it like any other auth failure and rotate the key.

## Rotation and the grace window

When you rotate a key from the dashboard, the **previous key keeps working for 24 hours** while you roll over your fleet. After the window closes, auth fails with the generic "invalid or revoked" response — semantically distinct from a hard revoke (which is immediate and irreversible) but surfaced the same way to the caller.

Rotated keys in the grace window return a `Tally-Rotation-Grace-Until` response header (work in progress in the SDK; see BUILD\_LOG) so callers can warn dev when they're about to lose access. The full rotation flow is in [API keys](/api-keys).

## Best practices

* **Environment variables only.** Never hardcode keys. Never commit them.
* **One key per environment.** Don't share `TALLY_API_KEY` across staging and production. The mode prefix gives you natural separation — use it.
* **Rotate after exposure.** If a key is ever logged, screenshotted, or pushed accidentally, rotate immediately. The 24-hour grace window means you can do this without downtime.
* **Validate the prefix at boot.** A startup check that `TALLY_API_KEY` starts with the expected prefix for the environment catches misconfigured deploys before the first request.
* **Server-side only.** API keys are server credentials — never put one in a browser bundle, a mobile app, or any client outside your trusted infrastructure. If you need a browser-callable surface, build a thin proxy in your backend.
