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

# Auth

> JWT lifecycle for authenticated Phoenix API and Rise SDK sessions.

Phoenix auth issues short-lived access JWTs and longer-lived refresh tokens. The Rise SDK can manage the session, attach bearer tokens to authenticated requests, refresh before expiry, and persist the rotated token pair.

Most Phoenix market and trader read routes are public. You can fetch exchange params, market stats, orderbooks, candles, and trader state without first creating a session.

Use auth when your integration needs an identified caller, higher API limits for authenticated traffic, or authenticated workflows. Auth is required for referral-code onboarding with `/v1/referral/activate-tx` and for notifications.

<Note>
  Auth does not replace Solana transaction signing. Phoenix instructions still need the correct wallet, authority, position authority, or fee payer signature depending on the instruction.
</Note>

## Lifecycle

1. Request a challenge from Phoenix.
2. Sign the challenge with the authority wallet.
3. Exchange the signature for an auth response.
4. Use `Authorization: Bearer <access_token>` on authenticated routes.
5. Refresh with `POST /v1/auth/refresh` before the access token expires.
6. Reauthenticate when refresh fails with a terminal auth error.

The auth response shape is:

```json theme={null}
{
  "token_type": "Bearer",
  "access_token": "eyJ...",
  "expires_in": 900,
  "refresh_token": "eyJ...",
  "refresh_expires_in": 2592000
}
```

Store the full response. Refresh rotates the access token and refresh token; the previous access token is accepted for a short grace window.

## Endpoints

| Step                         | Endpoint                                     | Body or query                                     | Notes                                                                               |
| ---------------------------- | -------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Wallet nonce                 | `GET /v1/auth/nonce?wallet_pubkey=...`       | query `wallet_pubkey`                             | Returns `nonce_id`, `message`, and `expires_at`.                                    |
| Wallet login                 | `POST /v1/auth/login/wallet`                 | `wallet_pubkey`, `signature`, `nonce_id`          | `signature` signs the exact nonce `message`.                                        |
| Wallet transaction challenge | `POST /v1/auth/wallet/transaction-challenge` | `wallet_pubkey`                                   | Alternative for wallets that cannot sign arbitrary messages.                        |
| Wallet transaction login     | `POST /v1/auth/login/wallet/transaction`     | `wallet_pubkey`, `nonce_id`, `signed_transaction` | Exchanges the signed memo transaction for JWTs.                                     |
| Refresh                      | `POST /v1/auth/refresh`                      | `refresh_token`                                   | SDKs include the current bearer token when available and store the rotated session. |
| Logout                       | `POST /v1/auth/logout`                       | none                                              | Requires `Authorization: Bearer <access_token>` and revokes the session.            |

Routes that can benefit from or require auth:

* Referral-code onboarding: see [Trader Onboarding](/sdk/register#with-a-referral-code).
* Notifications over REST or WebSocket.
* Any protected account, user, or builder workflow marked with bearer auth in the [API reference](/api).
* Authenticated integrations that need higher rate limits than anonymous public traffic.

## Wallet login

This TypeScript example is written for a browser wallet that supports `signMessage`.

<CodeGroup>
  ```ts TypeScript theme={null}
  import {
    LocalStorageAuthSessionStorage,
    createPhoenixClient,
  } from "@ellipsis-labs/rise";
  import bs58 from "bs58";

  const client = createPhoenixClient({
    apiUrl: "https://perp-api.phoenix.trade",
    auth: true,
    authConfig: {
      storage: new LocalStorageAuthSessionStorage(),
    },
    ws: false,
  });

  const auth = client.auth!;
  const walletPubkey = wallet.publicKey.toString();

  const nonce = await auth.getWalletNonce(walletPubkey);
  const message = new TextEncoder().encode(nonce.message);
  const signature = await wallet.signMessage(message);

  const session = await auth.loginWithWalletSignature(
    walletPubkey,
    bs58.encode(signature),
    nonce.nonce_id
  );

  console.log(session.accessToken);
  ```

  ```rust Rust theme={null}
  use phoenix_rise::PhoenixHttpClient;
  use solana_keypair::Keypair;
  use solana_signer::Signer;

  let client = PhoenixHttpClient::builder("https://perp-api.phoenix.trade")
      .enable_auth()
      .build()?;
  let auth = client.auth().expect("auth enabled");

  let wallet = Keypair::new(); // Load the user's wallet in production.
  let wallet_pubkey = wallet.pubkey().to_string();

  let nonce = auth.get_wallet_nonce(&wallet_pubkey).await?;
  let signature = wallet.sign_message(nonce.message.as_bytes());

  let session = auth
      .login_with_wallet_signature(
          &wallet_pubkey,
          signature.as_ref(),
          nonce.nonce_id,
      )
      .await?;

  println!("{}", session.access_token());
  ```
</CodeGroup>

<Tip>
  In Rust builds with the `solana-keypair` feature, `auth.login_with_wallet_keypair(&keypair).await?` wraps the nonce, signing, and login sequence.
</Tip>

## Refresh

With a managed SDK session, refresh is normally automatic before authenticated HTTP and WebSocket requests. Manual refresh is still useful when you want to force rotation after loading a saved session.

<CodeGroup>
  ```ts TypeScript theme={null}
  const auth = client.auth!;
  const manager = client.sessionManager!;
  const current = await manager.getSession();

  if (!current) {
    throw new Error("No Phoenix auth session");
  }

  const refreshed = await auth.refresh(current.refreshToken);
  console.log(refreshed.accessToken);
  ```

  ```rust Rust theme={null}
  let auth = client.auth().expect("auth enabled");
  let refreshed = auth.refresh_session().await?;

  println!("{}", refreshed.access_token());
  ```
</CodeGroup>

Terminal refresh failures mean the user must sign in again:

* `invalid_refresh_token`
* `refresh_expired`
* `session_missing`

## Auth errors

Auth errors use the standard API error shape:

```json theme={null}
{
  "error": "invalid_access_token"
}
```

Common unauthenticated or reauthentication cases:

| Error code              | Status         | Meaning                                                                                           |
| ----------------------- | -------------- | ------------------------------------------------------------------------------------------------- |
| `missing_access_token`  | `401`          | The route requires an authenticated access token, but none was available to the route guard.      |
| `missing_bearer_token`  | `401`          | The route expects an `Authorization: Bearer ...` header.                                          |
| `invalid_access_token`  | `401`          | The access JWT is malformed, expired, signed by an unknown key, or otherwise failed verification. |
| `access_token_expired`  | `401`          | The access token is expired. Refresh and retry.                                                   |
| `session_missing`       | `401` or `404` | The server-side session was revoked, expired, or missing. Reauthenticate.                         |
| `access_jti_mismatch`   | `401`          | The access token is no longer the current token for the session. Refresh or reauthenticate.       |
| `invalid_refresh_token` | `401`          | The refresh token is invalid, expired, or already consumed. Reauthenticate.                       |
| `refresh_expired`       | SDK-side       | The SDK knows the stored refresh token is past its expiry. Reauthenticate.                        |
| `no_auth_session`       | SDK-side       | Auth was enabled, but no session is loaded. Sign in first.                                        |
| `user_only`             | `403`          | The route requires user auth.                                                                     |
| `admin_only`            | `403`          | The route requires admin auth.                                                                    |
| `insufficient_role`     | `403`          | The authenticated role cannot access the route.                                                   |
