Skip to content

Centrali JavaScript/TypeScript SDK

The Centrali SDK provides a simple, type-safe way to interact with Centrali's APIs from JavaScript or TypeScript applications. It handles authentication, request formatting, and response parsing automatically.

Try it now! Explore the SDK in our Live Playground on StackBlitz - no setup required.

Installation

npm install @centrali-io/centrali-sdk
# or
yarn add @centrali-io/centrali-sdk

Quick Start

import { CentraliSDK } from '@centrali-io/centrali-sdk';

// Initialize the SDK
const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: 'your-workspace-slug',  // workspace slug, not UUID

  // Option 1: Use an existing token (from user login)
  token: 'your-bearer-token',

  // Option 2: Use service account credentials (auto-fetches token)
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret'
});

// Create a record
const product = await centrali.createRecord('Product', {
  name: 'Wireless Headphones',
  price: 99.99,
  inStock: true
});

console.log('Created product:', product.data);

Authentication

The SDK supports two authentication methods:

1. Bearer Token (User Authentication)

For user-authenticated requests, provide a JWT token:

const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: 'your-workspace-slug',  // workspace slug, not UUID
  token: 'jwt-token-from-login'
});

// Update the token later if needed
centrali.setToken('new-jwt-token');

2. Client Credentials (Service Account)

For server-to-server communication, use service account credentials:

const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: 'your-workspace-slug',  // workspace slug, not UUID
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET
});

// The SDK automatically fetches and manages the token
// You can also manually fetch a token:
const token = await centrali.fetchServiceAccountToken();

API Reference

Initialization Options

interface CentraliSDKOptions {
  /** Base URL of the Centrali API */
  baseUrl: string;

  /** Your workspace identifier */
  workspaceId: string;

  /** Optional bearer token for user authentication */
  token?: string;

  /** Optional service account client ID */
  clientId?: string;

  /** Optional service account client secret */
  clientSecret?: string;

  /** Optional custom axios configuration */
  axiosConfig?: AxiosRequestConfig;
}

Records Management

Create a Record

const record = await centrali.createRecord('CollectionName', {
  field1: 'value1',
  field2: 123,
  nested: {
    subField: 'value'
  }
});

console.log('Created record ID:', record.id);
Create with TTL
// Expire after 1 hour
const session = await centrali.createRecord('Sessions', {
  userId: 'user-123',
  token: 'abc-xyz',
}, { ttlSeconds: 3600 });

console.log(session.data.expiresAt); // ISO timestamp ~1 hour from now

// Expire at a specific date
const promo = await centrali.createRecord('Promotions', {
  code: 'SUMMER2026',
  discount: 20,
}, { expiresAt: '2026-09-01T00:00:00Z' });

The third argument accepts RecordTtlOptions:

Option Type Description
ttlSeconds number Seconds until expiration
expiresAt string ISO 8601 expiration timestamp

If neither is set, the record inherits the collection's defaultTtlSeconds (if configured). Note: clearTtl is only valid when updating an existing record and has no effect on creation.

Get a Record

const record = await centrali.getRecord('CollectionName', 'record-id');
console.log('Record data:', record.data);

Update a Record

const updated = await centrali.updateRecord('CollectionName', 'record-id', {
  field1: 'new value',
  field2: 456
});

console.log('Updated record:', updated.data);
Update TTL
// Extend session by 2 hours
await centrali.updateRecord('Sessions', 'record-id', {}, { ttlSeconds: 7200 });

// Remove TTL (make permanent)
await centrali.updateRecord('Sessions', 'record-id', {}, { clearTtl: true });

The fourth argument accepts RecordTtlOptions with an additional clearTtl option.

Upsert a Record

Atomically create or update a record based on match fields. Returns the record and whether it was created or updated.

const result = await centrali.upsertRecord('Product', {
  match: { sku: 'WDG-001' },
  data: {
    sku: 'WDG-001',
    name: 'Premium Widget',
    price: 29.99,
    inStock: true
  }
});

console.log(result.operation); // 'created' or 'updated'
console.log(result.data);      // the record

The match object contains the business key fields used to find an existing record. The data object contains the full record payload for create, or the fields to update. Match field values are protected from being overwritten on update.

This is safe for concurrent calls — the backend uses advisory locking to serialize requests with the same match criteria.

Delete a Record

