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

# Authentication

> How to authenticate with the Herodotus Cloud Services APIs

## Overview

To use the Herodotus Cloud Services APIs, you need to obtain an API key:

1. Visit the [Herodotus Console](https://herodotus.cloud/).
2. Register or log in using your GitHub account or in-browser wallet with Sign in with Ethereum.

<img src="https://mintcdn.com/herodotuscloudservices/S302XnSN--WAs0sk/images/auth/login.jpeg?fit=max&auto=format&n=S302XnSN--WAs0sk&q=85&s=aed2a16efae708416030f54c2a396dee" alt="Herodotus Console Login" width="3252" height="2210" data-path="images/auth/login.jpeg" />

3. Apply for credits or use an invite link to a project with credits.

<img src="https://mintcdn.com/herodotuscloudservices/S302XnSN--WAs0sk/images/auth/project.png?fit=max&auto=format&n=S302XnSN--WAs0sk&q=85&s=6f11a183c2d560eb8fccca173945c027" alt="Herodotus Console Project" width="3244" height="2196" data-path="images/auth/project.png" />

4. Once logged in, you can access your API key for your Project.

<img src="https://mintcdn.com/herodotuscloudservices/S302XnSN--WAs0sk/images/auth/api-keys.jpeg?fit=max&auto=format&n=S302XnSN--WAs0sk&q=85&s=afb8ba6b31a808ea00657791e84f3817" alt="Herodotus Console API Keys" width="3252" height="2210" data-path="images/auth/api-keys.jpeg" />

5. Include this API key in your requests to authenticate with the API.

## Programmatic wallet authentication

For AI agents, CLIs, and other non-browser clients, you can authenticate without the Herodotus Console UI using an EVM wallet and the EIP-712 challenge flow. The result is a Bearer access token plus an API key — no cookie jar required.

<Note>
  Want a copy-paste playbook for an AI assistant? Load the
  [Herodotus Auth AI Skill](/skills/herodotus-auth) — it includes the full
  protocol contract, anti-hallucination guardrails, and a viem reference
  example.
</Note>

**Base URL (prod):** `https://auth-billing.api.herodotus.cloud`

### Step 1 — Fetch a challenge

```http theme={null}
GET /auth/web3/challenge?wallet=0xYOUR_WALLET_ADDRESS
```

Response contains a short-lived `challengeToken` plus an `eip712` object with `domain`, `types`, `primaryType`, and `message`. Sign exactly those fields — do not reconstruct them client-side.

### Step 2 — Sign the typed data

Use any EIP-712 signer (viem, ethers, MetaMask, KMS, hardware wallet). The wallet that signs must match the address in the challenge URL.

### Step 3 — Exchange for a Bearer session

```http theme={null}
POST /auth/web3/session
Content-Type: application/json

{
  "wallet": "0xYOUR_WALLET_ADDRESS",
  "challengeToken": "<from step 1>",
  "signature": "<from step 2>",
  "channel": "bearer"
}
```

Response body:

```json theme={null}
{
  "accessToken": "...",
  "refreshToken": "...",
  "expiresAt": 1730000000,
  "selectedProject": "01J..."
}
```

When `channel` is `"bearer"`, no `Set-Cookie` is sent. Use `Authorization: Bearer <accessToken>` on every subsequent call. Omit `channel` (or pass `"cookie"`) to fall back to the browser cookie flow.

### Step 4 — Retrieve your API key

First-time wallets are auto-provisioned with a Personal project and one active API key. Read it with:

```http theme={null}
GET /api-keys?projectId=<selectedProject>&limit=10&offset=0
Authorization: Bearer <accessToken>
```

Response `data[0].apiKey` is the value to use against Atlantic API, Storage Proof API, etc. To mint additional keys later, `POST /api-keys` with `{ "projectId": "...", "type": { "name": "...", "color": "..." } }`.

### Step 5 — Refresh

When the access token approaches `expiresAt`, exchange the refresh token for a new pair:

```http theme={null}
POST /auth/refresh-token
Authorization: Bearer <refreshToken>
```

Response body returns a fresh `{ accessToken, refreshToken, expiresAt }`. The previous refresh token is invalidated. Cookie callers continue to refresh via cookies — the same endpoint serves both transports.

<Warning>
  **Channel binding is enforced server-side.** A token issued with
  `channel: "bearer"` is rejected if presented via cookie, and vice versa.
  This is a security property — do not extract a cookie session JWT and
  forward it as `Authorization: Bearer`. If you need both surfaces, run
  the cookie path and the bearer path independently.
</Warning>

### End-to-end example (viem)

```ts theme={null}
import { privateKeyToAccount } from 'viem/accounts';

const BASE = 'https://auth-billing.api.herodotus.cloud';
const account = privateKeyToAccount(process.env.HERODOTUS_WALLET_PRIVATE_KEY as `0x${string}`);

const challengeRes = await fetch(`${BASE}/auth/web3/challenge?wallet=${account.address}`);
if (!challengeRes.ok) throw new Error(`Challenge request failed: ${challengeRes.status}`);
const challenge = await challengeRes.json();

const signature = await account.signTypedData({
  domain: challenge.eip712.domain,
  types: challenge.eip712.types,
  primaryType: challenge.eip712.primaryType,
  message: challenge.eip712.message,
});

const sessionRes = await fetch(`${BASE}/auth/web3/session`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    wallet: account.address,
    challengeToken: challenge.challengeToken,
    signature,
    channel: 'bearer',
  }),
});
if (!sessionRes.ok) throw new Error(`Session exchange failed: ${sessionRes.status}`);
const { accessToken, refreshToken, selectedProject } = await sessionRes.json();

const keysRes = await fetch(`${BASE}/api-keys?projectId=${selectedProject}&limit=10&offset=0`, {
  headers: { authorization: `Bearer ${accessToken}` },
});
if (!keysRes.ok) throw new Error(`API keys request failed: ${keysRes.status}`);
const keys = await keysRes.json();

const apiKey = keys.data?.[0]?.apiKey;
if (!apiKey) throw new Error('No API key found for selected project');
```
