AI Agent Troubleshooting Guide¶
This page is designed to be read by AI agents and developers using AI tools (Copilot, Cursor, Claude, Codex, MCP clients) to interact with Centrali. It documents the most common failure modes observed in real AI agent sessions and provides the correct patterns to follow.
If you are an AI agent, read this page before generating any Centrali API calls, SDK code, or compute functions.
Host Routing¶
The single most common AI mistake is sending requests to the wrong host. Centrali uses separate hosts for authentication and data.
| Operation | Correct Host | Wrong Host |
|---|---|---|
| Token request (OAuth) | https://auth.centrali.io/oidc/token | https://api.centrali.io/oidc/token |
| Service account CRUD | https://auth.centrali.io/workspace/{slug}/api/v1/service-accounts | https://api.centrali.io/... |
| IAM (groups, roles, policies) | https://auth.centrali.io/workspace/{slug}/api/v1/... | https://api.centrali.io/... |
| Collections, records, functions | https://api.centrali.io/data/workspace/{slug}/api/v1/... | https://auth.centrali.io/... |
| Storage (files, folders) | https://api.centrali.io/storage/workspace/{slug}/api/v1/... | N/A |
| Search | https://api.centrali.io/search/workspace/{slug}/api/v1/... | N/A |
Rule: Authentication and IAM operations go to auth.centrali.io. Everything else goes to api.centrali.io with a service path prefix (/data/, /storage/, /search/).
Workspace Slug Mismatches¶
The workspace slug must be consistent across all requests. A token obtained for workspace acme-corp will not work for requests scoped to workspace acme or acme-corp-staging.
Check these:
- The
workspaceclaim in your JWT matches the workspace slug in your API URL - The service account was created in the same workspace you are targeting
- You are not mixing production and staging workspace slugs
# Decode the workspace from your token (base64 decode the payload)
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool | grep workspace
Service Account Identifiers¶
Service accounts have two identifiers that are used in different contexts. Confusing them causes silent failures.
| Identifier | Format | Where Used |
|---|---|---|
id (serviceAccountId) | Numeric integer (e.g., 123) | Service account management endpoints (update, rotate, delete, revoke) |
clientId | String with prefix (e.g., ci_a1b2c3d4...) | OAuth token requests, group assignment, JWT sub claim |
# Token request uses clientId (string)
curl -X POST "https://auth.centrali.io/oidc/token" \
-d "client_id=ci_a1b2c3d4e5f6g7h8i9j0" \
-d "client_secret=sk_..." \
-d "grant_type=client_credentials" \
-d "scope=openid"
# Management endpoints use numeric id
curl -X GET "https://auth.centrali.io/workspace/acme-corp/api/v1/service-accounts/123" \
-H "Authorization: Bearer $USER_TOKEN"
IAM Actions¶
Centrali uses retrieve for read operations, not read. Using read in policy definitions will silently fail to grant access.
Valid IAM actions:
| Action | Meaning |
|---|---|
create | Create new resources |
retrieve | Read a single resource |
list | List multiple resources |
update | Modify existing resources |
delete | Remove resources |
manage | Full administrative control |
validate | Run validation checks |
execute | Execute functions, triggers, queries |
// Wrong - "read" is not a valid action
{ action: 'read', resource: 'records', effect: 'allow' }
// Correct
{ action: 'retrieve', resource: 'records', effect: 'allow' }
Collection Name vs Structure ID¶
Centrali has two ways to reference a collection. Using the wrong one in the wrong context causes 404 errors.
| Identifier | Format | Where Used |
|---|---|---|
recordSlug (collection name) | Kebab-case string (e.g., my-products) | SDK methods, record CRUD, query endpoints |
structureId | UUID (e.g., f47ac10b-58cc-...) | Internal references, some API responses |
// SDK uses the collection name (recordSlug), not the structureId
const records = await centrali.queryRecords('my-products', {
'data.status': 'active'
});
// Wrong - structureId does not work in SDK methods
const records = await centrali.queryRecords('f47ac10b-58cc-4372-a567-0e02b2c3d479', {
'data.status': 'active'
});
SDK Query Filter Syntax¶
This is one of the most frequent AI mistakes. The Client SDK and the Compute SDK use different filter syntax.
Client SDK (your application code)¶
Filters are top-level query parameters with a data. prefix. Do not nest them under a filter key.
// Correct - top-level filters with data. prefix
const products = await centrali.queryRecords('Product', {
'data.inStock': true,
'data.price[gte]': 100,
sort: '-createdAt',
pageSize: 20
});
// Wrong - nested filter object does not work in Client SDK
const products = await centrali.queryRecords('Product', {
filter: { inStock: true, price: { gte: 100 } }
});
Operator syntax (Client SDK)¶
| Operation | Syntax | Example |
|---|---|---|
| Equals | 'data.field': value | 'data.status': 'active' |
| Greater than | 'data.field[gt]': value | 'data.price[gt]': 100 |
| Greater or equal | 'data.field[gte]': value | 'data.price[gte]': 100 |
| Less than | 'data.field[lt]': value | 'data.price[lt]': 50 |
| Less or equal | 'data.field[lte]': value | 'data.price[lte]': 50 |
| Not equal | 'data.field[ne]': value | 'data.status[ne]': 'archived' |
| In (multiple values) | 'data.field[in]': 'a,b,c' | 'data.status[in]': 'pending,processing' |
| Contains (string) | 'data.field[contains]': value | 'data.email[contains]': '@gmail.com' |
Compute SDK (inside compute functions)¶
Inside a compute function, api.queryRecords supports both the top-level syntax and nested filter: {}. This page documents the Client SDK only.
Compute Function Format¶
The current compute runtime requires async function run(). Older documentation and SDK examples may show exports.handler or module.exports patterns. Those are not valid in the current runtime.
// Correct - current runtime
async function run() {
const input = executionParams;
const config = triggerParams;
const result = await api.queryRecords('my-collection', { limit: 10 });
api.log({ found: result.data.length });
return { success: true, data: result };
}
// Wrong - legacy pattern, will not execute
exports.handler = async function(event, context) {
// ...
};
// Wrong - also legacy
module.exports = async function(event, context) {
// ...
};
Available globals inside compute functions¶
| Global | Purpose |
|---|---|
api | SDK-like client for Centrali APIs (api.queryRecords, api.createRecord, api.log, etc.) |
triggerParams | Parameters configured on the trigger definition |
executionParams | Parameters passed at invocation time (e.g., from centrali.triggers.invoke(...)) |
Compute function return contract¶
// Success
return { success: true, data: { /* your result */ } };
// Failure (business logic error)
return { success: false, error: 'Description of what went wrong' };
// If you need to signal a real failure to orchestrations, throw:
throw new Error('Unrecoverable error');
Returning success: true on provider errors
If your function calls an external API and the provider returns an error (e.g., 403), do not return { success: true }. The orchestration runtime treats a structured return as success. Either throw or return { success: false, error: ... } so the step is marked as failed.
Plan-Gated Features¶
Some features return 403 or 402 errors because they require a specific workspace plan tier. If you receive a 403 that mentions plan limits or feature access, check whether the workspace plan supports the feature.
Common plan-gated features include:
- Compute functions (execution quotas)
- Automations/orchestrations
- AI features (validation, anomaly insights, schema discovery)
- Pages
- Storage quotas
- Search index limits
A 403 that says "plan" or "quota" is not a permissions problem. It is a billing/plan problem.
Common Error Patterns¶
| Error | Likely Cause | Fix |
|---|---|---|
Invalid client credentials | Wrong client_id or client_secret, or extra whitespace | Verify credentials, check for trailing newlines |
Token expired | JWT tokens expire after 7 hours (25,200 seconds) | Fetch a new token; SDK handles this automatically |
[FORBIDDEN] Resource not found | Service account lacks permissions, or wrong workspace slug | Verify workspace slug and group membership |
Missing authorization header | No Authorization: Bearer ... header | Add the header to all API requests |
Collection not found | Wrong collection name, wrong workspace, or collection deleted | List collections to verify: GET /data/workspace/{slug}/api/v1/collections |
| 403 mentioning plan/quota | Feature not available on current plan | Check workspace plan tier |
Unauthorized after token request | Sent token request to api.centrali.io instead of auth.centrali.io | Use https://auth.centrali.io/oidc/token |
| 404 on service account endpoint | Used api.centrali.io for IAM | Use https://auth.centrali.io/workspace/{slug}/api/v1/... |
Debugging Checklist¶
Use this checklist before reporting an issue or retrying a failed operation.
1. Verify credentials and token¶
-
client_idstarts withci_ -
client_secretstarts withsk_ - No trailing whitespace or newlines in credential values
- Token request goes to
https://auth.centrali.io/oidc/token - Token response contains
access_tokenandexpires_in - Token has not expired (7-hour lifetime)
2. Verify workspace¶
- Workspace slug in API URL matches the
workspaceclaim in the JWT - Service account belongs to a group in this workspace
- Group has policies granting the required actions
3. Verify request shape¶
- Auth/IAM requests go to
auth.centrali.io - Data/storage/search requests go to
api.centrali.io -
Authorization: Bearer <token>header is present - Request body matches the expected schema (check API reference)
- SDK query filters use top-level
data.prefix, not nestedfilter: {}
4. Verify compute function¶
- Function uses
async function run(), notexports.handler - External API calls use
api.fetch()notfetch() - External domains are added to the allowed domains list
- Function returns
{ success: true, data: ... }or{ success: false, error: ... } - Provider errors are not masked as
success: true
5. Verify IAM¶
- Actions use
retrieve, notread - Resource names match the system resources table (e.g.,
records,structures,compute-functions) - Policies are attached to a group, and the service account is a member of that group
Quick Reference: Correct API Patterns¶
Obtain a token¶
curl -X POST "https://auth.centrali.io/oidc/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CENTRALI_CLIENT_ID" \
-d "client_secret=$CENTRALI_CLIENT_SECRET" \
-d "scope=openid"
List collections¶
curl "https://api.centrali.io/data/workspace/$WORKSPACE/api/v1/collections" \
-H "Authorization: Bearer $TOKEN"
Query records (SDK)¶
const results = await centrali.queryRecords('my-collection', {
'data.status': 'active',
'data.priority[in]': 'high,critical',
sort: '-createdAt',
pageSize: 50
});
Create a record (SDK)¶
const record = await centrali.createRecord('my-collection', {
title: 'New item',
status: 'todo',
priority: 'high'
});
Invoke a trigger (SDK)¶
await centrali.triggers.invoke('my-trigger-name', {
payload: { key: 'value' }
});
// Payload lands in executionParams inside the compute function
Compute function template¶
async function run() {
const input = executionParams;
api.log({ receivedInput: input });
try {
const records = await api.queryRecords('my-collection', {
limit: 100,
filter: { status: 'active' }
});
return { success: true, data: { count: records.data.length } };
} catch (error) {
api.log({ error: error.message });
return { success: false, error: error.message };
}
}
Related Documentation¶
- Troubleshooting Guide - General troubleshooting
- Service Accounts - Full service account setup
- Policies & Permissions - IAM resource and action reference
- SDK Reference - Client SDK documentation
- Writing Functions - Compute function code guide
- Error Handling - Error codes and handling patterns