await centrali.deleteRecord('CollectionName', 'record-id');
console.log('Record deleted');

Query Records

IMPORTANT: For the Client SDK (this SDK), filters must be passed at the TOP LEVEL of query params, not nested under a filter key. Use data. prefix for custom fields and bracket notation for operators.

Note: Compute Functions use a different internal SDK (api.queryRecords) which supports both nested filter: {} and top-level filters. This page documents the Client SDK only.

// Simple equality filter
const products = await centrali.queryRecords('Product', {
  'data.inStock': true,
  sort: '-createdAt',
  page: 1,
  pageSize: 10
});

console.log(`Found ${products.data.length} products`);

// Filter with operators (bracket notation)
const expensiveProducts = await centrali.queryRecords('Product', {
  'data.inStock': true,
  'data.price[gte]': 100,
  sort: '-price',
  pageSize: 20
});

// Multiple values with 'in' operator (comma-separated string)
const orders = await centrali.queryRecords('Order', {
  'data.status[in]': 'pending,processing',
  'data.total[gt]': 100,
  sort: 'createdAt',
  pageSize: 20
});

// String matching
const customers = await centrali.queryRecords('Customer', {
  'data.email[contains]': '@gmail.com',
  'data.verified': true
});

// Not equal filter
const activeItems = await centrali.queryRecords('Product', {
  'data.status[ne]': 'discontinued',
  pageSize: 100
});

Bulk Operations

Get multiple records by their IDs:

const recordIds = ['rec-1', 'rec-2', 'rec-3'];
const records = await centrali.getRecordsByIds('Product', recordIds);

console.log(`Retrieved ${records.data.length} records`);

Collections Management

Manage collections (formerly known as structures) programmatically via client.collections. This enables infrastructure-as-code workflows for defining and versioning your data models.

List Collections

const result = await centrali.collections.list();

// With pagination
const page2 = await centrali.collections.list({ page: 2, limit: 10 });

Get a Collection

// By ID
const structure = await centrali.collections.get('collection-uuid');

// By slug
const structure = await centrali.collections.getBySlug('orders');
console.log('Properties:', structure.data.properties);

Create a Collection

const structure = await centrali.collections.create({
  name: 'Orders',
  slug: 'orders',
  description: 'Customer orders',
  properties: [
    { name: 'title', type: 'string', required: true, maxLength: 200 },
    { name: 'amount', type: 'number', minimum: 0 },
    { name: 'status', type: 'string', enum: ['pending', 'completed', 'cancelled'] },
    { name: 'tags', type: 'array', items: { type: 'string' } },
    {
      name: 'customer',
      type: 'reference',
      target: 'customers',
      relationship: 'many-to-one',
      onDelete: 'restrict'
    },
  ],
  enableVersioning: true,
  schemaDiscoveryMode: 'strict',
  tags: ['core'],
});

Update a Collection

const updated = await centrali.collections.update('collection-uuid', {
  name: 'Updated Orders',
  properties: [
    { name: 'title', type: 'string', required: true },
    { name: 'amount', type: 'number', minimum: 0 },
    { name: 'priority', type: 'number' },  // new field
  ],
});
Set Default TTL
// All new records in this collection expire after 24 hours
await centrali.collections.update('collection-uuid', {
  defaultTtlSeconds: 86400,
});

// Remove default TTL
await centrali.collections.update('collection-uuid', {
  defaultTtlSeconds: null,
});

Delete a Collection

await centrali.collections.delete('collection-uuid');

Validate a Collection

Check slug uniqueness and property validity before creating:

const result = await centrali.collections.validate({
  slug: 'orders',
  properties: [{ name: 'title', type: 'string' }],
});

Property Types

Type Key Fields
string minLength, maxLength, pattern, renderAs, isSecret
number minimum, maximum, multipleOf, expression, autoIncrement
boolean
datetime earliestDate, latestDate, expression
array items, minItems, maxItems, uniqueItems, itemSchema
object properties, requiredProperties, strictProperties
reference target, relationship, onDelete, displayField

Compute Functions

Invoke a Function

Execute serverless compute functions:

const result = await centrali.invokeFunction('calculate-discount', {
  productId: 'prod-123',
  quantity: 2,
  couponCode: 'SAVE20'
});

console.log('Discount result:', result.data);

Manage Compute Functions

