Skip to content

Access Tokens

Access tokens are required to authenticate with the Gateway API. This section covers how to generate tokens using the OAuth2 client credentials grant.

The Gateway API uses OAuth2 Client Credentials Grant for application-level authentication. This grant type is ideal for:

  • Server-to-server integrations
  • Background jobs and automation
  • Service accounts
  • Applications without user context
POST /oauth/token
Content-Type: application/json
Accept: application/json
FieldTypeRequiredDescription
grant_typestring✅ YesMust be client_credentials
client_idstring✅ YesYour OAuth2 client ID
client_secretstring✅ YesYour OAuth2 client secret
scopestring✅ YesSpace-separated list of scopes

Request only the scopes your application needs:

Account Scopes:

  • app:account-read - Read account information
  • app:account-create - Create new accounts
  • app:account-update - Update existing accounts

Integration Scopes:

  • app:integration-read - List and view integrations
  • app:integration-action - Execute integration actions
  • app:integration-claim - Claim integrations for accounts
  • app:integration-create - Install new integrations
  • app:integration-update - Update integration configuration
  • app:integration-delete - Delete integrations

Service & Flow Scopes:

  • app:service-attach - View available services
  • app:flow-read - Access flows

Intent Scopes:

  • app:intent-create - Generate intent tokens
POST https://<GATEWAY_URL>/oauth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "9a1b2c3d-4e5f-6789-abcd-ef0123456789",
"client_secret": "your-secret-key-here",
"scope": "app:account-read app:account-create app:integration-read app:integration-action app:integration-claim app:service-attach app:flow-read app:intent-create"
}

Status: 200 OK

{
"token_type": "Bearer",
"expires_in": 31536000,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI5YTFiMmMzZC00ZTVmLTY3ODktYWJjZC1lZjAxMjM0NTY3ODkiLCJqdGkiOiI4YTJiM2M0ZDVlNmY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwiaWF0IjoxNjQwMDAwMDAwLCJuYmYiOjE2NDAwMDAwMDAsImV4cCI6MTY3MTUzNjAwMCwic3ViIjoiIiwic2NvcGVzIjpbImFwcDphY2NvdW50LXJlYWQiLCJhcHA6YWNjb3VudC1jcmVhdGUiLCJhcHA6aW50ZWdyYXRpb24tcmVhZCIsImFwcDppbnRlZ3JhdGlvbi1hY3Rpb24iLCJhcHA6aW50ZWdyYXRpb24tY2xhaW0iLCJhcHA6c2VydmljZS1hdHRhY2giLCJhcHA6Zmxvdy1yZWFkIiwiYXBwOmludGVudC1jcmVhdGUiXX0..."
}
FieldTypeDescription
token_typestringAlways Bearer
expires_inintegerToken lifetime in seconds (31536000 = 1 year)
access_tokenstringJWT access token for API requests

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

GET /api/2.0/app/integrations
Host: <GATEWAY_URL>
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...
Accept: application/json

