Skip to main content

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:

  1. Obtain API credentials:

    • Request access from platform administrator
    • Receive base URL and initial credentials
  2. Authenticate:

POST /auth/login
{
"username": "user@example.com",
"password": "..."
}

# Response includes access_token
  1. Make first API call:
GET /api/v4/core/PRD?$num_rows=10
Authorization: Bearer {access_token}
  1. Query metadata:
GET /api/v4/core/PRD/fields
# Discover available fields and their COD_ON_OFF flags
  1. 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:

  1. Missing Authorization header:
// ❌ Missing
fetch('/api/v4/core/PRD');

// ✅ Correct
fetch('/api/v4/core/PRD', {
headers: { 'Authorization': `Bearer ${token}` }
});
  1. Expired token:
// Check expiration
const payload = JSON.parse(atob(token.split('.')[1]));
if (payload.exp < Date.now() / 1000) {
// Refresh token
token = await refreshToken();
}
  1. 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:

  1. Missing grant:
// Check user grants
if (!user.grants.includes('products.write')) {
// Hide create/edit UI
}
  1. Source/centro_dett mismatch:
// Verify context
console.log(session.source); // Must match record
console.log(session.centro_dett); // Must match record
  1. 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:

  1. RabbitMQ queue backlog:
# Check queue depth
rabbitmqctl list_queues

# Scale subscribers if deep queue
kubectl scale deployment search-indexer --replicas=3
  1. 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
}
});
  1. 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)