Manage compute function definitions programmatically via client.functions. This enables CI/CD deployment of function code.

// List all functions
const fns = await centrali.functions.list();
const searched = await centrali.functions.list({ search: 'order', limit: 10 });

// Get a function by ID
const fn = await centrali.functions.get('function-uuid');
console.log('Code:', fn.data.code);

// Create a function
const fn = await centrali.functions.create({
  name: 'Process Order',
  slug: 'process-order',
  code: `async function run() {
    const order = await api.fetchRecord('orders', triggerParams.orderId);
    return { processed: true, total: order.data.amount };
  }`,
  description: 'Processes incoming orders',
  timeout: 60000,
});

// Update a function
const updated = await centrali.functions.update('function-uuid', {
  code: 'async function run() { return { v2: true }; }',
  timeout: 120000,
});

// Delete a function
await centrali.functions.delete('function-uuid');

Test Execute (Without Saving)

Validate function code before deploying:

const result = await centrali.functions.testExecute({
  code: `async function run() {
    return { sum: executionParams.a + executionParams.b };
  }`,
  params: { a: 1, b: 2 },
});

console.log('Output:', result.data.output);        // { sum: 3 }
console.log('Duration:', result.data.duration_ms);  // e.g., 45
console.log('Logs:', result.data.logs);             // console.log output
console.log('Success:', result.data.success);       // true

Triggers

Manage and invoke compute function triggers via client.triggers.

List Triggers

// List on-demand triggers only (default filter)
const onDemand = await centrali.triggers.list();

// List ALL triggers (any execution type)
const all = await centrali.triggers.listAll();

// Filter by execution type
const scheduled = await centrali.triggers.listAll({ executionType: 'scheduled' });

// Include health metrics
const withHealth = await centrali.triggers.listAll({ includeHealth: true });

Get a Trigger

// Get on-demand trigger (validates type)
const trigger = await centrali.triggers.get('trigger-id');

// Get any trigger type (no type restriction)
const trigger = await centrali.triggers.getDetails('trigger-id');

// With health metrics
const trigger = await centrali.triggers.getDetails('trigger-id', { includeHealth: true });

Invoke a Trigger

const result = await centrali.triggers.invoke('trigger-id', {
  payload: { orderId: 'order-456', action: 'process' },
});
// result.data → queued job ID

Create a Trigger

// Event-driven trigger
const trigger = await centrali.triggers.create({
  name: 'On Order Created',
  functionId: 'function-uuid',
  executionType: 'event-driven',
  triggerMetadata: { event: 'record.created', recordSlug: 'orders' },
});

// Scheduled trigger (cron)
const scheduled = await centrali.triggers.create({
  name: 'Daily Report',
  functionId: 'function-uuid',
  executionType: 'scheduled',
  triggerMetadata: {
    scheduleType: 'cron',
    cronExpression: '0 9 * * *',
    timezone: 'America/New_York',
  },
});

// On-demand trigger
const manual = await centrali.triggers.create({
  name: 'Manual Process',
  functionId: 'function-uuid',
  executionType: 'on-demand',
});

Update a Trigger

const updated = await centrali.triggers.update('trigger-uuid', {
  name: 'Updated Trigger',
  enabled: false,
  triggerMetadata: { cronExpression: '0 10 * * *' },
});

Delete a Trigger

await centrali.triggers.delete('trigger-uuid');

Pause / Resume a Trigger

await centrali.triggers.pauseTrigger('trigger-id');
await centrali.triggers.resumeTrigger('trigger-id');

Smart Queries

Manage and execute pre-defined queries via client.smartQueries. Smart queries support joins, filters, sorting, and variable substitution.

List Smart Queries

// All queries in workspace
const all = await centrali.smartQueries.listAll({ page: 1, limit: 20 });

// Queries for a specific collection
const queries = await centrali.smartQueries.list('orders');

Get a Smart Query

// By ID
const query = await centrali.smartQueries.get('orders', 'query-uuid');

// By name
const query = await centrali.smartQueries.getByName('orders', 'Active Orders');

Execute a Smart Query

const result = await centrali.smartQueries.execute('orders', 'query-uuid', {
  variables: {
    startDate: '2025-01-01',
    region: 'us-east',
  },
});
// result.data → array of matching records

Create a Smart Query

