Skip to content

API Client Setup ​

Configure API access for programmatic integration with Automatum.

Overview ​

The Automatum API provides programmatic access to all platform features. Use API clients to automate workflows, integrate with existing systems, and build custom solutions.

Authentication ​

The Automatum API uses OAuth 2.0 Client Credentials flow for secure, server-to-server authentication.

OAuth 2.0 Setup ​

Create OAuth Application:

  1. Go to Settings > Integrations > API Access
  2. Click Create OAuth Application
  3. Provide a descriptive name and description
  4. Select the scopes (permissions) your application needs
  5. Click Create
  6. Copy the Client ID and Secret immediately (secret shown only once)

OAuth Application Credentials:

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

Get Access Token ​

Exchange your client credentials for an access token:

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

The access_token is an opaque bearer token. Treat it as a secret and send it unchanged in the Authorization header.

Using Access Token:

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

Token Management

Access tokens expire after 1 hour. Implement automatic token refresh in your application. See the Authentication Guide for detailed token management examples.

Making API Calls ​

The Automatum API is a RESTful API that you can call directly using any HTTP client. Here are examples in common languages:

cURL ​

bash
# Get access token
TOKEN_RESPONSE=$(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=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET")

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

# List all private offers
curl -X GET https://api.automatum.io/api/v1/private-offers \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Create a new offer
curl -X POST https://api.automatum.io/api/v1/private-offers \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor": "aws",
    "listingId": "listing_123",
    "customerId": "cust_456",
    "details": {
      "pricing": {
        "discountPercentage": 20,
        "duration": 12
      }
    }
  }'

JavaScript/Node.js ​

javascript
// Helper function to get access token
async function getAccessToken() {
  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.AUTOMATUM_CLIENT_ID,
      client_secret: process.env.AUTOMATUM_CLIENT_SECRET
    })
  });
  
  const data = await response.json();
  return data.access_token;
}

// List all private offers
async function listPrivateOffers() {
  const token = await getAccessToken();
  
  const response = await fetch('https://api.automatum.io/api/v1/private-offers', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  return response.json();
}

// Create a new offer
async function createPrivateOffer(offerData) {
  const token = await getAccessToken();
  
  const response = await fetch('https://api.automatum.io/api/v1/private-offers', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(offerData)
  });
  
  return response.json();
}

// Usage
const offers = await listPrivateOffers();

const newOffer = await createPrivateOffer({
  vendor: 'aws',
  listingId: 'listing_123',
  customerId: 'cust_456',
  details: {
    pricing: {
      discountPercentage: 20,
      duration: 12
    }
  }
});

Python ​

python
import os
import requests
from typing import Dict, Any

def get_access_token() -> str:
    """Get OAuth access token"""
    response = requests.post(
        'https://api.automatum.io/oauth/token',
        data={
            'grant_type': 'client_credentials',
            'client_id': os.environ['AUTOMATUM_CLIENT_ID'],
            'client_secret': os.environ['AUTOMATUM_CLIENT_SECRET']
        }
    )
    response.raise_for_status()
    return response.json()['access_token']

def list_private_offers() -> Dict[str, Any]:
    """List all private offers"""
    token = get_access_token()
    response = requests.get(
        'https://api.automatum.io/api/v1/private-offers',
        headers={'Authorization': f'Bearer {token}'}
    )
    response.raise_for_status()
    return response.json()

def create_private_offer(offer_data: Dict[str, Any]) -> Dict[str, Any]:
    """Create a new private offer"""
    token = get_access_token()
    response = requests.post(
        'https://api.automatum.io/api/v1/private-offers',
        headers={
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        },
        json=offer_data
    )
    response.raise_for_status()
    return response.json()

# Usage
offers = list_private_offers()

new_offer = create_private_offer({
    'vendor': 'aws',
    'listingId': 'listing_123',
    'customerId': 'cust_456',
    'details': {
        'pricing': {
            'discountPercentage': 20,
            'duration': 12
        }
    }
})

TypeScript ​

typescript
interface TokenResponse {
  access_token: string;
  token_type: string;
  expires_in: number;
  scope: string;
}

