Skip to content

Authentication ​

Learn how to authenticate with the Automatum API using OAuth 2.0 Client Credentials flow.

Overview ​

The Automatum API uses OAuth 2.0 Client Credentials flow for secure, programmatic access. This authentication method is ideal for server-to-server integrations where your application needs to access Automatum data without user interaction.

Getting Started ​

1. Create an OAuth Application ​

Navigate to your Automatum dashboard:

  1. Go to Settings > Integrations > API Access
  2. Click Create OAuth Application
  3. Provide the following details:
    • Name: A descriptive name (e.g., "Production Integration")
    • Description: What this application will be used for
    • Scopes: Select the permissions your application needs

2. Save Your Credentials ​

After creating the application, you'll receive:

json
{
  "clientId": "app_1a2b3c4d5e6f",
  "clientSecret": "secret_7g8h9i0j1k2l3m4n5o6p"
}

Keep Your Secret Safe

The clientSecret is shown only once. Store it securely:

  • Use environment variables
  • Never commit to version control
  • Rotate periodically
  • Store in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)

OAuth 2.0 Client Credentials Flow ​

Request an Access Token ​

Exchange your client credentials for an access token:

Endpoint:

POST https://api.automatum.io/oauth/token

Request:

bash
curl -X POST https://api.automatum.io/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=app_1a2b3c4d5e6f" \
  -d "client_secret=secret_7g8h9i0j1k2l3m4n5o6p"

Response:

json
{
  "access_token": "at_9f8c7b6a5d4e3f2g1h0i9j8k7l6m5n4o",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:customers read:entitlements write:metering"
}

Opaque Tokens

Access tokens are opaque — a random base64url string, not a JWT. Do not decode or inspect them; they carry no readable claims and are validated server-side on every request.

Use the Access Token ​

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

bash
curl -X GET https://api.automatum.io/api/v1/customers \
  -H "Authorization: Bearer at_9f8c7b6a5d4e3f2g1h0i9j8k7l6m5n4o"

Scopes ​

Scopes define what your application can access. Request only the scopes you need:

Available Scopes ​

