Centrali Structures API Guide¶
This comprehensive guide covers everything you need to know about creating, updating, and retrieving structures in Centrali, including all validation rules, property types, and working examples.
Table of Contents¶
- Overview
- Authentication
- Structure Basics
- Property Types
- String Properties
- Number Properties
- Boolean Properties
- DateTime Properties
- Array Properties
- Object Properties
- Reference Properties
- API Operations
- Create a Structure
- Get a Structure
- List Structures
- Update a Structure
- Delete a Structure
- Restore a Structure
- Bulk Create Structures
- Bulk Delete Structures
- Bulk Delete Properties
- Validate a Structure
- Advanced Features
- Schema Migrations
- Validation Rules
- Common Errors
- Complete Examples
Overview¶
Structures in Centrali define the schema for your data records. They specify what fields (properties) your records can have, their data types, validation rules, and constraints. Think of structures as database table schemas or JSON schemas.
Key Concepts: - Structure: A schema definition that describes the shape of your data - Properties: Individual fields in a structure (like columns in a database) - Record Slug: A unique identifier for the type of records this structure describes - Schema Discovery Mode: Controls how the structure handles fields not defined in the schema (strict, auto-evolving, or schemaless) - Versioning: Track changes to structures over time
Authentication¶
All structure API requests require authentication via JWT token. See the Service Account Authentication Guide for details on obtaining tokens.
Headers Required:
Structure Basics¶
Structure Object Schema¶
{
"id": "uuid", // Auto-generated UUID
"workspaceSlug": "string", // Your workspace identifier
"recordSlug": "string", // Unique identifier for records of this type
"name": "string", // Human-readable name
"description": "string", // Optional description
"properties": [...], // Array of property definitions (see below)
"status": "active" | "inactive", // Structure status
"schemaDiscoveryMode": "strict" | "auto-evolving" | "schemaless", // How unrecognized fields are handled
"enableVersioning": boolean, // Enable structure version tracking
"retentionPolicy": {...}, // Optional retention policy for versions
"defaultSearchField": "string", // Default field for text search
"schemaDiscoveryConfig": {...}, // Schema discovery settings (when mode is not strict)
"metadata": { // System metadata (read-only)
"recordWritesLocked": boolean, // Prevents new record inserts during migration
"structureWritesLocked": boolean, // Prevents schema updates during migration
"migrationInProgress": boolean, // Whether a migration job is active
"migrationJobId": "string", // Active migration job ID
"lastMigrationId": "string" // Last completed migration ID
},
"tags": ["string"], // Tags for security/categorization
"createdBy": "string", // Creator user ID
"lastUpdatedBy": "string", // Last modifier user ID
"createdAt": "datetime", // Creation timestamp
"updatedAt": "datetime", // Last update timestamp
"isDeleted": boolean // Soft delete flag
}
Required Fields¶
name: Structure name (cannot be empty)properties: Array of property definitions (can be empty)recordSlug: Unique identifier (auto-generated from name if not provided)
Optional Fields¶
description: Structure descriptionschemaDiscoveryMode: Default is"strict". Controls how unrecognized fields are handled:"strict"-- reject records with fields not defined in the schema. Queries are validated against defined properties"auto-evolving"-- accept extra fields and suggest new schema properties. Queries are validated against registered properties with full type-aware behavior"schemaless"-- no schema enforcement. Queries can filter/sort on any field, but all fields are treated as strings (no numeric casting or type-aware sorting). Use auto-evolving mode if you need type-aware queriesschemaDiscoveryConfig: Configuration for schema discovery (applies when mode isauto-evolvingorschemaless):batchSize: Number of records to sample (default: 10)maxKeysPerRecord: Maximum keys per record to track (default: 100)maxNestingDepth: Maximum object nesting depth (default: 3)maxStringLengthSampled: Maximum string length to sample (default: 1000)enableVersioning: Default isfalsestatus: Default is"active"defaultSearchField: Used for text search operationsretentionPolicy: Version retention configurationtags: Array of tags for security
Property Types¶
Each property in a structure must specify a type. Centrali supports seven property types.
Common Property Fields¶
All property types share these base fields:
{
"id": "string", // Auto-generated unique ID (optional on create)
"name": "string", // Field name (required)
"type": "string", // Property type (required)
"description": "string", // Optional description
"required": boolean, // Is this field required? (default: false)
"nullable": boolean, // Can this field be null? (default: false)
"default": any, // Default value if not provided
"enum": [...], // Restrict to specific values
"isUnique": boolean, // Must values be unique? (default: false)
"immutable": boolean // Can't be changed after creation (default: false)
}
String Properties¶
Type: "string"
Additional Fields:
{
"type": "string",
"minLength": number, // Minimum string length
"maxLength": number, // Maximum string length
"pattern": "regex", // Regular expression pattern
"renderAs": "textarea" | "secret" | "color" | "code" | "html" | "markdown",
"isSecret": boolean, // Encrypt at rest, mask on read (default: false)
"not": [...] // Array of disallowed values
}
Validation Rules: - minLength must be ≤ maxLength - pattern must be a valid regex - enum values must all be strings matching constraints - default must satisfy minLength, maxLength, and pattern - not values cannot overlap with enum or default - When isSecret is true, the field value is encrypted at rest (AES-256-GCM) and masked in API responses. Requires the secrets:reveal permission to read the plaintext value.
Example:
{
"name": "email",
"type": "string",
"description": "User email address",
"required": true,
"minLength": 5,
"maxLength": 255,
"pattern": "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$",
"isUnique": true
}
Example with Enum:
{
"name": "status",
"type": "string",
"enum": ["draft", "published", "archived"],
"default": "draft",
"required": true
}
Example with renderAs:
Example with Secret Field:
{
"name": "apiToken",
"type": "string",
"isSecret": true,
"required": true,
"description": "Third-party API token (encrypted at rest)"
}
Note: Secret fields are encrypted with AES-256-GCM and returned as masked values (e.g.,
"••••••••") unless the caller hassecrets:revealpermission.
Number Properties¶
Type: "number"
Additional Fields:
{
"type": "number",
"minimum": number, // Minimum value (inclusive by default)
"maximum": number, // Maximum value (inclusive by default)
"exclusiveMinimum": boolean, // Make minimum exclusive (default: false)
"exclusiveMaximum": boolean, // Make maximum exclusive (default: false)
"multipleOf": number, // Value must be a multiple of this
"expression": "string", // Computed field expression
"computedMode": "persisted" | "virtual",
"autoIncrement": { // Auto-increment configuration
"startAt": number,
"incrementBy": number // Default: 1
}
}
Validation Rules: - minimum must be ≤ maximum - multipleOf must be a positive number - enum values must satisfy min/max/multipleOf constraints - default must satisfy all numeric constraints - If exclusiveMinimum is true, values must be > minimum (not >=) - If exclusiveMaximum is true, values must be < maximum (not <=) - autoIncrement.startAt and incrementBy must be numbers
Example:
{
"name": "price",
"type": "number",
"description": "Product price in USD",
"required": true,
"minimum": 0,
"exclusiveMinimum": true,
"multipleOf": 0.01
}
Example with Enum:
Example with Auto-Increment:
{
"name": "orderNumber",
"type": "number",
"autoIncrement": {
"startAt": 1000,
"incrementBy": 1
},
"required": true
}
Boolean Properties¶
Type: "boolean"
Additional Fields:
{
"type": "boolean",
"default": true | false,
"enum": [true, false] // Usually not needed for boolean
}
Validation Rules: - default must be a boolean - enum values (if provided) must be boolean
Example:
{
"name": "isActive",
"type": "boolean",
"description": "Whether the item is active",
"default": true,
"required": true
}
DateTime Properties¶
Type: "datetime"
Additional Fields:
{
"type": "datetime",
"earliestDate": "ISO8601", // Earliest allowed date
"latestDate": "ISO8601", // Latest allowed date
"exclusiveEarliest": boolean,// Make earliest exclusive
"exclusiveLatest": boolean, // Make latest exclusive
"expression": "string", // Computed field expression
"computedMode": "persisted" | "virtual"
}
Validation Rules: - Dates must be in ISO 8601 format (e.g., "2025-01-15T10:30:00Z") - earliestDate must be before latestDate - default must be a valid ISO 8601 datetime - enum values must be valid ISO 8601 datetimes - All dates must satisfy earliest/latest constraints
Example:
{
"name": "birthDate",
"type": "datetime",
"description": "User birth date",
"earliestDate": "1900-01-01T00:00:00Z",
"latestDate": "2010-12-31T23:59:59Z",
"required": true
}
Example with Default:
{
"name": "createdAt",
"type": "datetime",
"description": "Record creation timestamp",
"default": "2025-01-15T00:00:00Z",
"immutable": true
}
Array Properties¶
Type: "array"
Additional Fields:
{
"type": "array",
"items": { // Item type definition (required)
"type": "string" | "number" | "boolean" | "datetime" | "object"
},
"itemSchema": [...], // Array of property definitions (required when items.type is "object")
"isStrict": boolean, // For object items: whether to allow extra properties (default: true)
"minItems": number, // Minimum array length
"maxItems": number, // Maximum array length
"uniqueItems": boolean // All items must be unique (default: false)
}
Validation Rules: - items.type is required and must be a valid type - items.type cannot be "array" (no nested arrays directly) - When items.type is "object", itemSchema must be provided with at least one property - itemSchema properties are validated using the same rules as top-level properties - Property names within itemSchema must be unique - minItems must be ≤ maxItems
Example (String Array):
{
"name": "tags",
"type": "array",
"description": "Product tags",
"items": {
"type": "string"
},
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
Example (Number Array):
{
"name": "scores",
"type": "array",
"items": {
"type": "number"
},
"minItems": 0,
"maxItems": 100
}
Example (Array of Objects):
{
"name": "contacts",
"type": "array",
"description": "List of contact persons",
"items": {
"type": "object"
},
"itemSchema": [
{
"name": "name",
"type": "string",
"required": true,
"maxLength": 100
},
{
"name": "email",
"type": "string",
"required": true,
"pattern": "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"
},
{
"name": "phone",
"type": "string",
"required": false,
"pattern": "^\\+?[1-9]\\d{1,14}$"
},
{
"name": "isPrimary",
"type": "boolean",
"default": false
}
],
"isStrict": true,
"minItems": 1,
"maxItems": 5
}
Note on Object Arrays: - When items.type is "object", the itemSchema field defines the structure of each object in the array - Each object in the array will be validated against the itemSchema - Use isStrict: true (default) to reject objects with extra properties not defined in itemSchema - Use isStrict: false to allow additional properties beyond those in itemSchema - Properties in itemSchema can have all the same constraints as top-level properties (required, default, validation rules, etc.)
Note: The
isStrictfield on array and object properties controls validation of nested object fields. This is separate from the structure-levelschemaDiscoveryMode, which controls how the overall structure handles unrecognized top-level fields.
Object Properties¶
Type: "object"
Additional Fields:
{
"type": "object",
"properties": [...], // Array of nested property definitions
"requiredProperties": [...], // Names of required nested properties
"isStrict": boolean // Whether to allow extra fields (default: false)
}
Validation Rules: - Nested properties are validated recursively - Property names within an object must be unique - requiredProperties must reference existing properties - Nested properties can be any type (including nested objects)
Example:
{
"name": "address",
"type": "object",
"description": "User address",
"required": true,
"properties": [
{
"name": "street",
"type": "string",
"required": true,
"maxLength": 100
},
{
"name": "city",
"type": "string",
"required": true
},
{
"name": "zipCode",
"type": "string",
"pattern": "^\\d{5}(-\\d{4})?$"
},
{
"name": "country",
"type": "string",
"default": "USA"
}
],
"requiredProperties": ["street", "city"],
"isStrict": false
}
Reference Properties¶
Type: "reference"
Reference properties create relationships between records in different structures (similar to foreign keys in relational databases). They enable you to link records together and enforce referential integrity.
Additional Fields:
{
"type": "reference",
"target": "string", // Target structure's recordSlug (required)
"targetField": "string", // Field to match in target (default: "id")
"displayField": "string", // Field from target to show in UIs
"relationship": "many-to-one" | "one-to-one" | "many-to-many", // Relationship type (required)
"onDelete": "restrict" | "cascade" | "set_null" // Delete behavior (required)
}
Relationship Types:
| Type | Storage | Use Case |
|---|---|---|
many-to-one | Single UUID | Many orders → one customer (most common) |
one-to-one | Single UUID | One user → one profile (enforces uniqueness) |
many-to-many | Array of UUIDs | Many posts → many tags |
Delete Behaviors:
| Behavior | Description |
|---|---|
restrict | Prevent deletion if any records reference this one (returns HTTP 409) |
cascade | Automatically delete all referencing records |
set_null | Set the reference field to null (requires nullable: true) |
Validation Rules: - target must be an existing structure's recordSlug in the same workspace - targetField must exist on the target structure (if not "id") - displayField must exist on the target structure - set_null requires nullable: true on the property - Circular references are not allowed (A→B→A) - one-to-one enforces that no two records can reference the same target - Reference values are validated on create/update to ensure target records exist
Example (Many-to-One - Order to Customer):
{
"name": "customer",
"type": "reference",
"description": "Customer who placed this order",
"target": "customers",
"displayField": "name",
"relationship": "many-to-one",
"onDelete": "restrict",
"required": true
}
Example (One-to-One - User to Profile):
{
"name": "user",
"type": "reference",
"description": "User this profile belongs to",
"target": "users",
"displayField": "email",
"relationship": "one-to-one",
"onDelete": "cascade",
"required": true
}
Example (Many-to-Many - Post to Tags):
{
"name": "tags",
"type": "reference",
"description": "Tags for this post",
"target": "tags",
"displayField": "name",
"relationship": "many-to-many",
"onDelete": "restrict",
"required": false,
"nullable": true
}
Example (Set Null on Delete):
{
"name": "assignee",
"type": "reference",
"description": "Assigned team member",
"target": "team-members",
"displayField": "name",
"relationship": "many-to-one",
"onDelete": "set_null",
"required": false,
"nullable": true
}
Query Expansion:
When querying records, you can expand reference fields to include the full referenced record data:
# Expand single reference
GET /workspace/acme/api/v1/records/slug/orders?expand=customer
# Expand multiple references
GET /workspace/acme/api/v1/records/slug/orders?expand=customer,assignee
# Expand nested references
GET /workspace/acme/api/v1/records/slug/orders?expand=customer.address
The expanded data appears in an _expanded object within each record's data:
{
"id": "order-123",
"data": {
"customer": "customer-456",
"_expanded": {
"customer": {
"id": "customer-456",
"data": {
"name": "Acme Corp",
"email": "contact@acme.com"
}
}
}
}
}
API Operations¶
Create a Structure¶
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures
Request Body:
{
"name": "Products",
"description": "Product catalog structure",
"recordSlug": "products",
"schemaDiscoveryMode": "strict",
"enableVersioning": true,
"properties": [
{
"name": "name",
"type": "string",
"required": true,
"minLength": 3,
"maxLength": 100
},
{
"name": "price",
"type": "number",
"required": true,
"minimum": 0,
"exclusiveMinimum": true
},
{
"name": "inStock",
"type": "boolean",
"default": true
},
{
"name": "tags",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
]
}
curl Example:
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Products",
"description": "Product catalog structure",
"recordSlug": "products",
"schemaDiscoveryMode": "strict",
"properties": [
{
"name": "name",
"type": "string",
"required": true,
"minLength": 3,
"maxLength": 100
},
{
"name": "price",
"type": "number",
"required": true,
"minimum": 0
}
]
}'
Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"workspaceSlug": "acme-corp",
"recordSlug": "products",
"name": "Products",
"description": "Product catalog structure",
"properties": [
{
"id": "a1b2c3d4",
"name": "name",
"type": "string",
"required": true,
"minLength": 3,
"maxLength": 100
},
{
"id": "e5f6g7h8",
"name": "price",
"type": "number",
"required": true,
"minimum": 0
}
],
"status": "active",
"schemaDiscoveryMode": "strict",
"enableVersioning": false,
"isDeleted": false,
"createdBy": "user-uuid-123",
"lastUpdatedBy": "user-uuid-123",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
Notes: - recordSlug is auto-generated from name if not provided - Property id fields are auto-generated - The structure is automatically versioned if enableVersioning is true
Get a Structure¶
By ID¶
Endpoint: GET /workspace/{workspaceSlug}/api/v1/structures/{id}
curl -X GET "https://api.centrali.io/workspace/acme-corp/api/v1/structures/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
By Record Slug¶
Endpoint: GET /workspace/{workspaceSlug}/api/v1/structures/slug/{recordSlug}
curl -X GET "https://api.centrali.io/workspace/acme-corp/api/v1/structures/slug/products" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Response: Returns the structure object (same format as create response)
List Structures¶
Endpoint: GET /workspace/{workspaceSlug}/api/v1/structures
Query Parameters: - page (number): Page number (default: 1) - limit (number): Results per page (default: 500, max: 500) - search (string): Search term - searchField (string): Field to search on (default: "name") - sort[field] (string): Sort field - sort[direction] (string): "asc" or "desc" - filter[status] (string): Filter by status - filter[isDeleted] (boolean): Filter by deletion status
Example:
curl -X GET "https://api.centrali.io/workspace/acme-corp/api/v1/structures?page=1&limit=10&search=product&searchField=name" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Products",
"recordSlug": "products",
"...": "..."
}
],
"meta": {
"total": 1,
"page": 1,
"pageSize": 10
}
}
Update a Structure¶
Endpoint: PUT /workspace/{workspaceSlug}/api/v1/structures/{id}
Important Notes: - Updating properties requires providing the full properties array - Each existing property must include its id field - To add a new property, set its id to "new" (it will be auto-generated) - Omitting a property from the array does NOT delete it; use the bulk delete endpoint
Request Body:
{
"name": "Products Updated",
"properties": [
{
"id": "a1b2c3d4",
"name": "name",
"type": "string",
"required": true,
"minLength": 5,
"maxLength": 150
},
{
"id": "e5f6g7h8",
"name": "price",
"type": "number",
"required": true,
"minimum": 0
},
{
"id": "new",
"name": "sku",
"type": "string",
"required": true,
"isUnique": true
}
]
}
curl Example:
curl -X PUT "https://api.centrali.io/workspace/acme-corp/api/v1/structures/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Products Updated",
"properties": [
{
"id": "a1b2c3d4",
"name": "name",
"type": "string",
"required": true,
"minLength": 5,
"maxLength": 150
}
]
}'
Response: Returns the updated structure object
Delete a Structure¶
Soft Delete (Default)¶
Endpoint: DELETE /workspace/{workspaceSlug}/api/v1/structures/{id}
curl -X DELETE "https://api.centrali.io/workspace/acme-corp/api/v1/structures/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Hard Delete (Permanent)¶
Endpoint: DELETE /workspace/{workspaceSlug}/api/v1/structures/{id}?hard=true
curl -X DELETE "https://api.centrali.io/workspace/acme-corp/api/v1/structures/550e8400-e29b-41d4-a716-446655440000?hard=true" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Response: Returns the deleted structure object
Restore a Structure¶
Restore a soft-deleted structure.
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures/{id}/restore
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures/550e8400-e29b-41d4-a716-446655440000/restore" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Response: Returns the restored structure object with isDeleted: false
Bulk Create Structures¶
Create multiple structures at once (max 100).
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures/bulk
Request Body:
{
"items": [
{
"name": "Products",
"recordSlug": "products",
"properties": [...]
},
{
"name": "Categories",
"recordSlug": "categories",
"properties": [...]
}
]
}
Bulk Delete Structures¶
Delete multiple structures by IDs.
Endpoint: DELETE /workspace/{workspaceSlug}/api/v1/structures/bulk
Request Body:
Bulk Delete Properties¶
Remove specific properties from a structure.
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures/{id}/properties/delete
Request Body:
Validate a Structure¶
Test whether a structure definition is valid without creating it.
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures/validate
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures/validate" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Test Structure",
"properties": [
{
"name": "email",
"type": "string",
"minLength": 5,
"maxLength": 255
}
]
}'
Response (Valid):
Response (Invalid):
{
"valid": false,
"errors": [
{
"field": "minLength",
"message": "For property email Minimum length cannot exceed maximum length."
}
]
}
Advanced Features¶
Retention Policies¶
Configure automatic cleanup of old structure versions:
{
"retentionPolicy": {
"maxVersions": 10,
"maxAgeDays": 30,
"cleanupIntervalHours": 24,
"pinnedVersionsRetained": true
}
}
Computed Fields¶
Create fields whose values are calculated from other fields:
{
"name": "totalPrice",
"type": "number",
"expression": "price * quantity",
"computedMode": "virtual"
}
Auto-Increment Fields¶
Automatically assign sequential numbers:
{
"name": "invoiceNumber",
"type": "number",
"autoIncrement": {
"startAt": 1000,
"incrementBy": 1
}
}
Structure History¶
Get the full version history of a structure:
Endpoint: GET /workspace/{workspaceSlug}/api/v1/structures/{id}/history
curl -X GET "https://api.centrali.io/workspace/acme-corp/api/v1/structures/550e8400-e29b-41d4-a716-446655440000/history" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Schema Migrations¶
When you update a structure's properties, Centrali can analyze the impact on existing records and run migrations to keep data consistent.
Analyze Changes Before Updating¶
Preview what would happen if you applied changes to the structure.
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures/{id}/analyze-update
Request Body: Same as the update structure request body.
Response:
{
"requiresMigration": true,
"canSkipMigration": false,
"totalAffectedRecords": 1250,
"breakingChanges": {
"renamedFields": [],
"deletedFields": [{ "propertyId": "a1b2c3d4", "name": "oldField" }],
"typeChanges": [],
"constraintChanges": []
},
"nonBreakingChanges": { ... }
}
Upgrade Structure with Migration¶
Atomically update the structure schema and migrate existing records. The structure is locked during migration to prevent concurrent changes.
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures/{id}/upgrade
Request Body:
{
"name": "Products Updated",
"properties": [...],
"migrationOptions": {
"mode": "auto",
"synchronous": false
}
}
Migration Modes:
| Mode | Description |
|---|---|
auto | Centrali automatically handles migrations for affected records |
manual | You provide specific fixes for each breaking change |
skip | Skip migration (only when canSkipMigration is true) |
Track Migration Progress¶
Endpoint: GET /workspace/{workspaceSlug}/api/v1/structures/{id}/upgrade/{jobId}/progress
Response:
{
"jobId": "job-uuid",
"status": "running",
"progress": 75,
"totalRecords": 1250,
"processedRecords": 937
}
Force Unlock Structure¶
If a migration gets stuck, force-unlock the structure.
Endpoint: POST /workspace/{workspaceSlug}/api/v1/structures/{id}/force-unlock
Validation Rules¶
General Rules¶
- Unique Property Names: Property names must be unique within a structure
- Required Name: The
namefield is required for both structures and properties - Type Required: Every property must have a valid
type - No Duplicate IDs: Property IDs must be unique
String Validation¶
minLength≤maxLengthpatternmust be valid regexenumvalues must be stringsdefaultmust satisfy all constraintsnotarray cannot containdefaultorenumvalues
Number Validation¶
minimum≤maximummultipleOfmust be positiveenumvalues must satisfy min/max/multipleOf- Exclusive bounds work as:
value > minorvalue < max
DateTime Validation¶
- All dates must be ISO 8601 format
earliestDate<latestDatedefaultandenummust satisfy date bounds
Array Validation¶
items.typeis required- No nested arrays (array of arrays not supported)
minItems≤maxItems
Object Validation¶
- Nested properties validated recursively
requiredPropertiesmust reference existing properties- Nested object properties cannot have duplicate names
Reference Validation¶
targetmust be an existing structure'srecordSlugrelationshipmust be one of:many-to-one,one-to-one,many-to-manyonDeletemust be one of:restrict,cascade,set_nullset_nullrequiresnullable: truetargetField(if provided) must exist on the target structuredisplayField(if provided) must exist on the target structure- Circular references are blocked (A→B→A is not allowed)
- Reference values must point to existing active records
one-to-onerelationships enforce uniqueness (no two records can reference the same target)
Common Errors¶
Error: "Missing required field name"¶
Cause: Structure or property missing the name field
Solution: Ensure every structure and property has a name field
Error: "Duplicate property name 'email'"¶
Cause: Two properties have the same name
Solution: Ensure all property names are unique
Error: "For property price Minimum length cannot exceed maximum length"¶
Cause: minLength > maxLength (or minimum > maximum)
Solution: Fix the constraint values
Error: "Invalid regex pattern"¶
Cause: The pattern field contains invalid regex
Solution: Test your regex and escape special characters
Error: "Property 'type' is missing"¶
Cause: Property doesn't have a type field
Solution: Add the type field
Error: "Unsupported property type provided"¶
Cause: Invalid type value
Solution: Use one of: string, number, boolean, datetime, array, object, reference
Error: "Structure with record slug 'products' already exists"¶
Cause: A structure with that recordSlug already exists
Solution: Use a different recordSlug or update the existing structure
Error: "Property ID is required"¶
Cause: When updating, existing properties must include their id
Solution: Include the id field for all existing properties, or use "new" for new properties
{
"properties": [
{
"id": "a1b2c3d4",
"name": "existingField",
"type": "string"
},
{
"id": "new",
"name": "newField",
"type": "number"
}
]
}
Complete Examples¶
Example 1: E-Commerce Product Structure¶
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Products",
"description": "E-commerce product catalog",
"recordSlug": "products",
"schemaDiscoveryMode": "strict",
"enableVersioning": true,
"properties": [
{
"name": "sku",
"type": "string",
"description": "Stock Keeping Unit",
"required": true,
"isUnique": true,
"pattern": "^[A-Z]{3}-\\d{6}$",
"immutable": true
},
{
"name": "name",
"type": "string",
"description": "Product name",
"required": true,
"minLength": 3,
"maxLength": 200
},
{
"name": "description",
"type": "string",
"description": "Product description",
"renderAs": "markdown",
"maxLength": 5000
},
{
"name": "price",
"type": "number",
"description": "Product price in USD",
"required": true,
"minimum": 0,
"exclusiveMinimum": true,
"multipleOf": 0.01
},
{
"name": "compareAtPrice",
"type": "number",
"description": "Original price for comparison",
"minimum": 0,
"nullable": true
},
{
"name": "inStock",
"type": "boolean",
"description": "Is product in stock",
"default": true,
"required": true
},
{
"name": "category",
"type": "string",
"description": "Product category",
"enum": ["electronics", "clothing", "books", "home", "toys"],
"required": true
},
{
"name": "tags",
"type": "array",
"description": "Product tags",
"items": {
"type": "string"
},
"uniqueItems": true,
"maxItems": 10
},
{
"name": "releaseDate",
"type": "datetime",
"description": "Product release date",
"nullable": true
},
{
"name": "specifications",
"type": "object",
"description": "Product specifications",
"properties": [
{
"name": "weight",
"type": "number",
"description": "Weight in kg"
},
{
"name": "dimensions",
"type": "object",
"properties": [
{
"name": "length",
"type": "number"
},
{
"name": "width",
"type": "number"
},
{
"name": "height",
"type": "number"
}
]
},
{
"name": "color",
"type": "string"
},
{
"name": "material",
"type": "string"
}
]
}
]
}'
Example 2: User Profile Structure¶
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "User Profiles",
"recordSlug": "user-profiles",
"schemaDiscoveryMode": "auto-evolving",
"properties": [
{
"name": "email",
"type": "string",
"required": true,
"isUnique": true,
"pattern": "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"
},
{
"name": "firstName",
"type": "string",
"required": true,
"minLength": 1,
"maxLength": 50
},
{
"name": "lastName",
"type": "string",
"required": true,
"minLength": 1,
"maxLength": 50
},
{
"name": "birthDate",
"type": "datetime",
"earliestDate": "1900-01-01T00:00:00Z",
"latestDate": "2010-12-31T23:59:59Z"
},
{
"name": "phoneNumber",
"type": "string",
"pattern": "^\\+?[1-9]\\d{1,14}$",
"nullable": true
},
{
"name": "address",
"type": "object",
"properties": [
{
"name": "street",
"type": "string",
"required": true
},
{
"name": "city",
"type": "string",
"required": true
},
{
"name": "state",
"type": "string"
},
{
"name": "zipCode",
"type": "string",
"pattern": "^\\d{5}(-\\d{4})?$"
},
{
"name": "country",
"type": "string",
"default": "USA",
"required": true
}
],
"requiredProperties": ["street", "city", "country"]
},
{
"name": "interests",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
{
"name": "isVerified",
"type": "boolean",
"default": false
}
]
}'
Example 3: Blog Post Structure with Auto-Increment¶
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Blog Posts",
"recordSlug": "blog-posts",
"schemaDiscoveryMode": "strict",
"enableVersioning": true,
"properties": [
{
"name": "postNumber",
"type": "number",
"description": "Sequential post number",
"autoIncrement": {
"startAt": 1,
"incrementBy": 1
},
"required": true,
"immutable": true
},
{
"name": "title",
"type": "string",
"required": true,
"minLength": 5,
"maxLength": 150
},
{
"name": "slug",
"type": "string",
"description": "URL-friendly slug",
"required": true,
"isUnique": true,
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
},
{
"name": "content",
"type": "string",
"description": "Post content in markdown",
"required": true,
"renderAs": "markdown",
"minLength": 100
},
{
"name": "status",
"type": "string",
"enum": ["draft", "published", "archived"],
"default": "draft",
"required": true
},
{
"name": "publishedAt",
"type": "datetime",
"nullable": true
},
{
"name": "author",
"type": "object",
"required": true,
"properties": [
{
"name": "id",
"type": "string",
"required": true
},
{
"name": "name",
"type": "string",
"required": true
},
{
"name": "email",
"type": "string",
"required": true
}
],
"requiredProperties": ["id", "name", "email"],
"isStrict": true
},
{
"name": "categories",
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"maxItems": 5,
"uniqueItems": true
},
{
"name": "featured",
"type": "boolean",
"default": false
},
{
"name": "viewCount",
"type": "number",
"minimum": 0,
"default": 0
}
],
"retentionPolicy": {
"maxVersions": 20,
"maxAgeDays": 90,
"cleanupIntervalHours": 24
}
}'
Example 4: Order Management with References¶
This example demonstrates how to create related structures using reference properties.
Step 1: Create the Customers Structure
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Customers",
"recordSlug": "customers",
"schemaDiscoveryMode": "strict",
"properties": [
{
"name": "name",
"type": "string",
"required": true,
"maxLength": 200
},
{
"name": "email",
"type": "string",
"required": true,
"isUnique": true,
"pattern": "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"
},
{
"name": "tier",
"type": "string",
"enum": ["bronze", "silver", "gold", "platinum"],
"default": "bronze"
}
]
}'
Step 2: Create the Products Structure
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Products",
"recordSlug": "products",
"schemaDiscoveryMode": "strict",
"properties": [
{
"name": "sku",
"type": "string",
"required": true,
"isUnique": true
},
{
"name": "name",
"type": "string",
"required": true
},
{
"name": "price",
"type": "number",
"required": true,
"minimum": 0
}
]
}'
Step 3: Create the Orders Structure with References
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/structures" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Orders",
"recordSlug": "orders",
"schemaDiscoveryMode": "strict",
"enableVersioning": true,
"properties": [
{
"name": "orderNumber",
"type": "number",
"description": "Auto-generated order number",
"autoIncrement": {
"startAt": 10000,
"incrementBy": 1
},
"required": true,
"immutable": true
},
{
"name": "customer",
"type": "reference",
"description": "Customer who placed this order",
"target": "customers",
"displayField": "name",
"relationship": "many-to-one",
"onDelete": "restrict",
"required": true
},
{
"name": "items",
"type": "array",
"description": "Order line items",
"items": {
"type": "object"
},
"itemSchema": [
{
"name": "product",
"type": "reference",
"target": "products",
"displayField": "name",
"relationship": "many-to-one",
"onDelete": "restrict",
"required": true
},
{
"name": "quantity",
"type": "number",
"required": true,
"minimum": 1
},
{
"name": "unitPrice",
"type": "number",
"required": true,
"minimum": 0
}
],
"minItems": 1
},
{
"name": "status",
"type": "string",
"enum": ["pending", "confirmed", "shipped", "delivered", "cancelled"],
"default": "pending",
"required": true
},
{
"name": "assignedTo",
"type": "reference",
"description": "Team member handling this order",
"target": "team-members",
"displayField": "name",
"relationship": "many-to-one",
"onDelete": "set_null",
"required": false,
"nullable": true
},
{
"name": "totalAmount",
"type": "number",
"description": "Calculated total",
"expression": "items.reduce((sum, item) => sum + (item.quantity * item.unitPrice), 0)",
"computedMode": "virtual"
},
{
"name": "createdAt",
"type": "datetime",
"immutable": true
}
]
}'
Creating an Order Record:
curl -X POST "https://api.centrali.io/workspace/acme-corp/api/v1/records/slug/orders" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"data": {
"customer": "550e8400-e29b-41d4-a716-446655440001",
"items": [
{
"product": "550e8400-e29b-41d4-a716-446655440002",
"quantity": 2,
"unitPrice": 29.99
},
{
"product": "550e8400-e29b-41d4-a716-446655440003",
"quantity": 1,
"unitPrice": 49.99
}
],
"createdAt": "2025-01-15T10:30:00Z"
}
}'
Querying Orders with Expanded References:
# Get orders with customer details expanded
curl -X GET "https://api.centrali.io/workspace/acme-corp/api/v1/records/slug/orders?expand=customer" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Support & Resources¶
- API Documentation: See the full Centrali API Docs
- Authentication: Service Account Authentication Guide
- Records API: See the Records API Guide for creating/managing records with these structures
- Issue Reporting: GitHub Issues