const query = await centrali.smartQueries.create('orders', {
  name: 'Active Orders',
  description: 'All orders with active status',
  queryDefinition: {
    where: { status: { $eq: 'active' } },
    sort: [{ field: 'createdAt', direction: 'desc' }],
    limit: 100,
  },
});

Update a Smart Query

const updated = await centrali.smartQueries.update('orders', 'query-uuid', {
  name: 'Active Orders v2',
  queryDefinition: {
    where: { status: { $in: ['active', 'processing'] } },
    limit: 200,
  },
});

Delete a Smart Query

await centrali.smartQueries.delete('orders', 'query-uuid');

Test a Smart Query (Without Saving)

Preview results before saving:

const result = await centrali.smartQueries.test('orders', {
  queryDefinition: {
    where: { amount: { $gte: 100 } },
    select: ['id', 'amount', 'status'],
    limit: 5,
  },
});
console.log('Preview:', result.data);

File Uploads

Upload files to Centrali's storage service:

// In a browser environment
const fileInput = document.getElementById('file-input') as HTMLInputElement;
const file = fileInput.files[0];

// Upload to default location (/root/shared)
const uploadResult = await centrali.uploadFile(file);

// Upload to a specific folder (folder must exist)
const uploadResult = await centrali.uploadFile(
  file,
  '/root/shared/product-images',  // Full path to target folder
  true                             // Make publicly accessible
);

console.log('File URL:', uploadResult.data);

Upload Parameters

Parameter Required Default Description
file Yes - File object to upload
location No /root/shared Target folder path (must be under /root/)
isPublic No false If true, file is publicly accessible

Getting File URLs

After uploading, use the render ID to get URLs for displaying or downloading the file:

// Upload a file
const result = await centrali.uploadFile(file, '/root/shared/images');
const renderId = result.data;  // e.g., "kvHJ4ipZ3Q6EAoguKrWmU7KYyDHcU03C"

// Get URL for displaying inline (e.g., in an <img> tag)
const renderUrl = centrali.getFileRenderUrl(renderId);

// Get URL with image transformations (resize, compress, convert)
const thumbnailUrl = centrali.getFileRenderUrl(renderId, { width: 200 });
const optimizedUrl = centrali.getFileRenderUrl(renderId, {
  width: 800,
  quality: 60,
  format: 'webp'
});

// Get URL for downloading as attachment
const downloadUrl = centrali.getFileDownloadUrl(renderId);

Important Notes

  • Paths must be absolute: Use full paths like /root/shared/images, not just images
  • Folders must exist: Create folders via the console or API before uploading to them
  • Only use /root/: The /system/ and /support/ folders are reserved for system use

Complete Examples

E-commerce Product Catalog

import { CentraliSDK } from '@centrali-io/centrali-sdk';

class ProductCatalog {
  private centrali: CentraliSDK;

  constructor() {
    this.centrali = new CentraliSDK({
      baseUrl: 'https://api.centrali.io',
      workspaceId: 'my-store',
      clientId: process.env.CENTRALI_CLIENT_ID,
      clientSecret: process.env.CENTRALI_CLIENT_SECRET
    });
  }

  // Create a new product
  async createProduct(productData: any) {
    const product = await this.centrali.createRecord('Product', {
      ...productData,
      createdAt: new Date().toISOString()
    });

    // Trigger inventory update
    await this.centrali.invokeFunction('update-inventory', {
      productId: product.id,
      action: 'initialize',
      quantity: productData.initialStock || 0
    });

    return product;
  }

  // Search products by category
  async searchProducts(category?: string) {
    const filter: Record<string, any> = {};
    if (category) {
      filter.category = category;
    }

    // Filters passed at top level with 'data.' prefix
    return await this.centrali.queryRecords('Product', {
      ...filter,  // spread filter params at top level
      sort: '-popularity',
      pageSize: 20
    });
  }

  // Get product with reviews
  async getProductWithReviews(productId: string) {
    // Get product
    const product = await this.centrali.getRecord('Product', productId);

    // Get reviews (filter at top level)
    const reviews = await this.centrali.queryRecords('Review', {
      'data.productId': productId,
      sort: '-createdAt',
      pageSize: 10
    });

    return {
      ...product.data,
      reviews: reviews.data
    };
  }

