> For the complete documentation index, see [llms.txt](https://docs.brain.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.brain.fi/api-reference/authentication.md).

# Authentication

Brain authenticates three caller types: humans, internal agents, and external agents. The same API endpoints serve all three. Only the credential differs.

| Caller             | Mode                                                             | Credential                            |
| ------------------ | ---------------------------------------------------------------- | ------------------------------------- |
| **Human**          | Self-serve email + password, **or** a linked wallet              | Bearer owner JWT                      |
| **Internal agent** | Brain-issued service token (your own backend)                    | Bearer service token                  |
| **API partner**    | Tenant API key in the sandbox integration environment, read-only | Bearer `brain_sk_…` key               |
| **External agent** | SIWX (EIP-4361 over Base) + on-chain scope                       | `access_token` from the SIWX exchange |

Every credential is presented the same way: `Authorization: Bearer <token>`. There is one bearer mechanism, not several. The `brain_sk_test_…` / `brain_sk_live_…` value is a tenant API key when API-key authentication is enabled for that environment. See [Server API key](#server-api-key-brain_sk_) below.

{% hint style="warning" %}
At launch, first-class API-key authentication is enabled in the sandbox integration environment and is limited to read-only scopes. It is deliberately not enabled on the production API, where `brain_sk_` bearers fail closed as invalid keys. Production enablement remains a separate release gate after staging acceptance and auth-surface review; no public enablement date is committed. Production access continues to use the supported human, service, and agent credentials described on this page.
{% endhint %}

{% hint style="info" %}
Self-serve signup is gated by the `BRAIN_SELF_SERVE_SIGNUP` flag and is **sandbox-only** (RFC 0002): a new tenant can read and *propose*, but moves no money until the existing promotion + external-audit gates clear. Hosted SSO (Auth0/SAML) is **planned (roadmap)**, not in the MVP.
{% endhint %}

{% hint style="warning" %}
The internal `POST /v1/auth/service-token` mint route is a break-glass sandbox/testnet BFF credential path. It uses a shared secret, is not per-user auth, and must stay disabled for live-money or multi-customer production.
{% endhint %}

### Server API key (`brain_sk_`)

The credential a server-side integration uses is a Brain-issued tenant API key with a `brain_sk_` prefix. It authenticates directly as a bearer credential; there is no token exchange step.

| Property            | Value                                                                                                                       |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Format**          | `brain_sk_test_…` (sandbox) / `brain_sk_live_…` (live)                                                                      |
| **Availability**    | Sandbox integration environment at launch; production API-key auth is deliberately disabled pending a separate release gate |
| **Issued by**       | Tenant-admin key routes when API-key auth is enabled for the environment                                                    |
| **Presented as**    | `Authorization: Bearer brain_sk_…`, or `new Brain({ apiKey: "brain_sk_…" })` in the SDK                                     |
| **Scopes**          | `ledger:read`, `audit:read`, `governance:read` only. API keys cannot ingest source data.                                    |
| **Sandbox vs live** | Distinct `brain_sk_test_…` and `brain_sk_live_…` key formats                                                                |
| **Lifetime**        | No automatic TTL at issuance. Revocation and rotation are supported; an optional `expires_at` is enforced when populated.   |

The plaintext secret is returned only when a key is issued or rotated. The database retains only a server-peppered SHA-256 digest. Rate limits, idempotency, last-used tracking, and audit attribution are keyed off the authenticated key.

### Human Authentication (self-serve email + password)

A developer self-provisions a sandbox tenant, verifies their email, then logs in for a short-lived **owner JWT** carrying management/read/approve scopes only. Never `payment_intent:propose` / `payment_intent:execute` / `execution:propose` (money movement is an agent + §6-gate concern, never a human-login capability).

**1. Sign up**. Provisions a sandbox tenant + owner.

```http
POST /v1/signup
Content-Type: application/json

{ "email": "founder@example.com", "password": "a-strong-passphrase-12+" }

→ 201 { "tenant_id": "tnt_…", "user_id": "user_…", "status": "pending",
        "verification_token": "…" }   // returned outside production; emailed in prod
```

In production, the API emails the token through the configured ESP client (`EMAIL_ENDPOINT`, `EMAIL_API_KEY`, optional `EMAIL_FROM`). If `BRAIN_SELF_SERVE_SIGNUP` is enabled in production without ESP credentials, API boot fails before signup routes are served.

**2. Verify the email**. Single-use, short-TTL token, scoped to the tenant.

```http
POST /v1/auth/verify-email
{ "tenant_id": "tnt_…", "token": "<verification_token>" }

→ 200 { "verified": true, "user_id": "user_…", "status": "active" }
```

**3. Log in**. Email + password -> owner JWT.

```http
POST /v1/auth/login
{ "email": "founder@example.com", "password": "a-strong-passphrase-12+" }

→ 200 { "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 900,
        "principal": { "type": "user", "tenantId": "tnt_…",
                       "scopes": ["ledger:read","wiki:read","raw:read","raw:write",
                                  "policy:read","policy:write","audit:read","execution:read",
                                  "payment_intent:approve","surfaces:admin"] } }
```

An unknown email and a wrong password return the **same** `401 auth_invalid_credentials` (no user enumeration); an unverified account returns `403 auth_email_unverified`.

```http
GET /v1/ledger/transactions
Authorization: Bearer <access_token>
```

### Wallet Authentication (SIWX). Agents and humans

External agents. And humans who **link a wallet**. Authenticate with **Sign-In With X** (EIP-4361 over Base). An owner can link a wallet to their tenant:

```http
POST /v1/tenants/{tenant_id}/wallets        (owner JWT)
{ "address": "0x…", "principal_type": "human" }   // or "agent" + principal_id
```

At sign-in, SIWX resolves the wallet: one linked to a **human** mints an **owner JWT** (the same management scopes as email login); an **agent** wallet (registered + active in `BrainMCPAgentRegistry`) mints an **agent token**.

#### Step 1: Construct the SIWX Message

```
brain.fi wants you to sign in with your Ethereum account:
0xAgentAddress

URI: https://api.brain.fi
Version: 1
Chain ID: 8453
Nonce: <server-issued nonce>
Issued At: 2025-09-01T12:00:00Z
Expiration Time: 2025-09-01T12:05:00Z
```

A nonce is obtained from `POST /v1/auth/siwx/challenge` (Redis-held, 5-minute TTL).

#### Step 2: Sign with the Identity Key

The agent (or human's linked wallet) signs the message with the key registered in `BrainMCPAgentRegistry` / linked via `wallet_identities`.

#### Step 3: Exchange for a Token

```http
POST /v1/auth/siwx
Content-Type: application/json

{ "message": "...", "signature": "0x...", "session_id": "..." }

→ {
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "principal": { "type": "agent", "tenantId": "tnt_…", "scopes": ["ledger:read", "payment_intent:propose"] }
}
```

#### Step 4: Use the Token

```http
POST /v1/agents/mcp
Authorization: Bearer <access_token>
```

{% hint style="info" %}
The MCP auth chain verifies that the agent record is `active` and that its stored `scope_hash` matches the hash registered in `BrainMCPAgentRegistry`. Agents can read, contribute evidence, and **propose**. Never **execute**. There is no execute tool, and every settlement passes the section 6 gate.
{% endhint %}

### Agent Scope Hash

The current scope mechanism is a hash commitment, not an EIP-712 scope-attestation object. Brain sorts an agent's plain scope strings lexicographically, joins them with `|`, and calculates `keccak256` over that value. The resulting `scope_hash` must match the agent's registered on-chain hash. It has no per-grant amount, resource, time-window, or nonce fields.

### Token Lifetimes

| Token                                                    | Default TTL      | Refreshable                           |
| -------------------------------------------------------- | ---------------- | ------------------------------------- |
| **Owner JWT** (email/wallet)                             | 15 minutes       | Yes. Log in / re-sign again           |
| **Agent token (SIWX)**                                   | 1 hour           | Yes, by re-signing SIWX               |
| **Server API key** (`brain_sk_`, sandbox only at launch) | No automatic TTL | Rotated or revoked by tenant admin    |
| **Service-token mint** (`/v1/auth/service-token`)        | 1 hour           | Re-mint (break-glass sandbox/testnet) |
| **Email-verification token**                             | 24 hours         | No, single-use                        |
| **Policy verdict**                                       | 60 seconds       | No, single-use                        |

Owner JWTs include `surfaces:admin` so a tenant admin can connect or revoke Slack, Teams, and email approval surfaces. Surface onboarding endpoints derive the Brain tenant from this bearer principal, not from request bodies.

### Agent Control

| Operation   | Endpoint                                 | Behavior                                                                                                                                                                                  |
| ----------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Halt**    | `POST /v1/agents/{agent_id}/halt`        | Requires `payment_intent:approve`. Pauses approved in-flight payment intents from the agent and quarantines its local agent record.                                                       |
| **Restore** | `POST /v1/agents/{agent_id}/restore`     | Requires `payment_intent:approve`. Restores a quarantined local agent record to `active`. Returns 404 when the agent is not registered and 409 unless its current state is `quarantined`. |
| **Token**   | Re-authenticate or rotate the credential | Tokens are short-lived by design and expire after their configured lifetime.                                                                                                              |

### What's Next

<table data-view="cards"><thead><tr><th></th><th></th><th data-type="content-ref"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>🌐 API Overview</strong></td><td>Endpoints, versioning, rate limits.</td><td><a href="/api-reference/overview.md">Overview</a></td><td></td></tr><tr><td><strong>📜 BrainMCPAgentRegistry</strong></td><td>The on-chain agent registry.</td><td><a href="/smart-contracts/brainmcpagentregistry.md">BrainMCPAgentRegistry</a></td><td></td></tr></tbody></table>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.brain.fi/api-reference/authentication.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