async function getAccessToken(): Promise<string> {
  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.AUTOMATUM_CLIENT_ID!,
      client_secret: process.env.AUTOMATUM_CLIENT_SECRET!
    })
  });
  
  const data: TokenResponse = await response.json();
  return data.access_token;
}

async function listPrivateOffers() {
  const token = await getAccessToken();
  
  const response = await fetch('https://api.automatum.io/api/v1/private-offers', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  return response.json();
}

async function createPrivateOffer(offerData: any) {
  const token = await getAccessToken();
  
  const response = await fetch('https://api.automatum.io/api/v1/private-offers', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(offerData)
  });
  
  return response.json();
}

Token Management ​

Access tokens expire after 1 hour. Implement token caching and automatic refresh in your application:

JavaScript Token Cache Example ​

javascript
let cachedToken = null;
let tokenExpiresAt = 0;

async function getValidToken() {
  // Check if token is still valid (with 5 minute buffer)
  if (cachedToken && Date.now() < tokenExpiresAt - 300000) {
    return cachedToken;
  }
  
  // Get new token
  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.AUTOMATUM_CLIENT_ID,
      client_secret: process.env.AUTOMATUM_CLIENT_SECRET
    })
  });
  
  const data = await response.json();
  cachedToken = data.access_token;
  tokenExpiresAt = Date.now() + (data.expires_in * 1000);
  
  return cachedToken;
}

Python Token Cache Example ​

python
import time
from typing import Optional

class TokenManager:
    def __init__(self):
        self.token: Optional[str] = None
        self.expires_at: float = 0
    
    def get_valid_token(self) -> str:
        # Check if token is still valid (with 5 minute buffer)
        if self.token and time.time() < self.expires_at - 300:
            return self.token
        
        # Get new token
        response = requests.post(
            'https://api.automatum.io/oauth/token',
            data={
                'grant_type': 'client_credentials',
                'client_id': os.environ['AUTOMATUM_CLIENT_ID'],
                'client_secret': os.environ['AUTOMATUM_CLIENT_SECRET']
            }
        )
        response.raise_for_status()
        data = response.json()
        
        self.token = data['access_token']
        self.expires_at = time.time() + data['expires_in']
        
        return self.token

# Usage
token_manager = TokenManager()
token = token_manager.get_valid_token()

Environment Variables ​

Recommended environment configuration:

bash
# .env file
AUTOMATUM_CLIENT_ID=app_1a2b3c4d5e6f
AUTOMATUM_CLIENT_SECRET=secret_7g8h9i0j1k2l3m4n5o6p
AUTOMATUM_BASE_URL=https://api.automatum.io/api/v1
AUTOMATUM_TOKEN_URL=https://api.automatum.io/oauth/token

API Resources ​

Available Resources ​

ResourceEndpointDescription
private-offers/api/v1/private-offersManage private offers
customers/api/v1/customersCustomer management
entitlements/api/v1/entitlementsCustomer entitlements
metering/api/v1/meteringReport usage metering
analytics/contracts/api/v1/analytics/contractsContract analytics

Resource Methods ​

Each resource supports standard CRUD operations. See the API Reference for complete endpoint documentation:

javascript
// List with cursor-based pagination (only `limit` and `cursor` are supported)
const token = await getValidToken();
const response = await fetch(
  'https://api.automatum.io/api/v1/private-offers?limit=20',
  {
    headers: { 'Authorization': `Bearer ${token}` }
  }
);
const { data } = await response.json();
const items = data.items;
const nextCursor = data.nextCursor;

// Get by ID
const itemResponse = await fetch(
  'https://api.automatum.io/api/v1/private-offers/offer_123',
  {
    headers: { 'Authorization': `Bearer ${token}` }
  }
);
const item = await itemResponse.json();

// Create new
const created = await fetch('https://api.automatum.io/api/v1/private-offers', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ ... })
});

// Update existing
const updated = await fetch('https://api.automatum.io/api/v1/private-offers/offer_123', {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ ... })
});