  // Update inventory
  async updateInventory(productId: string, quantity: number, operation: 'add' | 'subtract') {
    const result = await this.centrali.invokeFunction('update-inventory', {
      productId,
      quantity,
      operation
    });

    if (!result.data.success) {
      throw new Error(`Inventory update failed: ${result.data.error}`);
    }

    return result.data;
  }
}

// Usage
const catalog = new ProductCatalog();

// Create a product
const product = await catalog.createProduct({
  name: 'Wireless Mouse',
  description: 'Ergonomic wireless mouse with 6 buttons',
  price: 29.99,
  category: 'Electronics',
  initialStock: 100
});

// Search products
const searchResults = await catalog.searchProducts('wireless', 'Electronics');

User Authentication Flow

import { CentraliSDK } from '@centrali-io/centrali-sdk';

class AuthService {
  private centrali: CentraliSDK;

  constructor() {
    this.centrali = new CentraliSDK({
      baseUrl: 'https://api.centrali.io',
      workspaceId: 'my-app'
    });
  }

  // Register a new user
  async register(email: string, password: string, profile: any) {
    // Create user record
    const user = await this.centrali.createRecord('User', {
      email,
      profile,
      createdAt: new Date().toISOString()
    });

    // Trigger welcome email
    await this.centrali.invokeFunction('send-welcome-email', {
      userId: user.id,
      email
    });

    return user;
  }

  // Login (assuming you have a login endpoint that returns a JWT)
  async login(email: string, password: string): Promise<string> {
    // This would typically call your auth endpoint
    const response = await fetch('https://auth.centrali.io/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });

    const { token } = await response.json();

    // Set the token for future SDK calls
    this.centrali.setToken(token);

    return token;
  }

  // Get user profile
  async getUserProfile(userId: string) {
    return await this.centrali.getRecord('User', userId);
  }

  // Update user preferences
  async updatePreferences(userId: string, preferences: any) {
    return await this.centrali.updateRecord('User', userId, {
      preferences,
      updatedAt: new Date().toISOString()
    });
  }
}

Real-time Data Sync

import { CentraliSDK } from '@centrali-io/centrali-sdk';

class DataSyncService {
  private centrali: CentraliSDK;
  private syncInterval: NodeJS.Timer | null = null;

  constructor() {
    this.centrali = new CentraliSDK({
      baseUrl: 'https://api.centrali.io',
      workspaceId: 'my-app',
      clientId: process.env.CENTRALI_CLIENT_ID,
      clientSecret: process.env.CENTRALI_CLIENT_SECRET
    });
  }

  // Sync local data with Centrali
  async syncData(localData: any[]) {
    const results = {
      created: 0,
      updated: 0,
      errors: []
    };

    for (const item of localData) {
      try {
        if (item.centraliId) {
          // Update existing record
          await this.centrali.updateRecord('SyncedData', item.centraliId, item);
          results.updated++;
        } else {
          // Create new record
          const record = await this.centrali.createRecord('SyncedData', item);
          item.centraliId = record.id; // Store ID for future syncs
          results.created++;
        }
      } catch (error) {
        results.errors.push({ item, error: error.message });
      }
    }

    return results;
  }

  // Poll for changes (filter at top level with bracket notation)
  async pollChanges(since: Date, callback: (changes: any[]) => void) {
    const changes = await this.centrali.queryRecords('SyncedData', {
      'updatedAt[gt]': since.toISOString(),
      sort: 'updatedAt',
      pageSize: 100
    });

    if (changes.data.length > 0) {
      callback(changes.data);
    }

    return changes.data;
  }

  // Start continuous sync
  startSync(interval: number = 30000) {
    let lastSync = new Date();

    this.syncInterval = setInterval(async () => {
      try {
        await this.pollChanges(lastSync, (changes) => {
          console.log(`Received ${changes.length} changes`);
          // Process changes
        });
        lastSync = new Date();
      } catch (error) {
        console.error('Sync error:', error);
      }
    }, interval);
  }

