Skip to main content

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 (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)
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.

Authentication Flow

Step 2: Request an Access Token

Exchange your Client ID and Client Secret for a JWT access token:
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"
  }'
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;
};
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']
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();
    }
}
Successful Response:
{
  "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:
curl -X GET "https://api.sandbox.zuba.com/v1/payouts" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6..." \
  -H "Content-Type: application/json"
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();
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
)
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();

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:
Example: Token Cache
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:
Always test with the Test environment before moving to production. Use separate credentials for each environment.

Authentication Errors

Token Request Errors

ErrorCauseSolution
access_denied / UnauthorizedInvalid Client ID or Client SecretVerify credentials are correct and not expired
invalid_grantInvalid grant_typeEnsure grant_type is “client_credentials”
invalid_audienceWrong audience valueUse the correct audience for your environment

API Request Errors

HTTP StatusErrorCauseSolution
401UnauthorizedMissing, invalid, or expired tokenRequest a new access token and retry
403ForbiddenToken lacks required permissionsContact support to verify account permissions
Example error response:
{
  "statusCode": 401,
  "message": "Unauthorized"
}

Security Best Practices

Never hardcode credentials in your application code. Use environment variables:
.env
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
const clientId = process.env.ZUBA_CLIENT_ID;
const clientSecret = process.env.ZUBA_CLIENT_SECRET;
Always use HTTPS for both token requests and API calls. Never send credentials or tokens over HTTP.
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
  • 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)
Request only the permissions your application needs. Contact support to configure specific scopes for your credentials.

Testing Authentication

Test your authentication setup before making actual API calls:
# 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"
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();
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()
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());
        }
    }
}
A successful authentication test will return your account balances or an empty array if no balances exist yet.

Next Steps

Quickstart Guide

Start making your first API calls

API Reference

Explore all available endpoints