ScopeDescriptionAccess
read:customersView customer informationGET /api/v1/customers/*
write:customersCreate and update customersPOST, PUT /api/v1/customers/*
read:entitlementsView entitlementsGET /api/v1/entitlements/*
read:analyticsAccess analytics dataGET /api/v1/analytics/*
write:meteringSubmit metering dataPOST /api/v1/metering
read:private-offersView private offersGET /api/v1/private-offers/*
write:private-offersCreate/update private offersPOST, PUT /api/v1/private-offers/*

Scope Format ​

When creating an OAuth application, scopes are specified as an array:

json
{
  "name": "My Integration",
  "description": "Production integration",
  "scopes": [
    "read:customers",
    "read:entitlements",
    "write:metering"
  ]
}

Token Management ​

Token Expiration ​

Access tokens expire after 1 hour (3600 seconds). When a token expires, you'll receive:

json
{
  "code": 401,
  "message": "Token expired"
}

Refreshing Tokens ​

Request a new token before the current one expires:

typescript
let tokenExpiresAt = Date.now() + (3600 * 1000);

async function getValidToken() {
  if (Date.now() >= tokenExpiresAt - 60000) { // Refresh 1 min before expiry
    const response = await fetch('https://api.automatum.io/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: process.env.CLIENT_ID,
        client_secret: process.env.CLIENT_SECRET
      })
    });
    
    const data = await response.json();
    tokenExpiresAt = Date.now() + (data.expires_in * 1000);
    return data.access_token;
  }
  
  return currentToken;
}

Revoking Tokens ​

Tokens are automatically revoked when:

  • They expire (after 1 hour)
  • The OAuth application is deleted
  • You regenerate the client secret

Managing OAuth Applications ​

List Applications ​

View all OAuth applications in your organization:

bash
GET /oauth/applications

Get Application Details ​

bash
GET /oauth/applications/:id

Update Application ​

bash
PUT /oauth/applications/:id

{
  "name": "Updated Name",
  "description": "New description",
  "scopes": ["read:customers", "write:metering"]
}

Regenerate Client Secret ​

If your secret is compromised, regenerate it immediately:

bash
POST /oauth/applications/:id/regenerate-secret

Impact of Regeneration

  • Old secret becomes invalid immediately
  • All active tokens for this application are revoked
  • Update your application with the new secret to restore access

Delete Application ​

bash
DELETE /oauth/applications/:id

This will:

  • Revoke all active tokens
  • Remove the application permanently
  • Cannot be undone

Security Best Practices ​

1. Secure Credential Storage ​

typescript
// ✅ Good: Use environment variables
const clientId = process.env.AUTOMATUM_CLIENT_ID;
const clientSecret = process.env.AUTOMATUM_CLIENT_SECRET;

// ❌ Bad: Hardcoded credentials
const clientId = 'app_1a2b3c4d5e6f';
const clientSecret = 'secret_7g8h9i0j1k2l3m4n5o6p';

2. Principle of Least Privilege ​

Only request scopes your application actually needs:

typescript
// ✅ Good: Minimal scopes
{
  scopes: ['read:customers', 'write:metering']
}

// ❌ Bad: All scopes "just in case"
{
  scopes: [
    'read:customers', 'write:customers',
    'read:entitlements', 'read:analytics',
    'write:metering', 'read:private-offers',
    'write:private-offers'
  ]
}

3. Rotate Credentials Regularly ​

Set up a rotation schedule:

  • Development: Every 90 days
  • Production: Every 30 days
  • Compromised: Immediately

4. Monitor API Usage ​

Track API calls in your application dashboard:

  • Request counts
  • Error rates
  • Rate limit status
  • Last access time

5. Handle Errors Gracefully ​

typescript
async function makeAPIRequest(endpoint) {
  try {
    const token = await getValidToken();
    const response = await fetch(endpoint, {
      headers: { 'Authorization': `Bearer ${token}` }
    });
    
    if (response.status === 401) {
      // Token expired, refresh and retry
      const newToken = await refreshToken();
      return makeAPIRequest(endpoint);
    }
    
    if (response.status === 403) {
      throw new Error('Insufficient permissions');
    }
    
    return await response.json();
  } catch (error) {
    console.error('API request failed:', error);
    throw error;
  }
}

Rate Limits ​

Each OAuth application is limited to 1,000 requests per hour.

Rate Limit Headers ​

Check your current rate limit status in response headers:

http
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1642334400

Handling Rate Limits ​

When you exceed the rate limit:

json
{
  "code": 429,
  "message": "Rate limit exceeded",
  "retryAfter": 60
}

Implement exponential backoff:

typescript
async function makeRequestWithBackoff(endpoint, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(endpoint);
      
      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
        await sleep(retryAfter * 1000 * Math.pow(2, i));
        continue;
      }
      
      return await response.json();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}

Code Examples ​

Node.js / TypeScript ​

typescript
import axios from 'axios';

class AutomatumClient {
  private clientId: string;
  private clientSecret: string;
  private accessToken?: string;
  private tokenExpiresAt?: number;

  constructor(clientId: string, clientSecret: string) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
  }

  async getAccessToken(): Promise<string> {
    if (this.accessToken && this.tokenExpiresAt && Date.now() < this.tokenExpiresAt - 60000) {
      return this.accessToken;
    }

    const response = await axios.post(
      'https://api.automatum.io/oauth/token',
      new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: this.clientId,
        client_secret: this.clientSecret,
      }),
      {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      }
    );

    this.accessToken = response.data.access_token;
    this.tokenExpiresAt = Date.now() + (response.data.expires_in * 1000);

    return this.accessToken;
  }

  async request(method: string, endpoint: string, data?: any) {
    const token = await this.getAccessToken();
    
    return axios({
      method,
      url: `https://api.automatum.io${endpoint}`,
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      data,
    });
  }

  // Convenience methods
  async getCustomers() {
    const response = await this.request('GET', '/api/v1/customers');
    return response.data;
  }

  async submitMetering(data: any) {
    const response = await this.request('POST', '/api/v1/metering', data);
    return response.data;
  }
}

// Usage
const client = new AutomatumClient(
  process.env.AUTOMATUM_CLIENT_ID!,
  process.env.AUTOMATUM_CLIENT_SECRET!
);

const customers = await client.getCustomers();

Python ​

python
import os
import requests
from datetime import datetime, timedelta
from typing import Optional

class AutomatumClient:
    def __init__(self, client_id: str, client_secret: str):
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token: Optional[str] = None
        self.token_expires_at: Optional[datetime] = None
        self.base_url = 'https://api.automatum.io'

    def get_access_token(self) -> str:
        if (self.access_token and self.token_expires_at and 
            datetime.now() < self.token_expires_at - timedelta(minutes=1)):
            return self.access_token

        response = requests.post(
            f'{self.base_url}/oauth/token',
            data={
                'grant_type': 'client_credentials',
                'client_id': self.client_id,
                'client_secret': self.client_secret,
            },
            headers={'Content-Type': 'application/x-www-form-urlencoded'}
        )
        response.raise_for_status()

        data = response.json()
        self.access_token = data['access_token']
        self.token_expires_at = datetime.now() + timedelta(seconds=data['expires_in'])

        return self.access_token

    def request(self, method: str, endpoint: str, json=None):
        token = self.get_access_token()
        
        response = requests.request(
            method,
            f'{self.base_url}{endpoint}',
            headers={
                'Authorization': f'Bearer {token}',
                'Content-Type': 'application/json',
            },
            json=json
        )
        response.raise_for_status()
        return response.json()

    # Convenience methods
    def get_customers(self):
        return self.request('GET', '/api/v1/customers')

    def submit_metering(self, data):
        return self.request('POST', '/api/v1/metering', json=data)

# Usage
client = AutomatumClient(
    os.environ['AUTOMATUM_CLIENT_ID'],
    os.environ['AUTOMATUM_CLIENT_SECRET']
)

customers = client.get_customers()

cURL ​

bash
# 1. Get access token
TOKEN_RESPONSE=$(curl -s -X POST https://api.automatum.io/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET")

ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')

# 2. Make API request
curl -X GET https://api.automatum.io/api/v1/customers \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Troubleshooting ​

Invalid Client Credentials ​

json
{
  "code": 401,
  "message": "Invalid client credentials"
}

Solutions:

  • Verify client_id and client_secret are correct
  • Check if the application was deleted
  • Ensure credentials haven't been regenerated

Insufficient Scope ​

json
{
  "code": 403,
  "message": "Insufficient scope for this endpoint"
}

Solutions:

  • Update application scopes in dashboard
  • Request a new token after updating scopes
  • Verify endpoint requires the scope you have

Token Expired ​

json
{
  "code": 401,
  "message": "Token expired"
}

Solutions:

  • Request a new access token
  • Implement automatic token refresh
  • Cache tokens and check expiration before use

Next Steps ​

Automatum GTM Platform