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:
- Go to Settings > Integrations > API Access
- Click Create OAuth Application
- 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/tokenRequest:
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
| Scope | Description | Access |
|---|---|---|
read:customers | View customer information | GET /api/v1/customers/* |
write:customers | Create and update customers | POST, PUT /api/v1/customers/* |
read:entitlements | View entitlements | GET /api/v1/entitlements/* |
read:analytics | Access analytics data | GET /api/v1/analytics/* |
write:metering | Submit metering data | POST /api/v1/metering |
read:private-offers | View private offers | GET /api/v1/private-offers/* |
write:private-offers | Create/update private offers | POST, 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/applicationsGet Application Details
bash
GET /oauth/applications/:idUpdate 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-secretImpact 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/:idThis 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: 8234Handling 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_idandclient_secretare 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
- API Reference - Explore all available endpoints
- Webhooks Setup - Receive real-time events
- API Integration Guide - Complete integration guide