  // Stop sync
  stopSync() {
    if (this.syncInterval) {
      clearInterval(this.syncInterval);
      this.syncInterval = null;
    }
  }
}

Error Handling

The SDK throws errors for failed requests. Always wrap API calls in try-catch blocks:

try {
  const record = await centrali.createRecord('Product', productData);
  console.log('Success:', record);
} catch (error) {
  if (error.response) {
    // API returned an error response
    console.error('API Error:', error.response.data);
    console.error('Status:', error.response.status);

    if (error.response.status === 400) {
      // Validation error
      console.error('Validation failed:', error.response.data.error);
    } else if (error.response.status === 401) {
      // Authentication failed
      console.error('Authentication required');
    }
  } else if (error.request) {
    // Request was made but no response received
    console.error('Network error:', error.message);
  } else {
    // Something else happened
    console.error('Error:', error.message);
  }
}

TypeScript Support

The SDK is written in TypeScript and provides full type definitions:

import {
  CentraliSDK,
  CentraliSDKOptions,
  ApiResponse,
  // Structure types
  Structure,
  PropertyDefinition,
  CreateStructureInput,
  // Compute function types
  ComputeFunction,
  CreateComputeFunctionInput,
  TestComputeFunctionResult,
  // Trigger types
  FunctionTrigger,
  CreateTriggerInput,
  TriggerWithHealth,
  // Smart query types
  SmartQuery,
  CreateSmartQueryInput,
} from '@centrali-io/centrali-sdk';

// Define your data types
interface Product {
  id?: string;
  name: string;
  price: number;
  description: string;
  category: string;
  inStock: boolean;
}

// Use generic types for type-safe responses
const product = await centrali.createRecord<Product>('Product', {
  name: 'Laptop',
  price: 999.99,
  description: 'High-performance laptop',
  category: 'Electronics',
  inStock: true
});

// TypeScript knows product.data is of type Product
console.log(product.data.price); // Type-safe access

Best Practices

1. Environment Variables

Store credentials securely:

// .env file
CENTRALI_BASE_URL=https://centrali.io
CENTRALI_WORKSPACE=my-workspace
CENTRALI_CLIENT_ID=your-client-id
CENTRALI_CLIENT_SECRET=your-client-secret

// Usage
const centrali = new CentraliSDK({
  baseUrl: process.env.CENTRALI_BASE_URL,
  workspaceId: process.env.CENTRALI_WORKSPACE,
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET
});

2. Singleton Pattern

Create a single SDK instance for your application:

// centrali.ts
import { CentraliSDK } from '@centrali-io/centrali-sdk';

let instance: CentraliSDK | null = null;

export function getCentraliClient(): CentraliSDK {
  if (!instance) {
    instance = new CentraliSDK({
      baseUrl: process.env.CENTRALI_BASE_URL!,
      workspaceId: process.env.CENTRALI_WORKSPACE!,
      clientId: process.env.CENTRALI_CLIENT_ID,
      clientSecret: process.env.CENTRALI_CLIENT_SECRET
    });
  }
  return instance;
}

// Usage in other files
import { getCentraliClient } from './centrali';

const centrali = getCentraliClient();

3. Retry Logic

Implement retry logic for transient failures:

async function retryOperation<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  delay: number = 1000
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      // Only retry on network errors or 5xx status codes
      if (error.response && error.response.status < 500) {
        throw error;
      }

      await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
    }
  }
  throw new Error('Max retries exceeded');
}

// Usage
const record = await retryOperation(() =>
  centrali.createRecord('Product', productData)
);

4. Batch Operations

Process large datasets efficiently:

async function batchCreate(records: any[], batchSize: number = 10) {
  const results = [];

  for (let i = 0; i < records.length; i += batchSize) {
    const batch = records.slice(i, i + batchSize);
    const promises = batch.map(record =>
      centrali.createRecord('Product', record)
        .catch(error => ({ error, record }))
    );

    const batchResults = await Promise.all(promises);
    results.push(...batchResults);
  }

  return results;
}

Troubleshooting

Authentication Issues

// Check if token is valid
const token = centrali.getToken();
console.log('Current token:', token);

// Manually refresh service account token
if (centrali.options.clientId) {
  const newToken = await centrali.fetchServiceAccountToken();
  centrali.setToken(newToken);
}

Debug Logging

Enable axios debug logging:

const centrali = new CentraliSDK({
  baseUrl: 'https://api.centrali.io',
  workspaceId: 'my-workspace',
  token: 'your-token',
  axiosConfig: {
    // Add request/response interceptors for logging
    validateStatus: (status) => {
      console.log('Response status:', status);
      return status < 500;
    }
  }
});

Common Errors

