Appearance
Errors
This page documents common authentication errors and how to resolve them.
OAuth Token Errors
Errors from the /oauth/token endpoint follow OAuth 2.0 error format:
json
{
"error": "error_code",
"error_description": "Human-readable description"
}invalid_request (400)
The request is malformed or missing required parameters.
json
{
"error": "invalid_request",
"error_description": "The request is missing required parameters: client_id, client_secret"
}Causes:
- Missing
grant_type,client_id, orclient_secret - Wrong Content-Type header (must be
application/x-www-form-urlencoded)
Solution:
bash
# Ensure all required parameters are included
curl -X POST https://identity.retailsuccessplatform.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"unsupported_grant_type (400)
The specified grant type is not supported.
json
{
"error": "unsupported_grant_type",
"error_description": "The authorization grant type is not supported. Use 'client_credentials'."
}Causes:
- Using a grant type other than
client_credentials - Typo in grant_type parameter
Solution:
bash
# Only client_credentials is supported for service accounts
-d "grant_type=client_credentials"invalid_client (401)
Client authentication failed.
json
{
"error": "invalid_client",
"error_description": "Client authentication failed"
}Causes:
- Invalid client_id
- Invalid client_secret
- Client secret has been rotated
- Service account doesn't exist
Solution:
- Verify your client_id and client_secret are correct
- Check if credentials have been rotated
- Contact your administrator to verify the service account exists
client_revoked (401)
The service account has been deactivated.
json
{
"error": "invalid_client",
"error_description": "The client has been revoked"
}Causes:
- Service account was deactivated by an administrator
- Service account was deleted
Solution: Contact your administrator to reactivate the service account or create a new one.
invalid_scope (400)
The requested scope is not allowed.
json
{
"error": "invalid_scope",
"error_description": "The requested scope is invalid or not authorized"
}Causes:
- Requesting a scope not assigned to the service account
- Typo in scope name
- Scope doesn't exist
Solution:
bash
# Only request scopes assigned to your service account
# Check your service account configuration for allowed scopes
# Example: if your account has engagehq:content.view
-d "scope=engagehq:content.view"
# Not allowed if you only have view access:
-d "scope=engagehq:content.create" # Will failrate_limit_exceeded (429)
Too many requests in a short period.
json
{
"error": "rate_limit_exceeded",
"error_description": "Too many requests. Please wait before trying again.",
"retry_after": 60
}Causes:
- Requesting more than 60 tokens per minute
- Not caching tokens properly
Solution:
- Implement token caching
- Wait for the
retry_afterperiod - Implement exponential backoff
javascript
// Cache tokens to avoid rate limiting
let cachedToken = null;
let tokenExpiry = 0;
async function getToken() {
if (cachedToken && Date.now() < tokenExpiry - 300000) {
return cachedToken;
}
// Request new token...
}API Authentication Errors
Errors when using tokens with API endpoints:
401 Unauthorized
Token is missing, invalid, or expired.
json
{
"success": false,
"message": "Unauthenticated."
}Common Causes:
| Cause | Solution |
|---|---|
| Missing Authorization header | Add Authorization: Bearer {token} |
| Token expired | Request a new token |
| Token malformed | Check for copy/paste errors |
| Wrong token type | Use Bearer not Basic |
Debugging:
javascript
// Check if token is expired
const decoded = decodeToken(token);
const isExpired = decoded.payload.exp * 1000 < Date.now();
if (isExpired) {
token = await getNewToken();
}403 Forbidden
Token is valid but lacks required permissions.
json
{
"success": false,
"message": "You do not have permission to access this resource."
}Common Causes:
| Cause | Solution |
|---|---|
| Missing required scope | Request a token with the correct scope |
| Wrong organization context | Verify organization_id in token |
| Resource not accessible | Check resource permissions |
Debugging:
javascript
// Check token scopes
const decoded = decodeToken(token);
console.log('Token scopes:', decoded.payload.scopes);
console.log('Token organization:', decoded.payload.organization_id);Error Handling Best Practices
Retry Logic
javascript
async function makeRequestWithRetry(url, token, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
return response.json();
}
// Handle specific errors
if (response.status === 401) {
// Token might be expired, get a new one
token = await getNewToken();
continue;
}
if (response.status === 429) {
// Rate limited, wait and retry
const retryAfter = response.headers.get('Retry-After') || 60;
await sleep(retryAfter * 1000);
continue;
}
if (response.status >= 500) {
// Server error, exponential backoff
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
// Non-retryable error
throw new Error(`Request failed: ${response.status}`);
}
throw new Error('Max retries exceeded');
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}Token Refresh Strategy
javascript
class TokenManager {
constructor(clientId, clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.token = null;
this.expiry = 0;
}
async getValidToken() {
// Refresh if expired or expiring within 5 minutes
if (!this.token || Date.now() > this.expiry - 300000) {
await this.refreshToken();
}
return this.token;
}
async refreshToken() {
const response = await fetch('/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
})
});
if (!response.ok) {
const error = await response.json();
throw new TokenError(error.error, error.error_description);
}
const data = await response.json();
this.token = data.access_token;
this.expiry = Date.now() + (data.expires_in * 1000);
}
}
class TokenError extends Error {
constructor(code, description) {
super(description);
this.code = code;
}
}Logging Errors
javascript
function logAuthError(error, context) {
console.error('Authentication error:', {
code: error.code || 'unknown',
message: error.message,
context: context,
timestamp: new Date().toISOString(),
// Don't log sensitive data
// clientId: '***', // Redacted
// token: '***', // Redacted
});
}Error Code Reference
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | invalid_request | Missing or invalid parameters |
| 400 | unsupported_grant_type | Wrong grant type |
| 400 | invalid_scope | Scope not allowed |
| 401 | invalid_client | Invalid credentials |
| 401 | Unauthenticated | Token invalid/expired |
| 403 | Forbidden | Insufficient permissions |
| 429 | rate_limit_exceeded | Too many requests |
| 500 | server_error | Internal server error |
Getting Help
If you're still experiencing issues:
- Check the status page - Verify there are no service outages
- Review audit logs - Check for detailed error information
- Contact support - Provide error codes and timestamps
Changelog
| Date | Change |
|---|---|
| 2026-02-27 | Documentation corrections. |
| 2026-01-15 | Initial publication. |