6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
382 lines
17 KiB
JSON
382 lines
17 KiB
JSON
{
|
||
"openapi": "3.1.0",
|
||
"info": {
|
||
"title": "Sim Tables API v2",
|
||
"version": "2.0.0-preview",
|
||
"description": "Read access to Sim tables with the typed predicate filter grammar and opaque cursor pagination. This surface is feature-gated (`tables-v2-api`): when the flag is off for the caller, every endpoint returns 404 as if it does not exist. Filters are predicate trees — `{\"all\": [...]}` (AND) or `{\"any\": [...]}` (OR) groups whose members are `{field, op, value}` conditions or nested groups. Built-in columns `id`, `createdAt`, and `updatedAt` (camelCase) are filterable and sortable alongside user columns."
|
||
},
|
||
"servers": [{ "url": "https://www.sim.ai" }],
|
||
"security": [{ "apiKey": [] }],
|
||
"paths": {
|
||
"/api/v2/tables": {
|
||
"get": {
|
||
"operationId": "v2ListTables",
|
||
"summary": "List Tables",
|
||
"description": "List every table in a workspace with its column schema and row count.",
|
||
"tags": ["Tables v2"],
|
||
"parameters": [
|
||
{
|
||
"name": "workspaceId",
|
||
"in": "query",
|
||
"required": true,
|
||
"schema": { "type": "string", "minLength": 1 }
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Tables in the workspace. Served with `Cache-Control: private, no-store`.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": ["success", "data"],
|
||
"properties": {
|
||
"success": { "const": true },
|
||
"data": {
|
||
"type": "object",
|
||
"required": ["tables", "totalCount"],
|
||
"properties": {
|
||
"tables": {
|
||
"type": "array",
|
||
"items": { "$ref": "#/components/schemas/TableSummary" }
|
||
},
|
||
"totalCount": { "type": "integer" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": { "$ref": "#/components/responses/ValidationError" },
|
||
"401": { "$ref": "#/components/responses/Unauthorized" },
|
||
"403": { "$ref": "#/components/responses/Forbidden" },
|
||
"404": { "$ref": "#/components/responses/NotFoundOrGated" },
|
||
"429": { "$ref": "#/components/responses/RateLimited" }
|
||
}
|
||
}
|
||
},
|
||
"/api/v2/tables/{tableId}/query": {
|
||
"post": {
|
||
"operationId": "v2QueryTableRows",
|
||
"summary": "Query Rows",
|
||
"description": "Query rows with a typed predicate filter, an ordered sort spec, and opaque cursor pagination. Row `data` is keyed by column NAME; `select` cells return option names, and filter operands on select columns accept option names (resolved case-insensitively).\n\n**Pagination contract:** page by passing the previous response's `nextCursor` back as `cursor`, and stop only when it is `null` — a page may return fewer than `limit` rows and still have more behind it, so page fullness is never a termination signal. A cursor is bound to the exact query shape it was minted under: keyset cursors to the default row order, offset cursors (sorted views) to that sort. Replaying one under a different `sort` returns 400 `CURSOR_SORT_CONFLICT`. `totalCount` is computed on the first page only (requests with a `cursor` return `totalCount: null`).",
|
||
"tags": ["Tables v2"],
|
||
"parameters": [
|
||
{
|
||
"name": "tableId",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": { "type": "string", "minLength": 1 }
|
||
}
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"description": "Bodies over 1 MB are rejected with 413.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": ["workspaceId"],
|
||
"properties": {
|
||
"workspaceId": { "type": "string", "minLength": 1 },
|
||
"predicate": { "$ref": "#/components/schemas/Predicate" },
|
||
"sort": {
|
||
"type": "array",
|
||
"maxItems": 16,
|
||
"description": "Ordered sort spec, highest priority first.",
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["field", "direction"],
|
||
"properties": {
|
||
"field": { "type": "string" },
|
||
"direction": { "enum": ["asc", "desc"] }
|
||
}
|
||
}
|
||
},
|
||
"limit": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 1000,
|
||
"default": 100,
|
||
"description": "Omitted → 100. `1..1000` → page size. `0` → the ENTIRE matching result in one response; fails with 400 `TABLE_QUERY_RESULT_TOO_LARGE` if it exceeds the 5 MB row-data budget (narrow the predicate or page instead)."
|
||
},
|
||
"cursor": {
|
||
"type": "string",
|
||
"description": "Opaque token from a previous response's `nextCursor`. Pass back verbatim. Mutually exclusive with `sort`."
|
||
}
|
||
}
|
||
},
|
||
"examples": {
|
||
"filtered": {
|
||
"summary": "Multi-select membership + negated pattern",
|
||
"value": {
|
||
"workspaceId": "ws_123",
|
||
"predicate": {
|
||
"all": [
|
||
{ "field": "Color", "op": "contains", "value": "Purple" },
|
||
{ "field": "name", "op": "nlike", "value": "G*" }
|
||
]
|
||
},
|
||
"limit": 100
|
||
}
|
||
},
|
||
"builtinColumns": {
|
||
"summary": "Built-in column range (UTC, timezone-independent)",
|
||
"value": {
|
||
"workspaceId": "ws_123",
|
||
"predicate": {
|
||
"all": [
|
||
{ "field": "createdAt", "op": "gte", "value": "2026-07-24T03:00:00.000Z" },
|
||
{ "field": "createdAt", "op": "lte", "value": "2026-07-25T02:59:59.999Z" }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "A page of rows. Served with `Cache-Control: private, no-store`.",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"type": "object",
|
||
"required": ["success", "data"],
|
||
"properties": {
|
||
"success": { "const": true },
|
||
"data": {
|
||
"type": "object",
|
||
"required": ["rows", "rowCount", "nextCursor"],
|
||
"properties": {
|
||
"rows": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["id", "data", "createdAt", "updatedAt"],
|
||
"properties": {
|
||
"id": { "type": "string" },
|
||
"data": {
|
||
"type": "object",
|
||
"description": "Column-NAME-keyed cell values.",
|
||
"additionalProperties": true
|
||
},
|
||
"createdAt": { "type": "string", "format": "date-time" },
|
||
"updatedAt": { "type": "string", "format": "date-time" }
|
||
}
|
||
}
|
||
},
|
||
"rowCount": { "type": "integer", "description": "Rows in THIS page." },
|
||
"totalCount": {
|
||
"type": ["integer", "null"],
|
||
"description": "Rows matching the predicate across all pages. First page only; null when a cursor was supplied."
|
||
},
|
||
"limit": { "type": ["integer", "null"] },
|
||
"nextCursor": {
|
||
"type": ["string", "null"],
|
||
"description": "Non-null ⇒ more rows exist. The ONLY termination signal is null."
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"description": "Validation failure. Machine-readable `code` values include `INVALID_FILTER` (unknown column, operator/type mismatch, malformed tree), `INVALID_ORDER`, `INVALID_CURSOR`, `CURSOR_SORT_CONFLICT`, and `TABLE_QUERY_RESULT_TOO_LARGE` (unbounded result exceeded the 5 MB budget).",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/ErrorBody" }
|
||
}
|
||
}
|
||
},
|
||
"401": { "$ref": "#/components/responses/Unauthorized" },
|
||
"403": { "$ref": "#/components/responses/Forbidden" },
|
||
"404": { "$ref": "#/components/responses/NotFoundOrGated" },
|
||
"413": {
|
||
"description": "Request body exceeded the 1 MB cap.",
|
||
"content": {
|
||
"application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } }
|
||
}
|
||
},
|
||
"429": { "$ref": "#/components/responses/RateLimited" }
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"components": {
|
||
"securitySchemes": {
|
||
"apiKey": { "type": "apiKey", "in": "header", "name": "X-API-Key" }
|
||
},
|
||
"schemas": {
|
||
"Predicate": {
|
||
"description": "A predicate tree: exactly one of `all` (every member must match) or `any` (at least one must). Members are conditions or nested groups; nesting expresses mixed AND/OR logic. Groups must be non-empty (1–100 members), trees at most 10 levels deep and 500 nodes total. Nodes are STRICT: unknown keys, or a node carrying both a group key and condition keys, are rejected rather than ignored.",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"required": ["all"],
|
||
"additionalProperties": false,
|
||
"properties": {
|
||
"all": {
|
||
"type": "array",
|
||
"minItems": 1,
|
||
"maxItems": 100,
|
||
"items": { "$ref": "#/components/schemas/PredicateNode" }
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"type": "object",
|
||
"required": ["any"],
|
||
"additionalProperties": false,
|
||
"properties": {
|
||
"any": {
|
||
"type": "array",
|
||
"minItems": 1,
|
||
"maxItems": 100,
|
||
"items": { "$ref": "#/components/schemas/PredicateNode" }
|
||
}
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"PredicateNode": {
|
||
"oneOf": [
|
||
{ "$ref": "#/components/schemas/Predicate" },
|
||
{ "$ref": "#/components/schemas/Condition" }
|
||
]
|
||
},
|
||
"Condition": {
|
||
"type": "object",
|
||
"required": ["field", "op"],
|
||
"additionalProperties": false,
|
||
"properties": {
|
||
"field": {
|
||
"type": "string",
|
||
"maxLength": 128,
|
||
"description": "Column name, or a built-in: `id`, `createdAt`, `updatedAt` (camelCase — snake_case is treated as a user column and matches nothing)."
|
||
},
|
||
"op": {
|
||
"enum": [
|
||
"eq",
|
||
"ne",
|
||
"gt",
|
||
"gte",
|
||
"lt",
|
||
"lte",
|
||
"in",
|
||
"nin",
|
||
"contains",
|
||
"ncontains",
|
||
"startsWith",
|
||
"endsWith",
|
||
"like",
|
||
"ilike",
|
||
"nlike",
|
||
"nilike",
|
||
"isEmpty",
|
||
"isNotEmpty",
|
||
"isNull",
|
||
"isNotNull"
|
||
],
|
||
"description": "`eq`/`ne`/`in`/`nin` are case-sensitive equality/membership. `contains`/`ncontains`/`startsWith`/`endsWith` are case-insensitive text matches — except on a multi-select column, where `contains`/`ncontains` mean set membership by option name. `like`/`nlike` are case-sensitive and `ilike`/`nilike` case-insensitive patterns with `*` as the only wildcard (literal `%`/`_` match themselves). `isEmpty`/`isNotEmpty` treat null and empty string as empty; `isNull`/`isNotNull` are strict null checks. The four `is*` operators take no `value`. Negated text matches retain rows where the cell is absent. `in`/`nin` require a non-empty array of at most 1000 values; other value-taking operators reject arrays. Select columns accept only equality/membership operators appropriate to their cardinality (single: eq/ne/in/nin; multi: contains/ncontains; both: the `is*` checks)."
|
||
},
|
||
"value": {
|
||
"description": "Operand. Omit for the `is*` operators. Ranges on `number` columns require numbers, on `date` columns ISO strings (compared as UTC, independent of any session timezone); ranges on `boolean`/`json` columns are rejected."
|
||
}
|
||
}
|
||
},
|
||
"TableSummary": {
|
||
"type": "object",
|
||
"required": ["id", "name", "schema", "rowCount", "maxRows", "createdAt", "updatedAt"],
|
||
"properties": {
|
||
"id": { "type": "string" },
|
||
"name": { "type": "string" },
|
||
"description": { "type": ["string", "null"] },
|
||
"schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"columns": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["name", "type"],
|
||
"properties": {
|
||
"id": { "type": "string" },
|
||
"name": { "type": "string" },
|
||
"type": { "enum": ["string", "number", "boolean", "date", "json", "select"] },
|
||
"required": { "type": "boolean" },
|
||
"unique": { "type": "boolean" },
|
||
"options": {
|
||
"type": "array",
|
||
"description": "Declared choices on a `select` column.",
|
||
"items": {
|
||
"type": "object",
|
||
"properties": { "id": { "type": "string" }, "name": { "type": "string" } }
|
||
}
|
||
},
|
||
"multiple": { "type": "boolean" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"rowCount": { "type": "integer" },
|
||
"maxRows": { "type": "integer" },
|
||
"createdAt": { "type": "string", "format": "date-time" },
|
||
"updatedAt": { "type": "string", "format": "date-time" }
|
||
}
|
||
},
|
||
"ErrorBody": {
|
||
"type": "object",
|
||
"required": ["error"],
|
||
"properties": {
|
||
"error": {
|
||
"type": "string",
|
||
"description": "Human-readable message naming the failing field/operator."
|
||
},
|
||
"code": {
|
||
"type": "string",
|
||
"description": "Machine-readable code, present on domain validation failures."
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"ValidationError": {
|
||
"description": "Malformed request.",
|
||
"content": {
|
||
"application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } }
|
||
}
|
||
},
|
||
"Unauthorized": {
|
||
"description": "Missing or invalid API key.",
|
||
"content": {
|
||
"application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } }
|
||
}
|
||
},
|
||
"Forbidden": {
|
||
"description": "The key's workspace scope does not cover this workspace, or the caller lacks read access.",
|
||
"content": {
|
||
"application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } }
|
||
}
|
||
},
|
||
"NotFoundOrGated": {
|
||
"description": "Table not found — or the `tables-v2-api` feature flag is off for this caller, in which case the entire surface answers 404. The gate is evaluated after authorization, so a 404 never distinguishes rollout cohort from missing resource for callers without access.",
|
||
"content": {
|
||
"application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } }
|
||
}
|
||
},
|
||
"RateLimited": {
|
||
"description": "Rate limit exceeded for this key.",
|
||
"content": {
|
||
"application/json": { "schema": { "$ref": "#/components/schemas/ErrorBody" } }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|