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

# Authentication

> Learn how to authenticate your API requests with Gero Nexus

Gero Nexus has two credential types. Pick the one that matches how you're calling the API:

<CardGroup cols={2}>
  <Card title="API key (recommended)" icon="key">
    **For servers, scripts, and integrations**

    * Sent in the `X-Api-Key` header
    * Long-lived — does not expire hourly
    * Created in the dashboard; keys start with `nxs_`
  </Card>

  <Card title="Session token (JWT)" icon="user">
    **For the web dashboard and mobile apps**

    * Sent as `Authorization: Bearer <token>`
    * Short-lived (1 hour), paired with a refresh token
    * Issued by `/api/auth/login` or `/api/auth/device`
  </Card>
</CardGroup>

<Warning>
  These are **not** interchangeable. An API key (`nxs_...`) goes in the **`X-Api-Key`** header —
  putting it in `Authorization: Bearer` returns **401 Unauthorized**. `Authorization: Bearer` is
  only for JWT session tokens.
</Warning>

## API key authentication (recommended)

For server-to-server access, scripts, and most integrations, use an API key. Unlike a session
token, it doesn't expire every hour.

### 1. Create a key

Sign in to the [dashboard](https://nexus.gerowallet.io/dashboard/api-keys), create an API key, and
copy it. Keys are shown once and look like `nxs_uYc...`. Each key is scoped to a specific network.

### 2. Send it in the `X-Api-Key` header

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://nexus.gerowallet.io/api/blocks?page=1&pageSize=100" \
    -H "X-Api-Key: nxs_your_api_key_here"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://nexus.gerowallet.io/api/blocks?page=1&pageSize=100', {
    headers: { 'X-Api-Key': 'nxs_your_api_key_here' }
  });

  const blocks = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      'https://nexus.gerowallet.io/api/blocks',
      params={'page': 1, 'pageSize': 100},
      headers={'X-Api-Key': 'nxs_your_api_key_here'}
  )

  blocks = response.json()
  ```
</CodeGroup>

That's the whole protocol for API-key access: HTTPS plus an `X-Api-Key` header. No token
exchange, no refresh loop.

<Note>
  Most endpoints accept a `network` query parameter. Supported networks:

  | Chain    | Networks                                                           |
  | -------- | ------------------------------------------------------------------ |
  | Cardano  | `CARDANO_MAINNET`, `CARDANO_PREPROD`, `CARDANO_PREVIEW`            |
  | Midnight | `MIDNIGHT_MAINNET`, `MIDNIGHT_PREPROD`                             |
  | Apex     | `APEX_PRIME_MAINNET`, `APEX_VECTOR_MAINNET`, `APEX_VECTOR_TESTNET` |
  | Bitcoin  | `BITCOIN_MAINNET`, `BITCOIN_TESTNET4`                              |

  A key is scoped to one network at creation time and cannot query a different network.
</Note>

## Session authentication (browser & mobile apps)

The web dashboard and mobile apps authenticate **users** with short-lived JWT session tokens
instead of an API key. You only need this if you're building a user-facing app that logs people
in — for server-to-server API calls, use an API key (above).

### 1. Get an access token

<Tabs>
  <Tab title="User Login">
    ```bash theme={null}
    curl -X POST "https://nexus.gerowallet.io/api/auth/login" \
      -H "Content-Type: application/json" \
      -d '{
        "email": "your-email@example.com",
        "password": "your-password"
      }'
    ```

    **Response:**

    ```json theme={null}
    {
      "accessToken": "eyJhbGciOiJIUzUxMiJ9...",
      "refreshToken": "eyJhbGciOiJIUzUxMiJ9...",
      "tokenType": "Bearer",
      "expiresIn": 3600000,
      "user": {
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "email": "your-email@example.com",
        "roles": ["USER"]
      }
    }
    ```
  </Tab>

  <Tab title="Device Auth">
    ```bash theme={null}
    curl -X POST "https://nexus.gerowallet.io/api/auth/device" \
      -H "Content-Type: application/json" \
      -d '{
        "deviceId": "unique-device-identifier",
        "deviceName": "My iPhone",
        "platform": "IOS"
      }'
    ```

    **Response:**

    ```json theme={null}
    {
      "accessToken": "eyJhbGciOiJIUzUxMiJ9...",
      "refreshToken": "eyJhbGciOiJIUzUxMiJ9...",
      "tokenType": "Bearer",
      "expiresIn": 3600000
    }
    ```
  </Tab>
</Tabs>

### 2. Use the Token

Include the access token in the `Authorization` header for all API requests:

```bash theme={null}
curl -X GET "https://nexus.gerowallet.io/api/blocks/latest" \
  -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9..."