#!/bin/bash
# Get access token
TOKEN_RESPONSE=$(curl -s -X POST https://<GATEWAY_URL>/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"scope": "app:account-read app:integration-read app:integration-action"
}')
# Extract access token
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')
# Use token to make API request
curl -X GET https://<GATEWAY_URL>/api/2.0/app/integrations \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"
use Illuminate\Support\Facades\Http;
class GatewayClient
{
private $baseUrl;
private $clientId;
private $clientSecret;
private $accessToken;
private $tokenExpiry;
public function __construct()
{
$this->baseUrl = config('gateway.url');
$this->clientId = config('gateway.client_id');
$this->clientSecret = config('gateway.client_secret');
}
public function getAccessToken(): string
{
// Return cached token if still valid
if ($this->accessToken && $this->tokenExpiry > now()->addMinutes(5)) {
return $this->accessToken;
}
// Request new token
$response = Http::post("{$this->baseUrl}/oauth/token", [
'grant_type' => 'client_credentials',
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'scope' => 'app:account-read app:integration-read app:integration-action',
]);
if ($response->failed()) {
throw new \Exception('Failed to obtain access token');
}
$data = $response->json();
$this->accessToken = $data['access_token'];
$this->tokenExpiry = now()->addSeconds($data['expires_in']);
return $this->accessToken;
}
public function getIntegrations(): array
{
$token = $this->getAccessToken();
$response = Http::withToken($token)
->get("{$this->baseUrl}/api/2.0/app/integrations");
return $response->json()['data'];
}
}
const axios = require('axios');
class GatewayClient {
constructor(baseUrl, clientId, clientSecret) {
this.baseUrl = baseUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessToken = null;
this.tokenExpiry = null;
}
async getAccessToken() {
// Return cached token if still valid
if (this.accessToken && this.tokenExpiry > Date.now() + 300000) {
return this.accessToken;
}
// Request new token
const response = await axios.post(`${this.baseUrl}/oauth/token`, {
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
scope: 'app:account-read app:integration-read app:integration-action'
});
this.accessToken = response.data.access_token;
this.tokenExpiry = Date.now() + (response.data.expires_in * 1000);
return this.accessToken;
}
async getIntegrations() {
const token = await this.getAccessToken();
const response = await axios.get(`${this.baseUrl}/api/2.0/app/integrations`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.data.data;
}
}
// Usage
const gateway = new GatewayClient(
process.env.GATEWAY_URL,
process.env.GATEWAY_CLIENT_ID,
process.env.GATEWAY_CLIENT_SECRET
);
const integrations = await gateway.getIntegrations();
console.log(integrations);
import requests
import time
from typing import Optional
class GatewayClient:
def __init__(self, base_url: str, client_id: str, client_secret: str):
self.base_url = base_url
self.client_id = client_id
self.client_secret = client_secret
self.access_token: Optional[str] = None
self.token_expiry: Optional[float] = None
def get_access_token(self) -> str:
"""Get access token, using cached token if still valid."""
# Return cached token if still valid (with 5-minute buffer)
if self.access_token and self.token_expiry > time.time() + 300:
return self.access_token
# Request new token
response = requests.post(
f'{self.base_url}/oauth/token',
json={
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret,
'scope': 'app:account-read app:integration-read app:integration-action'
}
)
response.raise_for_status()
data = response.json()
self.access_token = data['access_token']
self.token_expiry = time.time() + data['expires_in']
return self.access_token
def get_integrations(self) -> list:
"""Fetch all integrations."""
token = self.get_access_token()
response = requests.get(
f'{self.base_url}/api/2.0/app/integrations',
headers={'Authorization': f'Bearer {token}'}
)
response.raise_for_status()
return response.json()['data']
# Usage
import os
gateway = GatewayClient(
os.getenv('GATEWAY_URL'),
os.getenv('GATEWAY_CLIENT_ID'),
os.getenv('GATEWAY_CLIENT_SECRET')
)
integrations = gateway.get_integrations()
print(integrations)

Access tokens expire after 1 year (31,536,000 seconds).

  1. Cache Tokens: Store tokens in memory and reuse until near expiration
  2. Refresh Buffer: Request new token 5 minutes before expiration
  3. Handle 401 Errors: If you receive 401 Unauthorized, request a new token
  4. Secure Storage: Never commit tokens to version control

[!NOTE] Client Credentials Grant does NOT include a refresh token. Simply request a new access token when the current one expires.

async function makeAuthenticatedRequest(url) {
try {
const token = await getAccessToken();
return await axios.get(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
} catch (error) {
if (error.response?.status === 401) {
// Token expired, get new one
this.accessToken = null; // Clear cached token
const token = await getAccessToken();
return await axios.get(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
}
throw error;
}
}

Cause: Invalid credentials or malformed request

{
"error": "invalid_client",
"error_description": "Client authentication failed",
"message": "Client authentication failed"
}

Solutions:

  • Verify client_id and client_secret are correct
  • Check request payload format
  • Ensure Content-Type: application/json header is set

Cause: Invalid client credentials

{
"message": "Unauthenticated."
}

Solutions:

  • Verify credentials are correct
  • Check that OAuth client exists and is active

.env
GATEWAY_URL=https://your-gateway.com
GATEWAY_CLIENT_ID=your-client-id
GATEWAY_CLIENT_SECRET=your-client-secret

[!CAUTION] Never commit .env files to version control!

// ❌ BAD - Hardcoded credentials
const clientId = "hardcoded-id";
// ✅ GOOD - Environment variables
const clientId = process.env.GATEWAY_CLIENT_ID;

Request only necessary scopes:

{
"scope": "app:integration-read"
}

All requests must use HTTPS to protect tokens in transit.