Limits & Quotas¶
This page documents the rate limits, quotas, and constraints for the Centrali platform.
Note: Limits may vary by plan tier. Check your workspace settings or contact support for plan-specific limits.
API Rate Limits¶
Request Rates¶
| Endpoint Type | Free Tier | Pro Tier | Enterprise |
|---|---|---|---|
| Read Operations (GET) | 1,000/min | 10,000/min | Custom |
| Write Operations (POST/PUT/PATCH) | 100/min | 1,000/min | Custom |
| Delete Operations (DELETE) | 50/min | 500/min | Custom |
| Query Operations | 200/min | 2,000/min | Custom |
| Search Operations | 100/min | 1,000/min | Custom |
Rate Limit Headers:
When Rate Limited¶
HTTP 429 Response:
Headers:
Handling Rate Limits:
async function makeRequestWithRetry(fn) {
try {
return await fn();
} catch (error) {
if (error.response?.status === 429) {
const retryAfter = parseInt(error.response.headers['retry-after']) || 60;
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return await fn(); // Retry once
}
throw error;
}
}
Request Size Limits¶
Payload Sizes¶
| Type | Maximum Size |
|---|---|
| Single Record | 1 MB |
| Bulk Create (records array) | 10 MB (max 1,000 records) |
| File Upload | 100 MB per file |
| Query Response | 10 MB |
| Function Code | 5 MB (compressed) |
Request Headers¶
| Header | Maximum Size |
|---|---|
| Authorization Header | 8 KB |
| Total Request Headers | 16 KB |
URL Limits¶
| Type | Maximum Length |
|---|---|
| URL Path | 2,048 characters |
| Query String | 4,096 characters |
Example Violations:
// ❌ Payload too large
await centrali.createRecord('Product', {
description: 'a'.repeat(2 * 1024 * 1024) // 2 MB string
});
// Error: 413 Payload Too Large
// ❌ Too many records in bulk
await centrali.bulkCreate('Product', Array(2000).fill({ name: 'Test' }));
// Error: 400 Bad Request - Maximum 1,000 records per bulk operation
Storage Limits¶
Workspace Storage¶
| Plan | File Storage | Record Storage | Database Size |
|---|---|---|---|
| Free | 1 GB | Unlimited | 1 GB |
| Pro | 100 GB | Unlimited | 10 GB |
| Enterprise | Custom | Unlimited | Custom |
Per-Structure Limits¶
| Limit | Value |
|---|---|
| Fields per Structure | 100 |
| Nested Object Depth | 5 levels |
| Array Items (stored) | 10,000 per field |
| String Field Length | 10,000 characters (text), 100,000 (longtext) |
| Total Structures per Workspace | 1,000 |
File Storage¶
| Limit | Value |
|---|---|
| File Size (single upload) | 100 MB |
| Total Files per Workspace | See storage quota |
| File Name Length | 255 characters |
| Allowed File Types | All (no restrictions) |
Compute Function Limits¶
Execution Limits¶
| Limit | Value |
|---|---|
| Execution Timeout | 30 seconds |
| Memory per Function | 512 MB |
| Concurrent Executions | 100 (per workspace) |
| Function Code Size | 5 MB (compressed) |
| npm Dependencies Size | 50 MB |
Function Constraints¶
| Limit | Value |
|---|---|
| Environment Variables | 100 per function |
| Environment Variable Size | 4 KB per variable |
| Triggers per Function | 50 |
| Functions per Workspace | 1,000 |
Example:
// ❌ Function runs too long
exports.handler = async (event, context) => {
// This will timeout after 30 seconds
await new Promise(resolve => setTimeout(resolve, 35000));
};
// Error: Function execution timed out
Handling Timeouts:
// ✅ Break long operations into smaller chunks
exports.handler = async (event, context) => {
const batchSize = 100;
const records = await centrali.records.query({ limit: batchSize });
for (const record of records) {
await processRecord(record);
}
// If more records, trigger another function run
if (records.length === batchSize) {
await centrali.functions.invoke('processBatch', {
offset: event.offset + batchSize
});
}
return { processed: records.length };
};
Query Limits¶
Query Constraints¶
| Limit | Value |
|---|---|
| Results per Query | 1,000 (use pagination) |
| Query Complexity | 100 operations |
| Nested Includes | 5 levels deep |
| Filter Conditions | 50 per query |
| Sort Fields | 5 per query |
Example:
// ❌ Too many results
await centrali.queryRecords('Product', { limit: 10000 });
// Error: Maximum limit is 1,000
// ✅ Use pagination
let offset = 0;
const limit = 1000;
let hasMore = true;
while (hasMore) {
const results = await centrali.queryRecords('Product', { limit, offset });
processRecords(results);
hasMore = results.length === limit;
offset += limit;
}
Search Limits¶
| Limit | Value |
|---|---|
| Search Results | 1,000 (use pagination) |
| Search Query Length | 500 characters |
| Facets per Query | 10 |
| Indexed Fields per Structure | 50 |
Webhook Limits¶
Delivery Constraints¶
| Limit | Value |
|---|---|
| Webhooks per Workspace | 100 |
| Delivery Timeout | 10 seconds |
| Retry Attempts | 3 |
| Payload Size | 1 MB |
Delivery Behavior¶
Retry Schedule: - 1st retry: 1 minute after failure - 2nd retry: 5 minutes after first retry - 3rd retry: 15 minutes after second retry - After 3 failures: Webhook marked as failed
Example:
// Your webhook endpoint should respond quickly
app.post('/webhook', async (req, res) => {
// ✅ Respond immediately
res.status(200).send('Received');
// ❌ Don't do heavy processing in webhook handler
// processHeavyWorkload(req.body);
// ✅ Queue for async processing
await queue.add('process-webhook', req.body);
});
Service Account Limits¶
| Limit | Value |
|---|---|
| Service Accounts per Workspace | 100 |
| Active Tokens per Service Account | 10 |
| Token Lifetime | 7 hours |
| Client Secret Length | 64 characters |
Workspace Limits¶
| Limit | Free | Pro | Enterprise |
|---|---|---|---|
| Workspaces per Account | 3 | 10 | Unlimited |
| Team Members per Workspace | 5 | 25 | Unlimited |
| API Keys per Workspace | 10 | 50 | Unlimited |
Naming Constraints¶
Structure Names¶
- Pattern:
^[A-Za-z][A-Za-z0-9_]{0,63}$ - Length: 1-64 characters
- Requirements: Start with letter, alphanumeric + underscore only
Field Names¶
- Pattern:
^[a-z][a-zA-Z0-9_]{0,63}$ - Length: 1-64 characters
- Requirements: Start with lowercase letter, camelCase
Workspace Slugs¶
- Pattern:
^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$ - Length: 3-64 characters
- Requirements: Lowercase alphanumeric + hyphens, no leading/trailing hyphens
Examples:
// ✅ Valid
name: "BlogPost"
field: "userName"
slug: "my-app-prod"
// ❌ Invalid
name: "Blog Post" // No spaces
name: "123Post" // Must start with letter
field: "UserName" // Must start with lowercase
slug: "My-App" // Must be lowercase
slug: "-myapp" // No leading hyphen
Concurrency Limits¶
| Operation | Concurrent Limit |
|---|---|
| API Requests per IP | 1,000 |
| Concurrent File Uploads | 10 |
| Concurrent Function Executions | 100 |
| Concurrent Queries | 50 |
Best Practices for Staying Within Limits¶
1. Implement Pagination¶
// ✅ Paginate large result sets
async function fetchAllRecords(structure) {
const allRecords = [];
let offset = 0;
const limit = 1000; // Max limit
while (true) {
const batch = await centrali.queryRecords(structure, { limit, offset });
allRecords.push(...batch);
if (batch.length < limit) break;
offset += limit;
}
return allRecords;
}
2. Batch Operations¶
// ✅ Batch creates instead of individual calls
const records = [/* 100 records */];
await centrali.bulkCreate('Product', records); // 1 API call
// ❌ Don't do this
for (const record of records) {
await centrali.createRecord('Product', record); // 100 API calls!
}
3. Use Webhooks Instead of Polling¶
// ❌ Polling (many API calls)
setInterval(async () => {
const records = await centrali.queryRecords('Order', {
filter: 'status = "pending"'
});
processOrders(records);
}, 5000); // Every 5 seconds
// ✅ Webhooks (no polling needed)
// Configure webhook in dashboard for "Order created" event
app.post('/webhook/order-created', (req, res) => {
processOrder(req.body);
res.status(200).send('OK');
});
4. Cache Responses¶
// ✅ Cache frequently accessed data
const cache = new Map();
async function getStructure(id) {
if (cache.has(id)) {
return cache.get(id);
}
const structure = await centrali.getStructure(id);
cache.set(id, structure);
// Clear cache after 5 minutes
setTimeout(() => cache.delete(id), 5 * 60 * 1000);
return structure;
}
5. Monitor Your Usage¶
// Check rate limit headers
const response = await centrali.queryRecords('Product', { limit: 100 });
console.log('Rate limit:', response.headers['x-ratelimit-limit']);
console.log('Remaining:', response.headers['x-ratelimit-remaining']);
console.log('Resets at:', new Date(response.headers['x-ratelimit-reset'] * 1000));
Exceeding Limits¶
What Happens¶
Rate Limits: - HTTP 429 response - Retry-After header indicates wait time - Limits reset based on time window (per minute, per hour)
Storage/Quota Limits: - HTTP 413 (Payload Too Large) or 507 (Insufficient Storage) - Must upgrade plan or delete data
Hard Limits: - HTTP 400 (Bad Request) for constraint violations - Cannot be exceeded without plan upgrade
Requesting Limit Increases¶
For Enterprise plans or special requirements: 1. Contact support at support@centrali.io 2. Provide use case and required limits 3. Custom limits can be configured per workspace
Related Documentation¶
- Error Handling - Handling limit errors
- Troubleshooting - Common issues
Summary¶
Key Limits to Remember: - API Requests: 1,000/min (read), 100/min (write) on free tier - Record Size: 1 MB max - Query Results: 1,000 max (use pagination) - Function Timeout: 30 seconds - File Upload: 100 MB max - Bulk Operations: 1,000 records max
Always: - Implement pagination for large datasets - Use bulk operations when possible - Cache frequently accessed data - Monitor rate limit headers - Use webhooks instead of polling
Need higher limits? Upgrade your plan or contact Enterprise sales.