Error Codes & Handling
Complete reference for Loyalteez API error codes, common issues, and solutions.
Error Response Format
All Loyalteez APIs return errors in this standard format:
{
"error": "Error type",
"message": "Human-readable description",
"details": "Additional context (optional)",
"code": "ERROR_CODE (optional)"
}
Example Error:
{
"error": "Invalid event data",
"message": "Missing required field: eventType",
"details": ["eventType is required", "userEmail must be valid email format"]
}
HTTP Status Codes
| Status | Name | When It Occurs |
|---|---|---|
| 200 | OK | Request succeeded |
| 400 | Bad Request | Invalid input data |
| 401 | Unauthorized | Missing/invalid auth token |
| 403 | Forbidden | Access denied (automation disabled, contract not whitelisted) |
| 404 | Not Found | Endpoint doesn't exist |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error (contact support) |
| 503 | Service Unavailable | Service temporarily down |
Event Handler Errors
400 - Invalid Event Data
Error:
{
"error": "Invalid event data",
"details": ["Missing required field: eventType"]
}
Cause: Missing or invalid required fields
Solution:
// ❌ Wrong
{
brandId: 'abc123',
// Missing eventType and userEmail
}
// ✅ Correct
{
brandId: 'abc123',
eventType: 'account_creation',
userEmail: '[email protected]'
}
400 - Missing Required Fields
Error:
{
"error": "Missing required fields",
"message": "brandId, eventType, and userEmail are required"
}
Required Fields:
brandId- Your Loyalteez Brand IDeventType- Type of event (see supported events)userEmail- User's email address
Solution:
const requiredFields = {
brandId: process.env.LOYALTEEZ_BRAND_ID,
eventType: 'account_creation',
userEmail: user.email
};
// Validate before sending
if (!requiredFields.brandId || !requiredFields.eventType || !requiredFields.userEmail) {
console.error('Missing required fields');
return;
}
await trackEvent(requiredFields);
400 - Event Type Not Configured
Error:
{
"error": "Event type not configured",
"message": "custom_event is not configured for rewards"
}
Cause: Using an event type that doesn't have a reward configured
Supported Event Types:
account_creation→ 100 LTZcomplete_survey→ 75 LTZnewsletter_subscribe→ 25 LTZrate_experience→ 50 LTZsubscribe_renewal→ 200 LTZform_submit→ 10 LTZ
Solution:
// ❌ Wrong - custom event type
eventType: 'my_custom_event'
// ✅ Correct - use supported event type
eventType: 'account_creation'
403 - Automation Disabled
Error:
{
"success": false,
"error": "Automation disabled",
"message": "Reward automation is currently disabled for this brand. Please enable it in your dashboard settings.",
"brandId": "your-brand-id"
}
Cause: Reward automation is turned off in Partner Portal
Solution:
- Log into Partner Portal
- Go to Settings → LTZ Distribution
- Toggle "Enable Automation" to ON
- Save settings
Handling in Code:
const response = await fetch('https://api.loyalteez.app/loyalteez-api/manual-event', {
method: 'POST',
body: JSON.stringify(eventData)
});
if (response.status === 403) {
const error = await response.json();
if (error.error === 'Automation disabled') {
// Show user-friendly message
console.warn('Rewards are currently disabled. Contact support.');
// Maybe queue the event for later?
}
}
429 - Rate Limit Exceeded
Error:
{
"error": "Rate limit exceeded",
"message": "Too many events from this email. Maximum 1 reward per event type per day per user."
}
Cause: User has already received reward for this event type today
Rate Limits:
- Event Handler: 1 reward per event type per user per day
- Gas Relayer: 35 transactions per user per hour
- Pregeneration: 100 requests per brand per minute
Solution - Add Deduplication:
// Track which events user has already been rewarded for
const userRewardCache = new Map();
async function trackEventWithDedup(userEmail, eventType) {
const cacheKey = `${userEmail}-${eventType}-${getTodayDate()}`;
if (userRewardCache.has(cacheKey)) {
console.log('User already rewarded for this event today');
return { success: false, reason: 'already_rewarded' };
}
try {
const response = await fetch('https://api.loyalteez.app/loyalteez-api/manual-event', {
method: 'POST',
body: JSON.stringify({
brandId: 'your-brand-id',
eventType,
userEmail
})
});
if (response.status === 429) {
// Cache it to avoid future calls
userRewardCache.set(cacheKey, true);
return { success: false, reason: 'rate_limited' };
}
const data = await response.json();
userRewardCache.set(cacheKey, true);
return { success: true, data };
} catch (error) {
console.error('Event tracking error:', error);
return { success: false, reason: 'error', error };
}
}
function getTodayDate() {
return new Date().toISOString().split('T')[0]; // YYYY-MM-DD
}
500 - Internal Server Error
Error:
{
"error": "Internal server error",
"message": "Failed to process event"
}
Cause: Unexpected server error (database issue, blockchain issue, etc.)
Solution - Implement Retry Logic:
async function trackEventWithRetry(eventData, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('https://api.loyalteez.app/loyalteez-api/manual-event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData)
});
if (response.status === 500) {
console.warn(`Attempt ${attempt}/${maxRetries} failed with 500 error`);
if (attempt < maxRetries) {
// Exponential backoff
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw new Error('Max retries reached');
}
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries) {
console.error('Event tracking failed after retries:', error);
// Queue for later processing or alert ops team
await queueFailedEvent(eventData);
throw error;
}
}
}
}
503 - Service Unavailable
Error:
{
"error": "Database not configured - DNS verification not available"
}
Cause: Required service (database, blockchain) temporarily unavailable
Solution:
async function trackWithFallback(eventData) {
try {
return await trackEvent(eventData);
} catch (error) {
if (error.status === 503) {
// Queue event for later processing
console.warn('Service unavailable, queuing event');
await queueEventForLater(eventData);
return { success: false, queued: true };
}
throw error;
}
}
async function queueEventForLater(eventData) {
// Store in your database or queue system
await db.queuedEvents.create({
...eventData,
queuedAt: new Date(),
retryCount: 0
});
}
Gas Relayer Errors
401 - Invalid Privy Token
Error:
{
"error": "Invalid or expired Privy token"
}
Cause: Missing or expired Privy access token
Solution:
import { usePrivy } from '@privy-io/react-auth';
function MyComponent() {
const { getAccessToken, ready, authenticated } = usePrivy();
async function executeGaslessTransaction() {
// Check authentication first
if (!ready || !authenticated) {
console.error('User not authenticated');
return;
}
try {
// Get fresh token
const token = await getAccessToken();
if (!token) {
console.error('Failed to get access token');
return;
}
const response = await fetch('https://relayer.loyalteez.app/relay', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` // ✅ Include token
},
body: JSON.stringify(transactionData)
});
if (response.status === 401) {
// Token expired, get new one
const newToken = await getAccessToken();
// Retry with new token
return executeGaslessTransaction();
}
return await response.json();
} catch (error) {
console.error('Transaction error:', error);
}
}
}
403 - Contract Not Whitelisted
Error:
{
"error": "Transaction validation failed: Contract not whitelisted"
}
Cause: Trying to call a contract that's not on the whitelist
Whitelisted Contracts:
- Loyalteez Token (LTZ) -
0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9 - PerkNFT -
0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0 - PointsSale -
0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72
Solution:
const WHITELISTED_CONTRACTS = {
LTZ_TOKEN: '0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9',
PERK_NFT: '0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0',
POINTS_SALE: '0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72'
};
function validateContract(contractAddress) {
const whitelistedAddresses = Object.values(WHITELISTED_CONTRACTS);
if (!whitelistedAddresses.includes(contractAddress.toLowerCase())) {
throw new Error(`Contract ${contractAddress} is not whitelisted`);
}
}
// Use before calling gas relayer
validateContract(targetContract);
429 - Gas Relayer Rate Limit
Error:
{
"error": "Rate limit exceeded. Max 35 transactions per hour."
}
Cause: User has made 35+ gasless transactions in the last hour
Solution - Track Usage:
const userTransactionCount = new Map();
async function executeGaslessWithRateLimit(userAddress, txData) {
const hourKey = `${userAddress}-${getCurrentHour()}`;
const count = userTransactionCount.get(hourKey) || 0;
if (count >= 35) {
return {
success: false,
error: 'Rate limit reached',
message: 'You can make up to 35 gasless transactions per hour. Please try again later.',
resetAt: getNextHourTimestamp()
};
}
try {
const response = await fetch('https://relayer.loyalteez.app/relay', {
method: 'POST',
body: JSON.stringify(txData)
});
if (response.status === 429) {
return {
success: false,
error: 'Rate limit exceeded',
message: 'Transaction limit reached. Please try again in the next hour.'
};
}
const result = await response.json();
// Increment count
userTransactionCount.set(hourKey, count + 1);
return { success: true, ...result };
} catch (error) {
console.error('Transaction error:', error);
return { success: false, error: error.message };
}
}
function getCurrentHour() {
const now = new Date();
return `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}-${now.getHours()}`;
}
function getNextHourTimestamp() {
const nextHour = new Date();
nextHour.setHours(nextHour.getHours() + 1, 0, 0, 0);
return nextHour.getTime();
}
Pregeneration Errors
400 - Invalid Discord ID
Error:
{
"error": "Invalid Discord user ID",
"details": "Discord user IDs must be 17-20 digit numeric strings (snowflake IDs)",
"example": "123456789012345678"
}
Cause: Discord user ID doesn't match snowflake format
Solution:
function validateDiscordId(userId) {
// Discord IDs are 17-20 digit numeric strings
if (!/^\d{17,20}$/.test(userId)) {
throw new Error('Invalid Discord ID format');
}
return userId;
}
// Use before calling pregeneration
const validDiscordId = validateDiscordId(discordUser.id);
await pregenerateWallet('discord', validDiscordId, discordUser.username);
400 - Invalid OAuth Credentials
Error:
{
"error": "Invalid OAuth credentials",
"hint": "This endpoint requires REAL OAuth user IDs and usernames from actual discord accounts. Test credentials are rejected by Privy for security.",
"provider": "discord"
}
Cause: Trying to use fake/test OAuth credentials
Solution:
// ❌ Wrong - test credentials
await pregenerateWallet('discord', 'test123', 'testuser');
// ✅ Correct - real Discord user ID from actual account
const realDiscordUser = await discordClient.users.fetch(userId);
await pregenerateWallet(
'discord',
realDiscordUser.id, // Real snowflake ID
realDiscordUser.username // Real username
);
Network Errors
CORS Errors
Error (Browser Console):
Access to fetch at 'https://api.loyalteez.app/...' from origin 'https://yoursite.com'
has been blocked by CORS policy
Cause: CORS preflight failure or missing headers
Solution:
// Ensure correct headers
const response = await fetch('https://api.loyalteez.app/loyalteez-api/manual-event', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // ✅ Required
},
body: JSON.stringify(data)
});
Note: All Loyalteez APIs support CORS by default. If you still see CORS errors:
- Check you're using HTTPS (not HTTP)
- Verify the endpoint URL is correct
- Check browser console for actual error
Timeout Errors
Error:
Error: Request timeout after 30000ms
Cause: Request took too long (network issue, server busy)
Solution:
async function fetchWithTimeout(url, options, timeout = 30000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
// Usage
try {
const response = await fetchWithTimeout(
'https://api.loyalteez.app/loyalteez-api/manual-event',
{
method: 'POST',
body: JSON.stringify(data)
},
10000 // 10 second timeout
);
} catch (error) {
if (error.message === 'Request timeout') {
console.error('Request took too long, retry later');
// Queue for retry
}
}
Error Handling Best Practices
1. Comprehensive Try-Catch
async function robustAPICall(endpoint, data) {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
// Check HTTP status
if (!response.ok) {
const error = await response.json();
// Handle specific status codes
switch (response.status) {
case 400:
console.error('Invalid data:', error.details);
break;
case 403:
console.error('Access denied:', error.message);
break;
case 429:
console.warn('Rate limited, retry later');
break;
case 500:
console.error('Server error, will retry');
throw new Error('Retriable error');
default:
console.error('Unknown error:', error);
}
return { success: false, error };
}
return { success: true, data: await response.json() };
} catch (error) {
// Network errors, timeouts, etc.
console.error('Request failed:', error.message);
return { success: false, error: error.message };
}
}
2. Retry with Exponential Backoff
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.min(1000 * Math.pow(2, i), 10000); // Max 10s
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
await retryWithBackoff(async () => {
const response = await fetch(endpoint, options);
if (!response.ok) throw new Error('Request failed');
return response.json();
});
3. Error Logging & Monitoring
function logAPIError(endpoint, error, context) {
const errorLog = {
timestamp: new Date().toISOString(),
endpoint,
error: {
message: error.message,
status: error.status,
details: error.details
},
context,
userId: context.userId,
brandId: context.brandId
};
// Log to your monitoring service
console.error('[API Error]', JSON.stringify(errorLog));
// Send to error tracking (Sentry, etc.)
if (window.Sentry) {
window.Sentry.captureException(error, {
extra: errorLog
});
}
}
4. User-Friendly Error Messages
function getUserFriendlyMessage(error) {
const messages = {
400: 'Please check your input and try again.',
401: 'Please log in again to continue.',
403: 'You don\'t have permission to do this.',
429: 'You\'re doing that too often. Please wait a moment.',
500: 'Something went wrong on our end. We\'re looking into it.',
503: 'Service is temporarily unavailable. Please try again shortly.'
};
return messages[error.status] || 'An unexpected error occurred.';
}
// Usage
try {
await trackEvent(data);
} catch (error) {
const userMessage = getUserFriendlyMessage(error);
showNotification(userMessage, 'error');
}
Quick Reference
Common Issues Checklist
- Invalid event data? → Check all required fields present
- Rate limited? → Implement deduplication logic
- Automation disabled? → Enable in Partner Portal settings
- CORS error? → Verify using HTTPS and correct headers
- 401 Unauthorized? → Refresh Privy token
- Contract not whitelisted? → Use only whitelisted contracts
- 500 error? → Implement retry logic with backoff
Support
Still Having Issues?
- Check Status: status.loyalteez.app
- Search Docs: docs.loyalteez.app
- Ask Community: Discord
- Contact Support: [email protected]
Include in Support Request:
- Error message (full JSON)
- Request timestamp
- Endpoint called
- Request body (remove sensitive data)
- Your Brand ID
Related Documentation
- REST API Reference - Complete API documentation
- Event Handler API - Event tracking guide
- Gas Relayer API - Gasless transactions
- Pregeneration API - OAuth wallet creation