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:
- Go to Settings > Integrations > API Access
- Click Create OAuth Application
- Provide a descriptive name and description
- Select the scopes (permissions) your application needs
- Click Create
- 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": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:customers read:entitlements write:metering"
}Using Access Token:
bash
curl -X GET https://api.automatum.io/api/v1/customers \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."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-offer \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Create a new offer
curl -X POST https://api.automatum.io/api/v1/private-offer \
-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-offer', {
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-offer', {
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-offer',
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-offer',
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-offer', {
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-offer', {
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/tokenAPI Resources
Available Resources
| Resource | Description |
|---|---|
privateOffers | Manage private offers |
listings | Product listings |
customers | Customer management |
entitlements | Customer entitlements |
meteringEvents | Usage metering |
organizations | Organization settings |
activities | Activity feed |
Resource Methods
Each resource supports standard CRUD operations. See the API Reference for complete endpoint documentation:
javascript
// List with filters and pagination
const token = await getValidToken();
const response = await fetch(
'https://api.automatum.io/api/v1/listing?vendor=aws&status=active&page=1&limit=20',
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
const items = await response.json();
// Get by ID
const itemResponse = await fetch(
'https://api.automatum.io/api/v1/listing/listing_123',
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
const item = await itemResponse.json();
// Create new
const created = await fetch('https://api.automatum.io/api/v1/listing', {
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/listing/listing_123', {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ... })
});
// Delete
await fetch('https://api.automatum.io/api/v1/listing/listing_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 API key
- 50 requests per minute burst limit
Handling Rate Limits
javascript
const client = new Automatum({
apiKey: 'sk_live_...',
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: 1642334400Pagination
Cursor-Based Pagination
javascript
// First page
const token = await getValidToken();
const page1 = await fetch(
'https://api.automatum.io/api/v1/customer?limit=50',
{ headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());
// Next page using cursor
const page2 = await fetch(
`https://api.automatum.io/api/v1/customer?limit=50&cursor=${page1.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/customer?limit=100&cursor=${cursor}`
: 'https://api.automatum.io/api/v1/customer?limit=100';
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
for (const customer of data.data) {
yield customer;
}
cursor = data.nextCursor;
} while (cursor);
}
// Usage
for await (const customer of getAllCustomers()) {
console.log(customer.name);
}Page-Based Pagination
javascript
const token = await getValidToken();
const response = await fetch(
'https://api.automatum.io/api/v1/listing?page=2&limit=20',
{ headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());
console.log({
currentPage: response.pagination.page,
totalPages: response.pagination.pages,
totalItems: response.pagination.total
});Filtering and Sorting
Query Filters
javascript
const token = await getValidToken();
// Build query parameters
const params = new URLSearchParams({
vendor: 'aws',
status: 'accepted',
'createdAt[gte]': '2026-01-01',
'createdAt[lte]': '2026-12-31',
'value[gt]': '10000'
});
const offers = await fetch(
`https://api.automatum.io/api/v1/private-offer?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());Sorting
javascript
const token = await getValidToken();
// Single field sort
const customers = await fetch(
'https://api.automatum.io/api/v1/customer?sort=-createdAt', // Descending
{ headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());
// Or ascending
const customersAsc = await fetch(
'https://api.automatum.io/api/v1/customer?sort=name',
{ headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());
// Multiple sort fields
const customersMulti = await fetch(
'https://api.automatum.io/api/v1/customer?sort=-value,name',
{ headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());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-offer', {
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-offer`, {
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-offer'),
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
- Store credentials securely - Use environment variables, never commit to code
- Implement token caching - Cache access tokens and refresh before expiration (tokens expire after 1 hour)
- Handle errors gracefully - Implement proper error handling for all HTTP status codes
- Use retries - Implement automatic retries for transient failures (429, 5xx errors)
- Implement idempotency - Use
Idempotency-Keyheader to prevent duplicate operations - Monitor usage - Track API usage, rate limits, and errors
- Respect rate limits - Implement exponential backoff when rate limited
- Rotate secrets periodically - Regenerate client secrets for security
- Use HTTPS - Always use HTTPS for all API requests
- 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
- Authentication Guide - Complete OAuth 2.0 setup and token management
- API Reference - Explore all available endpoints
- Webhooks Setup - Receive real-time events
- API Integration Guide - Common integration patterns
Need Help?
Contact api@automatum.io for API client support.