```

## User Authentication

For web applications and services that require user accounts.

### Register a New Account

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://nexus.gerowallet.io/api/auth/register" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "user@example.com",
      "password": "securePassword123",
      "username": "myusername"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://nexus.gerowallet.io/api/auth/register', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: 'user@example.com',
      password: 'securePassword123',
      username: 'myusername'
    })
  });

  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://nexus.gerowallet.io/api/auth/register',
      json={
          'email': 'user@example.com',
          'password': 'securePassword123',
          'username': 'myusername'
      }
  )

  data = response.json()
  ```
</CodeGroup>

### Login

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://nexus.gerowallet.io/api/auth/login" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "user@example.com",
      "password": "securePassword123"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://nexus.gerowallet.io/api/auth/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: 'user@example.com',
      password: 'securePassword123'
    })
  });

  const { accessToken, refreshToken } = await response.json();

  // Store tokens securely
  localStorage.setItem('accessToken', accessToken);
  localStorage.setItem('refreshToken', refreshToken);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://nexus.gerowallet.io/api/auth/login',
      json={
          'email': 'user@example.com',
          'password': 'securePassword123'
      }
  )

  tokens = response.json()
  access_token = tokens['accessToken']
  refresh_token = tokens['refreshToken']
  ```
</CodeGroup>

### Logout

Invalidates the current session and refresh token:

```bash theme={null}
curl -X POST "https://nexus.gerowallet.io/api/auth/logout" \
  -H "Authorization: Bearer your-access-token"
```

## Device Authentication

For mobile applications that don't require user credentials. Devices are automatically registered on first use.

### Authenticate Device

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://nexus.gerowallet.io/api/auth/device" \
    -H "Content-Type: application/json" \
    -d '{
      "deviceId": "550e8400-e29b-41d4-a716-446655440000",
      "deviceName": "iPhone 15 Pro",
      "platform": "IOS"
    }'
  ```

  ```javascript JavaScript theme={null}
  // Generate or retrieve persistent device ID
  const deviceId = await getDeviceId(); // Your device ID logic

  const response = await fetch('https://nexus.gerowallet.io/api/auth/device', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      deviceId: deviceId,
      deviceName: 'My App',
      platform: 'IOS' // or 'ANDROID', 'WEB'
    })
  });

  const { accessToken, refreshToken } = await response.json();
  ```

  ```swift Swift (iOS) theme={null}
  struct DeviceAuthRequest: Codable {
      let deviceId: String
      let deviceName: String
      let platform: String
  }

  func authenticateDevice() async throws -> TokenResponse {
      let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString

      let request = DeviceAuthRequest(
          deviceId: deviceId,
          deviceName: UIDevice.current.name,
          platform: "IOS"
      )

      var urlRequest = URLRequest(url: URL(string: "https://nexus.gerowallet.io/api/auth/device")!)
      urlRequest.httpMethod = "POST"
      urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
      urlRequest.httpBody = try JSONEncoder().encode(request)

      let (data, _) = try await URLSession.shared.data(for: urlRequest)
      return try JSONDecoder().decode(TokenResponse.self, from: data)
  }
  ```

  ```kotlin Kotlin (Android) theme={null}
  data class DeviceAuthRequest(
      val deviceId: String,
      val deviceName: String,
      val platform: String
  )

  suspend fun authenticateDevice(): TokenResponse {
      val deviceId = Settings.Secure.getString(
          context.contentResolver,
          Settings.Secure.ANDROID_ID
      )

      val request = DeviceAuthRequest(
          deviceId = deviceId,
          deviceName = Build.MODEL,
          platform = "ANDROID"
      )

      return apiService.authenticateDevice(request)
  }
  ```
</CodeGroup>

### Platform Values

| Platform  | Description                |
| --------- | -------------------------- |
| `IOS`     | iOS devices (iPhone, iPad) |
| `ANDROID` | Android devices            |
| `WEB`     | Web browsers               |

## Token Management

### Token Structure

When you authenticate, you receive:

| Field          | Description                                        |
| -------------- | -------------------------------------------------- |
| `accessToken`  | Short-lived token for API requests (1 hour)        |
| `refreshToken` | Long-lived token to get new access tokens (7 days) |
| `tokenType`    | Always `Bearer`                                    |
| `expiresIn`    | Access token lifetime in milliseconds              |

### Refresh Tokens

Before your access token expires, use the refresh token to get a new one:

<Tabs>
  <Tab title="User Refresh">
    ```bash theme={null}
    curl -X POST "https://nexus.gerowallet.io/api/auth/refresh" \
      -H "Content-Type: application/json" \
      -d '{
        "refreshToken": "your-refresh-token"
      }'
    ```
  </Tab>

  <Tab title="Device Refresh">
    ```bash theme={null}
    curl -X POST "https://nexus.gerowallet.io/api/auth/device/refresh" \
      -H "Content-Type: application/json" \
      -d '{
        "refreshToken": "your-refresh-token"
      }'
    ```
  </Tab>
</Tabs>

<Note>
  Refresh tokens are single-use. Each refresh returns a new refresh token.
</Note>

## Making session-authenticated requests

Once you have a session access token, include it in the `Authorization` header. (For
server-to-server access, use an [API key](#api-key-authentication-recommended) in the `X-Api-Key`
header instead.)

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://nexus.gerowallet.io/api/blocks/latest" \
    -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9..."
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://nexus.gerowallet.io/api/blocks/latest', {
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
  });

  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      'https://nexus.gerowallet.io/api/blocks/latest',
      headers={
          'Authorization': f'Bearer {access_token}'
      }
  )

  data = response.json()
  ```
</CodeGroup>

## Example: Secure API Client

Here's a production-ready example with automatic token refresh:

<CodeGroup>
  ```javascript JavaScript theme={null}
  class GeroNexusClient {
    constructor() {
      this.baseUrl = 'https://nexus.gerowallet.io';
      this.accessToken = null;
      this.refreshToken = null;
      this.tokenExpiry = null;
    }

    async authenticate(email, password) {
      const response = await fetch(`${this.baseUrl}/api/auth/login`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      });

      const data = await response.json();
      this.setTokens(data);
      return data;
    }

    async authenticateDevice(deviceId, deviceName, platform) {
      const response = await fetch(`${this.baseUrl}/api/auth/device`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ deviceId, deviceName, platform })
      });

      const data = await response.json();
      this.setTokens(data);
      return data;
    }

    setTokens(data) {
      this.accessToken = data.accessToken;
      this.refreshToken = data.refreshToken;
      this.tokenExpiry = Date.now() + data.expiresIn - 60000; // 1 min buffer
    }

    async ensureValidToken() {
      if (!this.tokenExpiry || Date.now() >= this.tokenExpiry) {
        await this.refreshAccessToken();
      }
    }

    async refreshAccessToken() {
      const response = await fetch(`${this.baseUrl}/api/auth/refresh`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refreshToken: this.refreshToken })
      });

      const data = await response.json();
      this.setTokens(data);
    }

    async request(endpoint, options = {}) {
      await this.ensureValidToken();

      const response = await fetch(`${this.baseUrl}${endpoint}`, {
        ...options,
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json',
          ...options.headers
        }
      });

      if (!response.ok) {
        throw new Error(`API error: ${response.status}`);
      }

      return response.json();
    }

    // Convenience methods
    getLatestBlock() {
      return this.request('/api/blocks/latest');
    }

    getTransaction(hash) {
      return this.request(`/api/transactions/${hash}`);
    }
  }

  // Usage
  const client = new GeroNexusClient();
  await client.authenticate('user@example.com', 'password');
  const block = await client.getLatestBlock();
  ```

  ```python Python theme={null}
  import requests
  import time
  from typing import Optional

  class GeroNexusClient:
      """Secure API client with automatic token refresh"""

      def __init__(self):
          self.base_url = 'https://nexus.gerowallet.io'
          self.access_token: Optional[str] = None
          self.refresh_token: Optional[str] = None
          self.token_expiry: Optional[float] = None
          self.session = requests.Session()

      def authenticate(self, email: str, password: str):
          """Authenticate with email and password"""
          response = self.session.post(
              f'{self.base_url}/api/auth/login',
              json={'email': email, 'password': password}
          )
          response.raise_for_status()
          self._set_tokens(response.json())

      def authenticate_device(self, device_id: str, device_name: str, platform: str):
          """Authenticate with device credentials"""
          response = self.session.post(
              f'{self.base_url}/api/auth/device',
              json={
                  'deviceId': device_id,
                  'deviceName': device_name,
                  'platform': platform
              }
          )
          response.raise_for_status()
          self._set_tokens(response.json())

      def _set_tokens(self, data: dict):
          self.access_token = data['accessToken']
          self.refresh_token = data['refreshToken']
          # Set expiry with 1 minute buffer
          self.token_expiry = time.time() + (data['expiresIn'] / 1000) - 60

      def _ensure_valid_token(self):
          if not self.token_expiry or time.time() >= self.token_expiry:
              self._refresh_access_token()

      def _refresh_access_token(self):
          response = self.session.post(
              f'{self.base_url}/api/auth/refresh',
              json={'refreshToken': self.refresh_token}
          )
          response.raise_for_status()
          self._set_tokens(response.json())

      def request(self, endpoint: str, method: str = 'GET', **kwargs):
          """Make authenticated API request"""
          self._ensure_valid_token()

          headers = {
              'Authorization': f'Bearer {self.access_token}',
              'Content-Type': 'application/json'
          }

          response = self.session.request(
              method,
              f'{self.base_url}{endpoint}',
              headers=headers,
              **kwargs
          )
          response.raise_for_status()
          return response.json()

      # Convenience methods
      def get_latest_block(self):
          return self.request('/api/blocks/latest')

      def get_transaction(self, tx_hash: str):
          return self.request(f'/api/transactions/{tx_hash}')


  # Usage
  if __name__ == '__main__':
      client = GeroNexusClient()
      client.authenticate('user@example.com', 'password')
      block = client.get_latest_block()
      print(block)
  ```
</CodeGroup>

## Error Handling

### Common Authentication Errors

<ResponseField name="401 Unauthorized" type="error">
  **Invalid or expired token**

  ```json theme={null}
  {
    "error": "Unauthorized",
    "message": "Invalid or expired access token"
  }
  ```

  **Solution**: Refresh your access token or re-authenticate.
</ResponseField>

<ResponseField name="401 Invalid Credentials" type="error">
  **Wrong email or password**

  ```json theme={null}
  {
    "error": "Invalid email or password"
  }
  ```

  **Solution**: Check your credentials and try again.
</ResponseField>

<ResponseField name="403 Forbidden" type="error">
  **Insufficient permissions**

  ```json theme={null}
  {
    "error": "Forbidden",
    "message": "You don't have permission to access this resource"
  }
  ```

  **Solution**: Check if your account has the required role/permissions.
</ResponseField>

## Best Practices

<AccordionGroup>
  <Accordion title="Secure Token Storage" icon="shield">
    **Web Applications:**

    * Store tokens in `httpOnly` cookies when possible
    * Use `localStorage` only if cookies aren't an option
    * Never store tokens in `sessionStorage` for persistent auth

    **Mobile Applications:**

    * Use iOS Keychain or Android Keystore
    * Never store tokens in plain text
    * Clear tokens on logout and app uninstall

    **Server Applications:**

    * Store tokens in secure environment variables
    * Use secret management services (AWS Secrets Manager, HashiCorp Vault)
    * Never log tokens
  </Accordion>

  <Accordion title="Token Refresh Strategy" icon="rotate">
    * Refresh tokens proactively before expiry (60 seconds buffer)
    * Implement retry logic for failed refreshes
    * Handle refresh token expiry by re-authenticating
    * Queue requests while refreshing to avoid race conditions
  </Accordion>

  <Accordion title="Device ID Generation" icon="mobile">
    **iOS:**

    ```swift theme={null}
    let deviceId = UIDevice.current.identifierForVendor?.uuidString
    ```

    **Android:**

    ```kotlin theme={null}
    val deviceId = Settings.Secure.getString(
        context.contentResolver,
        Settings.Secure.ANDROID_ID
    )
    ```

    **Web:**

    ```javascript theme={null}
    // Generate once and store
    const deviceId = localStorage.getItem('deviceId')
      || (localStorage.setItem('deviceId', crypto.randomUUID()),
          localStorage.getItem('deviceId'));
    ```
  </Accordion>

  <Accordion title="Error Recovery" icon="rotate-right">
    Handle token errors gracefully:

    1. **401 with valid refresh token** → Refresh and retry
    2. **401 with expired refresh token** → Re-authenticate
    3. **Network error** → Retry with exponential backoff
    4. **403** → Check user permissions, may need different credentials
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="/docs/quickstart">
    Make your first authenticated API call
  </Card>

  <Card title="Rate Limits" icon="gauge" href="/docs/concepts/rate-limits">
    Understand rate limiting and quotas
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/docs/concepts/error-handling">
    Learn how to handle API errors
  </Card>

  <Card title="API Reference" icon="book" href="/docs/api-reference">
    Explore all available endpoints
  </Card>
</CardGroup>

***

**Security Question?** Contact our security team at [security@gerowallet.io](mailto:security@gerowallet.io)
