Troubleshooting
Overview
Quick reference for diagnosing and resolving common Q01 Core API issues. Organized by error category with symptoms, causes, and solutions.
Error Categories:
- Authentication Errors
- Authorization Errors
- Field Visibility Errors
- Performance Issues
- Data Inconsistencies
- OUTBOX Event Issues
- Connection Issues
Authentication Errors
Error: 401 Unauthorized
Symptoms:
{
"error": "Unauthorized",
"message": "Invalid or expired token"
}
Common Causes:
- Expired access token
- Invalid token format
- Token not included in request
- Token revoked
Solutions:
// ✅ Check token expiration
const jwt = require('jsonwebtoken');
function isTokenExpired(token) {
try {
const decoded = jwt.decode(token);
if (!decoded || !decoded.exp) return true;
return Date.now() >= decoded.exp * 1000;
} catch (error) {
return true;
}
}
// ✅ Refresh token if expired
if (isTokenExpired(accessToken)) {
const newToken = await refreshAccessToken(refreshToken);
accessToken = newToken;
}
// ✅ Include token in header
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}` // Note: "Bearer" prefix required
}
});
Debug Steps:
- Check token format:
console.log(accessToken) - Decode token:
jwt.decode(accessToken) - Verify expiration:
decoded.exp * 1000 > Date.now() - Test with fresh token from login
Error: Token Refresh Failed
Symptoms:
{
"error": "Invalid refresh token"
}
Causes:
- Refresh token expired (> 7 days)
- Refresh token revoked
- User logged out
Solution:
async function handleTokenRefresh() {
try {
const response = await fetch('/api/v4/auth/refresh', {
method: 'POST',
body: JSON.stringify({refreshToken})
});
if (response.status === 401) {
// Refresh token expired - redirect to login
window.location.href = '/login';
return;
}
const {accessToken} = await response.json();
return accessToken;
} catch (error) {
// Network error - retry once
await sleep(1000);
return handleTokenRefresh();
}
}
Authorization Errors
Error: 403 Forbidden
Symptoms:
{
"error": "Forbidden",
"message": "Insufficient permissions"
}
Causes:
- Missing grant in TB_MENU
- Wrong peso level
- Wrong centro_dett context
Solutions:
// ✅ Check grants
async function checkGrant(dimension, operation) {
const grants = await fetch('/api/v4/core/menu').then(r => r.json());
const menu = grants.data.find(m => m.MENU_CODE === dimension);
if (!menu) {
console.error(`No menu entry for ${dimension}`);
return false;
}
const grantMap = {read: 'R', create: 'C', update: 'U', delete: 'D'};
const required = grantMap[operation];
if (!menu.GRANTS.includes(required)) {
console.error(`Missing ${operation} grant for ${dimension}`);
console.log('Available grants:', menu.GRANTS);
return false;
}
return true;
}
// Usage
if (!(await checkGrant('products', 'delete'))) {
alert('You do not have permission to delete products');
return;
}
Debug Steps:
- Fetch
/api/v4/core/menuto see all grants - Check
MENU_CODEmatches dimension - Verify
GRANTScontains required letter (R/C/U/D) - Check nested set hierarchy (NLEFT/NRIGHT)
Error: Record Not Found (403 vs 404)
Symptoms:
- Record exists but returns 403 (not 404)
Cause:
- Context filtering (source, peso, ambiente, centro_dett)
Solution:
// ✅ Verify context matches record
const headers = {
'X-Source': 'tenant_123', // Must match record's source
'X-Peso': '3', // Must allow access
'X-Ambiente': 'P', // Must match record's ambiente
'X-Centro-Dett': 'HQ' // Must match record's centro_dett
};
// Debug: Fetch without filters to see actual values
const rawRecord = await db.query(
'SELECT source, ambiente, centro_dett FROM TB_ANAG_PRD00 WHERE PRD_ID = ?',
[recordId]
);
console.log('Record context:', rawRecord);
console.log('Request context:', headers);
Field Visibility Errors
Error: Field Not Returned in Response
Symptoms:
- Field exists in database but not in API response
Cause:
- COD_ON_OFF flag not set for operation (L/D/N/M/R)
Solution:
// ✅ Check field metadata
const fields = await fetch('/api/v4/core/products/fields').then(r => r.json());
const field = fields.data.find(f => f.VAR === 'XPRD07');
console.log('COD_ON_OFF:', field.COD_ON_OFF);
// Example: "LDN" means visible in List, Detail, New (not Modify or seaRch)
// ✅ Request specific fields
const products = await fetch('/api/v4/core/products', {
params: {
fields: 'PRD_ID,XPRD01,XPRD02,XPRD07' // Explicit field list
}
});
COD_ON_OFF Flag Reference:
L- List (GET /dimension)D- Detail (GET /dimension/:id)N- New (POST /dimension)M- Modify (PUT/PATCH /dimension/:id)R- seaRch (filters)
Debug Steps:
- Check TB_VAR for field configuration
- Verify COD_ON_OFF contains required flag
- Test with explicit
fieldsparameter
Error: Field Validation Failed
Symptoms:
{
"error": "Validation failed",
"field": "XPRD02",
"message": "Field is required"
}
Causes:
- Missing required field (REQUIRED = 'Y' in TB_VAR)
- Wrong data type
- Value out of range
Solution:
// ✅ Validate before sending
function validateProduct(product, fieldMeta) {
const errors = [];
for (const field of fieldMeta) {
// Check required
if (field.REQUIRED === 'Y' && !product[field.VAR]) {
errors.push(`${field.VAR} is required`);
}
// Check type
if (product[field.VAR]) {
if (field.TYPE === 'numeric' && typeof product[field.VAR] !== 'number') {
errors.push(`${field.VAR} must be a number`);
}
if (field.TYPE === 'string' && typeof product[field.VAR] !== 'string') {
errors.push(`${field.VAR} must be a string`);
}
}
}
return errors;
}
// Usage
const fieldMeta = await fetch('/api/v4/core/products/fields').then(r => r.json());
const errors = validateProduct(productData, fieldMeta.data);
if (errors.length > 0) {
console.error('Validation errors:', errors);
return;
}
Performance Issues
Issue: Slow Response Times
Symptoms:
- API responses taking > 1 second
- Timeouts
Causes:
- Missing database indexes
- Fetching too many fields
- Large result sets without pagination
- N+1 query problem
Solutions:
-- ✅ Add indexes for filtered fields
CREATE INDEX idx_prd_category ON TB_ANAG_PRD00(XPRD05);
CREATE INDEX idx_prd_active ON TB_ANAG_PRD00(XPRD06);
CREATE INDEX idx_prd_cat_active ON TB_ANAG_PRD00(XPRD05, XPRD06);
-- ✅ Check query execution
EXPLAIN SELECT * FROM TB_ANAG_PRD00 WHERE XPRD05 = 5;
// ✅ Fetch only required fields
const products = await fetch('/api/v4/core/products', {
params: {
fields: 'PRD_ID,XPRD01,XPRD02', // Only 3 fields instead of 50
limit: 50 // Reasonable limit
}
});
// ✅ Use pagination
async function* fetchAllProducts() {
let offset = 0;
const limit = 100;
while (true) {
const batch = await fetch('/api/v4/core/products', {
params: {limit, offset}
});
if (batch.data.length === 0) break;
yield batch.data;
offset += limit;
}
}
// ✅ Use expansion instead of N+1 queries
const products = await fetch('/api/v4/core/products', {
params: {
limit: 100,
expand: 'XPRD05' // Expand category in single query
}
});
Debug Steps:
- Enable slow query log
- Check database indexes
- Profile API response times
- Monitor network waterfall
Issue: High Memory Usage
Cause:
- Fetching too many records at once
Solution:
// ❌ Bad - Loads all 100k products in memory
const allProducts = await fetch('/api/v4/core/products?limit=100000');
// ✅ Good - Stream in batches
async function processAllProducts() {
for await (const batch of fetchAllProducts()) {
processBatch(batch); // Process 100 at a time
}
}
Data Inconsistencies
Issue: Stale Cache Data
Symptoms:
- Updates not reflected in UI
- Old data returned
Cause:
- Cache not invalidated on update
Solution:
class CachedAPIClient {
async update(dimension, id, data) {
// Update via API
const result = await api.patch(`/api/v4/core/${dimension}/${id}`, data);
// Invalidate cache
this.cache.delete(`${dimension}:${id}`);
this.cache.delete(`${dimension}:list`);
return result;
}
}
// ✅ Subscribe to OUTBOX events for invalidation
websocket.on('ProductUpdated', (event) => {
cache.delete(`product:${event.data.PRD_ID}`);
cache.delete('products:list');
});
Issue: Duplicate Records
Symptoms:
- Same record appears multiple times
Causes:
- Concurrent inserts without locking
- Retry logic creating duplicates
- No unique constraints
Solution:
// ✅ Use idempotency key
async function createProduct(product, idempotencyKey) {
// Check if already processed
const existing = await redis.get(`idempotency:${idempotencyKey}`);
if (existing) {
return JSON.parse(existing);
}
// Create product
const result = await api.post('/api/v4/core/products', product);
// Store result with idempotency key (24h TTL)
await redis.set(
`idempotency:${idempotencyKey}`,
JSON.stringify(result),
'EX',
86400
);
return result;
}
// Usage with UUID
const idempotencyKey = uuidv4();
await createProduct(productData, idempotencyKey);
OUTBOX Event Issues
Issue: Events Not Processed
Symptoms:
- Search index not updated
- Cache not invalidated
- Webhooks not sent
Causes:
- Subscriber not running
- RabbitMQ connection issue
- Event processing error
Debug Steps:
# Check RabbitMQ queue
rabbitmqctl list_queues
# Check subscriber logs
docker logs search-indexer
# Check OUTBOX table
SELECT * FROM OUTBOX
WHERE PUBLISHED = 'N'
ORDER BY CREATED_AT DESC
LIMIT 10;
Solution:
// ✅ Monitor subscriber health
class HealthySubscriber extends OutboxSubscriber {
async start() {
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: this.isConnected ? 'healthy' : 'unhealthy',
queue: this.queueName,
lastMessage: this.lastMessageTime
});
});
// Reconnect on failure
this.connection.on('error', async (error) => {
console.error('Connection error:', error);
await this.reconnect();
});
}
async reconnect() {
await sleep(5000);
await this.connect();
}
}
Connection Issues
Error: Connection Timeout
Symptoms:
Error: ETIMEDOUT
Error: ECONNRESET
Causes:
- Network issues
- API server down
- Firewall blocking
Solution:
// ✅ Implement retry with exponential backoff
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetch(url, {
...options,
timeout: 10000 // 10 second timeout
});
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000;
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms...`);
await sleep(delay);
}
}
}
Diagnostic Tools
API Response Inspector
// Log all API interactions
api.interceptors.request.use(config => {
console.log('[Request]', config.method, config.url);
console.log('[Headers]', config.headers);
console.log('[Body]', config.data);
config.metadata = {startTime: Date.now()};
return config;
});
api.interceptors.response.use(
response => {
const duration = Date.now() - response.config.metadata.startTime;
console.log('[Response]', response.status, `${duration}ms`);
console.log('[Data]', response.data);
return response;
},
error => {
console.error('[Error]', error.response?.status, error.message);
console.error('[Response]', error.response?.data);
throw error;
}
);
Database Query Profiler
-- Enable slow query log
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 0.5;
-- Check slow queries
SELECT * FROM mysql.slow_log
ORDER BY start_time DESC
LIMIT 10;
-- Analyze query
EXPLAIN SELECT * FROM TB_ANAG_PRD00 WHERE XPRD05 = 5;
Quick Reference
HTTP Status Codes
| Code | Meaning | Common Cause |
|---|---|---|
| 200 | OK | Success |
| 201 | Created | POST success |
| 400 | Bad Request | Invalid input |
| 401 | Unauthorized | Missing/invalid token |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Record doesn't exist |
| 409 | Conflict | Duplicate key |
| 422 | Validation Error | Field validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error |
| 503 | Service Unavailable | Server down |
Debug Checklist
Authentication Issues:
- Token included in Authorization header
- Token not expired
- "Bearer" prefix present
- Token format valid (JWT)
Authorization Issues:
- Grant exists in TB_MENU
- Context headers correct (source, peso, ambiente, centro_dett)
- Peso level sufficient
- Centro_dett matches record
Field Visibility:
- COD_ON_OFF flag set for operation
- Field requested explicitly
- Field not hidden by peso
Performance:
- Indexes created
- Pagination used
- Only required fields fetched
- Caching implemented
Summary
Common Issue Resolution Times:
- Authentication: < 5 minutes
- Authorization: 10-30 minutes (TB_MENU verification)
- Field visibility: 5-10 minutes (COD_ON_OFF check)
- Performance: 1-2 hours (indexing, optimization)
- Data inconsistency: 30-60 minutes (debugging)
Key Troubleshooting Steps:
- Check error message and HTTP status
- Verify authentication (token valid)
- Verify authorization (grants present)
- Check context headers
- Validate field metadata
- Profile performance
- Check logs
Related Concepts
- Security Tips - Security best practices
- Performance Tips - Optimization
- Anti-Patterns - Common mistakes
- Best Practices - Overview