Appearance
Authentication
The EngageHQ Public API uses JWT-based authentication with organization context. This guide explains how authentication works and how to properly configure your requests.
Authentication Flow
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
│ Your App │────▶│ Identity Service│────▶│ EngageHQ API│
│ │◀────│ (JWT Token) │ │ │
└─────────────┘ └─────────────────┘ └─────────────┘- Your application authenticates with the ShopHero Identity service
- Identity service returns a JWT token
- Include the token in all EngageHQ API requests
Required Headers
Authorization Header
Every request must include the Authorization header with a valid JWT token:
http
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...X-Organization-Context Header
The X-Organization-Context header identifies which organization or location to return content for:
http
X-Organization-Context: org_00000a1B2c3D4e5Required for CDN endpoint
When using the CDN endpoint (cdn.engagehq.*), always include X-Organization-Context. This header is used as the CDN cache key for per-location isolation — without it, caching won't work correctly.
The value can be:
- Your JWT's organization (the retailer/parent org)
- A descendant location (a store within your organization's hierarchy)
When a descendant location is provided, the API validates that it belongs to your JWT's organization hierarchy and scopes all responses to that location. This also enables content distribution filtering — content distributed from parent organizations will be included.
location_id Query Parameter
You can also target a specific location using the location_id query parameter:
http
GET /api/v1/public/content?location_id=org_00000x1Y2z3W4v5When both X-Organization-Context header and location_id query parameter are provided, the query parameter takes precedence as an explicit override.
Organization Resolution (Precedence)
location_idquery parameter (explicit override)X-Organization-Contextheader (recommended for CDN caching)- JWT token organization claim (automatic fallback)
Example Request
javascript
const API_BASE = 'https://cdn.engagehq.retailsuccessplatform.com/api/v1/public';
// Fetch content for a specific store location
async function fetchContent(token, locationId) {
const response = await fetch(`${API_BASE}/content`, {
headers: {
'Authorization': `Bearer ${token}`,
'X-Organization-Context': locationId, // Store's org hashkey
'Content-Type': 'application/json',
},
});
return response.json();
}
// Or use location_id query parameter for explicit targeting
async function fetchContentForLocation(token, locationId) {
const response = await fetch(`${API_BASE}/content?location_id=${locationId}`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
return response.json();
}Token Claims
Your JWT token includes claims that the API uses for authorization:
| Claim | Description |
|---|---|
user_id | Your user identifier |
organization_id | Your active organization |
organizations | Array of accessible organization IDs |
roles | Your permission roles |
exp | Token expiration timestamp |
Error Responses
401 Unauthorized
Returned when the token is missing, invalid, or expired:
json
{
"success": false,
"message": "Unauthorized",
"errors": null
}Solutions:
- Check that the Authorization header is present
- Verify the token hasn't expired
- Ensure the token format is correct (
Bearer <token>)
403 Forbidden
Returned when the token is valid but you don't have access to the requested organization:
json
{
"success": false,
"message": "Access denied to organization",
"errors": null
}Solutions:
- Verify the organization ID is correct
- Confirm your account has access to the organization
- Contact your administrator for access
403 Location Not Accessible
Returned when the X-Organization-Context header or location_id parameter specifies an organization that is not accessible from your JWT token:
json
{
"success": false,
"message": "Location not accessible from token organization.",
"error": "location_not_accessible"
}Solutions:
- Ensure the location is a descendant of your JWT's organization (e.g., a store within your retailer)
- Verify the organization hashkey is correct
- Contact your administrator if you need access to additional locations
400 Organization Context Required
Returned when no organization context can be determined:
json
{
"success": false,
"message": "Organization context required",
"errors": null
}Solutions:
- Pass the
X-Organization-Contextheader (recommended) - Ensure your token includes an active organization claim
Token Refresh
JWT tokens expire after a set period (typically 1 hour). Implement token refresh in your application:
javascript
class ApiClient {
constructor(identityService, organizationId) {
this.identityService = identityService;
this.organizationId = organizationId;
this.token = null;
this.tokenExpiry = null;
}
async getToken() {
// Refresh if token is expired or will expire in 5 minutes
const bufferMs = 5 * 60 * 1000;
if (!this.token || Date.now() > this.tokenExpiry - bufferMs) {
const { token, expires_at } = await this.identityService.refresh();
this.token = token;
this.tokenExpiry = new Date(expires_at).getTime();
}
return this.token;
}
async fetch(endpoint) {
const token = await this.getToken();
return fetch(`${API_BASE}${endpoint}`, {
headers: {
'Authorization': `Bearer ${token}`,
'X-Organization-Context': this.organizationId,
},
});
}
}Security Best Practices
Never expose tokens in client-side code
Store tokens securely and never include them in URLs or client-side JavaScript that could be inspected.
- Use HTTPS only - Never send tokens over unencrypted connections
- Store tokens securely - Use httpOnly cookies or secure storage
- Implement token refresh - Don't let tokens expire mid-session
- Validate on the server - When building a backend, validate tokens server-side
- Handle errors gracefully - Redirect to login on 401 responses
Next Steps
- Content API Reference - Start fetching content
- Caching Guide - Optimize performance with caching
Changelog
| Date | Change |
|---|---|
| 2026-02-23 | Updated for CloudFront CDN and Lambda@Edge delivery. |
| 2026-01-28 | Expanded documentation. |
| 2026-01-15 | Initial publication. |