The GoSmarter API uses OAuth 2.0 with JWT (JSON Web Tokens) for authentication. All API requests must include a valid access token.
Overview
Authentication follows the OAuth 2.0 client credentials flow:
Exchange your client credentials for an access token
Include the access token in the Authorization header of every API request
Refresh the token when it expires (typically after 1 hour)
Getting API Credentials
Contact your GoSmarter administrator to obtain:
Client ID - Your application's unique identifier
Client Secret - Your application's secret key (keep this secure!)
Tenant ID - Your organization's tenant identifier
API Scope - The permission scope for API access
Never expose your client secret in client-side code or public repositories. Store it securely in environment variables or secret management systems.
Obtaining an Access Token
Request
POST https://{tenant}.ciamlogin.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type = client_credentials
& client_id = { YOUR_CLIENT_ID}
& client_secret = { YOUR_CLIENT_SECRET}
& scope = api:// { API_ID}/access_as_user
cURL Example
curl --request POST \
--url ' https://36078520-1cde-40ad-940e-6f26f1a90414.ciamlogin.com/36078520-1cde-40ad-940e-6f26f1a90414/oauth2/v2.0/token ' \
--header ' Content-Type: application/x-www-form-urlencoded ' \
--data-urlencode ' grant_type=client_credentials ' \
--data-urlencode ' client_id=YOUR_CLIENT_ID ' \
--data-urlencode ' client_secret=YOUR_CLIENT_SECRET ' \
--data-urlencode ' scope=api://0399c3db-81cb-4a1e-8ff6-9fcfca8dec21/access_as_user '
Response
{
" token_type " : " Bearer " ,
" expires_in " : 3599 ,
" access_token " : " eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1yNS1BVWl... "
}
Using the Access Token
Include the access token in the Authorization header of every API request:
curl --request GET \
--url ' https://your-gateway.zuplo.app/api/resource ' \
--header ' Authorization: Bearer YOUR_ACCESS_TOKEN '
Code Examples
JavaScript/Node.js
const axios = require ( " axios " );
// Get access token
async function getAccessToken () {
const params = new URLSearchParams ({
grant_type : " client_credentials " ,
client_id : process . env . CLIENT_ID ,
client_secret : process . env . CLIENT_SECRET ,
scope : process . env . API_SCOPE ,
});
const response = await axios . post ( process . env . TOKEN_URL , params , {
headers : { " Content-Type " : " application/x-www-form-urlencoded " },
});
return response . data . access_token ;
}
// Use access token
async function callAPI () {
const token = await getAccessToken ();
const response = await axios . get (
" https://your-gateway.zuplo.app/api/resource " ,
{ headers : { Authorization : ` Bearer ${ token } ` } }
);
return response . data ;
}
Python
import requests
import os
def get_access_token ():
""" Get OAuth 2.0 access token """
data = {
' grant_type ' : ' client_credentials ' ,
' client_id ' : os . environ [ ' CLIENT_ID ' ],
' client_secret ' : os . environ [ ' CLIENT_SECRET ' ],
' scope ' : os . environ [ ' API_SCOPE ' ]
}
response = requests . post (
os . environ [ ' TOKEN_URL ' ],
data = data ,
headers ={ ' Content-Type ' : ' application/x-www-form-urlencoded ' }
)
response . raise_for_status ()
return response . json ()[ ' access_token ' ]
def call_api ():
""" Make authenticated API call """
token = get_access_token ()
response = requests . get (
' https://your-gateway.zuplo.app/api/resource ' ,
headers ={ ' Authorization ' : f 'Bearer { token } ' }
)
response . raise_for_status ()
return response . json ()
C#
using System ;
using System . Net . Http ;
using System . Net . Http . Headers ;
using System . Threading . Tasks ;
using System . Collections . Generic ;
public class GoSmarterAPIClient
{
private readonly HttpClient _httpClient ;
private readonly string _tokenUrl ;
private readonly string _clientId ;
private readonly string _clientSecret ;
private readonly string _scope ;
public async Task < string > GetAccessTokenAsync ()
{
var content = new FormUrlEncodedContent ( new Dictionary < string , string >
{
[ " grant_type " ] = " client_credentials " ,
[ " client_id " ] = _clientId ,
[ " client_secret " ] = _clientSecret ,
[ " scope " ] = _scope
});
var response = await _httpClient . PostAsync ( _tokenUrl , content );
response . EnsureSuccessStatusCode ();
var result = await response . Content . ReadAsAsync < TokenResponse >();
return result . AccessToken ;
}
public async Task < string > CallAPIAsync ( string endpoint )
{
var token = await GetAccessTokenAsync ();
_httpClient . DefaultRequestHeaders . Authorization =
new AuthenticationHeaderValue ( " Bearer " , token );
var response = await _httpClient . GetAsync ( endpoint );
response . EnsureSuccessStatusCode ();
return await response . Content . ReadAsStringAsync ();
}
}
Token Management
Token Expiration
Access tokens typically expire after 1 hour (3600 seconds). The expires_in field in the token response tells you the exact expiration time.
Token Caching
To optimize performance:
Cache the token - Don't request a new token for every API call
Track expiration - Store the token with its expiration time
Refresh proactively - Get a new token before the current one expires
Example with Caching
class TokenManager {
constructor () {
this . token = null ;
this . expiresAt = null ;
}
async getToken () {
// Check if token is still valid
if ( this . token && this . expiresAt > Date . now () + 60000 ) {
return this . token ;
}
// Get new token
const response = await fetch ( TOKEN_URL , {
method : " POST " ,
headers : { " Content-Type " : " application/x-www-form-urlencoded " },
body : new URLSearchParams ({
grant_type : " client_credentials " ,
client_id : CLIENT_ID ,
client_secret : CLIENT_SECRET ,
scope : API_SCOPE ,
}),
});
const data = await response . json ();
this . token = data . access_token ;
this . expiresAt = Date . now () + data . expires_in * 1000 ;
return this . token ;
}
}
Security Best Practices
Keep Credentials Secure
✓ Store credentials in environment variables or secure vaults
✓ Use HTTPS for all API requests
✓ Rotate client secrets regularly
✗ Never commit credentials to version control
✗ Never expose credentials in client-side code
✗ Never log access tokens
Environment Variables
# .env file (never commit this!)
CLIENT_ID = your-client-id
CLIENT_SECRET = your-client-secret
TOKEN_URL = https://your-tenant.ciamlogin.com/.../oauth2/v2.0/token
API_SCOPE = api://your-api-id/access_as_user
GATEWAY_URL = https://your-gateway.zuplo.app
Troubleshooting
Invalid Client Error
{
" error " : " invalid_client " ,
" error_description " : " Client authentication failed "
}
Solutions:
Verify your client ID and secret are correct
Check that your client is registered in the OAuth provider
Ensure you're using the correct token endpoint URL
Invalid Scope Error
{
" error " : " invalid_scope " ,
" error_description " : " The requested scope is invalid "
}
Solutions:
Verify the scope matches what your client is authorized for
Check that the API scope is correctly configured
Contact your administrator to grant the necessary permissions
401 Unauthorized
Causes:
Token has expired
Token is malformed or invalid
Missing Authorization header
Wrong Bearer token format
Solutions:
# Correct format
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
# Common mistakes to avoid
Authorization: eyJ0eXAiOiJKV1QiLCJhbGc... # Missing "Bearer"
Authorization: Bearer: eyJ0eXAiOiJKV1Qi... # Extra colon
Rate Limit Exceeded
{
" error " : " rate_limit_exceeded " ,
" message " : " Too many token requests. Try again in 60 seconds. "
}
Solutions:
Implement token caching (see above)
Reduce the frequency of token requests
Use exponential backoff for retries
Next Steps
Last modified on December 11, 2025