Data API

Typed-table CRUD service with dynamic row schemas, full-text search, bulk operations, CSV import/export, and real-time SSE change streams.

Tables

Tables define the schema for your data. Each table has typed columns that are enforced on write. Schemas can be migrated (add/drop columns) without downtime.

MethodPathDescription
GET /v1/tables List all tables for the org
POST /v1/tables Create a new table with column definitions
DELETE /v1/tables/{table} Drop a table and all its data
POST /v1/tables/{table}/migrate Migrate schema (add/drop columns)

SDK Examples

// Create a table
const table = await client.data.createTable({
  name: 'contacts',
  columns: [
    { name: 'email', type: 'text', required: true },
    { name: 'name', type: 'text' },
    { name: 'age', type: 'integer' },
    { name: 'active', type: 'boolean', default: true },
  ],
});

// List tables
const { data: tables } = await client.data.listTables();

// Add a column
await client.data.migrateTable('contacts', {
  add: [{ name: 'company', type: 'text' }],
});

// Drop a table
await client.data.dropTable('contacts');
// Rust SDK
use kapable_sdk::data::types::*;

let table = client.data().create_table(&CreateTableRequest {
    name: "contacts".into(),
    columns: vec![
        ColumnDef { name: "email".into(), col_type: "text".into(), required: Some(true), default: None },
        ColumnDef { name: "name".into(), col_type: "text".into(), required: None, default: None },
        ColumnDef { name: "age".into(), col_type: "integer".into(), required: None, default: None },
    ],
}).await?;

let tables = client.data().list_tables().await?;

client.data().migrate_table("contacts", &MigrateTableRequest {
    add: Some(vec![ColumnDef { name: "company".into(), col_type: "text".into(), required: None, default: None }]),
    drop: None,
}).await?;

client.data().drop_table("contacts").await?;

Row CRUD

Rows are dynamic JSON objects whose shape matches the table schema. The Data service validates column types on write and returns errors for type mismatches.

MethodPathDescription
GET /v1/{table} List rows with pagination and filters
POST /v1/{table} Create one or more rows
GET /v1/{table}/{id} Get a single row by UUID
PUT /v1/{table}/{id} Replace a row (full update)
PATCH /v1/{table}/{id} Patch a row (partial update)
DELETE /v1/{table}/{id} Delete a row
POST /v1/{table}/search Full-text search across all text columns

SDK Examples

// Create a single row
const row = await client.data.createRows('contacts', {
  email: 'alice@example.com',
  name: 'Alice',
  age: 30,
});

// Create multiple rows (batch)
const rows = await client.data.createRows('contacts', [
  { email: 'bob@example.com', name: 'Bob', age: 25 },
  { email: 'carol@example.com', name: 'Carol', age: 35 },
]);

// List with pagination
const { data, total } = await client.data.listRows('contacts', {
  limit: 20,
  offset: 0,
  sort: 'name',
  order: 'asc',
});

// Get a single row
const contact = await client.data.getRow('contacts', rowId);

// Patch a row (partial update)
await client.data.patchRow('contacts', rowId, {
  name: 'Alice Updated',
});

// Full-text search
const results = await client.data.searchRows('contacts', {
  query: 'alice',
  limit: 10,
});

// Auto-paginate
for await (const row of client.data.paginateRows('contacts')) {
  console.log(row.email, row.name);
}
// Rust SDK
use serde_json::json;

let row = client.data().create_rows("contacts", &json!({
    "email": "alice@example.com",
    "name": "Alice",
    "age": 30
})).await?;

let result = client.data().list_rows("contacts", &DataListParams {
    limit: Some(20),
    offset: Some(0),
    sort: Some("name".into()),
    order: Some("asc".into()),
    ..Default::default()
}).await?;

let contact = client.data().get_row("contacts", row_id).await?;

client.data().patch_row("contacts", row_id, &json!({
    "name": "Alice Updated"
})).await?;

let search = client.data().search_rows("contacts", &SearchRowsRequest {
    query: "alice".into(),
    limit: Some(10),
    ..Default::default()
}).await?;

Bulk Operations

Bulk endpoints accept arrays of IDs or objects and operate in a single database transaction for consistency.

MethodPathDescription
POST /v1/{table}/bulk/update Update multiple rows by ID
POST /v1/{table}/bulk/delete Delete multiple rows by ID
POST /v1/{table}/import Import rows from CSV
POST /v1/{table}/export Export all rows as JSON (max 10,000)

SDK Examples

// Bulk update
await client.data.bulkUpdate('contacts', {
  rows: [
    { id: 'uuid-1', data: { active: false } },
    { id: 'uuid-2', data: { active: false } },
  ],
});

// Bulk delete
await client.data.bulkDelete('contacts', {
  ids: ['uuid-1', 'uuid-2', 'uuid-3'],
});

// CSV import
const csv = `email,name,age
dave@example.com,Dave,40
eve@example.com,Eve,28`;
const imported = await client.data.importCsv('contacts', csv);

// Export all rows as JSON
const allRows = await client.data.exportData('contacts');
// Rust SDK
client.data().bulk_update("contacts", &BulkUpdateRequest {
    rows: vec![
        BulkUpdateRow { id: uuid1, data: json!({"active": false}) },
        BulkUpdateRow { id: uuid2, data: json!({"active": false}) },
    ],
}).await?;

client.data().bulk_delete("contacts", &BulkDeleteRequest {
    ids: vec![uuid1, uuid2, uuid3],
}).await?;

SSE Change Streams

Subscribe to real-time change events via Server-Sent Events. The stream emits insert, update, and delete events as they happen.

MethodPathDescription
GET /v1/sse SSE stream for table change notifications (query: tables=a,b)

Usage

// Subscribe to changes on the "contacts" table
const es = new EventSource(
  'https://api.kapable.ai/v1/sse?tables=contacts',
  // Add auth header via polyfill or proxy
);

es.addEventListener('insert', (e) => {
  const row = JSON.parse(e.data);
  console.log('New contact:', row);
});

es.addEventListener('update', (e) => {
  const row = JSON.parse(e.data);
  console.log('Updated:', row.id);
});

es.addEventListener('delete', (e) => {
  const { id } = JSON.parse(e.data);
  console.log('Deleted:', id);
});
Column Types

Supported column types: text, integer, float, boolean, timestamp, json, uuid. Each column can be marked required and given a default value.