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

# Authentication

> Learn how to authenticate with the Zuba API using OAuth 2.0

## Overview

The Zuba API uses **OAuth 2.0 Client Credentials flow** for secure M2M (machine-to-machine) authentication. You'll exchange your Client ID and Client Secret for a short-lived JWT access token, which you'll use to authenticate API requests.

## Getting Your API Credentials

### Step 1: Generate Credentials

1. Log in to your [Zuba Dashboard](https://sandbox.zuba.com) (use `sandbox.zuba.com` for test environment)
2. Navigate to **API Settings**
3. Click **Generate API Key** or **Create API Credentials**
4. **Important:** Copy and save your credentials immediately - you won't be able to see the Client Secret again!

You'll receive the following credentials:

* **Client ID**: Your application's public identifier (e.g., `ao6CMrtuM0pdUtAbcYRr5nlJCma90B2S`)
* **Client Secret**: Your application's secret key (e.g., `aS-abvckfP2ZoDh9...`)
* **Token URL**: Auth0 endpoint to exchange credentials for tokens (e.g., `https://zuba-test.us.auth0.com/oauth/token`)
* **Audience**: API identifier (typically `https://api.zuba.com`)

<Warning>
  **Keep your Client Secret secure!** Never share it publicly or commit it to version control. Treat it like a password. If compromised, immediately rotate your credentials in the dashboard.
</Warning>

## Authentication Flow

### Step 2: Request an Access Token

Exchange your Client ID and Client Secret for a JWT access token:

<CodeGroup>
  ```bash curl theme={"dark"}
  curl -X POST "https://zuba-test.us.auth0.com/oauth/token" \
    -H "Content-Type: application/json" \
    -d '{
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET",
      "audience": "https://api.zuba.com",
      "grant_type": "client_credentials"
    }'
  ```

  ```javascript JavaScript theme={"dark"}
  const getAccessToken = async () => {
    const response = await fetch('https://zuba-test.us.auth0.com/oauth/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        client_id: process.env.ZUBA_CLIENT_ID,
        client_secret: process.env.ZUBA_CLIENT_SECRET,
        audience: 'https://api.zuba.com',
        grant_type: 'client_credentials'
      })
    });

    const data = await response.json();
    return data.access_token;
  };
  ```

  ```python Python theme={"dark"}
  import requests
  import os

  def get_access_token():
      response = requests.post(
          'https://zuba-test.us.auth0.com/oauth/token',
          json={
              'client_id': os.environ['ZUBA_CLIENT_ID'],
              'client_secret': os.environ['ZUBA_CLIENT_SECRET'],
              'audience': 'https://api.zuba.com',
              'grant_type': 'client_credentials'
          }
      )
      return response.json()['access_token']
  ```

  ```java Java theme={"dark"}
  import java.net.http.*;
  import java.net.URI;
  import com.google.gson.Gson;
  import com.google.gson.JsonObject;

  public class ZubaAuth {
      public static String getAccessToken() throws Exception {
          String clientId = System.getenv("ZUBA_CLIENT_ID");
          String clientSecret = System.getenv("ZUBA_CLIENT_SECRET");

          JsonObject requestBody = new JsonObject();
          requestBody.addProperty("client_id", clientId);
          requestBody.addProperty("client_secret", clientSecret);
          requestBody.addProperty("audience", "https://api.zuba.com");
          requestBody.addProperty("grant_type", "client_credentials");

          HttpClient client = HttpClient.newHttpClient();
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create("https://zuba-test.us.auth0.com/oauth/token"))
              .header("Content-Type", "application/json")
              .POST(HttpRequest.BodyPublishers.ofString(new Gson().toJson(requestBody)))
              .build();

          HttpResponse<String> response = client.send(request,
              HttpResponse.BodyHandlers.ofString());

          JsonObject jsonResponse = new Gson().fromJson(response.body(), JsonObject.class);
          return jsonResponse.get("access_token").getAsString();
      }
  }
  ```
</CodeGroup>

**Successful Response:**

```json theme={"dark"}
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6...",
  "token_type": "Bearer",
  "expires_in": 86400
}
```

The `access_token` is a JWT that's valid for 24 hours (86400 seconds). You'll need to request a new token when it expires.

### Step 3: Use the Access Token

Include the access token in the `Authorization` header of every API request:

<CodeGroup>
  ```bash curl theme={"dark"}
  curl -X GET "https://api.sandbox.zuba.com/v1/payouts" \
    -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6..." \
    -H "Content-Type: application/json"
  ```

  ```javascript JavaScript theme={"dark"}
  const accessToken = await getAccessToken();

  const response = await fetch('https://api.sandbox.zuba.com/v1/payouts', {
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    }
  });

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

  ```python Python theme={"dark"}
  access_token = get_access_token()

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

  response = requests.get(
      'https://api.sandbox.zuba.com/v1/payouts',
      headers=headers
  )
  ```

  ```java Java theme={"dark"}
  String accessToken = ZubaAuth.getAccessToken();

  HttpClient client = HttpClient.newHttpClient();
  HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://api.sandbox.zuba.com/v1/payouts"))
      .header("Authorization", "Bearer " + accessToken)
      .header("Content-Type", "application/json")
      .GET()
      .build();

  HttpResponse<String> response = client.send(request,
      HttpResponse.BodyHandlers.ofString());

  String responseBody = response.body();
  ```
</CodeGroup>

## Token Management

### Token Expiration

Access tokens expire after 24 hours. Implement token caching and refresh logic to avoid requesting a new token for every API call:

```javascript Example: Token Cache theme={"dark"}
let cachedToken = null;
let tokenExpiry = null;

async function getValidToken() {
  const now = Date.now();

  // Return cached token if still valid (with 5-minute buffer)
  if (cachedToken && tokenExpiry && now < tokenExpiry - 300000) {
    return cachedToken;
  }

  // Request new token
  const response = await fetch('https://zuba-test.us.auth0.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_id: process.env.ZUBA_CLIENT_ID,
      client_secret: process.env.ZUBA_CLIENT_SECRET,
      audience: 'https://api.zuba.com',
      grant_type: 'client_credentials'
    })
  });

  const data = await response.json();

  // Cache token and expiry time
  cachedToken = data.access_token;
  tokenExpiry = now + (data.expires_in * 1000);

  return cachedToken;
}
```

## Environments

Zuba provides separate environments for development and production:

| Environment    | Dashboard                                            | Token URL                                                                                | API Base URL                                                 |
| -------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| **Test**       | [https://sandbox.zuba.com](https://sandbox.zuba.com) | [https://zuba-test.us.auth0.com/oauth/token](https://zuba-test.us.auth0.com/oauth/token) | [https://api.sandbox.zuba.com](https://api.sandbox.zuba.com) |
| **Production** | [https://dash.zuba.com](https://dash.zuba.com)       | [https://zuba.us.auth0.com/oauth/token](https://zuba.us.auth0.com/oauth/token)           | [https://api.zuba.com](https://api.zuba.com)                 |

<Note>
  Always test with the **Test environment** before moving to production. Use separate credentials for each environment.
</Note>

## Authentication Errors

### Token Request Errors

| Error                            | Cause                              | Solution                                       |
| -------------------------------- | ---------------------------------- | ---------------------------------------------- |
| `access_denied` / `Unauthorized` | Invalid Client ID or Client Secret | Verify credentials are correct and not expired |
| `invalid_grant`                  | Invalid grant\_type                | Ensure grant\_type is "client\_credentials"    |
| `invalid_audience`               | Wrong audience value               | Use the correct audience for your environment  |

### API Request Errors

| HTTP Status | Error        | Cause                              | Solution                                      |
| ----------- | ------------ | ---------------------------------- | --------------------------------------------- |
| `401`       | Unauthorized | Missing, invalid, or expired token | Request a new access token and retry          |
| `403`       | Forbidden    | Token lacks required permissions   | Contact support to verify account permissions |

Example error response:

```json theme={"dark"}
{
  "statusCode": 401,
  "message": "Unauthorized"
}
```

## Security Best Practices

<AccordionGroup>
  <Accordion title="Environment Variables">
    **Never hardcode credentials** in your application code. Use environment variables:

    ```bash .env theme={"dark"}
    ZUBA_CLIENT_ID=ao6CMrtuM0pdUtAbcYRr5nlJCma90B2S
    ZUBA_CLIENT_SECRET=aS-abvckfP2ZoDh9100SS2ix_CigMC27Kc4y18iMIRVBxKlMPopL5kQr_bGY56hE
    ZUBA_TOKEN_URL=https://zuba-test.us.auth0.com/oauth/token
    ZUBA_API_BASE_URL=https://api.sandbox.zuba.com
    ```

    ```javascript theme={"dark"}
    const clientId = process.env.ZUBA_CLIENT_ID;
    const clientSecret = process.env.ZUBA_CLIENT_SECRET;
    ```
  </Accordion>

  <Accordion title="HTTPS Only">
    **Always use HTTPS** for both token requests and API calls. Never send credentials or tokens over HTTP.
  </Accordion>

  <Accordion title="Credential Rotation">
    Regularly rotate your credentials:

    1. Generate new credentials in the dashboard
    2. Update your environment variables
    3. Deploy the changes
    4. Delete the old credentials in the dashboard
  </Accordion>

  <Accordion title="Token Storage">
    * Store tokens in memory, not in databases or files
    * Never log tokens in application logs
    * Clear tokens when they expire
    * For serverless environments, request a new token for each execution (they're cached by Auth0)
  </Accordion>

  <Accordion title="Least Privilege">
    Request only the permissions your application needs. Contact support to configure specific scopes for your credentials.
  </Accordion>
</AccordionGroup>

## Testing Authentication

Test your authentication setup before making actual API calls:

<CodeGroup>
  ```bash curl theme={"dark"}
  # Step 1: Get access token
  TOKEN=$(curl -s -X POST "https://zuba-test.us.auth0.com/oauth/token" \
    -H "Content-Type: application/json" \
    -d '{
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET",
      "audience": "https://api.zuba.com",
      "grant_type": "client_credentials"
    }' | jq -r '.access_token')

  # Step 2: Test API request
  curl -X GET "https://api.sandbox.zuba.com/v1/ledger/balances" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json"
  ```

  ```javascript JavaScript theme={"dark"}
  async function testAuthentication() {
    try {
      // Step 1: Get access token
      const tokenResponse = await fetch('https://zuba-test.us.auth0.com/oauth/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          client_id: process.env.ZUBA_CLIENT_ID,
          client_secret: process.env.ZUBA_CLIENT_SECRET,
          audience: 'https://api.zuba.com',
          grant_type: 'client_credentials'
        })
      });

      const { access_token } = await tokenResponse.json();
      console.log('✅ Token obtained successfully');

      // Step 2: Test API request
      const apiResponse = await fetch('https://api.sandbox.zuba.com/v1/ledger/balances', {
        headers: {
          'Authorization': `Bearer ${access_token}`,
          'Content-Type': 'application/json'
        }
      });

      if (apiResponse.ok) {
        console.log('✅ Authentication working correctly');
        const data = await apiResponse.json();
        console.log('Response:', data);
      } else {
        console.log('❌ API request failed:', apiResponse.status);
      }
    } catch (error) {
      console.error('❌ Error:', error);
    }
  }

  testAuthentication();
  ```

  ```python Python theme={"dark"}
  import requests
  import os

  def test_authentication():
      try:
          # Step 1: Get access token
          token_response = requests.post(
              'https://zuba-test.us.auth0.com/oauth/token',
              json={
                  'client_id': os.environ['ZUBA_CLIENT_ID'],
                  'client_secret': os.environ['ZUBA_CLIENT_SECRET'],
                  'audience': 'https://api.zuba.com',
                  'grant_type': 'client_credentials'
              }
          )

          access_token = token_response.json()['access_token']
          print('✅ Token obtained successfully')

          # Step 2: Test API request
          api_response = requests.get(
              'https://api.sandbox.zuba.com/v1/ledger/balances',
              headers={
                  'Authorization': f'Bearer {access_token}',
                  'Content-Type': 'application/json'
              }
          )

          if api_response.ok:
              print('✅ Authentication working correctly')
              print('Response:', api_response.json())
          else:
              print(f'❌ API request failed: {api_response.status_code}')

      except Exception as error:
          print(f'❌ Error: {error}')

  test_authentication()
  ```

  ```java Java theme={"dark"}
  import java.net.http.*;
  import java.net.URI;
  import com.google.gson.Gson;
  import com.google.gson.JsonObject;

  public class TestAuthentication {
      public static void main(String[] args) {
          try {
              // Step 1: Get access token
              String accessToken = ZubaAuth.getAccessToken();
              System.out.println("✅ Token obtained successfully");

              // Step 2: Test API request
              HttpClient client = HttpClient.newHttpClient();
              HttpRequest request = HttpRequest.newBuilder()
                  .uri(URI.create("https://api.sandbox.zuba.com/v1/ledger/balances"))
                  .header("Authorization", "Bearer " + accessToken)
                  .header("Content-Type", "application/json")
                  .GET()
                  .build();

              HttpResponse<String> response = client.send(request,
                  HttpResponse.BodyHandlers.ofString());

              if (response.statusCode() == 200) {
                  System.out.println("✅ Authentication working correctly");
                  System.out.println("Response: " + response.body());
              } else {
                  System.out.println("❌ API request failed: " + response.statusCode());
              }

          } catch (Exception error) {
              System.err.println("❌ Error: " + error.getMessage());
          }
      }
  }
  ```
</CodeGroup>

A successful authentication test will return your account balances or an empty array if no balances exist yet.

## Next Steps

<CardGroup cols={2}>
  <Card title="Quickstart Guide" icon="rocket" href="/quickstart">
    Start making your first API calls
  </Card>

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