Files
sim/apps/docs/openapi-v2-tables.json
T

382 lines
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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" } }
}
}
}
}
}