Error Cause Solution
401 Unauthorized Invalid or expired token Refresh token or check credentials
403 Forbidden Insufficient permissions Check workspace access rights
404 Not Found Invalid record ID or collection name Verify the resource exists
400 Bad Request Invalid data format Check field types and required fields
429 Too Many Requests Rate limit exceeded Implement backoff and retry

Migration from REST API

If you're currently using direct REST API calls, here's how to migrate:

Before (REST API)

// Direct API call
const response = await fetch('https://api.centrali.io/data/workspace/my-workspace/api/v1/records/slug/Product', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer token',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Product',
    price: 99.99
  })
});
const product = await response.json();

After (SDK)

// Using SDK
const product = await centrali.createRecord('Product', {
  name: 'Product',
  price: 99.99
});

Realtime Events

Subscribe to live record events using Server-Sent Events (SSE). The SDK handles connection management, authentication, and automatic reconnection.

Basic Usage

const subscription = centrali.realtime.subscribe({
  collections: ['order'],
  events: ['record_created', 'record_updated'],
  onEvent: (event) => {
    console.log('Event:', event.event, event.recordId);
    console.log('Data:', event.data);
  },
  onError: (error) => {
    console.error('Error:', error.code, error.message);
  }
});

// Later: stop receiving events
subscription.unsubscribe();

Subscription Options

interface RealtimeSubscribeOptions {
  // Filter by collection slugs (empty = all)
  collections?: string[];

  // Filter by event types (empty = all)
  events?: ('record_created' | 'record_updated' | 'record_deleted')[];

  // CFL filter expression for data filtering
  filter?: string;

  // Required: handle incoming events
  onEvent: (event: RealtimeRecordEvent) => void;

  // Optional callbacks
  onError?: (error: RealtimeError) => void;
  onConnected?: () => void;
  onDisconnected?: (reason?: string) => void;
}

Event Payload

interface RealtimeRecordEvent {
  event: 'record_created' | 'record_updated' | 'record_deleted';
  workspaceSlug: string;
  recordSlug: string;      // Collection's slug
  recordId: string;
  data: object;            // Record data
  timestamp: string;       // ISO timestamp
  createdBy?: string;
  updatedBy?: string;
}

Filtering Events

// By collection
centrali.realtime.subscribe({
  collections: ['order', 'invoice'],
  onEvent: handleEvent
});

// By event type
centrali.realtime.subscribe({
  events: ['record_created'],
  onEvent: handleEvent
});

// By data values (CFL filter)
centrali.realtime.subscribe({
  collections: ['order'],
  filter: 'data.status:shipped',
  onEvent: handleEvent
});

// Combine filters
centrali.realtime.subscribe({
  collections: ['order'],
  events: ['record_updated'],
  filter: 'data.total:gt:1000',
  onEvent: handleEvent
});

React Integration

function OrderList() {
  const [orders, setOrders] = useState<Order[]>([]);

  useEffect(() => {
    const centrali = new CentraliSDK({...});

    // 1. Fetch initial data
    centrali.queryRecords('order', { pageSize: 50 })
      .then(res => setOrders(res.data));

    // 2. Subscribe to updates
    const sub = centrali.realtime.subscribe({
      collections: ['order'],
      onEvent: (event) => {
        if (event.event === 'record_created') {
          setOrders(prev => [event.data as Order, ...prev]);
        } else if (event.event === 'record_updated') {
          setOrders(prev => prev.map(o =>
            o.id === event.recordId ? event.data.after : o
          ));
        }
      }
    });

    // 3. Cleanup
    return () => sub.unsubscribe();
  }, []);

  return <ul>{orders.map(o => <li key={o.id}>{o.name}</li>)}</ul>;
}

Error Handling

centrali.realtime.subscribe({
  onEvent: handleEvent,
  onError: (error) => {
    switch (error.code) {
      case 'TOKEN_EXPIRED':
        // Refresh token - SDK will reconnect
        refreshToken().then(t => centrali.setToken(t));
        break;
      case 'RATE_LIMIT_EXCEEDED':
        // Too many connections
        showUpgradePrompt();
        break;
      case 'FORBIDDEN':
        // No permission
        showAccessDenied();
        break;
    }
  }
});

For complete realtime documentation, see: - Realtime Quickstart - Realtime Authentication - Realtime Filtering

Support

For issues or questions: - Check the API Overview for endpoint details - Review error codes above - Contact support at support@centrali.io