Complete Guide to Querying Records in Centrali¶
This guide covers all the ways to query records in Centrali with comprehensive examples.
Table of Contents¶
- Quick Start
- HTTP API (REST)
- Compute Functions (SDK)
- Aggregations
- Filter Reference
- Common Patterns
- Best Practices
- Troubleshooting
Quick Start¶
Which method should I use?¶
- Building a frontend? → Use HTTP API
- Writing automation/workflows? → Use Compute Functions (SDK)
- Need analytics/reports? → Use Aggregations
There are three ways to query records in Centrali:
- REST API — Send HTTP requests with filter query parameters
- SDK — Use
centrali.queryRecords()from the JavaScript/TypeScript SDK - Compute Functions — Use the
api.queryRecords()method inside serverless functions
All three support the same filter operators, sorting, and pagination. The examples below show both the REST API and SDK approaches.
Basic Example¶
Filter Syntax
HTTP API: Pass filters as top-level query parameters with bracket notation for operators. Use the data. prefix for your custom fields.
Compute Functions / SDK: Use the filter object with operator syntax, or pass filters at the top level with bracket notation. Both approaches work.
HTTP API:
Compute Function / SDK:
// Using filter object (recommended for compute functions)
const customers = await api.queryRecords('customers', {
filter: {
'data.status': 'active'
},
page: 1,
pageSize: 20
});
// Alternative: top-level bracket notation (also works)
const customers = await api.queryRecords('customers', {
'data.status': 'active',
page: 1,
pageSize: 20
});
HTTP API (REST)¶
Base Endpoint¶
Authentication¶
All requests require a JWT token in the Authorization header:
Query Parameters¶
| Parameter | Type | Description | Example |
|---|---|---|---|
search | string | Search term to match | ?search=john |
searchFields | string | Comma-separated fields to search (default: uses structure's defaultSearchField) | ?searchFields=name,email |
page | number | Page number (1-indexed) | ?page=2 |
pageSize | number | Records per page (1-500, default: 50) | ?pageSize=25 |
sort | string | Sort fields (prefix - for descending) | ?sort=-createdAt,name |
fields | string | Comma-separated fields to return | ?fields=id,name,email |
all | boolean | Include soft-deleted records | ?all=true |
{field} | any | Filter by field (exact match) | ?status=active |
{field}[op] | any | Filter with operator | ?age[gte]=18 |
Basic Query Examples¶
1. Get All Records¶
Response:
{
"data": [
{
"id": "rec-123",
"recordSlug": "customers",
"data": {
"name": "John Doe",
"email": "john@example.com",
"age": 30
},
"status": "active",
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
],
"meta": {
"total": 150,
"page": 1,
"pageSize": 50
}
}
2. Search by Name¶
Searches for "john" in both name and email fields (case-insensitive).
3. Filter by Status¶
Returns only customers where status equals "active".
4. Paginate Results¶
Returns records 26-50 (page 2 with 25 records per page).
5. Sort Results¶
# Sort by createdAt descending
GET /workspace/acme/api/v1/records/customers?sort=-createdAt
# Multi-sort: status ascending, then createdAt descending
GET /workspace/acme/api/v1/records/customers?sort=status,-createdAt
6. Select Specific Fields¶
Returns only the specified fields (reduces response size).
Advanced Filtering¶
Filter Operators¶
Use bracket notation field[operator] or dot notation field.operator:
# Age greater than or equal to 18
GET /workspace/acme/api/v1/records/customers?age[gte]=18
# Or with dot notation
GET /workspace/acme/api/v1/records/customers?age.gte=18
Available operators:
eq- Equal (default)ne- Not equalgt- Greater thangte- Greater than or equallt- Less thanlte- Less than or equalin- In list (comma-separated)nin- Not in listcontains- Contains substring (case-insensitive)startswith- Starts with (case-insensitive)endswith- Ends with (case-insensitive)hasAny- Array has any of (for array fields)hasAll- Array has all of (for array fields)eqAny- Equals any of the given valueseqNone- Equals none of the given values
Complex Filter Examples¶
Age between 18 and 65:
Status is active OR pending:
Email contains "@gmail.com":
Name starts with "John":
Tags has any of ["vip", "enterprise"]:
Created after January 1, 2024:
Combining Everything¶
GET /workspace/acme/api/v1/records/customers?\
search=john&\
searchFields=name,email&\
status[in]=active,pending&\
age[gte]=18&\
age[lte]=65&\
createdAt[gte]=2024-01-01&\
sort=-createdAt&\
page=1&\
pageSize=25&\
fields=id,name,email,age,status
This query: - Searches for "john" in name and email - Filters for active or pending status - Filters for age between 18-65 - Filters for records created after Jan 1, 2024 - Sorts by creation date (newest first) - Returns page 1 with 25 results - Returns only id, name, email, age, and status fields
JavaScript/TypeScript Example¶
async function fetchCustomers() {
const params = new URLSearchParams({
search: 'john',
searchFields: 'name,email',
status: 'active',
'age[gte]': '18',
page: '1',
pageSize: '25',
sort: '-createdAt'
});
const response = await fetch(
`/workspace/acme/api/v1/records/customers?${params}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
const result = await response.json();
return result.data;
}
Compute Functions (SDK)¶
When to Use Compute Functions¶
Use compute functions for: - Automated workflows - Scheduled tasks - Webhooks and event-driven logic - Complex business logic - Batch processing - Data transformations
Basic Structure¶
async function run() {
// The `api` object is automatically available as a global
// The `triggerParams` and `executionParams` are also available as globals
// Available API methods:
// - api.queryRecords() - Query records
// - api.aggregateRecords() - Run aggregations
// - api.fetchRecord() - Get single record by ID
// - api.fetchRecordByUniqueField() - Get by unique field
// - api.createRecord() - Create new record
// - api.updateRecord() - Update record
// - api.deleteRecord() - Delete record
// - api.incrementField() - Atomic increment
// - api.decrementField() - Atomic decrement
// - api.log() - Log messages
// - api.formatDate() - Format dates (uses dayjs)
// - api.uuid() - Generate UUID
// - api.lodash - Limited lodash functions (chunk, flatten, uniq, merge)
// - api.math - Math operations (evaluate, add, subtract, multiply, divide, random)
// - api.httpGet/Post/Put/Delete() - HTTP calls (domain-restricted)
// Your code here
}
Important Constraints: - No direct Node.js imports (fs, path, etc.) - No console.log - use api.log() instead - HTTP calls are domain-restricted (domains must be whitelisted) - No access to file system or environment variables - All dates use ISO 8601 strings
Query Method¶
Parameters:
recordSlug(string): The slug of the record structure (e.g., "customers")options(object): Query options
Options:
{
filter?: {
// Field filters using operators
[fieldName]: value | { operator: value }
},
search?: string, // Search term
searchFields?: string[], // Fields to search in
page?: number, // Page number (default: 1)
pageSize?: number, // Records per page (default: 50, max: 500)
sort?: Array<{ // Sort options
field: string,
direction: 'asc' | 'desc'
}>,
includeDeleted?: boolean, // Include soft-deleted records
includeTotal?: boolean, // Include total count in response
dateWindow?: { // Date range filter
field: string, // Usually 'createdAt' or 'updatedAt'
from?: string, // ISO date string
to?: string // ISO date string
}
}
Returns:
{
data: Array<Record>, // Array of matching records
meta: {
total?: number, // Total count (if includeTotal: true)
page: number, // Current page
pageSize: number // Page size
},
// Deprecated — use data and meta instead:
items: Array<Record>,
total?: number,
page: number,
pageSize: number
}
Basic Examples¶
1. Get All Records¶
async function run() {
const result = await api.queryRecords('customers');
api.log({
message: `Found ${result.data.length} customers`,
count: result.data.length
});
return result.data;
}
2. Search by Name¶
async function run() {
const result = await api.queryRecords('customers', {
search: 'john',
searchFields: ['name', 'email']
});
return result.data;
}
3. Filter by Status¶
async function run() {
const result = await api.queryRecords('customers', {
filter: {
status: 'active'
}
});
return result.data;
}
4. Paginate Results¶
async function run() {
const result = await api.queryRecords('customers', {
page: 2,
pageSize: 25
});
api.log(`Page ${result.meta.page} of customers`);
return result.data;
}
5. Sort Results¶
async function run() {
const result = await api.queryRecords('customers', {
sort: [
{ field: 'createdAt', direction: 'desc' }
],
pageSize: 10
});
return result.data;
}
Advanced Filtering Examples¶
Age Range Filter¶
async function run() {
const result = await api.queryRecords('customers', {
filter: {
'data.age': { gte: 18, lte: 65 }
}
});
return result.data;
}
Multiple Filters¶
async function run() {
const result = await api.queryRecords('customers', {
filter: {
status: 'active',
'data.age': { gte: 18 },
'data.verified': true
}
});
return result.data;
}
IN Operator (Multiple Values)¶
async function run() {
const result = await api.queryRecords('customers', {
filter: {
status: { in: ['active', 'pending', 'trial'] }
}
});
return result.data;
}
String Operators¶
async function run() {
const result = await api.queryRecords('customers', {
filter: {
'data.email': { contains: '@gmail.com' }
}
});
return result.data;
}
Date Range Filter¶
async function run() {
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const result = await api.queryRecords('orders', {
dateWindow: {
field: 'createdAt',
from: thirtyDaysAgo.toISOString()
}
});
return result.data;
}
Real-World Use Cases¶
1. Process Pending Orders¶
async function run() {
const result = await api.queryRecords('orders', {
filter: {
status: 'pending',
'data.paymentReceived': true
},
sort: [
{ field: 'createdAt', direction: 'asc' }
],
pageSize: 50 // Process in batches
});
api.log({
message: 'Processing pending orders',
count: result.data.length
});
for (const order of result.data) {
// Generate tracking number
const trackingNumber = `TRK-${api.uuid().substring(0, 8).toUpperCase()}`;
// Get current timestamp
const now = api.formatDate(new Date().toISOString(), 'YYYY-MM-DD HH:mm:ss');
// Update order status
await api.updateRecord(order.id, {
status: 'processing',
'data.processedAt': now,
'data.trackingNumber': trackingNumber
});
api.log({
message: 'Processed order',
orderId: order.id,
trackingNumber
});
}
return {
processed: result.data.length,
timestamp: new Date().toISOString()
};
}
2. Send Reminder Emails¶
async function run() {
// Calculate date 7 days from now
const today = new Date();
const sevenDaysFromNow = new Date(today);
sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);
const result = await api.queryRecords('customers', {
filter: {
'data.plan': 'trial',
'data.trialEndDate': {
gte: today.toISOString(),
lte: sevenDaysFromNow.toISOString()
},
'data.reminderSent': { ne: true }
},
pageSize: 100
});
api.log({
message: 'Found customers to remind',
count: result.data.length
});
let sentCount = 0;
let failedCount = 0;
for (const customer of result.data) {
try {
// Format trial end date for display
const trialEndFormatted = api.formatDate(
customer.data.trialEndDate,
'MMMM D, YYYY'
);
// Send reminder email via whitelisted email service
// Note: email-service.com must be in your domain whitelist
await api.httpPost('https://email-service.com/send', {
to: customer.data.email,
subject: 'Your trial is expiring soon',
template: 'trial-expiration',
data: {
customerName: customer.data.name,
trialEndDate: trialEndFormatted
}
});
// Mark reminder as sent with timestamp
await api.updateRecord(customer.id, {
'data.reminderSent': true,
'data.reminderSentAt': new Date().toISOString()
});
sentCount++;
} catch (error) {
api.log({
message: 'Failed to send reminder',
customerId: customer.id,
error: error.message
});
failedCount++;
}
}
return {
total: result.data.length,
sent: sentCount,
failed: failedCount
};
}
3. Data Cleanup Job¶
async function run() {
const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
const result = await api.queryRecords('logs', {
filter: {
status: 'archived'
},
dateWindow: {
field: 'createdAt',
to: oneYearAgo.toISOString()
},
includeTotal: true
});
api.log(`Found ${result.meta.total} old logs to delete`);
// Process in batches
let deletedCount = 0;
for (const log of result.data) {
await api.deleteRecord(log.id, true); // hard delete
deletedCount++;
}
return {
total: result.meta.total,
deleted: deletedCount
};
}
4. Generate Monthly Report¶
async function run() {
const startOfMonth = new Date();
startOfMonth.setDate(1);
startOfMonth.setHours(0, 0, 0, 0);
const endOfMonth = new Date();
api.log({
message: 'Generating monthly report',
period: {
start: api.formatDate(startOfMonth.toISOString(), 'YYYY-MM-DD'),
end: api.formatDate(endOfMonth.toISOString(), 'YYYY-MM-DD')
}
});
// Get aggregated stats first (faster than fetching all records)
const stats = await api.aggregateRecords('orders', {
filter: {
createdAt: { gte: startOfMonth.toISOString() }
},
operations: {
totalRevenue: { sum: 'data.amount' },
avgOrderValue: { avg: 'data.amount' },
maxOrderValue: { max: 'data.amount' },
orderCount: { count: '*' }
}
});
// Get top 10 customers by order count
const topCustomers = await api.aggregateRecords('orders', {
filter: {
createdAt: { gte: startOfMonth.toISOString() }
},
groupBy: ['data.customerId'],
operations: {
orderCount: { count: '*' },
totalSpent: { sum: 'data.amount' }
}
});
// Sort by total spent and take top 10
const sortedCustomers = api.lodash.chunk(
topCustomers.sort((a, b) => b.totalSpent - a.totalSpent),
10
)[0] || [];
// Calculate growth compared to previous month
const prevMonthStart = new Date(startOfMonth);
prevMonthStart.setMonth(prevMonthStart.getMonth() - 1);
const prevMonthStats = await api.aggregateRecords('orders', {
filter: {
createdAt: {
gte: prevMonthStart.toISOString(),
lt: startOfMonth.toISOString()
}
},
operations: {
totalRevenue: { sum: 'data.amount' },
orderCount: { count: '*' }
}
});
// Calculate growth percentages
const revenueGrowth = prevMonthStats.totalRevenue
? api.math.multiply(
api.math.divide(
api.math.subtract(stats.totalRevenue, prevMonthStats.totalRevenue),
prevMonthStats.totalRevenue
),
100
)
: 0;
const orderGrowth = prevMonthStats.orderCount
? api.math.multiply(
api.math.divide(
api.math.subtract(stats.orderCount, prevMonthStats.orderCount),
prevMonthStats.orderCount
),
100
)
: 0;
api.log({
message: 'Report generated successfully',
revenue: stats.totalRevenue,
orders: stats.orderCount,
revenueGrowth: `${revenueGrowth.toFixed(1)}%`
});
return {
period: {
start: api.formatDate(startOfMonth.toISOString(), 'MMMM D, YYYY'),
end: api.formatDate(endOfMonth.toISOString(), 'MMMM D, YYYY')
},
currentMonth: {
totalRevenue: stats.totalRevenue,
avgOrderValue: stats.avgOrderValue,
maxOrderValue: stats.maxOrderValue,
orderCount: stats.orderCount
},
growth: {
revenue: `${revenueGrowth.toFixed(1)}%`,
orders: `${orderGrowth.toFixed(1)}%`
},
topCustomers: sortedCustomers,
generatedAt: new Date().toISOString()
};
}
5. Batch Update Based on Criteria¶
async function run() {
// Find customers eligible for upgrade
const result = await api.queryRecords('customers', {
filter: {
'data.plan': 'basic',
'data.revenue': { gte: 1000 },
'data.accountAge': { gte: 90 } // days
}
});
api.log(`Found ${result.data.length} customers eligible for upgrade`);
for (const customer of result.data) {
await api.updateRecord(customer.id, {
'data.plan': 'premium',
'data.upgradedAt': new Date().toISOString(),
'data.upgradeReason': 'automatic-qualification'
});
}
return { upgraded: result.data.length };
}
Pagination Handling¶
Process All Records in Batches¶
async function run() {
let currentPage = 1;
const pageSize = 100;
let totalProcessed = 0;
while (true) {
const result = await api.queryRecords('customers', {
filter: { status: 'active' },
page: currentPage,
pageSize: pageSize,
includeTotal: true
});
if (result.data.length === 0) {
break; // No more records
}
api.log(`Processing page ${currentPage}, ${result.data.length} records`);
// Process this batch
for (const customer of result.data) {
// Do something with customer
await api.updateRecord(customer.id, {
'data.lastProcessed': new Date().toISOString()
});
totalProcessed++;
}
// Check if we've processed all records
if (totalProcessed >= result.meta.total) {
break;
}
currentPage++;
}
api.log(`Processed ${totalProcessed} customers total`);
return { processed: totalProcessed };
}
Aggregations¶
Aggregations allow you to compute statistics (sum, avg, min, max, count) across records.
HTTP API Aggregations¶
POST /workspace/{workspaceSlug}/api/v1/records/{recordSlug}/aggregate
Content-Type: application/json
{
"filter": { "status": "completed" },
"operations": {
"totalRevenue": { "sum": "data.amount" },
"avgOrderValue": { "avg": "data.amount" },
"maxOrderValue": { "max": "data.amount" },
"minOrderValue": { "min": "data.amount" },
"orderCount": { "count": "*" }
}
}
Response:
{
"totalRevenue": 125000.50,
"avgOrderValue": 250.00,
"maxOrderValue": 5000.00,
"minOrderValue": 10.00,
"orderCount": 500
}
SDK Aggregations¶
async function run() {
const stats = await api.aggregateRecords('orders', {
filter: {
status: 'completed',
createdAt: { gte: '2024-01-01T00:00:00Z' }
},
operations: {
totalRevenue: { sum: 'data.amount' },
avgOrderValue: { avg: 'data.amount' },
orderCount: { count: '*' }
}
});
return stats;
}
Group By¶
async function run() {
const stats = await api.aggregateRecords('orders', {
filter: { status: 'completed' },
groupBy: ['data.region'],
operations: {
totalRevenue: { sum: 'data.amount' },
orderCount: { count: '*' }
}
});
return stats;
}
Result:
[
{
"data.region": "US",
"totalRevenue": 75000,
"orderCount": 300
},
{
"data.region": "EU",
"totalRevenue": 50000,
"orderCount": 200
}
]
Filter Reference¶
Comparison Operators¶
| Operator | Description | Example |
|---|---|---|
eq | Equal (default) | { age: 18 } or { age: { eq: 18 } } |
ne | Not equal | { status: { ne: 'deleted' } } |
gt | Greater than | { age: { gt: 18 } } |
gte | Greater than or equal | { age: { gte: 18 } } |
lt | Less than | { age: { lt: 65 } } |
lte | Less than or equal | { age: { lte: 65 } } |
List Operators¶
| Operator | Description | Example |
|---|---|---|
in | Value is in list | { status: { in: ['active', 'pending'] } } |
nin | Value not in list | { status: { nin: ['deleted', 'banned'] } } |
String Operators¶
| Operator | Description | Example |
|---|---|---|
contains | Contains substring (case-insensitive) | { email: { contains: '@gmail.com' } } |
startswith | Starts with (case-insensitive) | { name: { startswith: 'John' } } |
endswith | Ends with (case-insensitive) | { email: { endswith: '.com' } } |
Array Operators¶
| Operator | Description | Example |
|---|---|---|
hasAny | Array contains any of the values | { tags: { hasAny: ['vip', 'premium'] } } |
hasAll | Array contains all of the values | { permissions: { hasAll: ['read', 'write'] } } |
Advanced Operators¶
| Operator | Description | Example |
|---|---|---|
eqAny | Equals any of the given values | { status: { eqAny: ['active', 'pending'] } } |
eqNone | Equals none of the given values | { status: { eqNone: ['deleted', 'banned'] } } |
Field Naming¶
Top-level fields (access directly): - id - recordSlug - status - workspaceSlug - createdAt - updatedAt
JSONB data fields (use data. prefix): - data.name - data.email - data.age - data.address.city (nested)
Common Patterns¶
1. Recent Records¶
// Get records from last 7 days
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const result = await api.queryRecords('customers', {
dateWindow: {
field: 'createdAt',
from: sevenDaysAgo.toISOString()
}
});
2. Active and Verified Users¶
const result = await api.queryRecords('users', {
filter: {
status: 'active',
'data.emailVerified': true,
'data.phoneVerified': true
}
});
3. High-Value Customers¶
const result = await api.queryRecords('customers', {
filter: {
'data.lifetimeValue': { gte: 10000 },
'data.plan': { in: ['premium', 'enterprise'] }
},
sort: [
{ field: 'data.lifetimeValue', direction: 'desc' }
]
});
4. Stale Records¶
const sixtyDaysAgo = new Date();
sixtyDaysAgo.setDate(sixtyDaysAgo.getDate() - 60);
const result = await api.queryRecords('leads', {
filter: {
status: 'pending'
},
dateWindow: {
field: 'updatedAt',
to: sixtyDaysAgo.toISOString()
}
});
5. Complex Business Logic¶
// Find customers who:
// - Are active
// - Have made at least 3 purchases
// - Have lifetime value > $500
// - Haven't been contacted in 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const result = await api.queryRecords('customers', {
filter: {
status: 'active',
'data.purchaseCount': { gte: 3 },
'data.lifetimeValue': { gt: 500 },
'data.lastContactDate': { lt: thirtyDaysAgo.toISOString() }
},
sort: [
{ field: 'data.lifetimeValue', direction: 'desc' }
]
});
Best Practices¶
1. Use Pagination for Large Datasets¶
// DON'T: Try to fetch all records at once
const result = await api.queryRecords('customers', {
pageSize: 10000 // ❌ May timeout or run out of memory
});
// DO: Process in smaller batches
let page = 1;
while (true) {
const result = await api.queryRecords('customers', {
page: page,
pageSize: 100
});
if (result.data.length === 0) break;
// Process batch
for (const customer of result.data) {
// ...
}
page++;
}
2. Use Filters Instead of Post-Processing¶
// DON'T: Fetch all and filter in code
const result = await api.queryRecords('customers');
const activeCustomers = result.data.filter(c => c.status === 'active'); // ❌ Wasteful
// DO: Filter at the database level
const result = await api.queryRecords('customers', {
filter: { status: 'active' } // ✅ Efficient
});
3. Request Only Fields You Need¶
# DON'T: Fetch all fields
GET /workspace/acme/api/v1/records/customers
# DO: Select specific fields
GET /workspace/acme/api/v1/records/customers?fields=id,name,email
4. Use includeTotal Sparingly¶
// Only request total count when you actually need it
const result = await api.queryRecords('customers', {
filter: { status: 'active' },
includeTotal: true // Only if you need the total count for UI/reporting
});
5. Leverage Indexes¶
Structure your filters to take advantage of database indexes: - Filter on top-level fields (id, status, createdAt, updatedAt) when possible - These are automatically indexed - JSONB fields (data.*) may not be indexed
6. Use Date Windows for Time-Based Queries¶
// More efficient than filtering large date ranges
const result = await api.queryRecords('orders', {
dateWindow: {
field: 'createdAt',
from: '2024-01-01T00:00:00Z',
to: '2024-12-31T23:59:59Z'
}
});
7. Handle Errors Gracefully¶
async function run() {
try {
const result = await api.queryRecords('customers', {
filter: { status: 'active' }
});
return result.data;
} catch (error) {
api.log(`Query failed: ${error.message}`);
return []; // Return empty array instead of throwing
}
}
Troubleshooting¶
Empty Results¶
Issue: Query returns no results but you expect records.
Solutions: 1. Check field names (remember data. prefix for JSONB fields) 2. Verify filter values match exactly (status: "active" vs "Active") 3. Check if records exist: remove all filters and search 4. Verify you're querying the correct recordSlug
// Debug: Remove all filters first
const all = await api.queryRecords('customers'); // Get everything
api.log(`Total records: ${all.data.length}`);
// Then add filters one by one
const filtered = await api.queryRecords('customers', {
filter: { status: 'active' }
});
api.log(`After status filter: ${filtered.data.length}`);
Query Timeout¶
Issue: Query takes too long or times out.
Solutions: 1. Reduce pageSize 2. Add more specific filters 3. Use pagination instead of fetching all at once 4. Consider using aggregations for large datasets
Invalid Field Error¶
Issue: "Property 'x' does not exist in structure"
Solutions: 1. Use data. prefix for JSONB fields: data.name not name 2. Check field name spelling 3. Verify field exists in the structure schema
Case Sensitivity¶
Issue: String filters not matching as expected.
Solution: String operators (contains, startswith, endswith) are case-insensitive. Use exact match if case matters:
// Case-insensitive (contains) - use bracket notation at top level
{ 'data.email[contains]': 'GMAIL' } // Matches gmail, Gmail, GMAIL
// Case-sensitive (exact match) - simple equality at top level
{ 'data.status': 'Active' } // Only matches "Active", not "active"
Additional Resources¶
- API Reference: Full REST API documentation
- Structure Schema: Define your record structures
- Compute Functions: Advanced workflow automation
- Webhooks: Event-driven integrations
Support¶
Need help? Contact support@centrali.com or check our documentation at docs.centrali.com.