Skip to main content

Troubleshooting

Overview

Quick reference for diagnosing and resolving common Q01 Core API issues. Organized by error category with symptoms, causes, and solutions.

Error Categories:

  1. Authentication Errors
  2. Authorization Errors
  3. Field Visibility Errors
  4. Performance Issues
  5. Data Inconsistencies
  6. OUTBOX Event Issues
  7. Connection Issues

Authentication Errors

Error: 401 Unauthorized

Symptoms:

{
"error": "Unauthorized",
"message": "Invalid or expired token"
}

Common Causes:

  1. Expired access token
  2. Invalid token format
  3. Token not included in request
  4. 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:

  1. Check token format: console.log(accessToken)
  2. Decode token: jwt.decode(accessToken)
  3. Verify expiration: decoded.exp * 1000 > Date.now()
  4. 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:

  1. Missing grant in TB_MENU
  2. Wrong peso level
  3. 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:

  1. Fetch /api/v4/core/menu to see all grants
  2. Check MENU_CODE matches dimension
  3. Verify GRANTS contains required letter (R/C/U/D)
  4. 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:

  1. Check TB_VAR for field configuration
  2. Verify COD_ON_OFF contains required flag
  3. Test with explicit fields parameter

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:

  1. Missing database indexes
  2. Fetching too many fields
  3. Large result sets without pagination
  4. 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:

  1. Enable slow query log
  2. Check database indexes
  3. Profile API response times
  4. 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:

  1. Concurrent inserts without locking
  2. Retry logic creating duplicates
  3. 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:

  1. Subscriber not running
  2. RabbitMQ connection issue
  3. 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:

  1. Network issues
  2. API server down
  3. 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

CodeMeaningCommon Cause
200OKSuccess
201CreatedPOST success
400Bad RequestInvalid input
401UnauthorizedMissing/invalid token
403ForbiddenInsufficient permissions
404Not FoundRecord doesn't exist
409ConflictDuplicate key
422Validation ErrorField validation failed
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error
503Service UnavailableServer 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:

  1. Check error message and HTTP status
  2. Verify authentication (token valid)
  3. Verify authorization (grants present)
  4. Check context headers
  5. Validate field metadata
  6. Profile performance
  7. Check logs