// Delete
await fetch('https://api.automatum.io/api/v1/private-offers/offer_123', {
  method: 'DELETE',
  headers: { 'Authorization': `Bearer ${token}` }
});

Error Handling ​

Error Handling ​

javascript
async function makeApiRequest(url, options = {}) {
  const token = await getValidToken();
  
  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  });
  
  if (!response.ok) {
    const error = await response.json();
    
    switch (response.status) {
      case 401:
        // Authentication error - token expired or invalid
        console.error('Authentication failed:', error.message);
        // Clear cached token and retry
        cachedToken = null;
        break;
      case 403:
        // Insufficient scope
        console.error('Insufficient permissions:', error.message);
        break;
      case 404:
        // Resource not found
        console.error('Resource not found:', error.message);
        break;
      case 422:
        // Validation error
        console.error('Validation failed:', error.errors);
        break;
      case 429:
        // Rate limit exceeded
        const retryAfter = response.headers.get('Retry-After');
        console.error('Rate limit exceeded, retry after:', retryAfter, 'seconds');
        break;
      default:
        console.error('API error:', error.message);
    }
    
    throw new Error(error.message || 'API request failed');
  }
  
  return response.json();
}

Error Response Structure ​

json
{
  "error": {
    "type": "validation_error",
    "message": "Invalid request parameters",
    "code": "INVALID_PARAMS",
    "errors": [
      {
        "field": "customerId",
        "message": "Customer ID is required"
      }
    ]
  }
}

Rate Limiting ​

Default Limits ​

  • 1000 requests per hour per OAuth application
  • 50 requests per minute burst limit

Handling Rate Limits ​

javascript
const client = new Automatum({
  clientId: process.env.AUTOMATUM_CLIENT_ID,
  clientSecret: process.env.AUTOMATUM_CLIENT_SECRET,
  maxRetries: 3,
  retryOnRateLimit: true, // Auto-retry on 429
});

Manual handling:

javascript
async function makeRequestWithRetry(url, options = {}, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    
    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
      console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
      await sleep(retryAfter * 1000);
      continue;
    }
    
    return response;
  }
  
  throw new Error('Max retries exceeded');
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Rate Limit Headers ​

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 850
X-RateLimit-Reset: 1642334400

Pagination ​

Cursor-Based Pagination ​

List endpoints are cursor-based. They accept only limit and cursor query parameters, and return { data: { items: [...], nextCursor } }. A nullnextCursor means there are no more pages.

javascript
// First page
const token = await getValidToken();
const page1 = await fetch(
  'https://api.automatum.io/api/v1/customers?limit=50',
  { headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());

// Next page using the cursor from the previous response
const page2 = await fetch(
  `https://api.automatum.io/api/v1/customers?limit=50&cursor=${page1.data.nextCursor}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());

// Iterate all pages
async function* getAllCustomers() {
  let cursor = null;
  const token = await getValidToken();
  
  do {
    const url = cursor 
      ? `https://api.automatum.io/api/v1/customers?limit=100&cursor=${cursor}`
      : 'https://api.automatum.io/api/v1/customers?limit=100';
    
    const response = await fetch(url, {
      headers: { 'Authorization': `Bearer ${token}` }
    });
    const { data } = await response.json();
    
    for (const customer of data.items) {
      yield customer;
    }
    
    cursor = data.nextCursor;
  } while (cursor);
}

// Usage
for await (const customer of getAllCustomers()) {
  console.log(customer.name);
}

Cursor Pagination Only

List endpoints do not support page-based pagination (page/pages/total) or filter and sort query parameters. limit and cursor are the only supported query parameters. Always follow nextCursor to page through results.

Idempotency ​

Prevent duplicate operations by including an Idempotency-Key header:

javascript
const token = await getValidToken();
const offer = await fetch('https://api.automatum.io/api/v1/private-offers', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'unique-request-id-123'
  },
  body: JSON.stringify({
    vendor: 'aws',
    listingId: 'listing_123',
    customerId: 'cust_456'
  })
}).then(r => r.json());

Webhooks ​

Register and manage webhooks:

javascript
// Create webhook
const token = await getValidToken();
const webhook = await fetch('https://api.automatum.io/api/v1/webhook', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://your-app.com/webhooks',
    events: ['entitlement.created', 'entitlement.updated'],
    name: 'My Webhook'
  })
}).then(r => r.json());

// Verify webhook signature (see Webhooks documentation for implementation)

Testing ​

Test/Staging Environment ​

Use test OAuth applications pointing to staging environment:

javascript
// Use staging base URL for testing
const STAGING_BASE_URL = 'https://api.staging.automatum.io/api/v1';
const STAGING_TOKEN_URL = 'https://api.staging.automatum.io/oauth/token';

async function getStagingToken() {
  const response = await fetch(STAGING_TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: process.env.AUTOMATUM_TEST_CLIENT_ID,
      client_secret: process.env.AUTOMATUM_TEST_CLIENT_SECRET
    })
  });
  
  const data = await response.json();
  return data.access_token;
}

// All operations use sandbox environment
const token = await getStagingToken();
const offer = await fetch(`${STAGING_BASE_URL}/private-offers`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({...})
}).then(r => r.json());

Mocking for Unit Tests ​

For unit tests, mock the fetch API:

javascript
// Mock fetch for testing
global.fetch = jest.fn(() =>
  Promise.resolve({
    ok: true,
    json: () => Promise.resolve({ data: [] })
  })
);

// In your tests
const offers = await listPrivateOffers();
expect(fetch).toHaveBeenCalledWith(
  expect.stringContaining('/private-offers'),
  expect.any(Object)
);

Logging ​

Request/Response Logging ​

Add logging to your API calls:

javascript
async function makeApiRequest(url, options = {}) {
  const token = await getValidToken();
  
  console.log('Request:', options.method || 'GET', url);
  
  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  });
  
  console.log('Response:', response.status, response.statusText);
  
  const data = await response.json();
  console.log('Response data:', data);
  
  return data;
}

Custom Logger ​

javascript
const logger = {
  debug: (msg) => console.debug(`[DEBUG] ${msg}`),
  info: (msg) => console.info(`[INFO] ${msg}`),
  warn: (msg) => console.warn(`[WARN] ${msg}`),
  error: (msg) => console.error(`[ERROR] ${msg}`)
};

async function makeApiRequest(url, options = {}) {
  logger.info(`Making ${options.method || 'GET'} request to ${url}`);
  
  try {
    const token = await getValidToken();
    const response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${token}`
      }
    });
    
    if (!response.ok) {
      logger.error(`Request failed: ${response.status} ${response.statusText}`);
    }
    
    return response.json();
  } catch (error) {
    logger.error(`Request error: ${error.message}`);
    throw error;
  }
}

Best Practices ​

  1. Store credentials securely - Use environment variables, never commit to code
  2. Implement token caching - Cache access tokens and refresh before expiration (tokens expire after 1 hour)
  3. Handle errors gracefully - Implement proper error handling for all HTTP status codes
  4. Use retries - Implement automatic retries for transient failures (429, 5xx errors)
  5. Implement idempotency - Use Idempotency-Key header to prevent duplicate operations
  6. Monitor usage - Track API usage, rate limits, and errors
  7. Respect rate limits - Implement exponential backoff when rate limited
  8. Rotate secrets periodically - Regenerate client secrets for security
  9. Use HTTPS - Always use HTTPS for all API requests
  10. Validate responses - Always validate API responses before using data

Troubleshooting ​

Common Issues ​

Authentication Error:

  • Verify client ID and secret are correct
  • Check if OAuth application was deleted
  • Ensure credentials haven't been regenerated
  • Verify access token hasn't expired (tokens expire after 1 hour)
  • Check that application has required scopes

Rate Limit Exceeded:

  • Implement exponential backoff
  • Cache responses where possible
  • Request rate limit increase if needed

Timeout Errors:

  • Increase timeout configuration
  • Check network connectivity
  • Verify endpoint is responsive

Next Steps ​

Need Help? ​

Contact hello@automatum.io for API client support.

Automatum GTM Platform