REST API Full Closed Beta
The loyalty egress REST API lets your backend query loyalty data on demand. Fetch action histories, point totals, or check your channel's egress configuration — all authenticated with an API key.
Closed Beta
The REST API is in Closed Beta. Endpoints, request parameters, and response shapes described here are subject to change. Contact us for access.
Authentication
All egress endpoints require an API key with the loyalty:read permission. Pass the key in the x-api-key header:
curl -H "x-api-key: your_api_key_here" \
https://api.fanfest.vip/egress/loyalty/actionsAPI keys are created in the API Keys section of your channel admin panel. When creating a key, ensure the loyalty:read permission is included.
If the API key is missing, invalid, or lacks the required permission, the API returns:
// 401 Unauthorized — missing or invalid API key
{
"error": "Unauthorized"
}
// 403 Forbidden — valid key but missing permission
{
"error": "Missing required permission: loyalty:read"
}Rate Limiting
All egress endpoints are rate-limited to 100 requests per minute per API key. Rate limits are tracked using your API key's fingerprint (not the raw key value).
When you exceed the rate limit, the API returns a 429 Too Many Requests response with a Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": "Rate limit exceeded"
}The Retry-After header indicates the number of seconds to wait before retrying (always 60, corresponding to the per-minute window).
TIP
If you need higher rate limits for your use case, contact us to discuss options.
Endpoints
GET /egress/loyalty/actions
Query loyalty actions for your channel. Returns paginated results in the format determined by your channel's resolution mode.
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
user_id | string | No | — | Filter actions by user ID |
app_action_name | string | No | — | Filter by action type (e.g., quiz_answer, poll_vote) |
from_date | ISO 8601 datetime | No | — | Start of date range filter |
to_date | ISO 8601 datetime | No | — | End of date range filter |
page | integer | No | 0 | Page number (zero-indexed) |
limit | integer | No | 50 | Results per page (max: 100) |
Example Request:
curl -H "x-api-key: your_api_key_here" \
"https://api.fanfest.vip/egress/loyalty/actions?user_id=usr_xyz789&limit=10"Example Response (aggregated mode):
{
"data": [
{
"resolution": "aggregated",
"channel_id": "ch_abc123",
"user_id": "usr_xyz789",
"app_action_name": "quiz_answer",
"total_points": 1250,
"total_occurrences": 47,
"community_ids": [],
"timestamp": "2025-06-15T14:32:00.000Z"
},
{
"resolution": "aggregated",
"channel_id": "ch_abc123",
"user_id": "usr_xyz789",
"app_action_name": "poll_vote",
"total_points": 300,
"total_occurrences": 15,
"community_ids": [],
"timestamp": "2025-06-15T14:32:00.000Z"
}
],
"total": 2
}INFO
In aggregated and day aggregated modes, results are grouped by user_id and app_action_name. The community_ids field returns an empty array for grouped results — community data is only available in high fidelity mode. See Resolution Modes for details.
GET /egress/loyalty/points
Get the total loyalty points for a specific user in your channel. Returns the sum of all settled point transactions.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | string | Yes | The user ID to query points for |
Example Request:
curl -H "x-api-key: your_api_key_here" \
"https://api.fanfest.vip/egress/loyalty/points?user_id=usr_xyz789"Example Response:
{
"user_id": "usr_xyz789",
"channel_id": "ch_abc123",
"total_points": 1550
}The total_points value includes only settled transactions. Transactions that are on hold (e.g., pending moderation) are excluded.
GET /egress/loyalty/config
Check your channel's egress configuration status. Useful for verifying that the integration is set up correctly.
Query Parameters: None.
Example Request:
curl -H "x-api-key: your_api_key_here" \
"https://api.fanfest.vip/egress/loyalty/config"Example Response (configured):
{
"configured": true,
"resolution_mode": "aggregated",
"is_enabled": true,
"active_webhook_count": 2
}Example Response (not configured):
{
"configured": false
}INFO
This endpoint never exposes webhook secrets or URLs. It only returns whether the configuration exists, the resolution mode, enabled status, and the count of active webhooks.
Error Responses
All endpoints return errors in a consistent format:
| Status Code | Meaning | Description |
|---|---|---|
400 | Bad Request | Invalid or missing query parameters |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Valid API key but missing loyalty:read permission |
429 | Too Many Requests | Rate limit exceeded (see Retry-After header) |
500 | Internal Server Error | Unexpected server error |
Error response body:
{
"error": "Description of the error"
}Middleware Chain
For each request, the following middleware runs in order:
- API Key Validation — Verifies the JWT-encoded API key is valid and looks up the associated channel
- Permission Check — Confirms the API key includes the
loyalty:readpermission - Rate Limiting — Checks the per-minute request count against the limit (100/min)
- Controller — Processes the request and returns the response
If any middleware step fails, the request is rejected and subsequent steps are not executed.
