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

# Read Authentication

Unauthenticated reads return data **delayed by 20 seconds**. This applies to `POST /info`,
`POST /read`, `POST /portfolio` and the `/ws` WebSocket. To read your own account live, send a
read credential: a short-lived EIP-712 session signed by the account owner or one of its approved
API wallets.

`POST /leaderboard` is a public exception: cached rankings, volume, PnL, ROI, account values
and Genesis badges come from the immediate follower, without an added 20-second delay.
`POST /class-progress` is also public and returns only the account's class-unlock percentage
from the immediate follower, with no added delay. Neither endpoint needs a connected wallet
or a read credential.

## Routes

| Route | Credential | Data |
| - | - | - |
| `POST /info` | None | Delayed by 20 seconds |
| `POST /info` | `NotionalRead` | Live; the credential's own account only (see [Info types](#info-types)) |
| `POST /info/live` | None | Live; only the [live types](#live-types) |
| `POST /info/onboarding` | None | Near-live; only the [onboarding types](#onboarding-types) |
| `POST /leaderboard` | None | Public cached leaderboard, including badges; no added delay |
| `POST /class-progress` | None | Public class-unlock percentage only; no added delay |
| `/ws` | None | Delayed by 20 seconds |
| `/ws/user` | `auth` message | Live; see [Authenticated WebSocket](/api-reference/websocket/user-socket) |

`POST /exchange` is unchanged and always reaches the live node.

## Public Class Progress

Send `POST /class-progress` with an account address:

```json theme={null}
{ "user": "0x1111111111111111111111111111111111111111" }
```

The response contains only a percentage between 0 and 100:

```json theme={null}
{ "progressPct": 65 }
```

This endpoint powers Rewards, progress notifications and Genesis badge eligibility on PnL share
cards, including before an agent is approved. It does not return points, trading activity or
positions. Invalid addresses return 400; unavailable progress data returns 503.

Use this endpoint instead of anonymous `/read` requests with `type: "classProgress"`, which
reach the delayed follower without PostgreSQL and return 503. Authenticated own-account
`classProgress` reads through `/read` remain supported.

## Read Credential

The credential is an EIP-712 `NotionalReadSession` message.

| Domain field | Value |
| - | - |
| `name` | `"NotionalRead"` |
| `version` | `"1"` |
| `chainId` | `1337` |
| `verifyingContract` | `0x0000000000000000000000000000000000000000` |

| Message field | Type | Value |
| - | - | - |
| `credentialVersion` | `uint32` | `1` |
| `notionalChain` | `string` | `"Mainnet"` or `"Testnet"`, matching the deployment |
| `user` | `address` | The account being read, lowercase hex |
| `issuedAt` | `uint64` | Unix seconds |
| `expiresAt` | `uint64` | Unix seconds; at most 900 seconds (15 minutes) after `issuedAt` |

The signer must be `user` itself or an API wallet currently approved for that account. The server
rejects a credential whose `issuedAt` is more than 60 seconds in its future, or whose `expiresAt`
has passed. Sign a new credential before the current one expires.

### Header

Send the credential in the standard `Authorization` header:

```text theme={null}
Authorization: NotionalRead <token>
```

`<token>` is the unpadded base64url encoding of this JSON object:

```json theme={null}
{
  "message": {
    "credentialVersion": 1,
    "notionalChain": "Mainnet",
    "user": "0x1111111111111111111111111111111111111111",
    "issuedAt": 1760000000,
    "expiresAt": 1760000900
  },
  "signature": "0x…"
}
```

The parse is strict: exactly these keys, integer seconds, a lowercase `user`, and a lowercase
65-byte hex `signature` ending in `1b` or `1c` (what viem returns). Key order and whitespace do
not matter.

### Signing Example

```typescript theme={null}
import type { Address, WalletClient } from "viem";

export async function createReadAuthorization(
  walletClient: WalletClient,
  user: Address,
  network: "Mainnet" | "Testnet",
): Promise<string> {
  const issuedAt = Math.floor(Date.now() / 1000);
  const message = {
    credentialVersion: 1,
    notionalChain: network,
    user: user.toLowerCase() as Address,
    issuedAt,
    expiresAt: issuedAt + 900,
  };
  const signature = await walletClient.signTypedData({
    account: walletClient.account!,
    domain: {
      name: "NotionalRead",
      version: "1",
      chainId: 1337,
      verifyingContract: "0x0000000000000000000000000000000000000000",
    },
    types: {
      NotionalReadSession: [
        { name: "credentialVersion", type: "uint32" },
        { name: "notionalChain", type: "string" },
        { name: "user", type: "address" },
        { name: "issuedAt", type: "uint64" },
        { name: "expiresAt", type: "uint64" },
      ],
    },
    primaryType: "NotionalReadSession",
    message: { ...message, issuedAt: BigInt(issuedAt), expiresAt: BigInt(message.expiresAt) },
  });
  const json = JSON.stringify({ message, signature });
  const token = btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
  return `NotionalRead ${token}`;
}
```

Use the returned value as the `Authorization` header and as the `credential` of a `/ws/user`
`auth` message. An API wallet can sign with a
[local account](https://viem.sh/docs/clients/wallet#local-accounts-private-key-mnemonic-etc), so no
wallet prompt is needed.

## Info Types

With a read credential, `POST /info` serves:

* the credential's own account data for `account`,
  `positions`, `openOrders`, `userFills`, `historicalOrders`, `userFundings`,
  `userNonFundingLedgerUpdates`, `userBorrowLendInterest`, `spotClearinghouseState`,
  `pendingWithdrawals`, `referrals`, `extraAgents`, `activeAssetData`, `protocolTransferQuote`,
  `transactionsByUser`, `twapListActive`, `twapListHistory` and `twapGetById`.
  `user` must equal the credential's `user`; another account returns `403`;
* `transactionByHash` and `protocolTransferStatus`, only when the credential's account took part
  in the transaction or transfer. Otherwise the response is the same as "not found";
* public market metadata: `perpMeta`, `spotMeta` and `predictionMeta`;
* the [live types](#live-types).

Block explorer and replay types are not served with a read credential. Use unauthenticated
`/info` for them, which returns delayed data.

`checkTerms` and `checkAccess` also return `403 type_not_allowed` on authenticated `/info`.
Use [`/info/onboarding`](#onboarding-types) for these types without a credential.

### Live Types

`POST /info/live` serves these types live without a credential: `config`, `borrowLendState`,
`protocolTransferRoutes` and `userRole`. Any other type returns `403`.

### Onboarding Types

`POST /info/onboarding` serves `checkTerms` and `checkAccess` without a credential, about one
second behind the live state. After `acceptTerms` or `setReferrer`, poll briefly until the change
is visible. Any other type returns `403`.

## Errors

A credential that fails a check is refused. The request never falls back to delayed data.

| Status | `error` contains | Meaning |
| - | - | - |
| `401` | `malformed`, `unsupported_version` | The header does not parse, or the version is unsupported |
| `401` | `wrong_network` | `notionalChain` does not match the deployment |
| `401` | `expired`, `not_yet_valid`, `lifetime_too_long` | The validity window check failed |
| `401` | `bad_signature`, `unauthorized_signer` | The signature is invalid, or the signer is not the account or an approved API wallet |
| `403` | `wrong_user` | The request reads another account |
| `403` | `type_not_allowed` | The type is not served on this route or for this caller |

Errors use the standard [error format](/api-reference/error-responses) with code `UNAUTHORIZED`,
and `error` reads `Read authorization failed: <reason>`. A credential signed by a newly approved
API wallet can get `401` from `/read` and `/portfolio` for about a second while the approval
propagates; retry briefly.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.