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

# Authentication

> Sign in with your wallet using SIWE to open a session, and provision scoped API keys for programmatic and market-maker access.

OCX authenticates you with **Sign-In With Ethereum (SIWE, EIP-4361)**. You prove
control of your wallet by signing a message; the exchange returns a **session**
that authorizes your trading, wallet, and account requests. For automation, you
then mint scoped **API keys**.

All requests go through the API gateway, referred to below as `{BASE_URL}`.

## Authentication tiers

<CardGroup cols={3}>
  <Card title="Public" icon="globe">
    No credentials. All market-data reads and public streams.
  </Card>

  <Card title="Session" icon="user">
    A SIWE sign-in yields an httpOnly session cookie (7-day validity). Required
    for placing orders and viewing your account, wallet, and keys.
  </Card>

  <Card title="API key" icon="key">
    An `x-api-key` header for programmatic access. Scoped `read` or `trade`.
    Accepted on high-throughput trading paths (previews, bulk quotes, cancel-all,
    algo streams).
  </Card>
</CardGroup>

<Note>
  API keys are created and revoked only from a signed-in session — a key cannot
  mint or revoke another key.
</Note>

## Sign in with your wallet

<Steps>
  <Step title="Request a nonce">
    Ask the gateway for a one-time, short-lived nonce to include in your signed
    message.

    ```json theme={null}
    POST {BASE_URL}/auth/nonce
    { "walletAddress": "0xYourAddress" }
    → { "nonce": "a1b2c3…" }
    ```

    The nonce is single-use. Request a fresh one on each sign-in.
  </Step>

  <Step title="Build and sign a SIWE message">
    Construct a standard EIP-4361 message in your wallet. It must include the
    `nonce` from step 1, the correct `chainId` and request `uri`/`domain` for
    OCX, and an `expirationTime` within the allowed window. Sign it with your
    wallet (for example `personal_sign` or wagmi's `signMessage`).

    <Warning>
      A signature minted for another site or chain is rejected — there is no
      cross-domain replay.
    </Warning>
  </Step>

  <Step title="Verify and open a session">
    Submit the exact message string and its signature. On success the response
    sets the session cookie and returns your identity.

    ```json theme={null}
    POST {BASE_URL}/auth/siwe
    { "message": "<the exact SIWE message string>", "signature": "0x<signature>" }
    → { "userId": "01H…", "walletAddress": "0xa1b2…c3d4" }
    ```
  </Step>

  <Step title="Make authenticated requests">
    In a browser the cookie is sent automatically. Confirm the current session or
    end it with the session helpers:

    ```text theme={null}
    GET  {BASE_URL}/auth/me       # → { userId, walletAddress }
    POST {BASE_URL}/auth/logout   # revokes the current session everywhere
    ```
  </Step>
</Steps>

<Note>
  New accounts may require an invite before trading is enabled. If invite gating
  is active, redeem a code once while authenticated:
  `POST {BASE_URL}/auth/redeem-invite` with `{ "code": "…" }`.
</Note>

### End-to-end example

<CodeGroup>
  ```bash curl theme={null}
  # 1. request a nonce
  NONCE=$(curl -s -X POST {BASE_URL}/auth/nonce \
    -H 'Content-Type: application/json' \
    -d '{"walletAddress":"0xabc..."}' | jq -r .nonce)

  # 2. build a SIWE message including $NONCE and sign it in your wallet → $MSG, $SIG

  # 3. verify and capture the session cookie
  curl -s -c cookies.txt -X POST {BASE_URL}/auth/siwe \
    -H 'Content-Type: application/json' \
    -d "{\"message\":\"$MSG\",\"signature\":\"$SIG\"}"

  # 4. authenticated call using the cookie jar
  curl -s -b cookies.txt {BASE_URL}/wallet/balance
  ```

  ```js JavaScript theme={null}
  // after your wallet produces `message` and `signature`
  const res = await fetch(`${BASE_URL}/auth/siwe`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    credentials: "include", // store the session cookie
    body: JSON.stringify({ message, signature }),
  });
  const { userId, walletAddress } = await res.json();
  ```
</CodeGroup>

## Provision an API key for automation

For bots, market makers, and agents, sign in once with SIWE, then mint an API key
scoped to what the client needs.

<Steps>
  <Step title="Create the key">
    ```json theme={null}
    POST {BASE_URL}/perps/me/api-keys
    { "name": "mm-bot-1", "scope": "trade", "allowedIps": ["203.0.113.7"], "expiresInDays": 90 }
    → { "id": "01H…", "name": "mm-bot-1", "scope": "trade", "secret": "ocx_sk_…" }
    ```

    <Warning>
      The `secret` is shown **once only** and can never be retrieved again. Copy
      it immediately into a secret store — never commit it to code or logs.
    </Warning>
  </Step>

  <Step title="Send it on every request">
    Attach the secret as a header on subsequent automated calls:

    ```bash theme={null}
    curl -H "x-api-key: ocx_sk_…" {BASE_URL}/perps/orders/preview -d '…'
    ```
  </Step>

  <Step title="List, rotate, and revoke">
    ```text theme={null}
    GET    {BASE_URL}/perps/me/api-keys        # metadata only; secrets never returned
    DELETE {BASE_URL}/perps/me/api-keys/{id}   # revoke
    ```

    Set `expiresInDays` for scheduled rotation and pin `allowedIps` to your
    egress IPs. Grant least privilege — `read` for monitoring, `trade` for
    execution.
  </Step>
</Steps>

## Scopes and access

| Capability                                                | Public | Session |   API key   |
| --------------------------------------------------------- | :----: | :-----: | :---------: |
| Market-data reads and public streams                      |    ✓   |    ✓    |      ✓      |
| Order preview, bulk quotes, cancel-all, algo streams      |        |    ✓    | ✓ (`trade`) |
| Place/cancel single orders, view your account & positions |        |    ✓    |             |
| Create / revoke API keys                                  |        |    ✓    |             |

## Rate limits

Requests are rate-limited per client. Authenticated and API-key traffic receives
higher limits than anonymous traffic. Every response carries standard
`X-RateLimit-*` headers.

<Tip>
  Back off when `X-RateLimit-Remaining` reaches 0, and handle `429` responses
  with retry-after logic rather than retrying blindly.
</Tip>
