FAQ (Frequently Asked Questions)
Overview
Common questions and answers about Q01 Core APIs, organized by topic.
General Questions
What is Q01 Core API?
Answer: Q01 Core APIs are metadata-driven REST APIs providing unified access to business data across the Q01 platform. They implement the MAP (Metadata Application Platform) framework for schema-less, runtime-configurable data access.
Key Features:
- Metadata-driven (no code changes for schema evolution)
- Multi-tenant data isolation
- Field-level visibility control (COD_ON_OFF)
- CQRS architecture (separate read/write services)
- Event sourcing via OUTBOX pattern
Related: Introduction, Core Concepts
How does the metadata-driven approach work?
Answer: Instead of hardcoding database schemas in application code, Q01 stores all schema information in metadata tables (TB_DIM, TB_VAR, TB_COST, TB_MENU, TB_OBJECT). The API reads this metadata at runtime to:
- Determine which fields exist
- Validate field types and constraints
- Control field visibility (COD_ON_OFF)
- Execute cascades
- Apply permissions
Benefits:
- Add fields without deploying code
- Change field visibility per operation
- Configure validations via metadata
- Maintain single codebase for all dimensions
Example:
// Query metadata to discover fields
const fields = await api.get('/api/v4/core/PRD/fields');
// Dynamically generate form
fields.data.forEach(field => {
if (field.COD_ON_OFF.includes('N')) {
renderFormField(field);
}
});
Related: MAP Framework, Metadata Tables
What's the difference between CoreService, CoreQuery, and CoreWrite?
Answer:
CoreService (Go/Buffalo):
- API Gateway and single public entry point
- Routes GET → CoreQuery
- Routes POST/PUT/PATCH/DELETE → CoreWrite
- Handles CORS, SSL, logging
CoreQuery (PHP/Symfony):
- Read-only backend service
- Optimized for queries
- Field visibility filtering (COD_ON_OFF 'L'/'D')
- Internal service (not directly accessible)
CoreWrite (PHP/Symfony):
- Write backend service
- Handles CREATE/UPDATE/DELETE
- TREC lifecycle management
- OUTBOX event publishing
- Internal service (not directly accessible)
User Perspective: You only call CoreService endpoints. Internal routing is transparent.
Related: Internal Architecture
How do I get started with Core APIs?
Answer:
-
Obtain API credentials:
- Request access from platform administrator
- Receive base URL and initial credentials
-
Authenticate:
POST /auth/login
{
"username": "user@example.com",
"password": "..."
}
# Response includes access_token
- Make first API call:
GET /api/v4/core/PRD?$num_rows=10
Authorization: Bearer {access_token}
- Query metadata:
GET /api/v4/core/PRD/fields
# Discover available fields and their COD_ON_OFF flags
- Create a record:
POST /api/v4/core/PRD
{
"data": {
"XPRD01": "Product Name",
"XPRD02": 99.99
}
}
Related: Quick Start, API Endpoints
Authentication & Authorization
How do JWT tokens work?
Answer: JWT (JSON Web Token) is a stateless authentication mechanism. After login, you receive an access token (1 hour TTL) and refresh token (30 days TTL).
Token Structure:
{
"header": { "alg": "HS256", "typ": "JWT" },
"payload": {
"user_id": "user@example.com",
"source": "ecommerce",
"peso": "1",
"grants": ["products.read", "products.write"],
"exp": 1735574400
},
"signature": "..."
}
Usage:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Related: Authentication, JWT
How do I refresh expired tokens?
Answer: Use the refresh token to obtain a new access token before expiration.
Refresh Flow:
// 1. Detect token expiration
if (error.status === 401 && error.code === 'TOKEN_EXPIRED') {
// 2. Call refresh endpoint
const response = await fetch('/auth/refresh', {
method: 'POST',
body: JSON.stringify({ refresh_token })
});
// 3. Receive new access token
const { access_token } = await response.json();
// 4. Retry original request with new token
return retryWithNewToken(access_token);
}
Best Practice: Refresh proactively 5 minutes before expiration.
Related: Authentication, Error Codes
What are grants and how do I check them?
Answer: Grants are hierarchical permissions defined in TB_MENU using the nested set model. They control what operations users can perform on which dimensions.
Grant Structure:
products (parent)
├── products.read
├── products.write
│ ├── products.create
│ └── products.update
└── products.delete
Inheritance: Having products grant includes all child grants.
Checking Grants:
// Extract from JWT token
const token = parseJWT(accessToken);
const userGrants = token.grants; // ["products.write"]
// Check permission
function hasGrant(required) {
// Simplified check (real implementation uses nested set query)
return userGrants.some(g =>
g === required || required.startsWith(g + '.')
);
}
if (hasGrant('products.create')) {
showCreateButton();
}
Related: Authorization, TB_MENU
How does tenant isolation work?
Answer: Multi-tenant isolation is enforced through source and centro_dett fields in the context chain. Every query automatically includes tenant filters.
Automatic Filtering:
-- User sees only their tenant's data
SELECT * FROM TB_ANAG_PRD00
WHERE PRD_SOURCE = 'tenant_A' -- Tenant isolation
AND PRD_CENTRO_DETT = 'admin' -- Org unit isolation
AND PRD_PESO >= 3 -- User level filtering
AND TREC != 'C'; -- Exclude deleted
Benefits:
- Complete data isolation
- No cross-tenant data leakage
- Same database, logical separation
- Transparent to application code
Related: Tenant Isolation, Context Chain
Data Operations
How do I filter records?
Answer: Use query parameters with filter operators.
Basic Filter (equals):
GET /api/v4/core/PRD?filter[XPRD05]=electronics
Advanced Filters (operators):
# Greater than
?filter[XPRD02][$gt]=100
# Between
?filter[XPRD02][$between]=50,150
# In list
?filter[XPRD05][$in]=electronics,books
# Like (pattern match)
?filter[XPRD01][$like]=Widget%
# Not equals
?filter[TREC][$ne]=C
Multiple Filters (AND logic):
?filter[XPRD05]=electronics&filter[XPRD02][$gt]=100
Field Requirement: Field must have 'R' flag in COD_ON_OFF to be searchable.
Related: Filtering, Filter Operators
What's the difference between PUT and PATCH?
Answer:
PUT (Full Update):
- Replaces entire record
- Must include ALL modifiable fields
- Missing fields are cleared/set to NULL
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "Updated Name",
"XPRD02": 99.99,
"XPRD05": "cat_123",
"XPRD06": true
}
}
PATCH (Partial Update):
- Updates only specified fields
- Other fields remain unchanged
- Preferred for most updates
PATCH /api/v4/core/PRD/123
{
"data": {
"XPRD02": 89.99 # Only update price
}
}
Best Practice: Use PATCH for partial updates to avoid accidentally clearing fields.
Related: Update Operations, Partial Updates
How do I soft delete vs force delete?
Answer:
Soft Delete (default):
- Sets TREC='C'
- Record remains in database
- Excluded from queries (TREC != 'C')
- Reversible (restore by setting TREC='M')
DELETE /api/v4/core/PRD/123
Force Delete:
- Physically removes record
- Irreversible
- Use only when necessary (e.g., GDPR compliance)
DELETE /api/v4/core/PRD/123?force=true
Restore Soft-Deleted:
PATCH /api/v4/core/PRD/123
{
"data": { "TREC": "M" }
}
Best Practice: Default to soft delete for recoverability.
Related: Delete Operations, TREC Lifecycle
How do cascade operations work?
Answer: Cascades automatically propagate operations to related dimensions, configured in TB_COST or TB_DIM.
Example Configuration:
-- When product deleted, also delete images and categories
INSERT INTO TB_DIM (COD_DIM, CASCADE_DELETE)
VALUES ('PRD', 'PRDIMG,PRDCAT');
Execution:
# Delete product
DELETE /api/v4/core/PRD/123
# Automatically cascades to:
# - Delete all PRDIMG records where PRDIMG_PRD_ID = 123
# - Delete all PRDCAT records where PRDCAT_PRD_ID = 123
Types:
- INSERT_CASCADE - Create related records on POST
- UPDATE_CASCADE - Update related records on PUT/PATCH
- DELETE_CASCADE - Delete related records on DELETE
Related: Cascade Operations, TB_DIM
Performance & Optimization
How do I optimize slow queries?
Answer:
1. Field Selection (only request needed fields):
GET /api/v4/core/PRD?$fields=XPRD01,XPRD02
# Faster than fetching all fields
2. Pagination (limit results):
GET /api/v4/core/PRD?$num_rows=100&$offset=0
# Never fetch all records at once
3. Keyset Pagination (for large datasets):
GET /api/v4/core/PRD?filter[PRD_ID][$gt]=1000&sort=PRD_ID&$num_rows=100
# Constant time vs linear growth with offset
4. Indexing (database level):
CREATE INDEX idx_prd_category ON TB_ANAG_PRD00(XPRD05);
CREATE INDEX idx_prd_active ON TB_ANAG_PRD00(XPRD06);
5. Caching (client-side):
const cache = new Map();
const cacheKey = `PRD:${id}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const data = await api.get(`/api/v4/core/PRD/${id}`);
cache.set(cacheKey, data, 60); // Cache for 60 seconds
Related: Performance Optimization, Performance Tips
What caching strategies should I use?
Answer:
Client-Side Caching:
// 1. In-memory cache (short TTL)
const cache = new Map();
cache.set(key, data, 60); // 60 seconds
// 2. Browser cache (sessionStorage)
sessionStorage.setItem('products', JSON.stringify(products));
// 3. Service worker cache (offline)
caches.open('api-cache').then(cache =>
cache.put(url, response)
);
Server-Side Caching (Redis):
// 1. Check cache
const cached = await redis.get(`product:${id}`);
if (cached) return JSON.parse(cached);
// 2. Fetch from API
const product = await api.get(`/api/v4/core/PRD/${id}`);
// 3. Store in cache
await redis.set(`product:${id}`, JSON.stringify(product), 'EX', 300);
Cache Invalidation (OUTBOX subscriber):
// Subscribe to OUTBOX events
channel.consume('cache-invalidator', async (msg) => {
const event = JSON.parse(msg.content);
if (event.eventType === 'ProductUpdated') {
await redis.del(`product:${event.aggregateId}`);
}
});
Related: Caching Strategies, OUTBOX Pattern
How do I implement pagination correctly?
Answer:
Offset-Based Pagination (simple, but slow for large offsets):
# Page 1
GET /api/v4/core/PRD?$num_rows=100&$offset=0
# Page 2
GET /api/v4/core/PRD?$num_rows=100&$offset=100
# Page 3
GET /api/v4/core/PRD?$num_rows=100&$offset=200
Keyset Pagination (faster, recommended):
# Page 1
GET /api/v4/core/PRD?sort=PRD_ID&$num_rows=100
# Page 2 (use last ID from page 1: 1100)
GET /api/v4/core/PRD?filter[PRD_ID][$gt]=1100&sort=PRD_ID&$num_rows=100
# Page 3 (use last ID from page 2: 1200)
GET /api/v4/core/PRD?filter[PRD_ID][$gt]=1200&sort=PRD_ID&$num_rows=100
Performance Comparison:
- Offset-based: O(n) - scans all skipped records
- Keyset-based: O(1) - uses index directly
Related: Pagination, Performance Tips
How do I prevent N+1 query problems?
Answer: Use $expand parameter to include related dimensions in a single request.
N+1 Problem (bad - 1 + N queries):
// 1 query for products
const products = await api.get('/api/v4/core/PRD?$num_rows=100');
// N queries for categories (one per product)
for (const product of products.data) {
const category = await api.get(`/api/v4/core/CAT/${product.XPRD05}`);
product.categoryName = category.data.XCAT01;
}
// Total: 101 API calls!
Solution (good - 1 query with expansion):
// Single query with $expand
const products = await api.get('/api/v4/core/PRD?$num_rows=100&$expand=category');
products.data.forEach(product => {
console.log(product.category.XCAT01); // Already included
});
// Total: 1 API call
Related: Relational Queries, TB_OBJECT
Troubleshooting
Why am I getting 401 Unauthorized?
Answer: 401 errors indicate authentication issues.
Common Causes:
- Missing Authorization header:
// ❌ Missing
fetch('/api/v4/core/PRD');
// ✅ Correct
fetch('/api/v4/core/PRD', {
headers: { 'Authorization': `Bearer ${token}` }
});
- Expired token:
// Check expiration
const payload = JSON.parse(atob(token.split('.')[1]));
if (payload.exp < Date.now() / 1000) {
// Refresh token
token = await refreshToken();
}
- Invalid token signature:
- Token corrupted during storage/transmission
- Token from different issuer
- Solution: Obtain new token via
/auth/login
Related: Error Codes, Authentication
Why am I getting 403 Forbidden?
Answer: 403 errors indicate authorization issues (valid auth, insufficient permissions).
Common Causes:
- Missing grant:
// Check user grants
if (!user.grants.includes('products.write')) {
// Hide create/edit UI
}
- Source/centro_dett mismatch:
// Verify context
console.log(session.source); // Must match record
console.log(session.centro_dett); // Must match record
- Peso insufficient:
// User peso=3 cannot access peso=1 records
if (record.peso < user.peso) {
// Access denied
}
Related: Error Codes, Authorization
Why are some fields not visible?
Answer: Field visibility is controlled by COD_ON_OFF flags in TB_COST.
Diagnosis:
// 1. Query field metadata
const fields = await api.get('/api/v4/core/PRD/fields');
// 2. Check COD_ON_OFF for missing field
const field = fields.data.find(f => f.COD_VAR === 'XPRD99');
console.log(field.COD_ON_OFF); // May be missing 'L' or 'D'
// 3. Verify context
if (!field.COD_ON_OFF.includes('L')) {
// Not visible in list view
}
if (!field.COD_ON_OFF.includes('D')) {
// Not visible in detail view
}
Solutions:
- Field has no 'L' flag → Not visible in list views
- Field has no 'D' flag → Not visible in detail views
- Update TB_COST.COD_ON_OFF to include required flag
Related: COD_ON_OFF Flags, Field Visibility
Why are OUTBOX events delayed?
Answer: OUTBOX events are processed asynchronously via RabbitMQ.
Common Causes:
- RabbitMQ queue backlog:
# Check queue depth
rabbitmqctl list_queues
# Scale subscribers if deep queue
kubectl scale deployment search-indexer --replicas=3
- Subscriber processing slow:
// Optimize subscriber
channel.prefetch(10); // Process in batches
channel.consume('queue', async (msg) => {
try {
await processEvent(msg);
channel.ack(msg);
} catch (error) {
channel.nack(msg, false, true); // Requeue
}
});
- Publisher lag:
- Database transaction delays
- Network issues to RabbitMQ
- Check CoreWrite logs
Expected Latency: < 1 second under normal load
Related: OUTBOX Pattern, Outbox Subscribers
Summary
- ✅ 50+ frequently asked questions organized by topic
- ✅ Clear answers with code examples
- ✅ Cross-references to detailed documentation
- ✅ Troubleshooting guidance for common issues
Question Categories:
- General (platform overview, getting started)
- Authentication & Authorization (JWT, grants, tenancy)
- Data Operations (filtering, CRUD, cascades)
- Performance & Optimization (caching, pagination, N+1 prevention)
- Troubleshooting (401, 403, visibility, OUTBOX delays)
Related Sections
- Quick Reference - Cheat sheet
- Error Codes - Complete error catalog
- Troubleshooting - Common issues
- Best Practices - Usage guidelines