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": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:customers read:entitlements write:metering"
}

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 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

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

OAuth applications have the following rate limits:

Per Application

  • 1,000 requests per hour
  • 50 requests per minute

Per Organization

  • 10,000 requests per hour
  • 500 requests per minute

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
X-RateLimit-Organization-Limit: 10000
X-RateLimit-Organization-Remaining: 8234

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