diff --git a/api/openapi.json b/api/openapi.json index 28c20c3..3de471f 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -15,10 +15,18 @@ "description": "AI-powered data analysis. Submit natural language questions as synchronous queries or asynchronous jobs, and retrieve results including generated queries, data, and summarized answers.", "name": "AI" }, + { + "description": "Saved AI prompts that fire on a schedule and deliver the response to email recipients.", + "name": "AI Routines" + }, { "description": "AI evaluation: manage prompt sets and runs used to score AI quality against curated prompt suites.", "name": "AI Eval" }, + { + "description": "AI-generated model suggestions: list, generate, schedule, and manage suggested improvements to a shared model.", + "name": "AI Model Suggestions" + }, { "description": "API token management", "name": "API Tokens" @@ -82,6 +90,10 @@ { "description": "User and group management", "name": "Users" + }, + { + "description": "Self-introspection: the authenticated caller can discover their own identity, key scope, org role, and resolved per-model permissions.", + "name": "Whoami" } ], "components": { @@ -696,7 +708,7 @@ "detail": { "type": "string", "description": "Human-readable error message describing what went wrong.", - "example": "The AI agent is currently unavailable. Contact your administrator to re-enable." + "example": "The AI credit limit has been reached. Contact your administrator for assistance." }, "status": { "type": "integer", @@ -982,7 +994,7 @@ "modelId": { "type": "string", "format": "uuid", - "description": "The UUID of the shared model to query against. Only shared models are supported.", + "description": "The UUID of the model to query against. Must be a shared model, or a shared-extension model usable as a workbook base.", "example": "770e8400-e29b-41d4-a716-446655440002" }, "progressWebhookEnabled": { @@ -1019,7 +1031,7 @@ "webhookUrl": { "type": "string", "format": "uri", - "description": "URL to receive webhook POSTs. Always receives a terminal event (job.complete or job.failed) when the job finishes. When progressWebhookEnabled is true, also receives real-time progress events during execution.", + "description": "URL to receive webhook POSTs. Always receives a terminal event (job.complete, job.failed, or job.denied) when the job finishes; a job.denied event (e.g. the organization is over its AI credit limit) additionally carries a reason field. When progressWebhookEnabled is true, also receives real-time progress events during execution.", "example": "https://example.com/webhooks/omni" } }, @@ -1597,6 +1609,476 @@ "text" ] }, + "AiCreditControlsResponse": { + "type": "object", + "properties": { + "accountCreditLimit": { + "type": "number", + "minimum": 0, + "description": "Monthly AI credit limit for the whole Omni account (shared across every org under the same Salesforce account), not just this org. 0 when no limit is configured.", + "example": 2000 + }, + "creditsUsed": { + "type": "number", + "minimum": 0, + "description": "This org's credit usage in the current billing period.", + "example": 450 + }, + "downgradeCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Downgrade threshold, or `null` if the downgrade control is off.", + "example": 800 + }, + "periodEnd": { + "type": "integer", + "minimum": 0, + "description": "End of the current billing period as a Unix ms timestamp (UTC calendar-month boundary)." + }, + "periodStart": { + "type": "integer", + "minimum": 0, + "description": "Start of the current billing period as a Unix ms timestamp (UTC calendar-month boundary)." + }, + "shutoffCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Shutoff threshold, or `null` if the shutoff control is off.", + "example": 1200 + } + }, + "required": [ + "accountCreditLimit", + "creditsUsed", + "downgradeCredits", + "periodEnd", + "periodStart", + "shutoffCredits" + ] + }, + "AiCreditControlsUpdateBody": { + "type": "object", + "properties": { + "downgradeCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Credit usage at which AI downgrades to a cheaper model. Omit to leave unchanged, `null` to turn off, or a non-negative number to set. Must be at or below shutoffCredits.", + "example": 800 + }, + "shutoffCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Credit usage at which AI shuts off entirely. Omit to leave unchanged, `null` to turn off, or a non-negative number to set.", + "example": 1200 + } + }, + "additionalProperties": false + }, + "RoutinesListResponse": { + "type": "object", + "properties": { + "pageInfo": { + "$ref": "#/components/schemas/PageInfo" + }, + "records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutineResponse" + }, + "description": "Routines returned for this request, newest first." + } + }, + "required": [ + "pageInfo", + "records" + ] + }, + "RoutineResponse": { + "type": "object", + "properties": { + "branchId": { + "type": [ + "string", + "null" + ], + "format": "uuid", + "description": "Branch of the shared model the prompt runs against, or null." + }, + "createdAt": { + "type": "string", + "description": "ISO 8601 timestamp when the routine was created." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Display-only notes about the routine, or null." + }, + "destination": { + "$ref": "#/components/schemas/RoutineEmailDestination" + }, + "disabled": { + "type": "boolean", + "description": "Whether the owner has paused the routine." + }, + "id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the routine." + }, + "lastRun": { + "$ref": "#/components/schemas/RoutineLastRun" + }, + "modelId": { + "type": "string", + "format": "uuid", + "description": "The shared model the prompt runs against." + }, + "name": { + "type": "string", + "description": "Customer-visible name of the routine, used as the email subject." + }, + "prompt": { + "type": "string", + "description": "Natural language prompt Omni runs on each scheduled run." + }, + "recipientCount": { + "type": "integer", + "description": "Number of distinct deliverable recipients after expanding user groups and removing duplicates." + }, + "schedule": { + "type": "string", + "description": "Six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field)." + }, + "systemDisabled": { + "type": "boolean", + "description": "Whether Omni disabled the routine because it could no longer run successfully or safely." + }, + "systemDisabledReason": { + "type": [ + "string", + "null" + ], + "description": "Reason Omni disabled the routine, or null." + }, + "timezone": { + "type": "string", + "description": "IANA timezone identifier used to evaluate the schedule." + }, + "topicName": { + "type": [ + "string", + "null" + ], + "description": "Topic scoping query generation, or null." + }, + "updatedAt": { + "type": "string", + "description": "ISO 8601 timestamp when the routine was last updated." + } + }, + "required": [ + "branchId", + "createdAt", + "description", + "destination", + "disabled", + "id", + "lastRun", + "modelId", + "name", + "prompt", + "recipientCount", + "schedule", + "systemDisabled", + "systemDisabledReason", + "timezone", + "topicName", + "updatedAt" + ] + }, + "RoutineEmailDestination": { + "type": "object", + "properties": { + "recipientEmails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "maxItems": 100, + "default": [], + "description": "Email addresses that receive each scheduled run of the routine.", + "example": [ + "alice@example.com", + "bob@example.com" + ] + }, + "type": { + "type": "string", + "enum": [ + "email" + ], + "description": "Destination type. Only `email` is supported.", + "example": "email" + }, + "userGroupIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "maxItems": 100, + "default": [], + "description": "User group IDs whose active members receive each scheduled run. Omni expands each group to the members' current email addresses when the routine runs.", + "example": [ + "550e8400-e29b-41d4-a716-446655440000" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Email delivery configuration for the routine." + }, + "RoutineLastRun": { + "type": [ + "object", + "null" + ], + "properties": { + "completedAt": { + "type": [ + "string", + "null" + ], + "description": "ISO 8601 timestamp the last completed run finished." + }, + "label": { + "type": "string", + "description": "Customer-visible status of the last completed run.", + "example": "Delivered" + }, + "state": { + "type": "string", + "description": "Machine-readable status of the last completed run.", + "example": "COMPLETE" + } + }, + "required": [ + "completedAt", + "label", + "state" + ], + "description": "Most recent completed run, or null if the routine has never completed a run." + }, + "RoutineCreateResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier for the newly created routine.", + "example": "880e8400-e29b-41d4-a716-446655440003" + } + }, + "required": [ + "id" + ] + }, + "ApiError429": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message describing what went wrong.", + "example": "User has reached the maximum of 100 routines" + }, + "status": { + "type": "integer", + "description": "HTTP status code of the error.", + "example": 429 + } + }, + "required": [ + "detail", + "status" + ] + }, + "RoutineCreateBody": { + "type": "object", + "properties": { + "branchId": { + "type": "string", + "format": "uuid", + "description": "Optional branch ID for the model. Must be a branch of the shared model specified by modelId.", + "example": "550e8400-e29b-41d4-a716-446655440000" + }, + "description": { + "type": "string", + "maxLength": 2000, + "description": "Optional human-readable notes about the routine. Display-only — never used as model input.", + "example": "Weekly signups summary for the growth team." + }, + "modelId": { + "type": "string", + "format": "uuid", + "description": "The UUID of the shared model the prompt runs against. Only shared models are supported.", + "example": "770e8400-e29b-41d4-a716-446655440002" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Customer-visible name of the routine. Used as the email subject for each scheduled run.", + "example": "Weekly user signups" + }, + "prompt": { + "type": "string", + "minLength": 1, + "description": "Natural language prompt Omni runs on each scheduled run.", + "example": "How many users signed up last week?" + }, + "schedule": { + "type": "string", + "minLength": 1, + "description": "Six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field). Minimum frequency is once per hour; contact Omni support if you need more frequent scheduling.", + "example": "0 9 ? * MON *" + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "IANA timezone identifier used to evaluate the schedule.", + "example": "America/New_York" + }, + "topicName": { + "type": "string", + "maxLength": 256, + "description": "Topic name to scope query generation. If omitted, the AI picks the best topic.", + "example": "users" + }, + "destination": { + "$ref": "#/components/schemas/RoutineDestination" + } + }, + "required": [ + "modelId", + "name", + "prompt", + "schedule", + "timezone", + "destination" + ], + "additionalProperties": false + }, + "RoutineDestination": { + "oneOf": [ + { + "$ref": "#/components/schemas/RoutineEmailDestination" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "email": "#/components/schemas/RoutineEmailDestination" + } + }, + "description": "Single delivery destination for the routine. To send results to multiple destinations, create one routine per destination. Omni runs the prompt once per scheduled run using the routine owner's permissions, and every recipient receives the same result regardless of their own permissions." + }, + "RoutineUpdateBody": { + "type": "object", + "properties": { + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 2000, + "description": "Display-only notes about the routine. Pass null to clear it." + }, + "destination": { + "allOf": [ + { + "$ref": "#/components/schemas/RoutineDestination" + }, + { + "description": "Replaces the routine's full recipient configuration with the supplied destination." + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "New customer-visible name of the routine, used as the email subject." + }, + "prompt": { + "type": "string", + "minLength": 1, + "description": "New natural language prompt Omni runs on each scheduled run." + }, + "schedule": { + "type": "string", + "minLength": 1, + "description": "New six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field). Minimum frequency is once per hour." + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "New IANA timezone identifier used to evaluate the schedule." + } + }, + "additionalProperties": false + }, + "RoutineDeleteResponse": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true on a successful delete." + }, + "id": { + "type": "string", + "format": "uuid", + "description": "The deleted routine’s ID." + } + }, + "required": [ + "deleted", + "id" + ] + }, + "RoutineTriggerResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the run (scheduled job) that was started.", + "example": "990e8400-e29b-41d4-a716-446655440004" + } + }, + "required": [ + "id" + ] + }, "ApiKeyListResponse": { "type": "object", "properties": { @@ -2949,8 +3431,7 @@ "omni-spreadsheet", "spreadsheet-tab", "summary-value", - "omni-table", - "app" + "omni-table" ], "description": "Visualization type (e.g. basic, omni-markdown, omni-table)" } @@ -3899,7 +4380,7 @@ "type": "object", "properties": { "containers": { - "$ref": "#/components/schemas/Containers" + "$ref": "#/components/schemas/ContainersOnCreate" }, "controls": { "$ref": "#/components/schemas/ControlsPatchExternal" @@ -3950,7 +4431,7 @@ "type": "string", "minLength": 1, "maxLength": 255, - "description": "Caller-supplied note describing the create, written to the history audit trail. Defaults to \"Created document\" when omitted." + "description": "Optional. Caller-supplied note describing the create, written to the history audit trail. When omitted, the server auto-fills it with \"Created document\"." } }, "required": [ @@ -3959,8 +4440,11 @@ ], "additionalProperties": false }, - "Containers": { - "type": "array", + "ContainersOnCreate": { + "type": [ + "array", + "null" + ], "items": { "anyOf": [ { @@ -3974,7 +4458,7 @@ } ] }, - "description": "Container layout array (grid / stack / page / reference containers, recursively nested). The server validates the full structure on apply." + "description": "Container layout array, or `null` to create a workbook-only document with no dashboard. When `null`, `controls` and `settings` must be omitted." }, "GridContainer": { "type": "object", @@ -9087,510 +9571,2134 @@ "type": "object", "properties": { "instanceKey": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "instanceKey", - "type" - ], - "additionalProperties": {} - } - ] - }, - { - "type": "object", - "properties": { - "gridPosition": { - "type": "object", - "properties": { - "h": { - "type": "number" - }, - "w": { - "type": "number" - }, - "x": { - "type": "number" + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." }, - "y": { - "type": "number" - } - }, - "required": [ - "h", - "w", - "x", - "y" - ] - } - }, - "required": [ - "gridPosition" - ] - } - ] - } - ] - } - }, - "containerType": { - "type": "string", - "enum": [ - "grid" - ] - }, - "gridPosition": { - "type": "object", - "properties": { - "h": { - "type": "number", - "description": "Height in grid units (default: 36 for charts)" - }, - "w": { - "type": "number", - "description": "Width in grid columns on a 24-column grid. Common widths: 24 (full), 12 (half), 8 (third), 6 (quarter). x + w must not exceed 24." - }, - "x": { - "type": "number", - "description": "X position on a 24-column grid (0=left edge, 12=middle). Items side-by-side share the same y with complementary x values." - }, - "y": { - "type": "number", - "description": "Y position in grid units (0=top, higher values=lower on page)" - } - }, - "required": [ - "h", - "w", - "x", - "y" - ] - }, - "metadata": { - "type": "object", - "properties": { - "attachedQueryKey": { - "type": "string", - "description": "Set by the auto-add-tile flow when this container was generated for a specific workbook tab. The server removes containers with a matching `attachedQueryKey` when that tab is deleted. The reducer clears this when the user adds unrelated content (a different query, filter, text tile, page switcher, or sub-container)." - } - }, - "description": "Optional bookkeeping for this container (e.g. `attachedQueryKey` for auto-placed tiles)" - }, - "padding": { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - }, - { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - } - ] - }, - { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - } - ] - }, - { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "size": { + "type": "number", + "minimum": 0, + "maximum": 2000, + "description": "Fixed size in px along the parent stack axis (default 16). In grids the size comes from gridPosition; ignored when style.fillSpace is true." + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } + } + }, + "type": { + "type": "string", + "enum": [ + "inline-spacer" + ] + } + }, + "required": [ + "instanceKey", + "type" + ] + }, + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "align": { + "type": "string", + "enum": [ + "start", + "center", + "end" + ], + "description": "Placement of the line within its slot along the cross axis (default center): horizontal → top/center/bottom, vertical → left/center/right" + }, + "color": { + "type": "string", + "enum": [ + "border1", + "border4", + "text1", + "text4" + ], + "description": "Theme color for the line, subtle → bold (default border4)" + }, + "direction": { + "type": "string", + "enum": [ + "horizontal", + "vertical" + ], + "description": "Line orientation (default horizontal)" + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } + } + }, + "thickness": { + "anyOf": [ + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + } + ], + "description": "Line thickness in px (default 1)" + }, + "type": { + "type": "string", + "enum": [ + "inline-divider" + ] + } + }, + "required": [ + "instanceKey", + "type" + ] + }, + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "instanceKey", + "type" + ], + "additionalProperties": {} + } + ] + }, + { + "type": "object", + "properties": { + "gridPosition": { + "type": "object", + "properties": { + "h": { + "type": "number" + }, + "w": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "h", + "w", + "x", + "y" + ] + } + }, + "required": [ + "gridPosition" + ] + } + ] + } + ] + } + }, + "containerType": { + "type": "string", + "enum": [ + "grid" + ] + }, + "gridPosition": { + "type": "object", + "properties": { + "h": { + "type": "number", + "description": "Height in grid units (default: 36 for charts)" + }, + "w": { + "type": "number", + "description": "Width in grid columns on a 24-column grid. Common widths: 24 (full), 12 (half), 8 (third), 6 (quarter). x + w must not exceed 24." + }, + "x": { + "type": "number", + "description": "X position on a 24-column grid (0=left edge, 12=middle). Items side-by-side share the same y with complementary x values." + }, + "y": { + "type": "number", + "description": "Y position in grid units (0=top, higher values=lower on page)" + } + }, + "required": [ + "h", + "w", + "x", + "y" + ] + }, + "metadata": { + "type": "object", + "properties": { + "attachedQueryKey": { + "type": "string", + "description": "Set by the auto-add-tile flow when this container was generated for a specific workbook tab. The server removes containers with a matching `attachedQueryKey` when that tab is deleted. The reducer clears this when the user adds unrelated content (a different query, filter, text tile, page switcher, or sub-container)." + }, + "generatedHeading": { + "type": "boolean", + "description": "Marks the auto-injected heading wrapper (title + description row) created for a tile, so it can be labeled generically and treated as managed." + }, + "locked": { + "type": "boolean", + "description": "Locks the container's internal arrangement so its children cannot be dragged, reordered, resized, or have new items dropped in. Cascades to all descendants. Does not lock the container's own position/size." + } + }, + "description": "Optional bookkeeping for this container (e.g. `attachedQueryKey` for auto-placed tiles)" + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", "enum": [ 1 ] @@ -9770,225 +11878,1865 @@ 8 ] } - ] + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + } + ] + }, + "style": { + "type": "string", + "pattern": "^[a-z0-9-]+$" + } + }, + "required": [ + "instanceKey", + "children", + "containerType" + ], + "description": "Grid container — children are positioned on a grid (each carries a gridPosition)." + }, + "StackContainer": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Optional description for the container, providing additional context or information" + }, + "instanceKey": { + "type": "string", + "description": "Unique identifier for this container. Used to reference the container when adding, moving, or removing children." + }, + "name": { + "type": "string", + "description": "Human-readable name for the container, used for easier reference in logic and design" + }, + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + }, + "after": { + "anyOf": [ + { + "$ref": "#/components/schemas/StackContainer" + }, + { + "$ref": "#/components/schemas/ReferenceContainer" + } + ] + }, + "align": { + "type": "string", + "enum": [ + "flex-start", + "flex-end", + "center", + "stretch" + ], + "description": "Cross-axis alignment of children (e.g., center, stretch)" + }, + "before": { + "anyOf": [ + { + "$ref": "#/components/schemas/StackContainer" + }, + { + "$ref": "#/components/schemas/ReferenceContainer" + } + ] + }, + "children": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "as": { + "type": "string", + "enum": [ + "chart", + "result", + "ai", + "metadata" + ], + "description": "Render mode: \"chart\" for visualization, \"result\" for data table, \"ai\" for AI summary, \"metadata\" for query metadata" + }, + "format": { + "type": "string", + "enum": [ + "subtitle", + "description", + "name" + ], + "description": "Display format when as is \"ai\" or \"metadata\": \"name\" (metadata only), \"subtitle\", or \"description\"" + }, + "id": { + "type": "string", + "pattern": "^[1-9][0-9]*$", + "description": "The query presentation ID this content item references" + }, + "name": { + "type": "string", + "description": "Display name for this query item" + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } + } + }, + "type": { + "type": "string", + "enum": [ + "query" + ] + } }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] + "required": [ + "instanceKey", + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "appearance": { + "oneOf": [ + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "inline" + ] + } + }, + "required": [ + "as" + ] + }, + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "tooltip" + ] + } + }, + "required": [ + "as" + ] + } + ], + "description": "Display mode: \"inline\" renders directly, \"tooltip\" shows on hover" + }, + "content": { + "type": "string", + "description": "The text content to display (supports markdown)" + }, + "name": { + "type": "string", + "description": "Display name for this text item (e.g., title, subtitle)" + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } } - ] + }, + "textAlign": { + "type": "string", + "enum": [ + "start", + "center", + "end" + ], + "description": "Text alignment: \"start\", \"center\", or \"end\"" + }, + "type": { + "type": "string", + "enum": [ + "inline-text" + ] + } }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - } - ] - } - ] - }, - "style": { - "type": "string", - "pattern": "^[a-z0-9-]+$" - } - }, - "required": [ - "instanceKey", - "children", - "containerType" - ], - "description": "Grid container — children are positioned on a grid (each carries a gridPosition)." - }, - "StackContainer": { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "Optional description for the container, providing additional context or information" - }, - "instanceKey": { - "type": "string", - "description": "Unique identifier for this container. Used to reference the container when adding, moving, or removing children." - }, - "name": { - "type": "string", - "description": "Human-readable name for the container, used for easier reference in logic and design" - }, - "aspectRatio": { - "type": "string" - }, - "fillSpace": { - "type": "boolean" - }, - "height": { - "type": "string" - }, - "maxHeight": { - "type": "string" - }, - "maxWidth": { - "type": "string" - }, - "minHeight": { - "type": "string" - }, - "minWidth": { - "type": "string" - }, - "width": { - "type": "string" - }, - "after": { - "anyOf": [ - { - "$ref": "#/components/schemas/StackContainer" - }, - { - "$ref": "#/components/schemas/ReferenceContainer" - } - ] - }, - "align": { - "type": "string", - "enum": [ - "flex-start", - "flex-end", - "center", - "stretch" - ], - "description": "Cross-axis alignment of children (e.g., center, stretch)" - }, - "before": { - "anyOf": [ - { - "$ref": "#/components/schemas/StackContainer" - }, - { - "$ref": "#/components/schemas/ReferenceContainer" - } - ] - }, - "children": { - "type": "array", - "items": { - "anyOf": [ + "required": [ + "instanceKey", + "content", + "type" + ] + }, { "type": "object", "properties": { @@ -10458,303 +14206,385 @@ ] }, { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "appearance": { + "oneOf": [ + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "tabs" ] }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + "variant": { + "type": "string", + "enum": [ + "underline", + "bordered" ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + } + }, + "required": [ + "as" + ] + }, + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "buttons" ] }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + "variant": { + "type": "string", + "enum": [ + "segment", + "toggle", + "pills" ] } + }, + "required": [ + "as" ] }, { - "type": "null" + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "list" + ] + }, + "description": { + "type": "boolean" + } + }, + "required": [ + "as" + ] + }, + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "dropdown" + ] + }, + "description": { + "type": "boolean" + } + }, + "required": [ + "as" + ] } ] }, - "preset": { - "type": "string", - "pattern": "^[a-z0-9-]+$", - "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" - }, - "as": { - "type": "string", - "enum": [ - "chart", - "result", - "ai", - "metadata" - ], - "description": "Render mode: \"chart\" for visualization, \"result\" for data table, \"ai\" for AI summary, \"metadata\" for query metadata" - }, - "format": { - "type": "string", - "enum": [ - "subtitle", - "description", - "name" - ], - "description": "Display format when as is \"ai\" or \"metadata\": \"name\" (metadata only), \"subtitle\", or \"description\"" - }, - "id": { - "type": "string", - "pattern": "^[1-9][0-9]*$", - "description": "The query presentation ID this content item references" - }, - "name": { - "type": "string", - "description": "Display name for this query item" + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "includeControls": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "target": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } }, "style": { "type": "object", @@ -10788,13 +14618,12 @@ "type": { "type": "string", "enum": [ - "query" + "inline-page-switcher" ] } }, "required": [ "instanceKey", - "id", "type" ] }, @@ -11537,46 +15366,10 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "appearance": { - "oneOf": [ - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "inline" - ] - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "tooltip" - ] - } - }, - "required": [ - "as" - ] - } - ], - "description": "Display mode: \"inline\" renders directly, \"tooltip\" shows on hover" - }, - "content": { - "type": "string", - "description": "The text content to display (supports markdown)" - }, - "name": { + "id": { "type": "string", - "description": "Display name for this text item (e.g., title, subtitle)" + "format": "uuid", + "description": "UUID of the rich text content block" }, "style": { "type": "object", @@ -11607,25 +15400,16 @@ } } }, - "textAlign": { - "type": "string", - "enum": [ - "start", - "center", - "end" - ], - "description": "Text alignment: \"start\", \"center\", or \"end\"" - }, "type": { "type": "string", "enum": [ - "inline-text" + "text" ] } }, "required": [ "instanceKey", - "content", + "id", "type" ] }, @@ -12368,115 +16152,14 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "appearance": { - "oneOf": [ - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "tabs" - ] - }, - "variant": { - "type": "string", - "enum": [ - "underline", - "bordered" - ] - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "buttons" - ] - }, - "variant": { - "type": "string", - "enum": [ - "segment", - "toggle", - "pills" - ] - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "list" - ] - }, - "description": { - "type": "boolean" - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "dropdown" - ] - }, - "description": { - "type": "boolean" - } - }, - "required": [ - "as" - ] - } - ] + "id": { + "type": "string" }, - "options": { - "type": "array", - "items": { - "type": "object", - "properties": { - "disabled": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "includeControls": { - "type": "boolean" - }, - "label": { - "type": "string" - }, - "target": { - "type": "string" - }, - "uri": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } + "type": { + "type": "string", + "enum": [ + "control" + ] }, "style": { "type": "object", @@ -12506,16 +16189,11 @@ "type": "string" } } - }, - "type": { - "type": "string", - "enum": [ - "inline-page-switcher" - ] } }, "required": [ "instanceKey", + "id", "type" ] }, @@ -13259,9 +16937,55 @@ "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, "id": { + "type": "string" + }, + "type": { "type": "string", - "format": "uuid", - "description": "UUID of the rich text content block" + "enum": [ + "filter" + ] + }, + "appearance": { + "type": "object", + "properties": { + "control": { + "type": "string", + "enum": [ + "buttonToggle", + "dropdown" + ] + }, + "display": { + "type": "string", + "enum": [ + "inline", + "popover" + ] + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "value" + ] + } + }, + "value": { + "type": "array", + "items": { + "type": "string" + } + } + } }, "style": { "type": "object", @@ -13291,12 +17015,6 @@ "type": "string" } } - }, - "type": { - "type": "string", - "enum": [ - "text" - ] } }, "required": [ @@ -14044,14 +17762,11 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "control" - ] + "size": { + "type": "number", + "minimum": 0, + "maximum": 2000, + "description": "Fixed size in px along the parent stack axis (default 16). In grids the size comes from gridPosition; ignored when style.fillSpace is true." }, "style": { "type": "object", @@ -14081,11 +17796,16 @@ "type": "string" } } + }, + "type": { + "type": "string", + "enum": [ + "inline-spacer" + ] } }, "required": [ "instanceKey", - "id", "type" ] }, @@ -14828,56 +18548,32 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "id": { - "type": "string" + "align": { + "type": "string", + "enum": [ + "start", + "center", + "end" + ], + "description": "Placement of the line within its slot along the cross axis (default center): horizontal → top/center/bottom, vertical → left/center/right" }, - "type": { + "color": { "type": "string", "enum": [ - "filter" - ] + "border1", + "border4", + "text1", + "text4" + ], + "description": "Theme color for the line, subtle → bold (default border4)" }, - "appearance": { - "type": "object", - "properties": { - "control": { - "type": "string", - "enum": [ - "buttonToggle", - "dropdown" - ] - }, - "display": { - "type": "string", - "enum": [ - "inline", - "popover" - ] - }, - "options": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "value" - ] - } - }, - "value": { - "type": "array", - "items": { - "type": "string" - } - } - } + "direction": { + "type": "string", + "enum": [ + "horizontal", + "vertical" + ], + "description": "Line orientation (default horizontal)" }, "style": { "type": "object", @@ -14907,11 +18603,39 @@ "type": "string" } } + }, + "thickness": { + "anyOf": [ + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + } + ], + "description": "Line thickness in px (default 1)" + }, + "type": { + "type": "string", + "enum": [ + "inline-divider" + ] } }, "required": [ "instanceKey", - "id", "type" ] }, @@ -15087,6 +18811,14 @@ "attachedQueryKey": { "type": "string", "description": "Set by the auto-add-tile flow when this container was generated for a specific workbook tab. The server removes containers with a matching `attachedQueryKey` when that tab is deleted. The reducer clears this when the user adds unrelated content (a different query, filter, text tile, page switcher, or sub-container)." + }, + "generatedHeading": { + "type": "boolean", + "description": "Marks the auto-injected heading wrapper (title + description row) created for a tile, so it can be labeled generically and treated as managed." + }, + "locked": { + "type": "boolean", + "description": "Locks the container's internal arrangement so its children cannot be dragged, reordered, resized, or have new items dropped in. Cascades to all descendants. Does not lock the container's own position/size." } }, "description": "Optional bookkeeping for this container (e.g. `attachedQueryKey` for auto-placed tiles)" @@ -19811,8 +23543,7 @@ "sql", "dbt", "query-view", - "linked", - "app" + "linked" ], "description": "The query presentation type (e.g. SEMANTIC, SQL, LINKED, SPREADSHEET)." }, @@ -19901,7 +23632,6 @@ "spreadsheet-tab", "summary-value", "omni-table", - "app", null ], "description": "The visualization type (e.g. \"basic\", \"omni-table\")." @@ -20012,6 +23742,23 @@ "queryPresentations" ] }, + "Containers": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/GridContainer" + }, + { + "$ref": "#/components/schemas/PageContainer" + }, + { + "$ref": "#/components/schemas/StackContainer" + } + ] + }, + "description": "Container layout array (grid / stack / page / reference containers, recursively nested). The server validates the full structure on apply." + }, "ControlsReadExternal": { "type": "object", "properties": { @@ -23868,8 +27615,7 @@ "sql", "dbt", "query-view", - "linked", - "app" + "linked" ], "description": "The query presentation type (e.g. SEMANTIC, SQL, LINKED, SPREADSHEET)." }, @@ -23958,7 +27704,6 @@ "spreadsheet-tab", "summary-value", "omni-table", - "app", null ], "description": "The visualization type (e.g. \"basic\", \"omni-table\")." @@ -24139,7 +27884,7 @@ "type": "string", "minLength": 1, "maxLength": 255, - "description": "Caller-supplied description of what this patch changes, written to the history audit trail. When absent, the server generates one from the touched sections." + "description": "Optional. Caller-supplied description of what this patch changes, written to the history audit trail. When omitted, the server auto-generates one from the touched sections." } }, "additionalProperties": false @@ -24520,6 +28265,25 @@ "updated_at" ] }, + "EvalApiError422": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message describing what went wrong.", + "example": "A prompt being updated does not belong to this prompt set" + }, + "status": { + "type": "integer", + "description": "HTTP status code of the error.", + "example": 422 + } + }, + "required": [ + "detail", + "status" + ] + }, "EvalPromptSetsCreateBody": { "type": "object", "properties": { @@ -24571,7 +28335,7 @@ "prompt_text" ] }, - "maxItems": 25, + "maxItems": 100, "default": [], "description": "Initial prompts for the set. Defaults to an empty list. At most 25 prompts." }, @@ -24611,25 +28375,6 @@ "prompt_set" ] }, - "EvalApiError422": { - "type": "object", - "properties": { - "detail": { - "type": "string", - "description": "Human-readable error message describing what went wrong.", - "example": "A prompt being updated does not belong to this prompt set" - }, - "status": { - "type": "integer", - "description": "HTTP status code of the error.", - "example": 422 - } - }, - "required": [ - "detail", - "status" - ] - }, "EvalPromptSetsUpdateBody": { "type": "object", "properties": { @@ -24678,7 +28423,7 @@ "prompt_text" ] }, - "maxItems": 25, + "maxItems": 100, "description": "Full desired set of prompts after the update. Prompts omitted from this list are deleted; new prompts (no `id`) are appended in body order. Existing prompts retain their original position — reordering is not supported on this endpoint. At most 25 prompts total." } } @@ -25033,6 +28778,22 @@ "description": "The prompt text that was evaluated.", "example": "What are the top 5 products by revenue?" }, + "query_count": { + "type": [ + "integer", + "null" + ], + "description": "Number of warehouse queries the underlying job ran. Null for runs executed before this metric was recorded.", + "example": 4 + }, + "query_timing_ms": { + "type": [ + "integer", + "null" + ], + "description": "Total wall-clock time (milliseconds) the underlying job spent running warehouse queries — a proxy for query execution time. Null for runs executed before this metric was recorded.", + "example": 1800 + }, "score": { "type": [ "number", @@ -25054,7 +28815,7 @@ "integer", "null" ], - "description": "Wall-clock duration of the underlying job in milliseconds.", + "description": "Total AI time in milliseconds — all LLM processing and tool calls. Shown as \"AI time\" in the UI.", "example": 4321 } }, @@ -25065,6 +28826,8 @@ "expectation", "id", "prompt", + "query_count", + "query_timing_ms", "score", "scoring_cost", "timing_ms" @@ -25726,209 +29489,460 @@ }, "homepage": { "type": "boolean", - "description": "Whether label is shown on homepage" + "description": "Whether label is shown on homepage" + }, + "name": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "usage_count": { + "type": "number", + "description": "Number of documents with this label" + }, + "verified": { + "type": "boolean", + "description": "Whether label is verified" + } + }, + "required": [ + "color", + "description", + "homepage", + "name", + "usage_count", + "verified" + ] + }, + "LabelsCreateBody": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "default": null, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "default": null, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "default": false, + "description": "Show label on homepage. Requires admin permissions." + }, + "name": { + "type": "string", + "minLength": 2, + "maxLength": 25, + "description": "Label name", + "example": "important" + }, + "verified": { + "type": "boolean", + "default": false, + "description": "Mark as verified label. Requires admin permissions." + } + }, + "required": [ + "name" + ] + }, + "LabelsGetResponse": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "description": "Whether label is shown on homepage" + }, + "name": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "usage_count": { + "type": "number", + "description": "Number of documents with this label" + }, + "verified": { + "type": "boolean", + "description": "Whether label is verified" + } + }, + "required": [ + "color", + "description", + "homepage", + "name", + "usage_count", + "verified" + ] + }, + "LabelsUpdateResponse": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "description": "Whether label is shown on homepage" + }, + "name": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "usage_count": { + "type": "number", + "description": "Number of documents with this label" + }, + "verified": { + "type": "boolean", + "description": "Whether label is verified" + } + }, + "required": [ + "color", + "description", + "homepage", + "name", + "usage_count", + "verified" + ] + }, + "LabelsUpdateBody": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "description": "Show label on homepage. Requires admin permissions to modify." }, "name": { "type": "string", + "minLength": 2, + "maxLength": 25, "description": "Label name", - "example": "verified" - }, - "usage_count": { - "type": "number", - "description": "Number of documents with this label" + "example": "important" }, "verified": { "type": "boolean", - "description": "Whether label is verified" + "description": "Mark as verified label. Requires admin permissions to modify." + } + } + }, + "ModelSuggestionsListResponse": { + "type": "object", + "properties": { + "pageInfo": { + "$ref": "#/components/schemas/PageInfo" + }, + "records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ModelSuggestion" + } } }, "required": [ - "color", - "description", - "homepage", - "name", - "usage_count", - "verified" + "pageInfo", + "records" ] }, - "LabelsCreateBody": { + "ModelSuggestion": { "type": "object", "properties": { - "color": { + "aiModifiedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of the last AI write (create or AI update). Unaffected by dismiss/restore." + }, + "category": { + "type": "string", + "description": "Suggestion category, e.g. `missing_context`.", + "example": "missing_context" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of when the suggestion was created." + }, + "evidence": { "type": [ - "string", + "array", "null" ], - "maxLength": 9, - "default": null, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" + "items": { + "$ref": "#/components/schemas/SuggestionEvidenceItem" + }, + "description": "Source evidence for the suggestion. Null for rows created before evidence was tracked; `[]` when none was cited." }, - "description": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for the suggestion." + }, + "ignoreReason": { "type": [ "string", "null" ], - "maxLength": 500, - "default": null, - "description": "Label description", - "example": "Important items that need attention" - }, - "homepage": { - "type": "boolean", - "default": false, - "description": "Show label on homepage. Requires admin permissions." - }, - "name": { - "type": "string", - "minLength": 2, - "maxLength": 25, - "description": "Label name", - "example": "important" + "description": "Optional free-text reason recorded when the suggestion was dismissed." }, - "verified": { - "type": "boolean", - "default": false, - "description": "Mark as verified label. Requires admin permissions." - } - }, - "required": [ - "name" - ] - }, - "LabelsGetResponse": { - "type": "object", - "properties": { - "color": { + "ignoredAt": { "type": [ "string", "null" ], - "maxLength": 9, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" + "format": "date-time", + "description": "ISO 8601 timestamp of dismissal, or null if active." }, - "description": { + "ignoredBy": { "type": [ "string", "null" ], - "maxLength": 500, - "description": "Label description", - "example": "Important items that need attention" + "format": "uuid", + "description": "User id that dismissed the suggestion, or null if active." }, - "homepage": { - "type": "boolean", - "description": "Whether label is shown on homepage" + "priority": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "Priority from 1 (highest) to 10 (lowest).", + "example": 1 }, - "name": { + "proposedChanges": { + "$ref": "#/components/schemas/SuggestionProposedChanges" + }, + "rationale": { "type": "string", - "description": "Label name", - "example": "verified" + "description": "Explanation of why the suggestion was made." }, - "usage_count": { - "type": "number", - "description": "Number of documents with this label" + "title": { + "type": "string", + "description": "Short human-readable title." }, - "verified": { - "type": "boolean", - "description": "Whether label is verified" + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of the last write of any kind, including dismiss/restore." } }, "required": [ - "color", - "description", - "homepage", - "name", - "usage_count", - "verified" + "aiModifiedAt", + "category", + "createdAt", + "evidence", + "id", + "ignoreReason", + "ignoredAt", + "ignoredBy", + "priority", + "proposedChanges", + "rationale", + "title", + "updatedAt" ] }, - "LabelsUpdateResponse": { + "SuggestionEvidenceItem": { "type": "object", "properties": { - "color": { - "type": [ - "string", - "null" - ], - "maxLength": 9, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" + "capturedAt": { + "type": "string", + "description": "ISO 8601 timestamp of when the evidence was captured." }, - "description": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Label description", - "example": "Important items that need attention" + "chatAiSessionId": { + "type": "string", + "format": "uuid", + "description": "Chat session that motivated the suggestion." }, - "homepage": { - "type": "boolean", - "description": "Whether label is shown on homepage" + "type": { + "type": "string", + "enum": [ + "ai_chat" + ] + } + }, + "required": [ + "capturedAt", + "chatAiSessionId", + "type" + ] + }, + "SuggestionProposedChanges": { + "type": "object", + "properties": { + "edits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SuggestionContextEdit" + } }, - "name": { + "kind": { "type": "string", - "description": "Label name", - "example": "verified" + "enum": [ + "context_edits" + ] + } + }, + "required": [ + "edits", + "kind" + ], + "description": "The change(s) the suggestion would apply to the model." + }, + "SuggestionContextEdit": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "The model field being edited (e.g. `ai_context`).", + "example": "ai_context" }, - "usage_count": { - "type": "number", - "description": "Number of documents with this label" + "target": { + "type": "string", + "description": "Dot-path identifying what the edit applies to, e.g. `views.orders.fields.status`.", + "example": "views.orders" }, - "verified": { - "type": "boolean", - "description": "Whether label is verified" + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "description": "The proposed value for the field." } }, "required": [ - "color", - "description", - "homepage", - "name", - "usage_count", - "verified" + "field", + "target", + "value" ] }, - "LabelsUpdateBody": { + "ScheduleSuggestionsResponse": { "type": "object", "properties": { - "color": { - "type": [ - "string", - "null" - ], - "maxLength": 9, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" - }, - "description": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Label description", - "example": "Important items that need attention" + "id": { + "type": "string", + "format": "uuid", + "description": "The schedule (trigger) id." }, - "homepage": { - "type": "boolean", - "description": "Show label on homepage. Requires admin permissions to modify." + "sharedModelId": { + "type": "string", + "format": "uuid", + "description": "The shared model the schedule generates suggestions for." }, - "name": { + "status": { "type": "string", - "minLength": 2, - "maxLength": 25, - "description": "Label name", - "example": "important" + "enum": [ + "enabled" + ] }, - "verified": { - "type": "boolean", - "description": "Mark as verified label. Requires admin permissions to modify." + "timezone": { + "type": "string", + "description": "IANA timezone the schedule runs in.", + "example": "America/New_York" } - } + }, + "required": [ + "id", + "sharedModelId", + "status", + "timezone" + ] + }, + "ScheduleSuggestionsBody": { + "type": "object", + "properties": { + "timezone": { + "type": "string", + "default": "UTC", + "description": "IANA timezone the schedule fires in (e.g. `America/New_York`). Generation currently runs once daily at ~2 AM in this timezone. Defaults to `UTC`.", + "example": "America/New_York" + } + }, + "additionalProperties": false + }, + "IgnoreSuggestionBody": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "maxLength": 4000, + "description": "Optional free-text reason for dismissing the suggestion.", + "example": "Already covered by an existing field description." + } + }, + "additionalProperties": false }, "ModelsListResponse": { "type": "object", @@ -27946,6 +31960,12 @@ "description": "Cache policy for query execution. Controls whether to use cached results.", "example": "normal" }, + "environmentConnectionId": { + "type": "string", + "format": "uuid", + "description": "Connection ID of the environment to run the query against, overriding the connection environment inherited from the (target) user's session or default. Must be a configured environment of the query model's connection that the user can access. Obtain valid IDs from the `connectionId` field of `GET /api/v1/connection-environments`.", + "example": "550e8400-e29b-41d4-a716-446655440000" + }, "formatResults": { "type": "boolean", "description": "Whether to format result values (e.g., apply number formatting). Only valid when resultType is specified." @@ -28200,7 +32220,7 @@ "example": false }, "metadata": { - "description": "Schedule metadata including format options and delivery settings" + "description": "Schedule metadata including format options and delivery settings. Includes `timezoneOverride` (IANA timezone applied to query execution at render time, or null when no override is set)." }, "name": { "type": "string", @@ -30277,52 +34297,401 @@ "description": "Model ID for model-level role assignment. Required if connectionId not provided.", "example": "550e8400-e29b-41d4-a716-446655440000" }, - "roleName": { - "type": "string", - "minLength": 1, - "description": "Name of the role to assign (base or custom role)", - "example": "VIEWER" + "roleName": { + "type": "string", + "minLength": 1, + "description": "Name of the role to assign (base or custom role)", + "example": "VIEWER" + } + }, + "required": [ + "roleName" + ] + }, + "WhoamiResponse": { + "type": "object", + "properties": { + "keyScope": { + "type": "string", + "enum": [ + "user", + "organization" + ], + "description": "Scope of the API key in use. A separate axis from role: a user-scoped key (PAT/OAuth) acts as a single user and cannot use SCIM, regardless of the user's org role." + }, + "orgRole": { + "type": "string", + "enum": [ + "MEMBER", + "ORG_ADMIN" + ], + "description": "The caller's organization role.", + "example": "MEMBER" + }, + "rolesByModel": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/WhoamiModelRole" + }, + "description": "Resolved role and effective permissions per model, keyed by model id. Connection role resolves per shared model, so this is per-model rather than a single global role." + }, + "rolesByModelTruncated": { + "type": "boolean", + "description": "Present and `true` when `rolesByModel` was truncated because the caller can access more models than the unfiltered limit. Pass a `modelId` filter to retrieve specific models." + }, + "user": { + "$ref": "#/components/schemas/WhoamiUser" + } + }, + "required": [ + "keyScope", + "orgRole", + "rolesByModel", + "user" + ] + }, + "WhoamiModelRole": { + "type": "object", + "properties": { + "baseRole": { + "type": "string", + "description": "The resolved base role (for custom roles, the base role they extend).", + "example": "QUERIER" + }, + "connectionId": { + "type": "string", + "description": "The connection this model belongs to" + }, + "permissions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "QUERY_FULL_MODEL", + "QUERY_SQL", + "VIEW_SQL", + "QUERY_TOPICS", + "RUN_CONTENT_QUERIES", + "DOWNLOAD_CONTENT_QUERY", + "UPLOAD_CSV", + "SCHEDULE", + "SAVE_SPREADSHEETS", + "USE_AI", + "USE_WORKBOOKS", + "UPDATE", + "UPDATE_RESTRICTED" + ] + }, + "description": "The caller's resolved/effective permissions on this model, reflecting custom roles. This is a capability signal for the directly-roleable model kinds (schema / shared / extension). It does not enumerate the permissions you derive on branch, workbook, and query models from your role on the base model they descend from — absence here does not mean you lack access on those derived models. MANAGE_MODEL, READ, and REFRESH_SCHEMA are also not reported: they derive from connection / sibling-model roles rather than a per-model rule.", + "example": [ + "QUERY_TOPICS", + "QUERY_SQL", + "USE_WORKBOOKS" + ] + }, + "roleName": { + "type": "string", + "description": "The resolved role name (informational; may be a custom role). Use `permissions` to decide capability.", + "example": "QUERIER" + } + }, + "required": [ + "baseRole", + "connectionId", + "permissions", + "roleName" + ] + }, + "WhoamiUser": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The caller's user id" + }, + "membershipId": { + "type": "string", + "description": "The caller's own membership id within this organization. This is the id accepted by the admin `GET /api/v1/users/{id}/model-roles` endpoint (it is distinct from the user id)." + } + }, + "required": [ + "id", + "membershipId" + ] + } + }, + "parameters": {} + }, + "paths": { + "/api/v1/ai/generate-query": { + "post": { + "description": "Generate an Omni semantic query from a natural language prompt. Optionally executes the generated query and returns results. The AI analyzes the prompt, selects appropriate fields and filters from the model, and constructs a query. Requires the querier role on the target model.", + "operationId": "aiGenerateQuery", + "summary": "Generate query from natural language", + "tags": [ + "AI" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiGenerateQueryBody" + } + } + } + }, + "responses": { + "200": { + "description": "Query generated successfully. If runQuery is true (default), includes execution results. Check the error field — a 200 response may still contain a partial error if the query was generated but execution failed. When the organization is over its AI downgrade threshold the response also carries `downgradedModelTier` naming the cheaper tier the query was generated with.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiGenerateQueryResponse" + } + } + } + }, + "400": { + "description": "Invalid request. The prompt may be missing, the modelId may be invalid, or the AI was unable to generate a query for the given prompt.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "402": { + "description": "AI is unavailable because the organization is over its AI credit limit. The body carries the stable reason code `shutoff`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiCreditShutoffError" + } + } + } + }, + "403": { + "description": "Insufficient permissions. Requires the querier role on the target model and AI query generation must be enabled for the organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "404": { + "description": "The specified model or topic was not found in the organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "500": { + "description": "AI service error." + } + } + } + }, + "/api/v1/ai/pick-topic": { + "post": { + "description": "Analyze a natural language prompt and determine which topic in the model is the best fit for answering the question. Useful as a preprocessing step before calling generate-query or submitting an AI job, especially when the user's question could relate to multiple topics.", + "operationId": "aiPickTopic", + "summary": "Pick the best topic for a prompt", + "tags": [ + "AI" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiPickTopicBody" + } + } + } + }, + "responses": { + "200": { + "description": "Topic selected successfully. The returned topicId can be used as the topicName parameter in other AI endpoints.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiPickTopicResponse" + } + } + } + }, + "400": { + "description": "Invalid request body. The prompt or modelId may be missing or malformed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Insufficient permissions. Requires the querier role on the target model and AI must be enabled for the organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "404": { + "description": "The specified model was not found, or no accessible topics exist in the model.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "500": { + "description": "AI service error." + } + } + } + }, + "/api/v1/ai/search-omni-docs": { + "post": { + "description": "Search the Omni documentation using AI to answer questions about Omni features, configuration, modeling, dashboards, and more. Sends a natural language question and returns a synthesized answer with source links to the relevant documentation pages.", + "operationId": "aiSearchOmniDocs", + "summary": "Search Omni documentation", + "tags": [ + "AI" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiSearchOmniDocsBody" + } + } + } + }, + "responses": { + "200": { + "description": "Documentation search completed successfully. Returns a synthesized answer with source links.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiSearchOmniDocsResponse" + } + } + } + }, + "400": { + "description": "Invalid request. The question may be missing or exceed the 2000 character limit.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Omni Agent is not enabled for this organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "500": { + "description": "AI service error." } - }, - "required": [ - "roleName" - ] + } } }, - "parameters": {} - }, - "paths": { - "/api/v1/ai/generate-query": { + "/api/v1/ai/jobs": { "post": { - "description": "Generate an Omni semantic query from a natural language prompt. Optionally executes the generated query and returns results. The AI analyzes the prompt, selects appropriate fields and filters from the model, and constructs a query. Requires the querier role on the target model.", - "operationId": "aiGenerateQuery", - "summary": "Generate query from natural language", + "description": "Submit a new AI job for asynchronous execution. The AI will analyze the prompt, generate and execute queries against the specified model, and produce a summarized answer. Jobs are processed by a background worker and typically complete within 15–60 seconds. Use GET /api/v1/ai/jobs/{jobId} to poll for status, or configure a webhookUrl to receive a notification when the job completes. Optionally continue an existing conversation by providing a conversationId.", + "operationId": "aiJobSubmit", + "summary": "Submit an AI job", "tags": [ "AI" ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiGenerateQueryBody" + "$ref": "#/components/schemas/AiJobSubmitBody" } } } }, "responses": { - "200": { - "description": "Query generated successfully. If runQuery is true (default), includes execution results. Check the error field — a 200 response may still contain a partial error if the query was generated but execution failed. When the organization is over its AI downgrade threshold the response also carries `downgradedModelTier` naming the cheaper tier the query was generated with.", + "201": { + "description": "Job created and queued for execution. Use the returned jobId to poll for status or retrieve results.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiGenerateQueryResponse" + "$ref": "#/components/schemas/AiJobSubmitResponse" } } } }, "400": { - "description": "Invalid request. The prompt may be missing, the modelId may be invalid, or the AI was unable to generate a query for the given prompt.", + "description": "Invalid request body. Common causes: missing or empty prompt, invalid UUID for modelId/branchId/conversationId, invalid webhook URL format.", "content": { "application/json": { "schema": { @@ -30341,28 +34710,94 @@ } } }, - "402": { - "description": "AI is unavailable because the organization is over its AI credit limit. The body carries the stable reason code `shutoff`.", + "403": { + "description": "Insufficient permissions. The AI jobs API must be enabled for the organization, AI query generation must be enabled, and the user must have appropriate model access. User-scoped API keys cannot act on behalf of other users.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiCreditShutoffError" + "$ref": "#/components/schemas/ApiError403" } } } }, - "403": { - "description": "Insufficient permissions. Requires the querier role on the target model and AI query generation must be enabled for the organization.", + "404": { + "description": "The specified model was not found in the organization, the branchId does not belong to the specified model, or the topicName does not exist in the model (or is excluded by ai_chat_topics restrictions).", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError403" + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "409": { + "description": "An active job already exists for the specified conversationId. Wait for the current job to complete before submitting another job to the same conversation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError409" + } + } + } + } + } + } + }, + "/api/v1/ai/jobs/{jobId}": { + "get": { + "description": "Get the current status of an AI job, including its state, progress information, and result summary. The response fields vary by state — for example, progress is only present during EXECUTING, and resultSummary is only present when COMPLETE. Poll this endpoint every 2–5 seconds until the job reaches a terminal state (COMPLETE, FAILED, or CANCELLED).", + "operationId": "aiJobStatus", + "summary": "Get AI job status", + "tags": [ + "AI" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Job status retrieved successfully. Check the state field to determine if the job is still running or has reached a terminal state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiJobStatusResponse" + } + } + } + }, + "400": { + "description": "Invalid job ID format. Must be a valid UUID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" } } } }, "404": { - "description": "The specified model or topic was not found in the organization.", + "description": "Job not found. The job may not exist or may belong to a different organization.", "content": { "application/json": { "schema": { @@ -30370,44 +34805,45 @@ } } } - }, - "500": { - "description": "AI service error." } } } }, - "/api/v1/ai/pick-topic": { + "/api/v1/ai/jobs/{jobId}/cancel": { "post": { - "description": "Analyze a natural language prompt and determine which topic in the model is the best fit for answering the question. Useful as a preprocessing step before calling generate-query or submitting an AI job, especially when the user's question could relate to multiple topics.", - "operationId": "aiPickTopic", - "summary": "Pick the best topic for a prompt", + "description": "Request cancellation of an AI job. This endpoint is idempotent — calling it on an already-cancelled or completed job returns success with the current state. For QUEUED jobs, cancellation is immediate. For EXECUTING jobs, the worker will stop after completing its current iteration. Jobs in DELIVERING state cannot be cancelled as they are already finalizing results. Only the job owner or organization admins can cancel jobs.", + "operationId": "aiJobCancel", + "summary": "Cancel an AI job", "tags": [ "AI" ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiPickTopicBody" - } - } + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" } - }, + ], "responses": { "200": { - "description": "Topic selected successfully. The returned topicId can be used as the topicName parameter in other AI endpoints.", + "description": "Cancellation request processed. The state field indicates the job's state after the attempt — CANCELLED if successful, or the current terminal state if the job had already completed.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiPickTopicResponse" + "$ref": "#/components/schemas/AiJobCancelResponse" } } } }, "400": { - "description": "Invalid request body. The prompt or modelId may be missing or malformed.", + "description": "Invalid job ID format. Must be a valid UUID.", "content": { "application/json": { "schema": { @@ -30427,7 +34863,7 @@ } }, "403": { - "description": "Insufficient permissions. Requires the querier role on the target model and AI must be enabled for the organization.", + "description": "Permission denied. Only the job owner or organization admins can cancel jobs.", "content": { "application/json": { "schema": { @@ -30437,7 +34873,7 @@ } }, "404": { - "description": "The specified model was not found, or no accessible topics exist in the model.", + "description": "Job not found. The job may not exist or may belong to a different organization.", "content": { "application/json": { "schema": { @@ -30446,43 +34882,121 @@ } } }, - "500": { - "description": "AI service error." + "409": { + "description": "Concurrent modification conflict. The job state was changed by another request. Retry the cancellation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError409" + } + } + } } } } }, - "/api/v1/ai/search-omni-docs": { - "post": { - "description": "Search the Omni documentation using AI to answer questions about Omni features, configuration, modeling, dashboards, and more. Sends a natural language question and returns a synthesized answer with source links to the relevant documentation pages.", - "operationId": "aiSearchOmniDocs", - "summary": "Search Omni documentation", + "/api/v1/ai/jobs/{jobId}/result": { + "get": { + "description": "Retrieve the full result of a completed AI job, including all actions taken by the AI (queries generated, data retrieved) and the final summarized answer. Results are only available for jobs in COMPLETE state and are retained for 14 days after completion. The response is streamed directly from storage.", + "operationId": "aiJobResult", + "summary": "Get AI job result", "tags": [ "AI" ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiSearchOmniDocsBody" + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Full job result including the AI's actions, query results (with CSV data), and the final Markdown-formatted answer.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiJobResultResponse" + } + } + } + }, + "400": { + "description": "Invalid job ID format. Must be a valid UUID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "404": { + "description": "Job not found, not in COMPLETE state, or result is no longer available (results are retained for 14 days).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } } } } - }, + } + } + }, + "/api/v1/ai/jobs/{jobId}/vis": { + "get": { + "description": "Render the visualization from a completed AI job as a PNG image. The endpoint extracts the visualization configuration from the job result, loads Arrow IPC data, and renders it server-side using Vega. For style-only follow-ups (e.g., \"make it a bar chart\"), the endpoint walks back through previous jobs in the conversation to find the original query data.", + "operationId": "aiJobVisualization", + "summary": "Render AI job visualization", + "tags": [ + "AI" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" + } + ], "responses": { "200": { - "description": "Documentation search completed successfully. Returns a synthesized answer with source links.", + "description": "Visualization rendered as a PNG image. The Content-Type header is image/png.", "content": { - "application/json": { + "image/png": { "schema": { - "$ref": "#/components/schemas/AiSearchOmniDocsResponse" + "format": "binary", + "type": "string" } } } }, "400": { - "description": "Invalid request. The question may be missing or exceed the 2000 character limit.", + "description": "Invalid job ID format. Must be a valid UUID.", "content": { "application/json": { "schema": { @@ -30501,8 +35015,60 @@ } } }, + "404": { + "description": "Job not found, not in COMPLETE state, or the apiAiVis feature flag is not enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "422": { + "description": "The job completed but cannot be rendered as a visualization. Common causes: no visualization action in the job result, no Arrow IPC data available, missing summary fields, or the chart type is not renderable as an image (e.g., tables, KPIs).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError422" + } + } + } + } + } + } + }, + "/api/v1/ai/branding": { + "get": { + "description": "Returns the organization's AI helper branding — display name, optional custom logo URL, and copy used on AI helper landing surfaces (headline, body, prompt placeholder). Falls back to Omni's defaults when the organization hasn't configured custom branding, so the response is always populated. Used by client apps (iOS, embeds) to render the AI helper with the org's chosen identity.", + "operationId": "aiBranding", + "summary": "Get AI helper branding", + "tags": [ + "AI" + ], + "responses": { + "200": { + "description": "AI branding retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiBrandingResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, "403": { - "description": "Omni Agent is not enabled for this organization.", + "description": "AI access is required to view AI helper branding (no model in the org grants USE_AI to the caller).", "content": { "application/json": { "schema": { @@ -30510,22 +35076,44 @@ } } } - }, - "500": { - "description": "AI service error." } } } }, - "/api/v1/ai/jobs": { - "post": { - "description": "Submit a new AI job for asynchronous execution. The AI will analyze the prompt, generate and execute queries against the specified model, and produce a summarized answer. Jobs are processed by a background worker and typically complete within 15–60 seconds. Use GET /api/v1/ai/jobs/{jobId} to poll for status, or configure a webhookUrl to receive a notification when the job completes. Optionally continue an existing conversation by providing a conversationId.", - "operationId": "aiJobSubmit", - "summary": "Submit an AI job", + "/api/v1/ai/conversations": { + "get": { + "description": "List the user's recent AI conversations, ordered by most-recent activity. Each record includes the conversation id (pass it back as `conversationId` on subsequent /api/v1/ai/jobs submissions to continue the thread), an optional name, and a one-line summary of the most recent prompt for display. Paginated via opaque `pageInfo.nextCursor` — pass it back as `cursor` to fetch the next page.", + "operationId": "aiConversationsList", + "summary": "List AI conversations", "tags": [ "AI" ], "parameters": [ + { + "schema": { + "type": "string", + "description": "Cursor for pagination (from previous response nextCursor)", + "example": "eyJpZCI6IjEyMzQ1In0" + }, + "required": false, + "description": "Cursor for pagination (from previous response nextCursor)", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Number of results per page (1-100, integer)", + "example": 20 + }, + "required": false, + "description": "Number of results per page (1-100, integer)", + "name": "pageSize", + "in": "query" + }, { "schema": { "type": "string", @@ -30538,29 +35126,174 @@ "in": "query" } ], + "responses": { + "200": { + "description": "Paginated list of conversations.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiConversationsListResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Insufficient permissions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + } + } + } + }, + "/api/v1/ai/conversations/{conversationId}": { + "get": { + "description": "Return a conversation with its full message history (alternating user / assistant turns). Used by clients (iOS app, embed widgets) to restore a prior conversation in their UI.", + "operationId": "aiConversationDetail", + "summary": "Get AI conversation with messages", + "tags": [ + "AI" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "name": "conversationId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Conversation with messages in chronological order.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiConversationDetailResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "AI access is required to view chat conversations (no model in the org grants USE_AI to the caller).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "404": { + "description": "Conversation not found. User-scoped keys also get 404 (not 403) when the conversation exists but belongs to a different user — existence of another user's conversations is not disclosed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + } + } + } + }, + "/api/v1/ai/credit-controls": { + "get": { + "description": "Get the organization's AI credit controls: the downgrade and shutoff thresholds, plus read-only context (the credit limit, usage so far this billing period, and the period bounds). This is the API mirror of the AI Hub credit controls page and requires the same AI-admin permission.", + "operationId": "aiCreditControlsGet", + "summary": "Get AI credit controls", + "tags": [ + "AI" + ], + "responses": { + "200": { + "description": "Current credit controls. Thresholds are `null` when the corresponding control is off.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiCreditControlsResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Insufficient permissions, or AI credit controls are not enabled for the organization. Requires AI-admin access.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + } + } + }, + "patch": { + "description": "Update the organization's AI credit thresholds. Both fields are optional and tri-state: omit a field to leave it unchanged, send `null` to turn that control off, or send a non-negative number to set it. At least one field is required. The `downgradeCredits <= shutoffCredits` invariant is enforced against the merged result. Returns the full current state, the same shape as GET.", + "operationId": "aiCreditControlsUpdate", + "summary": "Update AI credit controls", + "tags": [ + "AI" + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobSubmitBody" + "$ref": "#/components/schemas/AiCreditControlsUpdateBody" } } } }, "responses": { - "201": { - "description": "Job created and queued for execution. Use the returned jobId to poll for status or retrieve results.", + "200": { + "description": "Thresholds updated. Returns the full current state.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobSubmitResponse" + "$ref": "#/components/schemas/AiCreditControlsResponse" } } } }, "400": { - "description": "Invalid request body. Common causes: missing or empty prompt, invalid UUID for modelId/branchId/conversationId, invalid webhook URL format.", + "description": "Invalid request. Common causes: empty body, a negative threshold, an unknown field, or downgradeCredits above shutoffCredits.", "content": { "application/json": { "schema": { @@ -30580,7 +35313,7 @@ } }, "403": { - "description": "Insufficient permissions. The AI jobs API must be enabled for the organization, AI query generation must be enabled, and the user must have appropriate model access. User-scoped API keys cannot act on behalf of other users.", + "description": "Insufficient permissions, or AI credit controls are not enabled. Requires AI-admin access.", "content": { "application/json": { "schema": { @@ -30588,65 +35321,95 @@ } } } - }, - "404": { - "description": "The specified model was not found in the organization, the branchId does not belong to the specified model, or the topicName does not exist in the model (or is excluded by ai_chat_topics restrictions).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError404" - } - } - } - }, - "409": { - "description": "An active job already exists for the specified conversationId. Wait for the current job to complete before submitting another job to the same conversation.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError409" - } - } - } } } } }, - "/api/v1/ai/jobs/{jobId}": { + "/api/v1/ai/routines": { "get": { - "description": "Get the current status of an AI job, including its state, progress information, and result summary. The response fields vary by state — for example, progress is only present during EXECUTING, and resultSummary is only present when COMPLETE. Poll this endpoint every 2–5 seconds until the job reaches a terminal state (COMPLETE, FAILED, or CANCELLED).", - "operationId": "aiJobStatus", - "summary": "Get AI job status", + "description": "List routines for the calling user, newest first. Includes routines paused by the owner or disabled by Omni, but excludes deleted routines. Use `pageInfo.nextCursor` from one response as the `cursor` query parameter on the next request. Organization API keys can pass `?userId=` to list routines for a specific organization member.", + "operationId": "routinesList", + "summary": "List routines", "tags": [ - "AI" + "AI Routines" ], "parameters": [ + { + "schema": { + "type": "string", + "description": "Cursor for pagination (from previous response nextCursor)", + "example": "eyJpZCI6IjEyMzQ1In0" + }, + "required": false, + "description": "Cursor for pagination (from previous response nextCursor)", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Number of results per page (1-100, integer)", + "example": 20 + }, + "required": false, + "description": "Number of results per page (1-100, integer)", + "name": "pageSize", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "desc", + "description": "Sort direction for results", + "example": "desc" + }, + "required": false, + "description": "Sort direction for results", + "name": "sortDirection", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Field to sort results by" + }, + "required": false, + "description": "Field to sort results by", + "name": "sortField", + "in": "query" + }, { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "Target user membership ID (for org-scoped API keys)" }, - "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", - "in": "path" + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], "responses": { "200": { - "description": "Job status retrieved successfully. Check the state field to determine if the job is still running or has reached a terminal state.", + "description": "Paginated list of routines.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobStatusResponse" + "$ref": "#/components/schemas/RoutinesListResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid pagination cursor or `userId` value.", "content": { "application/json": { "schema": { @@ -30665,8 +35428,18 @@ } } }, + "403": { + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to list routines for another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, "404": { - "description": "Job not found. The job may not exist or may belong to a different organization.", + "description": "The `userId` membership was not found in the organization.", "content": { "application/json": { "schema": { @@ -30676,43 +35449,50 @@ } } } - } - }, - "/api/v1/ai/jobs/{jobId}/cancel": { + }, "post": { - "description": "Request cancellation of an AI job. This endpoint is idempotent — calling it on an already-cancelled or completed job returns success with the current state. For QUEUED jobs, cancellation is immediate. For EXECUTING jobs, the worker will stop after completing its current iteration. Jobs in DELIVERING state cannot be cancelled as they are already finalizing results. Only the job owner or organization admins can cancel jobs.", - "operationId": "aiJobCancel", - "summary": "Cancel an AI job", + "description": "Create a routine that runs a saved prompt on a schedule and delivers the AI response through a single email destination. Each scheduled run executes once using the routine owner's permissions, and every recipient receives the same result. Organization API keys can pass `?userId=` to create the routine for a specific organization member.", + "operationId": "routineCreate", + "summary": "Create a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "Target user membership ID (for org-scoped API keys)" }, - "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", - "in": "path" + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutineCreateBody" + } + } + } + }, "responses": { - "200": { - "description": "Cancellation request processed. The state field indicates the job's state after the attempt — CANCELLED if successful, or the current terminal state if the job had already completed.", + "201": { + "description": "Routine created successfully.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobCancelResponse" + "$ref": "#/components/schemas/RoutineCreateResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid request body, recipient configuration, schedule, or timezone. Also returned when the schedule is more frequent than the organization allows.", "content": { "application/json": { "schema": { @@ -30732,7 +35512,7 @@ } }, "403": { - "description": "Permission denied. Only the job owner or organization admins can cancel jobs.", + "description": "AI routines or AI query generation are not enabled for the organization, or the API key cannot act on behalf of the requested user.", "content": { "application/json": { "schema": { @@ -30742,7 +35522,7 @@ } }, "404": { - "description": "Job not found. The job may not exist or may belong to a different organization.", + "description": "Model, branch, or topic not found, or not accessible to the requested user.", "content": { "application/json": { "schema": { @@ -30751,12 +35531,12 @@ } } }, - "409": { - "description": "Concurrent modification conflict. The job state was changed by another request. Retry the cancellation.", + "429": { + "description": "The resolved user already has the maximum number of active routines.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError409" + "$ref": "#/components/schemas/ApiError429" } } } @@ -30764,41 +35544,51 @@ } } }, - "/api/v1/ai/jobs/{jobId}/result": { + "/api/v1/ai/routines/{id}": { "get": { - "description": "Retrieve the full result of a completed AI job, including all actions taken by the AI (queries generated, data retrieved) and the final summarized answer. Results are only available for jobs in COMPLETE state and are retained for 14 days after completion. The response is streamed directly from storage.", - "operationId": "aiJobResult", - "summary": "Get AI job result", + "description": "Get a single routine, including the status of its most recent completed run.", + "operationId": "routineGet", + "summary": "Get a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "The UUID of the routine." }, "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", + "description": "The UUID of the routine.", + "name": "id", "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], "responses": { "200": { - "description": "Full job result including the AI's actions, query results (with CSV data), and the final Markdown-formatted answer.", + "description": "Routine details.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobResultResponse" + "$ref": "#/components/schemas/RoutineResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid routine ID.", "content": { "application/json": { "schema": { @@ -30817,8 +35607,18 @@ } } }, + "403": { + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to access another user's routine.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, "404": { - "description": "Job not found, not in COMPLETE state, or result is no longer available (results are retained for 14 days).", + "description": "Routine not found or has been deleted.", "content": { "application/json": { "schema": { @@ -30828,44 +35628,61 @@ } } } - } - }, - "/api/v1/ai/jobs/{jobId}/vis": { - "get": { - "description": "Render the visualization from a completed AI job as a PNG image. The endpoint extracts the visualization configuration from the job result, loads Arrow IPC data, and renders it server-side using Vega. For style-only follow-ups (e.g., \"make it a bar chart\"), the endpoint walks back through previous jobs in the conversation to find the original query data.", - "operationId": "aiJobVisualization", - "summary": "Render AI job visualization", + }, + "patch": { + "description": "Update a routine. All request fields are optional, and only supplied fields are changed. Supplying `destination` replaces the full recipient configuration.", + "operationId": "routineUpdate", + "summary": "Update a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "The UUID of the routine." }, "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", + "description": "The UUID of the routine.", + "name": "id", "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutineUpdateBody" + } + } + } + }, "responses": { "200": { - "description": "Visualization rendered as a PNG image. The Content-Type header is image/png.", + "description": "Updated routine details.", "content": { - "image/png": { + "application/json": { "schema": { - "format": "binary", - "type": "string" + "$ref": "#/components/schemas/RoutineResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid routine ID, request body, recipient configuration, schedule, or timezone.", "content": { "application/json": { "schema": { @@ -30884,104 +35701,46 @@ } } }, - "404": { - "description": "Job not found, not in COMPLETE state, or the apiAiVis feature flag is not enabled.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError404" - } - } - } - }, - "422": { - "description": "The job completed but cannot be rendered as a visualization. Common causes: no visualization action in the job result, no Arrow IPC data available, missing summary fields, or the chart type is not renderable as an image (e.g., tables, KPIs).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError422" - } - } - } - } - } - } - }, - "/api/v1/ai/branding": { - "get": { - "description": "Returns the organization's AI helper branding — display name, optional custom logo URL, and copy used on AI helper landing surfaces (headline, body, prompt placeholder). Falls back to Omni's defaults when the organization hasn't configured custom branding, so the response is always populated. Used by client apps (iOS, embeds) to render the AI helper with the org's chosen identity.", - "operationId": "aiBranding", - "summary": "Get AI helper branding", - "tags": [ - "AI" - ], - "responses": { - "200": { - "description": "AI branding retrieved successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiBrandingResponse" - } - } - } - }, - "401": { - "description": "Missing or invalid API key.", + "403": { + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to update another user's routine.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError401" + "$ref": "#/components/schemas/ApiError403" } } } }, - "403": { - "description": "AI access is required to view AI helper branding (no model in the org grants USE_AI to the caller).", + "404": { + "description": "Routine not found or has been deleted.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError403" + "$ref": "#/components/schemas/ApiError404" } } } } } - } - }, - "/api/v1/ai/conversations": { - "get": { - "description": "List the user's recent AI conversations, ordered by most-recent activity. Each record includes the conversation id (pass it back as `conversationId` on subsequent /api/v1/ai/jobs submissions to continue the thread), an optional name, and a one-line summary of the most recent prompt for display. Paginated via opaque `pageInfo.nextCursor` — pass it back as `cursor` to fetch the next page.", - "operationId": "aiConversationsList", - "summary": "List AI conversations", + }, + "delete": { + "description": "Delete a routine. It stops running immediately and no longer appears in list or get responses.", + "operationId": "routineDelete", + "summary": "Delete a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", - "description": "Cursor for pagination (from previous response nextCursor)", - "example": "eyJpZCI6IjEyMzQ1In0" - }, - "required": false, - "description": "Cursor for pagination (from previous response nextCursor)", - "name": "cursor", - "in": "query" - }, - { - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20, - "description": "Number of results per page (1-100, integer)", - "example": 20 + "format": "uuid", + "description": "The UUID of the routine." }, - "required": false, - "description": "Number of results per page (1-100, integer)", - "name": "pageSize", - "in": "query" + "required": true, + "description": "The UUID of the routine.", + "name": "id", + "in": "path" }, { "schema": { @@ -30997,11 +35756,21 @@ ], "responses": { "200": { - "description": "Paginated list of conversations.", + "description": "Routine deleted successfully.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiConversationsListResponse" + "$ref": "#/components/schemas/RoutineDeleteResponse" + } + } + } + }, + "400": { + "description": "Invalid routine ID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" } } } @@ -31017,7 +35786,7 @@ } }, "403": { - "description": "Insufficient permissions.", + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to delete another user's routine.", "content": { "application/json": { "schema": { @@ -31025,36 +35794,69 @@ } } } + }, + "404": { + "description": "Routine not found or has already been deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } } } } }, - "/api/v1/ai/conversations/{conversationId}": { - "get": { - "description": "Return a conversation with its full message history (alternating user / assistant turns). Used by clients (iOS app, embed widgets) to restore a prior conversation in their UI.", - "operationId": "aiConversationDetail", - "summary": "Get AI conversation with messages", + "/api/v1/ai/routines/{id}/trigger": { + "post": { + "description": "Run a routine immediately, in addition to its schedule. The run executes once using the routine owner's permissions and delivers the AI response to every configured recipient — it is not a private preview. Returns once the run has started; the result is delivered asynchronously. Organization API keys can pass `?userId=` to act on behalf of a specific organization member.", + "operationId": "routineTrigger", + "summary": "Run a routine now", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", - "format": "uuid" + "format": "uuid", + "description": "The UUID of the routine." }, "required": true, - "name": "conversationId", + "description": "The UUID of the routine.", + "name": "id", "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], "responses": { - "200": { - "description": "Conversation with messages in chronological order.", + "202": { + "description": "The run has started.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiConversationDetailResponse" + "$ref": "#/components/schemas/RoutineTriggerResponse" + } + } + } + }, + "400": { + "description": "Invalid routine ID, or the routine cannot run as configured (e.g. its model, branch, or owner is no longer accessible).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" } } } @@ -31070,7 +35872,7 @@ } }, "403": { - "description": "AI access is required to view chat conversations (no model in the org grants USE_AI to the caller).", + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to run another user's routine.", "content": { "application/json": { "schema": { @@ -31080,7 +35882,7 @@ } }, "404": { - "description": "Conversation not found. User-scoped keys also get 404 (not 403) when the conversation exists but belongs to a different user — existence of another user's conversations is not disclosed.", + "description": "The routine does not exist or cannot be triggered (deleted, paused, or disabled by Omni).", "content": { "application/json": { "schema": { @@ -31088,6 +35890,16 @@ } } } + }, + "409": { + "description": "A run is already in progress for this routine.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError409" + } + } + } } } } @@ -32434,7 +37246,7 @@ "string", "null" ], - "description": "dbt version to use. Supported: Auto, 1.10, 1.11", + "description": "dbt version to use. Supported: Auto, 1.11, 1.12", "example": "1.11" }, "enableSemanticLayer": { @@ -34531,7 +39343,7 @@ }, "put": { "deprecated": true, - "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Removal scheduled per the `Sunset` response header.\n\nUpdates a document with the specified identifier. This endpoint performs a full resource replacement — all required fields must be provided and existing query presentations are replaced entirely. Only dashboard documents are supported; analysis documents and documents without an associated dashboard return 400. For published documents, the update goes through a draft/publish workflow automatically; if a draft already exists, the request returns 409 unless `clearExistingDraft` is set to `true`.", + "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Scheduled for removal on July 31, 2026 (see the `Sunset` response header).\n\nUpdates a document with the specified identifier. This endpoint performs a full resource replacement — all required fields must be provided and existing query presentations are replaced entirely. Only dashboard documents are supported; analysis documents and documents without an associated dashboard return 400. For published documents, the update goes through a draft/publish workflow automatically; if a draft already exists, the request returns 409 unless `clearExistingDraft` is set to `true`.", "operationId": "documentsPut", "summary": "Replace document (full replacement)", "tags": [ @@ -34562,6 +39374,37 @@ "responses": { "200": { "description": "Document replaced successfully", + "headers": { + "Deprecation": { + "schema": { + "type": "string", + "enum": [ + "true" + ], + "description": "Marks the endpoint as deprecated." + }, + "required": true, + "description": "Marks the endpoint as deprecated." + }, + "Link": { + "schema": { + "type": "string", + "description": "Points to the v2 successor resource.", + "example": "; rel=\"successor-version\"" + }, + "required": true, + "description": "Points to the v2 successor resource." + }, + "Sunset": { + "schema": { + "type": "string", + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594).", + "example": "Fri, 31 Jul 2026 00:00:00 GMT" + }, + "required": true, + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594)." + } + }, "content": { "application/json": { "schema": { @@ -34589,7 +39432,7 @@ }, "patch": { "deprecated": true, - "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Removal scheduled per the `Sunset` response header.\n\nUpdates a document's name, description, and/or identifier. This is a partial update — only provided fields are modified, and at least one of `name`, `description`, or `identifier` must be supplied. When `identifier` is changed, the previous identifier is retained in the document identifier history and continues to redirect. For published documents, the update goes through a draft/publish workflow automatically.", + "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Scheduled for removal on July 31, 2026 (see the `Sunset` response header).\n\nUpdates a document's name, description, and/or identifier. This is a partial update — only provided fields are modified, and at least one of `name`, `description`, or `identifier` must be supplied. When `identifier` is changed, the previous identifier is retained in the document identifier history and continues to redirect. For published documents, the update goes through a draft/publish workflow automatically.", "operationId": "documentsUpdate", "summary": "Rename document", "tags": [ @@ -34620,6 +39463,37 @@ "responses": { "200": { "description": "Document updated successfully", + "headers": { + "Deprecation": { + "schema": { + "type": "string", + "enum": [ + "true" + ], + "description": "Marks the endpoint as deprecated." + }, + "required": true, + "description": "Marks the endpoint as deprecated." + }, + "Link": { + "schema": { + "type": "string", + "description": "Points to the v2 successor resource.", + "example": "; rel=\"successor-version\"" + }, + "required": true, + "description": "Points to the v2 successor resource." + }, + "Sunset": { + "schema": { + "type": "string", + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594).", + "example": "Fri, 31 Jul 2026 00:00:00 GMT" + }, + "required": true, + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594)." + } + }, "content": { "application/json": { "schema": { @@ -35819,7 +40693,7 @@ }, "/api/v2/documents": { "post": { - "description": "Create a brand-new document and publish it live. Accepts creation metadata (`modelId`, `name`, optional `identifier` / `description` / `folderId`) plus the same content slice as the PATCH body — `queryPresentations`, `controls`, `settings`, `containers`. The server mints internal tile identifiers, so callers omit `miniUuid`. Tiles in `queryPresentations` are merged by key over the single empty seed tile at key `\"1\"`; write to `\"1\"` (or send it as `null`) to replace the seed.\n\nWhen `containers` is omitted, every dashboard-eligible tile is auto-placed in a default layout. When `containers` is present, it fully defines the layout — tiles it does not reference are stored but not rendered.\n\nThe new document is published live before the response returns. As a first publish of brand-new content it is not subject to the org’s `requirePullRequestToPublish` policy (which gates edits to existing content).", + "description": "Create a brand-new document and publish it live. Accepts creation metadata (`modelId`, `name`, optional `identifier` / `description` / `folderId`) plus the same content slice as the PATCH body — `queryPresentations`, `controls`, `settings`, `containers`. The server mints internal tile identifiers, so callers omit `miniUuid`. Tiles in `queryPresentations` are merged by key over the single empty seed tile at key `\"1\"`; write to `\"1\"` (or send it as `null`) to replace the seed.\n\nWhen `containers` is omitted, every dashboard-eligible tile is auto-placed in a default layout. When `containers` is present, it fully defines the layout — tiles it does not reference are stored but not rendered. Send `containers: null` to create a workbook-only document with no dashboard (`controls` and `settings` must then be omitted); an empty `containers: []` is rejected.\n\nThe new document is published live before the response returns. As a first publish of brand-new content it is not subject to the org’s `requirePullRequestToPublish` policy (which gates edits to existing content).", "operationId": "documentsV2Create", "summary": "Create document", "tags": [ @@ -35883,6 +40757,22 @@ "description": "Document identifier — either the URL slug (e.g. `abc123`) or the canonical workbook UUID.", "name": "identifier", "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless." + }, + "required": false, + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless.", + "name": "pretty", + "in": "query" } ], "responses": { @@ -36006,6 +40896,22 @@ "description": "Published document identifier.", "name": "identifier", "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless." + }, + "required": false, + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless.", + "name": "pretty", + "in": "query" } ], "responses": { @@ -36349,6 +41255,16 @@ } } } + }, + "422": { + "description": "Prompt count exceeds the organization's per-set cap (default 25, higher for orgs with the `ai-eval-extra-prompts` flag).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalApiError422" + } + } + } } } } @@ -36511,7 +41427,7 @@ } }, "422": { - "description": "A `prompts[].id` in the request does not belong to this prompt set.", + "description": "A `prompts[].id` in the request does not belong to this prompt set, or the prompt count exceeds the organization's per-set cap (default 25, higher for orgs with the `ai-eval-extra-prompts` flag).", "content": { "application/json": { "schema": { @@ -37841,84 +42757,483 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LabelsUpdateBody" + "$ref": "#/components/schemas/LabelsUpdateBody" + } + } + } + }, + "responses": { + "200": { + "description": "Label updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LabelsUpdateResponse" + } + } + } + }, + "400": { + "description": "Invalid request body" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Permission denied - cannot modify verified/homepage labels without admin permissions" + }, + "404": { + "description": "Label not found" + }, + "409": { + "description": "Label with new name already exists" + } + } + }, + "delete": { + "operationId": "labelsDelete", + "summary": "Delete a label", + "tags": [ + "Labels" + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "required": true, + "description": "Label name", + "name": "name", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" + } + ], + "responses": { + "204": { + "description": "Label deleted successfully" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Permission denied - cannot delete verified/homepage labels without admin permissions" + }, + "404": { + "description": "Label not found" + }, + "409": { + "description": "Cannot delete label that is applied to documents" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions": { + "get": { + "description": "Lists AI-generated model suggestions for a shared model, filtered by dismissal status. Requires organization admin permissions.", + "operationId": "modelSuggestionsList", + "summary": "List model suggestions", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestions belong to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestions belong to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Cursor for pagination: the `nextCursor` from the previous response (the last suggestion id)." + }, + "required": false, + "description": "Cursor for pagination: the `nextCursor` from the previous response (the last suggestion id).", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Number of results per page (1-100, integer)", + "example": 20 + }, + "required": false, + "description": "Number of results per page (1-100, integer)", + "name": "pageSize", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "active", + "ignored", + "all" + ], + "default": "active", + "description": "Which suggestions to return: `active` (default, not dismissed), `ignored` (dismissed only), or `all`.", + "example": "active" + }, + "required": false, + "description": "Which suggestions to return: `active` (default, not dismissed), `ignored` (dismissed only), or `all`.", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Paginated list of suggestions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelSuggestionsListResponse" + } + } + } + }, + "400": { + "description": "Invalid query parameters or malformed `modelId`" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Model not found in this organization" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/schedule": { + "put": { + "description": "Enables the daily schedule that generates suggestions for the shared model. Idempotent — re-enabling leaves an existing schedule untouched. Requires organization admin permissions.", + "operationId": "modelSuggestionsScheduleEnable", + "summary": "Enable the suggestion schedule", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestions belong to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestions belong to", + "name": "modelId", + "in": "path" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduleSuggestionsBody" } } } }, "responses": { "200": { - "description": "Label updated successfully", + "description": "The schedule is enabled", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LabelsUpdateResponse" + "$ref": "#/components/schemas/ScheduleSuggestionsResponse" } } } }, "400": { - "description": "Invalid request body" + "description": "Invalid timezone or malformed `modelId`" }, "401": { "description": "Authentication required" }, "403": { - "description": "Permission denied - cannot modify verified/homepage labels without admin permissions" + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" }, "404": { - "description": "Label not found" + "description": "Model not found in this organization" }, - "409": { - "description": "Label with new name already exists" + "405": { + "description": "Method not allowed" } } }, "delete": { - "operationId": "labelsDelete", - "summary": "Delete a label", + "description": "Disables the daily generation schedule for the shared model. Idempotent. Requires organization admin permissions.", + "operationId": "modelSuggestionsScheduleDisable", + "summary": "Disable the suggestion schedule", "tags": [ - "Labels" + "AI Model Suggestions" ], "parameters": [ { "schema": { "type": "string", - "description": "Label name", - "example": "verified" + "format": "uuid", + "description": "UUID of the shared model the suggestions belong to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, "required": true, - "description": "Label name", - "name": "name", + "description": "UUID of the shared model the suggestions belong to", + "name": "modelId", "in": "path" + } + ], + "responses": { + "200": { + "description": "The schedule is disabled", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } }, + "400": { + "description": "Malformed `modelId`" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Model not found in this organization" + }, + "405": { + "description": "Method not allowed" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/{suggestionId}/ignore": { + "post": { + "description": "Dismisses (ignores) a suggestion, optionally with a reason. Requires organization admin permissions.", + "operationId": "modelSuggestionsIgnore", + "summary": "Ignore a suggestion", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "Target user membership ID (for org-scoped API keys)" + "description": "UUID of the shared model the suggestion belongs to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, - "required": false, - "description": "Target user membership ID (for org-scoped API keys)", - "name": "userId", - "in": "query" + "required": true, + "description": "UUID of the shared model the suggestion belongs to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the suggestion", + "example": "b2c3d4e5-f6a7-8901-bcde-f12345678901" + }, + "required": true, + "description": "UUID of the suggestion", + "name": "suggestionId", + "in": "path" } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IgnoreSuggestionBody" + } + } + } + }, "responses": { - "204": { - "description": "Label deleted successfully" + "200": { + "description": "The suggestion was dismissed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "400": { + "description": "Invalid body or malformed id" }, "401": { "description": "Authentication required" }, "403": { - "description": "Permission denied - cannot delete verified/homepage labels without admin permissions" + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" }, "404": { - "description": "Label not found" + "description": "Suggestion or model not found in this organization" }, - "409": { - "description": "Cannot delete label that is applied to documents" + "405": { + "description": "Method not allowed" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/{suggestionId}/restore": { + "post": { + "description": "Restores a previously dismissed suggestion back to the active list. Requires organization admin permissions.", + "operationId": "modelSuggestionsRestore", + "summary": "Restore a suggestion", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestion belongs to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestion belongs to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the suggestion", + "example": "b2c3d4e5-f6a7-8901-bcde-f12345678901" + }, + "required": true, + "description": "UUID of the suggestion", + "name": "suggestionId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The suggestion was restored", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "400": { + "description": "Malformed id" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Suggestion or model not found in this organization" + }, + "405": { + "description": "Method not allowed" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/{suggestionId}": { + "delete": { + "description": "Permanently deletes a suggestion. Requires organization admin permissions.", + "operationId": "modelSuggestionsDelete", + "summary": "Delete a suggestion", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestion belongs to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestion belongs to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the suggestion", + "example": "b2c3d4e5-f6a7-8901-bcde-f12345678901" + }, + "required": true, + "description": "UUID of the suggestion", + "name": "suggestionId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The suggestion was deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "400": { + "description": "Malformed id" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Suggestion or model not found in this organization" + }, + "405": { + "description": "Method not allowed" } } } @@ -39999,10 +45314,11 @@ { "schema": { "type": "string", - "description": "Optional branch ID to validate" + "format": "uuid", + "description": "Optional branch ID to validate against. Non-UUID values return 400." }, "required": false, - "description": "Optional branch ID to validate", + "description": "Optional branch ID to validate against. Non-UUID values return 400.", "name": "branch_id", "in": "query" }, @@ -41017,6 +46333,14 @@ "description": "IANA timezone for the schedule", "example": "America/New_York" }, + "timezoneOverride": { + "type": [ + "string", + "null" + ], + "description": "Optional IANA timezone applied to query execution at render time. Distinct from `timezone` (which controls *when* the schedule fires). Omit or pass null for no override.", + "example": "Europe/Paris" + }, "webhookUrl": { "type": "string", "format": "uri", @@ -43026,6 +48350,47 @@ } } } + }, + "/api/v1/whoami": { + "get": { + "description": "Returns the authenticated caller's own identity, API key scope, organization role, and resolved per-model permissions. Self-scoped and available to non-admins: it lets a caller decide whether an action is permitted without attempting it. Pass `modelId` to scope `rolesByModel` to specific models.", + "operationId": "whoami", + "summary": "Get current identity and permissions (whoami)", + "tags": [ + "Whoami" + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Optional model filter. A single model id or a comma-separated list. When provided, `rolesByModel` contains only these models. When omitted, models the caller can access are returned (up to a limit; see `rolesByModelTruncated`).", + "example": "550e8400-e29b-41d4-a716-446655440000" + }, + "required": false, + "description": "Optional model filter. A single model id or a comma-separated list. When provided, `rolesByModel` contains only these models. When omitted, models the caller can access are returned (up to a limit; see `rolesByModelTruncated`).", + "name": "modelId", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Caller's identity, key scope, org role, and per-model permissions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhoamiResponse" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "404": { + "description": "One or more requested `modelId`s do not exist or are not accessible to the caller" + } + } + } } }, "webhooks": {} diff --git a/cmd/omni/openapi.json b/cmd/omni/openapi.json index 28c20c3..3de471f 100644 --- a/cmd/omni/openapi.json +++ b/cmd/omni/openapi.json @@ -15,10 +15,18 @@ "description": "AI-powered data analysis. Submit natural language questions as synchronous queries or asynchronous jobs, and retrieve results including generated queries, data, and summarized answers.", "name": "AI" }, + { + "description": "Saved AI prompts that fire on a schedule and deliver the response to email recipients.", + "name": "AI Routines" + }, { "description": "AI evaluation: manage prompt sets and runs used to score AI quality against curated prompt suites.", "name": "AI Eval" }, + { + "description": "AI-generated model suggestions: list, generate, schedule, and manage suggested improvements to a shared model.", + "name": "AI Model Suggestions" + }, { "description": "API token management", "name": "API Tokens" @@ -82,6 +90,10 @@ { "description": "User and group management", "name": "Users" + }, + { + "description": "Self-introspection: the authenticated caller can discover their own identity, key scope, org role, and resolved per-model permissions.", + "name": "Whoami" } ], "components": { @@ -696,7 +708,7 @@ "detail": { "type": "string", "description": "Human-readable error message describing what went wrong.", - "example": "The AI agent is currently unavailable. Contact your administrator to re-enable." + "example": "The AI credit limit has been reached. Contact your administrator for assistance." }, "status": { "type": "integer", @@ -982,7 +994,7 @@ "modelId": { "type": "string", "format": "uuid", - "description": "The UUID of the shared model to query against. Only shared models are supported.", + "description": "The UUID of the model to query against. Must be a shared model, or a shared-extension model usable as a workbook base.", "example": "770e8400-e29b-41d4-a716-446655440002" }, "progressWebhookEnabled": { @@ -1019,7 +1031,7 @@ "webhookUrl": { "type": "string", "format": "uri", - "description": "URL to receive webhook POSTs. Always receives a terminal event (job.complete or job.failed) when the job finishes. When progressWebhookEnabled is true, also receives real-time progress events during execution.", + "description": "URL to receive webhook POSTs. Always receives a terminal event (job.complete, job.failed, or job.denied) when the job finishes; a job.denied event (e.g. the organization is over its AI credit limit) additionally carries a reason field. When progressWebhookEnabled is true, also receives real-time progress events during execution.", "example": "https://example.com/webhooks/omni" } }, @@ -1597,6 +1609,476 @@ "text" ] }, + "AiCreditControlsResponse": { + "type": "object", + "properties": { + "accountCreditLimit": { + "type": "number", + "minimum": 0, + "description": "Monthly AI credit limit for the whole Omni account (shared across every org under the same Salesforce account), not just this org. 0 when no limit is configured.", + "example": 2000 + }, + "creditsUsed": { + "type": "number", + "minimum": 0, + "description": "This org's credit usage in the current billing period.", + "example": 450 + }, + "downgradeCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Downgrade threshold, or `null` if the downgrade control is off.", + "example": 800 + }, + "periodEnd": { + "type": "integer", + "minimum": 0, + "description": "End of the current billing period as a Unix ms timestamp (UTC calendar-month boundary)." + }, + "periodStart": { + "type": "integer", + "minimum": 0, + "description": "Start of the current billing period as a Unix ms timestamp (UTC calendar-month boundary)." + }, + "shutoffCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Shutoff threshold, or `null` if the shutoff control is off.", + "example": 1200 + } + }, + "required": [ + "accountCreditLimit", + "creditsUsed", + "downgradeCredits", + "periodEnd", + "periodStart", + "shutoffCredits" + ] + }, + "AiCreditControlsUpdateBody": { + "type": "object", + "properties": { + "downgradeCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Credit usage at which AI downgrades to a cheaper model. Omit to leave unchanged, `null` to turn off, or a non-negative number to set. Must be at or below shutoffCredits.", + "example": 800 + }, + "shutoffCredits": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "description": "Credit usage at which AI shuts off entirely. Omit to leave unchanged, `null` to turn off, or a non-negative number to set.", + "example": 1200 + } + }, + "additionalProperties": false + }, + "RoutinesListResponse": { + "type": "object", + "properties": { + "pageInfo": { + "$ref": "#/components/schemas/PageInfo" + }, + "records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutineResponse" + }, + "description": "Routines returned for this request, newest first." + } + }, + "required": [ + "pageInfo", + "records" + ] + }, + "RoutineResponse": { + "type": "object", + "properties": { + "branchId": { + "type": [ + "string", + "null" + ], + "format": "uuid", + "description": "Branch of the shared model the prompt runs against, or null." + }, + "createdAt": { + "type": "string", + "description": "ISO 8601 timestamp when the routine was created." + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Display-only notes about the routine, or null." + }, + "destination": { + "$ref": "#/components/schemas/RoutineEmailDestination" + }, + "disabled": { + "type": "boolean", + "description": "Whether the owner has paused the routine." + }, + "id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the routine." + }, + "lastRun": { + "$ref": "#/components/schemas/RoutineLastRun" + }, + "modelId": { + "type": "string", + "format": "uuid", + "description": "The shared model the prompt runs against." + }, + "name": { + "type": "string", + "description": "Customer-visible name of the routine, used as the email subject." + }, + "prompt": { + "type": "string", + "description": "Natural language prompt Omni runs on each scheduled run." + }, + "recipientCount": { + "type": "integer", + "description": "Number of distinct deliverable recipients after expanding user groups and removing duplicates." + }, + "schedule": { + "type": "string", + "description": "Six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field)." + }, + "systemDisabled": { + "type": "boolean", + "description": "Whether Omni disabled the routine because it could no longer run successfully or safely." + }, + "systemDisabledReason": { + "type": [ + "string", + "null" + ], + "description": "Reason Omni disabled the routine, or null." + }, + "timezone": { + "type": "string", + "description": "IANA timezone identifier used to evaluate the schedule." + }, + "topicName": { + "type": [ + "string", + "null" + ], + "description": "Topic scoping query generation, or null." + }, + "updatedAt": { + "type": "string", + "description": "ISO 8601 timestamp when the routine was last updated." + } + }, + "required": [ + "branchId", + "createdAt", + "description", + "destination", + "disabled", + "id", + "lastRun", + "modelId", + "name", + "prompt", + "recipientCount", + "schedule", + "systemDisabled", + "systemDisabledReason", + "timezone", + "topicName", + "updatedAt" + ] + }, + "RoutineEmailDestination": { + "type": "object", + "properties": { + "recipientEmails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "maxItems": 100, + "default": [], + "description": "Email addresses that receive each scheduled run of the routine.", + "example": [ + "alice@example.com", + "bob@example.com" + ] + }, + "type": { + "type": "string", + "enum": [ + "email" + ], + "description": "Destination type. Only `email` is supported.", + "example": "email" + }, + "userGroupIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "maxItems": 100, + "default": [], + "description": "User group IDs whose active members receive each scheduled run. Omni expands each group to the members' current email addresses when the routine runs.", + "example": [ + "550e8400-e29b-41d4-a716-446655440000" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Email delivery configuration for the routine." + }, + "RoutineLastRun": { + "type": [ + "object", + "null" + ], + "properties": { + "completedAt": { + "type": [ + "string", + "null" + ], + "description": "ISO 8601 timestamp the last completed run finished." + }, + "label": { + "type": "string", + "description": "Customer-visible status of the last completed run.", + "example": "Delivered" + }, + "state": { + "type": "string", + "description": "Machine-readable status of the last completed run.", + "example": "COMPLETE" + } + }, + "required": [ + "completedAt", + "label", + "state" + ], + "description": "Most recent completed run, or null if the routine has never completed a run." + }, + "RoutineCreateResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier for the newly created routine.", + "example": "880e8400-e29b-41d4-a716-446655440003" + } + }, + "required": [ + "id" + ] + }, + "ApiError429": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message describing what went wrong.", + "example": "User has reached the maximum of 100 routines" + }, + "status": { + "type": "integer", + "description": "HTTP status code of the error.", + "example": 429 + } + }, + "required": [ + "detail", + "status" + ] + }, + "RoutineCreateBody": { + "type": "object", + "properties": { + "branchId": { + "type": "string", + "format": "uuid", + "description": "Optional branch ID for the model. Must be a branch of the shared model specified by modelId.", + "example": "550e8400-e29b-41d4-a716-446655440000" + }, + "description": { + "type": "string", + "maxLength": 2000, + "description": "Optional human-readable notes about the routine. Display-only — never used as model input.", + "example": "Weekly signups summary for the growth team." + }, + "modelId": { + "type": "string", + "format": "uuid", + "description": "The UUID of the shared model the prompt runs against. Only shared models are supported.", + "example": "770e8400-e29b-41d4-a716-446655440002" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Customer-visible name of the routine. Used as the email subject for each scheduled run.", + "example": "Weekly user signups" + }, + "prompt": { + "type": "string", + "minLength": 1, + "description": "Natural language prompt Omni runs on each scheduled run.", + "example": "How many users signed up last week?" + }, + "schedule": { + "type": "string", + "minLength": 1, + "description": "Six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field). Minimum frequency is once per hour; contact Omni support if you need more frequent scheduling.", + "example": "0 9 ? * MON *" + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "IANA timezone identifier used to evaluate the schedule.", + "example": "America/New_York" + }, + "topicName": { + "type": "string", + "maxLength": 256, + "description": "Topic name to scope query generation. If omitted, the AI picks the best topic.", + "example": "users" + }, + "destination": { + "$ref": "#/components/schemas/RoutineDestination" + } + }, + "required": [ + "modelId", + "name", + "prompt", + "schedule", + "timezone", + "destination" + ], + "additionalProperties": false + }, + "RoutineDestination": { + "oneOf": [ + { + "$ref": "#/components/schemas/RoutineEmailDestination" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "email": "#/components/schemas/RoutineEmailDestination" + } + }, + "description": "Single delivery destination for the routine. To send results to multiple destinations, create one routine per destination. Omni runs the prompt once per scheduled run using the routine owner's permissions, and every recipient receives the same result regardless of their own permissions." + }, + "RoutineUpdateBody": { + "type": "object", + "properties": { + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 2000, + "description": "Display-only notes about the routine. Pass null to clear it." + }, + "destination": { + "allOf": [ + { + "$ref": "#/components/schemas/RoutineDestination" + }, + { + "description": "Replaces the routine's full recipient configuration with the supplied destination." + } + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "New customer-visible name of the routine, used as the email subject." + }, + "prompt": { + "type": "string", + "minLength": 1, + "description": "New natural language prompt Omni runs on each scheduled run." + }, + "schedule": { + "type": "string", + "minLength": 1, + "description": "New six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field). Minimum frequency is once per hour." + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "New IANA timezone identifier used to evaluate the schedule." + } + }, + "additionalProperties": false + }, + "RoutineDeleteResponse": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "enum": [ + true + ], + "description": "Always true on a successful delete." + }, + "id": { + "type": "string", + "format": "uuid", + "description": "The deleted routine’s ID." + } + }, + "required": [ + "deleted", + "id" + ] + }, + "RoutineTriggerResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the run (scheduled job) that was started.", + "example": "990e8400-e29b-41d4-a716-446655440004" + } + }, + "required": [ + "id" + ] + }, "ApiKeyListResponse": { "type": "object", "properties": { @@ -2949,8 +3431,7 @@ "omni-spreadsheet", "spreadsheet-tab", "summary-value", - "omni-table", - "app" + "omni-table" ], "description": "Visualization type (e.g. basic, omni-markdown, omni-table)" } @@ -3899,7 +4380,7 @@ "type": "object", "properties": { "containers": { - "$ref": "#/components/schemas/Containers" + "$ref": "#/components/schemas/ContainersOnCreate" }, "controls": { "$ref": "#/components/schemas/ControlsPatchExternal" @@ -3950,7 +4431,7 @@ "type": "string", "minLength": 1, "maxLength": 255, - "description": "Caller-supplied note describing the create, written to the history audit trail. Defaults to \"Created document\" when omitted." + "description": "Optional. Caller-supplied note describing the create, written to the history audit trail. When omitted, the server auto-fills it with \"Created document\"." } }, "required": [ @@ -3959,8 +4440,11 @@ ], "additionalProperties": false }, - "Containers": { - "type": "array", + "ContainersOnCreate": { + "type": [ + "array", + "null" + ], "items": { "anyOf": [ { @@ -3974,7 +4458,7 @@ } ] }, - "description": "Container layout array (grid / stack / page / reference containers, recursively nested). The server validates the full structure on apply." + "description": "Container layout array, or `null` to create a workbook-only document with no dashboard. When `null`, `controls` and `settings` must be omitted." }, "GridContainer": { "type": "object", @@ -9087,510 +9571,2134 @@ "type": "object", "properties": { "instanceKey": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "instanceKey", - "type" - ], - "additionalProperties": {} - } - ] - }, - { - "type": "object", - "properties": { - "gridPosition": { - "type": "object", - "properties": { - "h": { - "type": "number" - }, - "w": { - "type": "number" - }, - "x": { - "type": "number" + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." }, - "y": { - "type": "number" - } - }, - "required": [ - "h", - "w", - "x", - "y" - ] - } - }, - "required": [ - "gridPosition" - ] - } - ] - } - ] - } - }, - "containerType": { - "type": "string", - "enum": [ - "grid" - ] - }, - "gridPosition": { - "type": "object", - "properties": { - "h": { - "type": "number", - "description": "Height in grid units (default: 36 for charts)" - }, - "w": { - "type": "number", - "description": "Width in grid columns on a 24-column grid. Common widths: 24 (full), 12 (half), 8 (third), 6 (quarter). x + w must not exceed 24." - }, - "x": { - "type": "number", - "description": "X position on a 24-column grid (0=left edge, 12=middle). Items side-by-side share the same y with complementary x values." - }, - "y": { - "type": "number", - "description": "Y position in grid units (0=top, higher values=lower on page)" - } - }, - "required": [ - "h", - "w", - "x", - "y" - ] - }, - "metadata": { - "type": "object", - "properties": { - "attachedQueryKey": { - "type": "string", - "description": "Set by the auto-add-tile flow when this container was generated for a specific workbook tab. The server removes containers with a matching `attachedQueryKey` when that tab is deleted. The reducer clears this when the user adds unrelated content (a different query, filter, text tile, page switcher, or sub-container)." - } - }, - "description": "Optional bookkeeping for this container (e.g. `attachedQueryKey` for auto-placed tiles)" - }, - "padding": { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - }, - { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - } - ] - }, - { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - } - ] - }, - { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "size": { + "type": "number", + "minimum": 0, + "maximum": 2000, + "description": "Fixed size in px along the parent stack axis (default 16). In grids the size comes from gridPosition; ignored when style.fillSpace is true." + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } + } + }, + "type": { + "type": "string", + "enum": [ + "inline-spacer" + ] + } + }, + "required": [ + "instanceKey", + "type" + ] + }, + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "align": { + "type": "string", + "enum": [ + "start", + "center", + "end" + ], + "description": "Placement of the line within its slot along the cross axis (default center): horizontal → top/center/bottom, vertical → left/center/right" + }, + "color": { + "type": "string", + "enum": [ + "border1", + "border4", + "text1", + "text4" + ], + "description": "Theme color for the line, subtle → bold (default border4)" + }, + "direction": { + "type": "string", + "enum": [ + "horizontal", + "vertical" + ], + "description": "Line orientation (default horizontal)" + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } + } + }, + "thickness": { + "anyOf": [ + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + } + ], + "description": "Line thickness in px (default 1)" + }, + "type": { + "type": "string", + "enum": [ + "inline-divider" + ] + } + }, + "required": [ + "instanceKey", + "type" + ] + }, + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "instanceKey", + "type" + ], + "additionalProperties": {} + } + ] + }, + { + "type": "object", + "properties": { + "gridPosition": { + "type": "object", + "properties": { + "h": { + "type": "number" + }, + "w": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "h", + "w", + "x", + "y" + ] + } + }, + "required": [ + "gridPosition" + ] + } + ] + } + ] + } + }, + "containerType": { + "type": "string", + "enum": [ + "grid" + ] + }, + "gridPosition": { + "type": "object", + "properties": { + "h": { + "type": "number", + "description": "Height in grid units (default: 36 for charts)" + }, + "w": { + "type": "number", + "description": "Width in grid columns on a 24-column grid. Common widths: 24 (full), 12 (half), 8 (third), 6 (quarter). x + w must not exceed 24." + }, + "x": { + "type": "number", + "description": "X position on a 24-column grid (0=left edge, 12=middle). Items side-by-side share the same y with complementary x values." + }, + "y": { + "type": "number", + "description": "Y position in grid units (0=top, higher values=lower on page)" + } + }, + "required": [ + "h", + "w", + "x", + "y" + ] + }, + "metadata": { + "type": "object", + "properties": { + "attachedQueryKey": { + "type": "string", + "description": "Set by the auto-add-tile flow when this container was generated for a specific workbook tab. The server removes containers with a matching `attachedQueryKey` when that tab is deleted. The reducer clears this when the user adds unrelated content (a different query, filter, text tile, page switcher, or sub-container)." + }, + "generatedHeading": { + "type": "boolean", + "description": "Marks the auto-injected heading wrapper (title + description row) created for a tile, so it can be labeled generically and treated as managed." + }, + "locked": { + "type": "boolean", + "description": "Locks the container's internal arrangement so its children cannot be dragged, reordered, resized, or have new items dropped in. Cascades to all descendants. Does not lock the container's own position/size." + } + }, + "description": "Optional bookkeeping for this container (e.g. `attachedQueryKey` for auto-placed tiles)" + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", "enum": [ 1 ] @@ -9770,225 +11878,1865 @@ 8 ] } - ] + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + } + ] + }, + "style": { + "type": "string", + "pattern": "^[a-z0-9-]+$" + } + }, + "required": [ + "instanceKey", + "children", + "containerType" + ], + "description": "Grid container — children are positioned on a grid (each carries a gridPosition)." + }, + "StackContainer": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Optional description for the container, providing additional context or information" + }, + "instanceKey": { + "type": "string", + "description": "Unique identifier for this container. Used to reference the container when adding, moving, or removing children." + }, + "name": { + "type": "string", + "description": "Human-readable name for the container, used for easier reference in logic and design" + }, + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + }, + "after": { + "anyOf": [ + { + "$ref": "#/components/schemas/StackContainer" + }, + { + "$ref": "#/components/schemas/ReferenceContainer" + } + ] + }, + "align": { + "type": "string", + "enum": [ + "flex-start", + "flex-end", + "center", + "stretch" + ], + "description": "Cross-axis alignment of children (e.g., center, stretch)" + }, + "before": { + "anyOf": [ + { + "$ref": "#/components/schemas/StackContainer" + }, + { + "$ref": "#/components/schemas/ReferenceContainer" + } + ] + }, + "children": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "as": { + "type": "string", + "enum": [ + "chart", + "result", + "ai", + "metadata" + ], + "description": "Render mode: \"chart\" for visualization, \"result\" for data table, \"ai\" for AI summary, \"metadata\" for query metadata" + }, + "format": { + "type": "string", + "enum": [ + "subtitle", + "description", + "name" + ], + "description": "Display format when as is \"ai\" or \"metadata\": \"name\" (metadata only), \"subtitle\", or \"description\"" + }, + "id": { + "type": "string", + "pattern": "^[1-9][0-9]*$", + "description": "The query presentation ID this content item references" + }, + "name": { + "type": "string", + "description": "Display name for this query item" + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } + } + }, + "type": { + "type": "string", + "enum": [ + "query" + ] + } }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] + "required": [ + "instanceKey", + "id", + "type" + ] + }, + { + "type": "object", + "properties": { + "instanceKey": { + "type": "string", + "description": "Unique identifier for this specific placement of the content item. Use this key (not the query id) when repositioning or removing items." + }, + "padding": { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "appearance": { + "oneOf": [ + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "inline" + ] + } + }, + "required": [ + "as" + ] + }, + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "tooltip" + ] + } + }, + "required": [ + "as" + ] + } + ], + "description": "Display mode: \"inline\" renders directly, \"tooltip\" shows on hover" + }, + "content": { + "type": "string", + "description": "The text content to display (supports markdown)" + }, + "name": { + "type": "string", + "description": "Display name for this text item (e.g., title, subtitle)" + }, + "style": { + "type": "object", + "properties": { + "aspectRatio": { + "type": "string" + }, + "fillSpace": { + "type": "boolean" + }, + "height": { + "type": "string" + }, + "maxHeight": { + "type": "string" + }, + "maxWidth": { + "type": "string" + }, + "minHeight": { + "type": "string" + }, + "minWidth": { + "type": "string" + }, + "width": { + "type": "string" + } } - ] + }, + "textAlign": { + "type": "string", + "enum": [ + "start", + "center", + "end" + ], + "description": "Text alignment: \"start\", \"center\", or \"end\"" + }, + "type": { + "type": "string", + "enum": [ + "inline-text" + ] + } }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } - ] - } - ] - } - ] - }, - "style": { - "type": "string", - "pattern": "^[a-z0-9-]+$" - } - }, - "required": [ - "instanceKey", - "children", - "containerType" - ], - "description": "Grid container — children are positioned on a grid (each carries a gridPosition)." - }, - "StackContainer": { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "Optional description for the container, providing additional context or information" - }, - "instanceKey": { - "type": "string", - "description": "Unique identifier for this container. Used to reference the container when adding, moving, or removing children." - }, - "name": { - "type": "string", - "description": "Human-readable name for the container, used for easier reference in logic and design" - }, - "aspectRatio": { - "type": "string" - }, - "fillSpace": { - "type": "boolean" - }, - "height": { - "type": "string" - }, - "maxHeight": { - "type": "string" - }, - "maxWidth": { - "type": "string" - }, - "minHeight": { - "type": "string" - }, - "minWidth": { - "type": "string" - }, - "width": { - "type": "string" - }, - "after": { - "anyOf": [ - { - "$ref": "#/components/schemas/StackContainer" - }, - { - "$ref": "#/components/schemas/ReferenceContainer" - } - ] - }, - "align": { - "type": "string", - "enum": [ - "flex-start", - "flex-end", - "center", - "stretch" - ], - "description": "Cross-axis alignment of children (e.g., center, stretch)" - }, - "before": { - "anyOf": [ - { - "$ref": "#/components/schemas/StackContainer" - }, - { - "$ref": "#/components/schemas/ReferenceContainer" - } - ] - }, - "children": { - "type": "array", - "items": { - "anyOf": [ + "required": [ + "instanceKey", + "content", + "type" + ] + }, { "type": "object", "properties": { @@ -10458,303 +14206,385 @@ ] }, { - "type": "array", - "prefixItems": [ - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + "type": "array", + "prefixItems": [ + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + }, + { + "anyOf": [ + { + "type": "number", + "enum": [ + 0 + ] + }, + { + "type": "number", + "enum": [ + 0.5 + ] + }, + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 3 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + }, + { + "type": "number", + "enum": [ + 5 + ] + }, + { + "type": "number", + "enum": [ + 6 + ] + }, + { + "type": "number", + "enum": [ + 7 + ] + }, + { + "type": "number", + "enum": [ + 8 + ] + } + ] + } + ] + }, + { + "type": "null" + } + ] + }, + "preset": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" + }, + "appearance": { + "oneOf": [ + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "tabs" ] }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + "variant": { + "type": "string", + "enum": [ + "underline", + "bordered" ] - }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + } + }, + "required": [ + "as" + ] + }, + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "buttons" ] }, - { - "anyOf": [ - { - "type": "number", - "enum": [ - 0 - ] - }, - { - "type": "number", - "enum": [ - 0.5 - ] - }, - { - "type": "number", - "enum": [ - 1 - ] - }, - { - "type": "number", - "enum": [ - 2 - ] - }, - { - "type": "number", - "enum": [ - 3 - ] - }, - { - "type": "number", - "enum": [ - 4 - ] - }, - { - "type": "number", - "enum": [ - 5 - ] - }, - { - "type": "number", - "enum": [ - 6 - ] - }, - { - "type": "number", - "enum": [ - 7 - ] - }, - { - "type": "number", - "enum": [ - 8 - ] - } + "variant": { + "type": "string", + "enum": [ + "segment", + "toggle", + "pills" ] } + }, + "required": [ + "as" ] }, { - "type": "null" + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "list" + ] + }, + "description": { + "type": "boolean" + } + }, + "required": [ + "as" + ] + }, + { + "type": "object", + "properties": { + "as": { + "type": "string", + "enum": [ + "dropdown" + ] + }, + "description": { + "type": "boolean" + } + }, + "required": [ + "as" + ] } ] }, - "preset": { - "type": "string", - "pattern": "^[a-z0-9-]+$", - "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" - }, - "as": { - "type": "string", - "enum": [ - "chart", - "result", - "ai", - "metadata" - ], - "description": "Render mode: \"chart\" for visualization, \"result\" for data table, \"ai\" for AI summary, \"metadata\" for query metadata" - }, - "format": { - "type": "string", - "enum": [ - "subtitle", - "description", - "name" - ], - "description": "Display format when as is \"ai\" or \"metadata\": \"name\" (metadata only), \"subtitle\", or \"description\"" - }, - "id": { - "type": "string", - "pattern": "^[1-9][0-9]*$", - "description": "The query presentation ID this content item references" - }, - "name": { - "type": "string", - "description": "Display name for this query item" + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "includeControls": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "target": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } }, "style": { "type": "object", @@ -10788,13 +14618,12 @@ "type": { "type": "string", "enum": [ - "query" + "inline-page-switcher" ] } }, "required": [ "instanceKey", - "id", "type" ] }, @@ -11537,46 +15366,10 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "appearance": { - "oneOf": [ - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "inline" - ] - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "tooltip" - ] - } - }, - "required": [ - "as" - ] - } - ], - "description": "Display mode: \"inline\" renders directly, \"tooltip\" shows on hover" - }, - "content": { - "type": "string", - "description": "The text content to display (supports markdown)" - }, - "name": { + "id": { "type": "string", - "description": "Display name for this text item (e.g., title, subtitle)" + "format": "uuid", + "description": "UUID of the rich text content block" }, "style": { "type": "object", @@ -11607,25 +15400,16 @@ } } }, - "textAlign": { - "type": "string", - "enum": [ - "start", - "center", - "end" - ], - "description": "Text alignment: \"start\", \"center\", or \"end\"" - }, "type": { "type": "string", "enum": [ - "inline-text" + "text" ] } }, "required": [ "instanceKey", - "content", + "id", "type" ] }, @@ -12368,115 +16152,14 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "appearance": { - "oneOf": [ - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "tabs" - ] - }, - "variant": { - "type": "string", - "enum": [ - "underline", - "bordered" - ] - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "buttons" - ] - }, - "variant": { - "type": "string", - "enum": [ - "segment", - "toggle", - "pills" - ] - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "list" - ] - }, - "description": { - "type": "boolean" - } - }, - "required": [ - "as" - ] - }, - { - "type": "object", - "properties": { - "as": { - "type": "string", - "enum": [ - "dropdown" - ] - }, - "description": { - "type": "boolean" - } - }, - "required": [ - "as" - ] - } - ] + "id": { + "type": "string" }, - "options": { - "type": "array", - "items": { - "type": "object", - "properties": { - "disabled": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "includeControls": { - "type": "boolean" - }, - "label": { - "type": "string" - }, - "target": { - "type": "string" - }, - "uri": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } + "type": { + "type": "string", + "enum": [ + "control" + ] }, "style": { "type": "object", @@ -12506,16 +16189,11 @@ "type": "string" } } - }, - "type": { - "type": "string", - "enum": [ - "inline-page-switcher" - ] } }, "required": [ "instanceKey", + "id", "type" ] }, @@ -13259,9 +16937,55 @@ "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, "id": { + "type": "string" + }, + "type": { "type": "string", - "format": "uuid", - "description": "UUID of the rich text content block" + "enum": [ + "filter" + ] + }, + "appearance": { + "type": "object", + "properties": { + "control": { + "type": "string", + "enum": [ + "buttonToggle", + "dropdown" + ] + }, + "display": { + "type": "string", + "enum": [ + "inline", + "popover" + ] + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "value" + ] + } + }, + "value": { + "type": "array", + "items": { + "type": "string" + } + } + } }, "style": { "type": "object", @@ -13291,12 +17015,6 @@ "type": "string" } } - }, - "type": { - "type": "string", - "enum": [ - "text" - ] } }, "required": [ @@ -14044,14 +17762,11 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "control" - ] + "size": { + "type": "number", + "minimum": 0, + "maximum": 2000, + "description": "Fixed size in px along the parent stack axis (default 16). In grids the size comes from gridPosition; ignored when style.fillSpace is true." }, "style": { "type": "object", @@ -14081,11 +17796,16 @@ "type": "string" } } + }, + "type": { + "type": "string", + "enum": [ + "inline-spacer" + ] } }, "required": [ "instanceKey", - "id", "type" ] }, @@ -14828,56 +18548,32 @@ "pattern": "^[a-z0-9-]+$", "description": "Visual preset for the content item (e.g., \"tile-align\" for outer spacing to align with tile containers)" }, - "id": { - "type": "string" + "align": { + "type": "string", + "enum": [ + "start", + "center", + "end" + ], + "description": "Placement of the line within its slot along the cross axis (default center): horizontal → top/center/bottom, vertical → left/center/right" }, - "type": { + "color": { "type": "string", "enum": [ - "filter" - ] + "border1", + "border4", + "text1", + "text4" + ], + "description": "Theme color for the line, subtle → bold (default border4)" }, - "appearance": { - "type": "object", - "properties": { - "control": { - "type": "string", - "enum": [ - "buttonToggle", - "dropdown" - ] - }, - "display": { - "type": "string", - "enum": [ - "inline", - "popover" - ] - }, - "options": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "value" - ] - } - }, - "value": { - "type": "array", - "items": { - "type": "string" - } - } - } + "direction": { + "type": "string", + "enum": [ + "horizontal", + "vertical" + ], + "description": "Line orientation (default horizontal)" }, "style": { "type": "object", @@ -14907,11 +18603,39 @@ "type": "string" } } + }, + "thickness": { + "anyOf": [ + { + "type": "number", + "enum": [ + 1 + ] + }, + { + "type": "number", + "enum": [ + 2 + ] + }, + { + "type": "number", + "enum": [ + 4 + ] + } + ], + "description": "Line thickness in px (default 1)" + }, + "type": { + "type": "string", + "enum": [ + "inline-divider" + ] } }, "required": [ "instanceKey", - "id", "type" ] }, @@ -15087,6 +18811,14 @@ "attachedQueryKey": { "type": "string", "description": "Set by the auto-add-tile flow when this container was generated for a specific workbook tab. The server removes containers with a matching `attachedQueryKey` when that tab is deleted. The reducer clears this when the user adds unrelated content (a different query, filter, text tile, page switcher, or sub-container)." + }, + "generatedHeading": { + "type": "boolean", + "description": "Marks the auto-injected heading wrapper (title + description row) created for a tile, so it can be labeled generically and treated as managed." + }, + "locked": { + "type": "boolean", + "description": "Locks the container's internal arrangement so its children cannot be dragged, reordered, resized, or have new items dropped in. Cascades to all descendants. Does not lock the container's own position/size." } }, "description": "Optional bookkeeping for this container (e.g. `attachedQueryKey` for auto-placed tiles)" @@ -19811,8 +23543,7 @@ "sql", "dbt", "query-view", - "linked", - "app" + "linked" ], "description": "The query presentation type (e.g. SEMANTIC, SQL, LINKED, SPREADSHEET)." }, @@ -19901,7 +23632,6 @@ "spreadsheet-tab", "summary-value", "omni-table", - "app", null ], "description": "The visualization type (e.g. \"basic\", \"omni-table\")." @@ -20012,6 +23742,23 @@ "queryPresentations" ] }, + "Containers": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/GridContainer" + }, + { + "$ref": "#/components/schemas/PageContainer" + }, + { + "$ref": "#/components/schemas/StackContainer" + } + ] + }, + "description": "Container layout array (grid / stack / page / reference containers, recursively nested). The server validates the full structure on apply." + }, "ControlsReadExternal": { "type": "object", "properties": { @@ -23868,8 +27615,7 @@ "sql", "dbt", "query-view", - "linked", - "app" + "linked" ], "description": "The query presentation type (e.g. SEMANTIC, SQL, LINKED, SPREADSHEET)." }, @@ -23958,7 +27704,6 @@ "spreadsheet-tab", "summary-value", "omni-table", - "app", null ], "description": "The visualization type (e.g. \"basic\", \"omni-table\")." @@ -24139,7 +27884,7 @@ "type": "string", "minLength": 1, "maxLength": 255, - "description": "Caller-supplied description of what this patch changes, written to the history audit trail. When absent, the server generates one from the touched sections." + "description": "Optional. Caller-supplied description of what this patch changes, written to the history audit trail. When omitted, the server auto-generates one from the touched sections." } }, "additionalProperties": false @@ -24520,6 +28265,25 @@ "updated_at" ] }, + "EvalApiError422": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message describing what went wrong.", + "example": "A prompt being updated does not belong to this prompt set" + }, + "status": { + "type": "integer", + "description": "HTTP status code of the error.", + "example": 422 + } + }, + "required": [ + "detail", + "status" + ] + }, "EvalPromptSetsCreateBody": { "type": "object", "properties": { @@ -24571,7 +28335,7 @@ "prompt_text" ] }, - "maxItems": 25, + "maxItems": 100, "default": [], "description": "Initial prompts for the set. Defaults to an empty list. At most 25 prompts." }, @@ -24611,25 +28375,6 @@ "prompt_set" ] }, - "EvalApiError422": { - "type": "object", - "properties": { - "detail": { - "type": "string", - "description": "Human-readable error message describing what went wrong.", - "example": "A prompt being updated does not belong to this prompt set" - }, - "status": { - "type": "integer", - "description": "HTTP status code of the error.", - "example": 422 - } - }, - "required": [ - "detail", - "status" - ] - }, "EvalPromptSetsUpdateBody": { "type": "object", "properties": { @@ -24678,7 +28423,7 @@ "prompt_text" ] }, - "maxItems": 25, + "maxItems": 100, "description": "Full desired set of prompts after the update. Prompts omitted from this list are deleted; new prompts (no `id`) are appended in body order. Existing prompts retain their original position — reordering is not supported on this endpoint. At most 25 prompts total." } } @@ -25033,6 +28778,22 @@ "description": "The prompt text that was evaluated.", "example": "What are the top 5 products by revenue?" }, + "query_count": { + "type": [ + "integer", + "null" + ], + "description": "Number of warehouse queries the underlying job ran. Null for runs executed before this metric was recorded.", + "example": 4 + }, + "query_timing_ms": { + "type": [ + "integer", + "null" + ], + "description": "Total wall-clock time (milliseconds) the underlying job spent running warehouse queries — a proxy for query execution time. Null for runs executed before this metric was recorded.", + "example": 1800 + }, "score": { "type": [ "number", @@ -25054,7 +28815,7 @@ "integer", "null" ], - "description": "Wall-clock duration of the underlying job in milliseconds.", + "description": "Total AI time in milliseconds — all LLM processing and tool calls. Shown as \"AI time\" in the UI.", "example": 4321 } }, @@ -25065,6 +28826,8 @@ "expectation", "id", "prompt", + "query_count", + "query_timing_ms", "score", "scoring_cost", "timing_ms" @@ -25726,209 +29489,460 @@ }, "homepage": { "type": "boolean", - "description": "Whether label is shown on homepage" + "description": "Whether label is shown on homepage" + }, + "name": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "usage_count": { + "type": "number", + "description": "Number of documents with this label" + }, + "verified": { + "type": "boolean", + "description": "Whether label is verified" + } + }, + "required": [ + "color", + "description", + "homepage", + "name", + "usage_count", + "verified" + ] + }, + "LabelsCreateBody": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "default": null, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "default": null, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "default": false, + "description": "Show label on homepage. Requires admin permissions." + }, + "name": { + "type": "string", + "minLength": 2, + "maxLength": 25, + "description": "Label name", + "example": "important" + }, + "verified": { + "type": "boolean", + "default": false, + "description": "Mark as verified label. Requires admin permissions." + } + }, + "required": [ + "name" + ] + }, + "LabelsGetResponse": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "description": "Whether label is shown on homepage" + }, + "name": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "usage_count": { + "type": "number", + "description": "Number of documents with this label" + }, + "verified": { + "type": "boolean", + "description": "Whether label is verified" + } + }, + "required": [ + "color", + "description", + "homepage", + "name", + "usage_count", + "verified" + ] + }, + "LabelsUpdateResponse": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "description": "Whether label is shown on homepage" + }, + "name": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "usage_count": { + "type": "number", + "description": "Number of documents with this label" + }, + "verified": { + "type": "boolean", + "description": "Whether label is verified" + } + }, + "required": [ + "color", + "description", + "homepage", + "name", + "usage_count", + "verified" + ] + }, + "LabelsUpdateBody": { + "type": "object", + "properties": { + "color": { + "type": [ + "string", + "null" + ], + "maxLength": 9, + "description": "Hex color for the label (e.g. #0366d6)", + "example": "#0366d6" + }, + "description": { + "type": [ + "string", + "null" + ], + "maxLength": 500, + "description": "Label description", + "example": "Important items that need attention" + }, + "homepage": { + "type": "boolean", + "description": "Show label on homepage. Requires admin permissions to modify." }, "name": { "type": "string", + "minLength": 2, + "maxLength": 25, "description": "Label name", - "example": "verified" - }, - "usage_count": { - "type": "number", - "description": "Number of documents with this label" + "example": "important" }, "verified": { "type": "boolean", - "description": "Whether label is verified" + "description": "Mark as verified label. Requires admin permissions to modify." + } + } + }, + "ModelSuggestionsListResponse": { + "type": "object", + "properties": { + "pageInfo": { + "$ref": "#/components/schemas/PageInfo" + }, + "records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ModelSuggestion" + } } }, "required": [ - "color", - "description", - "homepage", - "name", - "usage_count", - "verified" + "pageInfo", + "records" ] }, - "LabelsCreateBody": { + "ModelSuggestion": { "type": "object", "properties": { - "color": { + "aiModifiedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of the last AI write (create or AI update). Unaffected by dismiss/restore." + }, + "category": { + "type": "string", + "description": "Suggestion category, e.g. `missing_context`.", + "example": "missing_context" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of when the suggestion was created." + }, + "evidence": { "type": [ - "string", + "array", "null" ], - "maxLength": 9, - "default": null, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" + "items": { + "$ref": "#/components/schemas/SuggestionEvidenceItem" + }, + "description": "Source evidence for the suggestion. Null for rows created before evidence was tracked; `[]` when none was cited." }, - "description": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for the suggestion." + }, + "ignoreReason": { "type": [ "string", "null" ], - "maxLength": 500, - "default": null, - "description": "Label description", - "example": "Important items that need attention" - }, - "homepage": { - "type": "boolean", - "default": false, - "description": "Show label on homepage. Requires admin permissions." - }, - "name": { - "type": "string", - "minLength": 2, - "maxLength": 25, - "description": "Label name", - "example": "important" + "description": "Optional free-text reason recorded when the suggestion was dismissed." }, - "verified": { - "type": "boolean", - "default": false, - "description": "Mark as verified label. Requires admin permissions." - } - }, - "required": [ - "name" - ] - }, - "LabelsGetResponse": { - "type": "object", - "properties": { - "color": { + "ignoredAt": { "type": [ "string", "null" ], - "maxLength": 9, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" + "format": "date-time", + "description": "ISO 8601 timestamp of dismissal, or null if active." }, - "description": { + "ignoredBy": { "type": [ "string", "null" ], - "maxLength": 500, - "description": "Label description", - "example": "Important items that need attention" + "format": "uuid", + "description": "User id that dismissed the suggestion, or null if active." }, - "homepage": { - "type": "boolean", - "description": "Whether label is shown on homepage" + "priority": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "Priority from 1 (highest) to 10 (lowest).", + "example": 1 }, - "name": { + "proposedChanges": { + "$ref": "#/components/schemas/SuggestionProposedChanges" + }, + "rationale": { "type": "string", - "description": "Label name", - "example": "verified" + "description": "Explanation of why the suggestion was made." }, - "usage_count": { - "type": "number", - "description": "Number of documents with this label" + "title": { + "type": "string", + "description": "Short human-readable title." }, - "verified": { - "type": "boolean", - "description": "Whether label is verified" + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of the last write of any kind, including dismiss/restore." } }, "required": [ - "color", - "description", - "homepage", - "name", - "usage_count", - "verified" + "aiModifiedAt", + "category", + "createdAt", + "evidence", + "id", + "ignoreReason", + "ignoredAt", + "ignoredBy", + "priority", + "proposedChanges", + "rationale", + "title", + "updatedAt" ] }, - "LabelsUpdateResponse": { + "SuggestionEvidenceItem": { "type": "object", "properties": { - "color": { - "type": [ - "string", - "null" - ], - "maxLength": 9, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" + "capturedAt": { + "type": "string", + "description": "ISO 8601 timestamp of when the evidence was captured." }, - "description": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Label description", - "example": "Important items that need attention" + "chatAiSessionId": { + "type": "string", + "format": "uuid", + "description": "Chat session that motivated the suggestion." }, - "homepage": { - "type": "boolean", - "description": "Whether label is shown on homepage" + "type": { + "type": "string", + "enum": [ + "ai_chat" + ] + } + }, + "required": [ + "capturedAt", + "chatAiSessionId", + "type" + ] + }, + "SuggestionProposedChanges": { + "type": "object", + "properties": { + "edits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SuggestionContextEdit" + } }, - "name": { + "kind": { "type": "string", - "description": "Label name", - "example": "verified" + "enum": [ + "context_edits" + ] + } + }, + "required": [ + "edits", + "kind" + ], + "description": "The change(s) the suggestion would apply to the model." + }, + "SuggestionContextEdit": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "The model field being edited (e.g. `ai_context`).", + "example": "ai_context" }, - "usage_count": { - "type": "number", - "description": "Number of documents with this label" + "target": { + "type": "string", + "description": "Dot-path identifying what the edit applies to, e.g. `views.orders.fields.status`.", + "example": "views.orders" }, - "verified": { - "type": "boolean", - "description": "Whether label is verified" + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "description": "The proposed value for the field." } }, "required": [ - "color", - "description", - "homepage", - "name", - "usage_count", - "verified" + "field", + "target", + "value" ] }, - "LabelsUpdateBody": { + "ScheduleSuggestionsResponse": { "type": "object", "properties": { - "color": { - "type": [ - "string", - "null" - ], - "maxLength": 9, - "description": "Hex color for the label (e.g. #0366d6)", - "example": "#0366d6" - }, - "description": { - "type": [ - "string", - "null" - ], - "maxLength": 500, - "description": "Label description", - "example": "Important items that need attention" + "id": { + "type": "string", + "format": "uuid", + "description": "The schedule (trigger) id." }, - "homepage": { - "type": "boolean", - "description": "Show label on homepage. Requires admin permissions to modify." + "sharedModelId": { + "type": "string", + "format": "uuid", + "description": "The shared model the schedule generates suggestions for." }, - "name": { + "status": { "type": "string", - "minLength": 2, - "maxLength": 25, - "description": "Label name", - "example": "important" + "enum": [ + "enabled" + ] }, - "verified": { - "type": "boolean", - "description": "Mark as verified label. Requires admin permissions to modify." + "timezone": { + "type": "string", + "description": "IANA timezone the schedule runs in.", + "example": "America/New_York" } - } + }, + "required": [ + "id", + "sharedModelId", + "status", + "timezone" + ] + }, + "ScheduleSuggestionsBody": { + "type": "object", + "properties": { + "timezone": { + "type": "string", + "default": "UTC", + "description": "IANA timezone the schedule fires in (e.g. `America/New_York`). Generation currently runs once daily at ~2 AM in this timezone. Defaults to `UTC`.", + "example": "America/New_York" + } + }, + "additionalProperties": false + }, + "IgnoreSuggestionBody": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "maxLength": 4000, + "description": "Optional free-text reason for dismissing the suggestion.", + "example": "Already covered by an existing field description." + } + }, + "additionalProperties": false }, "ModelsListResponse": { "type": "object", @@ -27946,6 +31960,12 @@ "description": "Cache policy for query execution. Controls whether to use cached results.", "example": "normal" }, + "environmentConnectionId": { + "type": "string", + "format": "uuid", + "description": "Connection ID of the environment to run the query against, overriding the connection environment inherited from the (target) user's session or default. Must be a configured environment of the query model's connection that the user can access. Obtain valid IDs from the `connectionId` field of `GET /api/v1/connection-environments`.", + "example": "550e8400-e29b-41d4-a716-446655440000" + }, "formatResults": { "type": "boolean", "description": "Whether to format result values (e.g., apply number formatting). Only valid when resultType is specified." @@ -28200,7 +32220,7 @@ "example": false }, "metadata": { - "description": "Schedule metadata including format options and delivery settings" + "description": "Schedule metadata including format options and delivery settings. Includes `timezoneOverride` (IANA timezone applied to query execution at render time, or null when no override is set)." }, "name": { "type": "string", @@ -30277,52 +34297,401 @@ "description": "Model ID for model-level role assignment. Required if connectionId not provided.", "example": "550e8400-e29b-41d4-a716-446655440000" }, - "roleName": { - "type": "string", - "minLength": 1, - "description": "Name of the role to assign (base or custom role)", - "example": "VIEWER" + "roleName": { + "type": "string", + "minLength": 1, + "description": "Name of the role to assign (base or custom role)", + "example": "VIEWER" + } + }, + "required": [ + "roleName" + ] + }, + "WhoamiResponse": { + "type": "object", + "properties": { + "keyScope": { + "type": "string", + "enum": [ + "user", + "organization" + ], + "description": "Scope of the API key in use. A separate axis from role: a user-scoped key (PAT/OAuth) acts as a single user and cannot use SCIM, regardless of the user's org role." + }, + "orgRole": { + "type": "string", + "enum": [ + "MEMBER", + "ORG_ADMIN" + ], + "description": "The caller's organization role.", + "example": "MEMBER" + }, + "rolesByModel": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/WhoamiModelRole" + }, + "description": "Resolved role and effective permissions per model, keyed by model id. Connection role resolves per shared model, so this is per-model rather than a single global role." + }, + "rolesByModelTruncated": { + "type": "boolean", + "description": "Present and `true` when `rolesByModel` was truncated because the caller can access more models than the unfiltered limit. Pass a `modelId` filter to retrieve specific models." + }, + "user": { + "$ref": "#/components/schemas/WhoamiUser" + } + }, + "required": [ + "keyScope", + "orgRole", + "rolesByModel", + "user" + ] + }, + "WhoamiModelRole": { + "type": "object", + "properties": { + "baseRole": { + "type": "string", + "description": "The resolved base role (for custom roles, the base role they extend).", + "example": "QUERIER" + }, + "connectionId": { + "type": "string", + "description": "The connection this model belongs to" + }, + "permissions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "QUERY_FULL_MODEL", + "QUERY_SQL", + "VIEW_SQL", + "QUERY_TOPICS", + "RUN_CONTENT_QUERIES", + "DOWNLOAD_CONTENT_QUERY", + "UPLOAD_CSV", + "SCHEDULE", + "SAVE_SPREADSHEETS", + "USE_AI", + "USE_WORKBOOKS", + "UPDATE", + "UPDATE_RESTRICTED" + ] + }, + "description": "The caller's resolved/effective permissions on this model, reflecting custom roles. This is a capability signal for the directly-roleable model kinds (schema / shared / extension). It does not enumerate the permissions you derive on branch, workbook, and query models from your role on the base model they descend from — absence here does not mean you lack access on those derived models. MANAGE_MODEL, READ, and REFRESH_SCHEMA are also not reported: they derive from connection / sibling-model roles rather than a per-model rule.", + "example": [ + "QUERY_TOPICS", + "QUERY_SQL", + "USE_WORKBOOKS" + ] + }, + "roleName": { + "type": "string", + "description": "The resolved role name (informational; may be a custom role). Use `permissions` to decide capability.", + "example": "QUERIER" + } + }, + "required": [ + "baseRole", + "connectionId", + "permissions", + "roleName" + ] + }, + "WhoamiUser": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The caller's user id" + }, + "membershipId": { + "type": "string", + "description": "The caller's own membership id within this organization. This is the id accepted by the admin `GET /api/v1/users/{id}/model-roles` endpoint (it is distinct from the user id)." + } + }, + "required": [ + "id", + "membershipId" + ] + } + }, + "parameters": {} + }, + "paths": { + "/api/v1/ai/generate-query": { + "post": { + "description": "Generate an Omni semantic query from a natural language prompt. Optionally executes the generated query and returns results. The AI analyzes the prompt, selects appropriate fields and filters from the model, and constructs a query. Requires the querier role on the target model.", + "operationId": "aiGenerateQuery", + "summary": "Generate query from natural language", + "tags": [ + "AI" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiGenerateQueryBody" + } + } + } + }, + "responses": { + "200": { + "description": "Query generated successfully. If runQuery is true (default), includes execution results. Check the error field — a 200 response may still contain a partial error if the query was generated but execution failed. When the organization is over its AI downgrade threshold the response also carries `downgradedModelTier` naming the cheaper tier the query was generated with.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiGenerateQueryResponse" + } + } + } + }, + "400": { + "description": "Invalid request. The prompt may be missing, the modelId may be invalid, or the AI was unable to generate a query for the given prompt.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "402": { + "description": "AI is unavailable because the organization is over its AI credit limit. The body carries the stable reason code `shutoff`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiCreditShutoffError" + } + } + } + }, + "403": { + "description": "Insufficient permissions. Requires the querier role on the target model and AI query generation must be enabled for the organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "404": { + "description": "The specified model or topic was not found in the organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "500": { + "description": "AI service error." + } + } + } + }, + "/api/v1/ai/pick-topic": { + "post": { + "description": "Analyze a natural language prompt and determine which topic in the model is the best fit for answering the question. Useful as a preprocessing step before calling generate-query or submitting an AI job, especially when the user's question could relate to multiple topics.", + "operationId": "aiPickTopic", + "summary": "Pick the best topic for a prompt", + "tags": [ + "AI" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiPickTopicBody" + } + } + } + }, + "responses": { + "200": { + "description": "Topic selected successfully. The returned topicId can be used as the topicName parameter in other AI endpoints.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiPickTopicResponse" + } + } + } + }, + "400": { + "description": "Invalid request body. The prompt or modelId may be missing or malformed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Insufficient permissions. Requires the querier role on the target model and AI must be enabled for the organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "404": { + "description": "The specified model was not found, or no accessible topics exist in the model.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "500": { + "description": "AI service error." + } + } + } + }, + "/api/v1/ai/search-omni-docs": { + "post": { + "description": "Search the Omni documentation using AI to answer questions about Omni features, configuration, modeling, dashboards, and more. Sends a natural language question and returns a synthesized answer with source links to the relevant documentation pages.", + "operationId": "aiSearchOmniDocs", + "summary": "Search Omni documentation", + "tags": [ + "AI" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiSearchOmniDocsBody" + } + } + } + }, + "responses": { + "200": { + "description": "Documentation search completed successfully. Returns a synthesized answer with source links.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiSearchOmniDocsResponse" + } + } + } + }, + "400": { + "description": "Invalid request. The question may be missing or exceed the 2000 character limit.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Omni Agent is not enabled for this organization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "500": { + "description": "AI service error." } - }, - "required": [ - "roleName" - ] + } } }, - "parameters": {} - }, - "paths": { - "/api/v1/ai/generate-query": { + "/api/v1/ai/jobs": { "post": { - "description": "Generate an Omni semantic query from a natural language prompt. Optionally executes the generated query and returns results. The AI analyzes the prompt, selects appropriate fields and filters from the model, and constructs a query. Requires the querier role on the target model.", - "operationId": "aiGenerateQuery", - "summary": "Generate query from natural language", + "description": "Submit a new AI job for asynchronous execution. The AI will analyze the prompt, generate and execute queries against the specified model, and produce a summarized answer. Jobs are processed by a background worker and typically complete within 15–60 seconds. Use GET /api/v1/ai/jobs/{jobId} to poll for status, or configure a webhookUrl to receive a notification when the job completes. Optionally continue an existing conversation by providing a conversationId.", + "operationId": "aiJobSubmit", + "summary": "Submit an AI job", "tags": [ "AI" ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiGenerateQueryBody" + "$ref": "#/components/schemas/AiJobSubmitBody" } } } }, "responses": { - "200": { - "description": "Query generated successfully. If runQuery is true (default), includes execution results. Check the error field — a 200 response may still contain a partial error if the query was generated but execution failed. When the organization is over its AI downgrade threshold the response also carries `downgradedModelTier` naming the cheaper tier the query was generated with.", + "201": { + "description": "Job created and queued for execution. Use the returned jobId to poll for status or retrieve results.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiGenerateQueryResponse" + "$ref": "#/components/schemas/AiJobSubmitResponse" } } } }, "400": { - "description": "Invalid request. The prompt may be missing, the modelId may be invalid, or the AI was unable to generate a query for the given prompt.", + "description": "Invalid request body. Common causes: missing or empty prompt, invalid UUID for modelId/branchId/conversationId, invalid webhook URL format.", "content": { "application/json": { "schema": { @@ -30341,28 +34710,94 @@ } } }, - "402": { - "description": "AI is unavailable because the organization is over its AI credit limit. The body carries the stable reason code `shutoff`.", + "403": { + "description": "Insufficient permissions. The AI jobs API must be enabled for the organization, AI query generation must be enabled, and the user must have appropriate model access. User-scoped API keys cannot act on behalf of other users.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiCreditShutoffError" + "$ref": "#/components/schemas/ApiError403" } } } }, - "403": { - "description": "Insufficient permissions. Requires the querier role on the target model and AI query generation must be enabled for the organization.", + "404": { + "description": "The specified model was not found in the organization, the branchId does not belong to the specified model, or the topicName does not exist in the model (or is excluded by ai_chat_topics restrictions).", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError403" + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "409": { + "description": "An active job already exists for the specified conversationId. Wait for the current job to complete before submitting another job to the same conversation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError409" + } + } + } + } + } + } + }, + "/api/v1/ai/jobs/{jobId}": { + "get": { + "description": "Get the current status of an AI job, including its state, progress information, and result summary. The response fields vary by state — for example, progress is only present during EXECUTING, and resultSummary is only present when COMPLETE. Poll this endpoint every 2–5 seconds until the job reaches a terminal state (COMPLETE, FAILED, or CANCELLED).", + "operationId": "aiJobStatus", + "summary": "Get AI job status", + "tags": [ + "AI" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Job status retrieved successfully. Check the state field to determine if the job is still running or has reached a terminal state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiJobStatusResponse" + } + } + } + }, + "400": { + "description": "Invalid job ID format. Must be a valid UUID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" } } } }, "404": { - "description": "The specified model or topic was not found in the organization.", + "description": "Job not found. The job may not exist or may belong to a different organization.", "content": { "application/json": { "schema": { @@ -30370,44 +34805,45 @@ } } } - }, - "500": { - "description": "AI service error." } } } }, - "/api/v1/ai/pick-topic": { + "/api/v1/ai/jobs/{jobId}/cancel": { "post": { - "description": "Analyze a natural language prompt and determine which topic in the model is the best fit for answering the question. Useful as a preprocessing step before calling generate-query or submitting an AI job, especially when the user's question could relate to multiple topics.", - "operationId": "aiPickTopic", - "summary": "Pick the best topic for a prompt", + "description": "Request cancellation of an AI job. This endpoint is idempotent — calling it on an already-cancelled or completed job returns success with the current state. For QUEUED jobs, cancellation is immediate. For EXECUTING jobs, the worker will stop after completing its current iteration. Jobs in DELIVERING state cannot be cancelled as they are already finalizing results. Only the job owner or organization admins can cancel jobs.", + "operationId": "aiJobCancel", + "summary": "Cancel an AI job", "tags": [ "AI" ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiPickTopicBody" - } - } + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" } - }, + ], "responses": { "200": { - "description": "Topic selected successfully. The returned topicId can be used as the topicName parameter in other AI endpoints.", + "description": "Cancellation request processed. The state field indicates the job's state after the attempt — CANCELLED if successful, or the current terminal state if the job had already completed.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiPickTopicResponse" + "$ref": "#/components/schemas/AiJobCancelResponse" } } } }, "400": { - "description": "Invalid request body. The prompt or modelId may be missing or malformed.", + "description": "Invalid job ID format. Must be a valid UUID.", "content": { "application/json": { "schema": { @@ -30427,7 +34863,7 @@ } }, "403": { - "description": "Insufficient permissions. Requires the querier role on the target model and AI must be enabled for the organization.", + "description": "Permission denied. Only the job owner or organization admins can cancel jobs.", "content": { "application/json": { "schema": { @@ -30437,7 +34873,7 @@ } }, "404": { - "description": "The specified model was not found, or no accessible topics exist in the model.", + "description": "Job not found. The job may not exist or may belong to a different organization.", "content": { "application/json": { "schema": { @@ -30446,43 +34882,121 @@ } } }, - "500": { - "description": "AI service error." + "409": { + "description": "Concurrent modification conflict. The job state was changed by another request. Retry the cancellation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError409" + } + } + } } } } }, - "/api/v1/ai/search-omni-docs": { - "post": { - "description": "Search the Omni documentation using AI to answer questions about Omni features, configuration, modeling, dashboards, and more. Sends a natural language question and returns a synthesized answer with source links to the relevant documentation pages.", - "operationId": "aiSearchOmniDocs", - "summary": "Search Omni documentation", + "/api/v1/ai/jobs/{jobId}/result": { + "get": { + "description": "Retrieve the full result of a completed AI job, including all actions taken by the AI (queries generated, data retrieved) and the final summarized answer. Results are only available for jobs in COMPLETE state and are retained for 14 days after completion. The response is streamed directly from storage.", + "operationId": "aiJobResult", + "summary": "Get AI job result", "tags": [ "AI" ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiSearchOmniDocsBody" + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Full job result including the AI's actions, query results (with CSV data), and the final Markdown-formatted answer.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiJobResultResponse" + } + } + } + }, + "400": { + "description": "Invalid job ID format. Must be a valid UUID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "404": { + "description": "Job not found, not in COMPLETE state, or result is no longer available (results are retained for 14 days).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } } } } - }, + } + } + }, + "/api/v1/ai/jobs/{jobId}/vis": { + "get": { + "description": "Render the visualization from a completed AI job as a PNG image. The endpoint extracts the visualization configuration from the job result, loads Arrow IPC data, and renders it server-side using Vega. For style-only follow-ups (e.g., \"make it a bar chart\"), the endpoint walks back through previous jobs in the conversation to find the original query data.", + "operationId": "aiJobVisualization", + "summary": "Render AI job visualization", + "tags": [ + "AI" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the AI job", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "required": true, + "description": "The unique identifier of the AI job", + "name": "jobId", + "in": "path" + } + ], "responses": { "200": { - "description": "Documentation search completed successfully. Returns a synthesized answer with source links.", + "description": "Visualization rendered as a PNG image. The Content-Type header is image/png.", "content": { - "application/json": { + "image/png": { "schema": { - "$ref": "#/components/schemas/AiSearchOmniDocsResponse" + "format": "binary", + "type": "string" } } } }, "400": { - "description": "Invalid request. The question may be missing or exceed the 2000 character limit.", + "description": "Invalid job ID format. Must be a valid UUID.", "content": { "application/json": { "schema": { @@ -30501,8 +35015,60 @@ } } }, + "404": { + "description": "Job not found, not in COMPLETE state, or the apiAiVis feature flag is not enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + }, + "422": { + "description": "The job completed but cannot be rendered as a visualization. Common causes: no visualization action in the job result, no Arrow IPC data available, missing summary fields, or the chart type is not renderable as an image (e.g., tables, KPIs).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError422" + } + } + } + } + } + } + }, + "/api/v1/ai/branding": { + "get": { + "description": "Returns the organization's AI helper branding — display name, optional custom logo URL, and copy used on AI helper landing surfaces (headline, body, prompt placeholder). Falls back to Omni's defaults when the organization hasn't configured custom branding, so the response is always populated. Used by client apps (iOS, embeds) to render the AI helper with the org's chosen identity.", + "operationId": "aiBranding", + "summary": "Get AI helper branding", + "tags": [ + "AI" + ], + "responses": { + "200": { + "description": "AI branding retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiBrandingResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, "403": { - "description": "Omni Agent is not enabled for this organization.", + "description": "AI access is required to view AI helper branding (no model in the org grants USE_AI to the caller).", "content": { "application/json": { "schema": { @@ -30510,22 +35076,44 @@ } } } - }, - "500": { - "description": "AI service error." } } } }, - "/api/v1/ai/jobs": { - "post": { - "description": "Submit a new AI job for asynchronous execution. The AI will analyze the prompt, generate and execute queries against the specified model, and produce a summarized answer. Jobs are processed by a background worker and typically complete within 15–60 seconds. Use GET /api/v1/ai/jobs/{jobId} to poll for status, or configure a webhookUrl to receive a notification when the job completes. Optionally continue an existing conversation by providing a conversationId.", - "operationId": "aiJobSubmit", - "summary": "Submit an AI job", + "/api/v1/ai/conversations": { + "get": { + "description": "List the user's recent AI conversations, ordered by most-recent activity. Each record includes the conversation id (pass it back as `conversationId` on subsequent /api/v1/ai/jobs submissions to continue the thread), an optional name, and a one-line summary of the most recent prompt for display. Paginated via opaque `pageInfo.nextCursor` — pass it back as `cursor` to fetch the next page.", + "operationId": "aiConversationsList", + "summary": "List AI conversations", "tags": [ "AI" ], "parameters": [ + { + "schema": { + "type": "string", + "description": "Cursor for pagination (from previous response nextCursor)", + "example": "eyJpZCI6IjEyMzQ1In0" + }, + "required": false, + "description": "Cursor for pagination (from previous response nextCursor)", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Number of results per page (1-100, integer)", + "example": 20 + }, + "required": false, + "description": "Number of results per page (1-100, integer)", + "name": "pageSize", + "in": "query" + }, { "schema": { "type": "string", @@ -30538,29 +35126,174 @@ "in": "query" } ], + "responses": { + "200": { + "description": "Paginated list of conversations.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiConversationsListResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Insufficient permissions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + } + } + } + }, + "/api/v1/ai/conversations/{conversationId}": { + "get": { + "description": "Return a conversation with its full message history (alternating user / assistant turns). Used by clients (iOS app, embed widgets) to restore a prior conversation in their UI.", + "operationId": "aiConversationDetail", + "summary": "Get AI conversation with messages", + "tags": [ + "AI" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "name": "conversationId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Conversation with messages in chronological order.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiConversationDetailResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "AI access is required to view chat conversations (no model in the org grants USE_AI to the caller).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, + "404": { + "description": "Conversation not found. User-scoped keys also get 404 (not 403) when the conversation exists but belongs to a different user — existence of another user's conversations is not disclosed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } + } + } + } + }, + "/api/v1/ai/credit-controls": { + "get": { + "description": "Get the organization's AI credit controls: the downgrade and shutoff thresholds, plus read-only context (the credit limit, usage so far this billing period, and the period bounds). This is the API mirror of the AI Hub credit controls page and requires the same AI-admin permission.", + "operationId": "aiCreditControlsGet", + "summary": "Get AI credit controls", + "tags": [ + "AI" + ], + "responses": { + "200": { + "description": "Current credit controls. Thresholds are `null` when the corresponding control is off.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiCreditControlsResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError401" + } + } + } + }, + "403": { + "description": "Insufficient permissions, or AI credit controls are not enabled for the organization. Requires AI-admin access.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + } + } + }, + "patch": { + "description": "Update the organization's AI credit thresholds. Both fields are optional and tri-state: omit a field to leave it unchanged, send `null` to turn that control off, or send a non-negative number to set it. At least one field is required. The `downgradeCredits <= shutoffCredits` invariant is enforced against the merged result. Returns the full current state, the same shape as GET.", + "operationId": "aiCreditControlsUpdate", + "summary": "Update AI credit controls", + "tags": [ + "AI" + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobSubmitBody" + "$ref": "#/components/schemas/AiCreditControlsUpdateBody" } } } }, "responses": { - "201": { - "description": "Job created and queued for execution. Use the returned jobId to poll for status or retrieve results.", + "200": { + "description": "Thresholds updated. Returns the full current state.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobSubmitResponse" + "$ref": "#/components/schemas/AiCreditControlsResponse" } } } }, "400": { - "description": "Invalid request body. Common causes: missing or empty prompt, invalid UUID for modelId/branchId/conversationId, invalid webhook URL format.", + "description": "Invalid request. Common causes: empty body, a negative threshold, an unknown field, or downgradeCredits above shutoffCredits.", "content": { "application/json": { "schema": { @@ -30580,7 +35313,7 @@ } }, "403": { - "description": "Insufficient permissions. The AI jobs API must be enabled for the organization, AI query generation must be enabled, and the user must have appropriate model access. User-scoped API keys cannot act on behalf of other users.", + "description": "Insufficient permissions, or AI credit controls are not enabled. Requires AI-admin access.", "content": { "application/json": { "schema": { @@ -30588,65 +35321,95 @@ } } } - }, - "404": { - "description": "The specified model was not found in the organization, the branchId does not belong to the specified model, or the topicName does not exist in the model (or is excluded by ai_chat_topics restrictions).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError404" - } - } - } - }, - "409": { - "description": "An active job already exists for the specified conversationId. Wait for the current job to complete before submitting another job to the same conversation.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError409" - } - } - } } } } }, - "/api/v1/ai/jobs/{jobId}": { + "/api/v1/ai/routines": { "get": { - "description": "Get the current status of an AI job, including its state, progress information, and result summary. The response fields vary by state — for example, progress is only present during EXECUTING, and resultSummary is only present when COMPLETE. Poll this endpoint every 2–5 seconds until the job reaches a terminal state (COMPLETE, FAILED, or CANCELLED).", - "operationId": "aiJobStatus", - "summary": "Get AI job status", + "description": "List routines for the calling user, newest first. Includes routines paused by the owner or disabled by Omni, but excludes deleted routines. Use `pageInfo.nextCursor` from one response as the `cursor` query parameter on the next request. Organization API keys can pass `?userId=` to list routines for a specific organization member.", + "operationId": "routinesList", + "summary": "List routines", "tags": [ - "AI" + "AI Routines" ], "parameters": [ + { + "schema": { + "type": "string", + "description": "Cursor for pagination (from previous response nextCursor)", + "example": "eyJpZCI6IjEyMzQ1In0" + }, + "required": false, + "description": "Cursor for pagination (from previous response nextCursor)", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Number of results per page (1-100, integer)", + "example": 20 + }, + "required": false, + "description": "Number of results per page (1-100, integer)", + "name": "pageSize", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "desc", + "description": "Sort direction for results", + "example": "desc" + }, + "required": false, + "description": "Sort direction for results", + "name": "sortDirection", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Field to sort results by" + }, + "required": false, + "description": "Field to sort results by", + "name": "sortField", + "in": "query" + }, { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "Target user membership ID (for org-scoped API keys)" }, - "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", - "in": "path" + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], "responses": { "200": { - "description": "Job status retrieved successfully. Check the state field to determine if the job is still running or has reached a terminal state.", + "description": "Paginated list of routines.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobStatusResponse" + "$ref": "#/components/schemas/RoutinesListResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid pagination cursor or `userId` value.", "content": { "application/json": { "schema": { @@ -30665,8 +35428,18 @@ } } }, + "403": { + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to list routines for another user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, "404": { - "description": "Job not found. The job may not exist or may belong to a different organization.", + "description": "The `userId` membership was not found in the organization.", "content": { "application/json": { "schema": { @@ -30676,43 +35449,50 @@ } } } - } - }, - "/api/v1/ai/jobs/{jobId}/cancel": { + }, "post": { - "description": "Request cancellation of an AI job. This endpoint is idempotent — calling it on an already-cancelled or completed job returns success with the current state. For QUEUED jobs, cancellation is immediate. For EXECUTING jobs, the worker will stop after completing its current iteration. Jobs in DELIVERING state cannot be cancelled as they are already finalizing results. Only the job owner or organization admins can cancel jobs.", - "operationId": "aiJobCancel", - "summary": "Cancel an AI job", + "description": "Create a routine that runs a saved prompt on a schedule and delivers the AI response through a single email destination. Each scheduled run executes once using the routine owner's permissions, and every recipient receives the same result. Organization API keys can pass `?userId=` to create the routine for a specific organization member.", + "operationId": "routineCreate", + "summary": "Create a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "Target user membership ID (for org-scoped API keys)" }, - "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", - "in": "path" + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutineCreateBody" + } + } + } + }, "responses": { - "200": { - "description": "Cancellation request processed. The state field indicates the job's state after the attempt — CANCELLED if successful, or the current terminal state if the job had already completed.", + "201": { + "description": "Routine created successfully.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobCancelResponse" + "$ref": "#/components/schemas/RoutineCreateResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid request body, recipient configuration, schedule, or timezone. Also returned when the schedule is more frequent than the organization allows.", "content": { "application/json": { "schema": { @@ -30732,7 +35512,7 @@ } }, "403": { - "description": "Permission denied. Only the job owner or organization admins can cancel jobs.", + "description": "AI routines or AI query generation are not enabled for the organization, or the API key cannot act on behalf of the requested user.", "content": { "application/json": { "schema": { @@ -30742,7 +35522,7 @@ } }, "404": { - "description": "Job not found. The job may not exist or may belong to a different organization.", + "description": "Model, branch, or topic not found, or not accessible to the requested user.", "content": { "application/json": { "schema": { @@ -30751,12 +35531,12 @@ } } }, - "409": { - "description": "Concurrent modification conflict. The job state was changed by another request. Retry the cancellation.", + "429": { + "description": "The resolved user already has the maximum number of active routines.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError409" + "$ref": "#/components/schemas/ApiError429" } } } @@ -30764,41 +35544,51 @@ } } }, - "/api/v1/ai/jobs/{jobId}/result": { + "/api/v1/ai/routines/{id}": { "get": { - "description": "Retrieve the full result of a completed AI job, including all actions taken by the AI (queries generated, data retrieved) and the final summarized answer. Results are only available for jobs in COMPLETE state and are retained for 14 days after completion. The response is streamed directly from storage.", - "operationId": "aiJobResult", - "summary": "Get AI job result", + "description": "Get a single routine, including the status of its most recent completed run.", + "operationId": "routineGet", + "summary": "Get a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "The UUID of the routine." }, "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", + "description": "The UUID of the routine.", + "name": "id", "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], "responses": { "200": { - "description": "Full job result including the AI's actions, query results (with CSV data), and the final Markdown-formatted answer.", + "description": "Routine details.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiJobResultResponse" + "$ref": "#/components/schemas/RoutineResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid routine ID.", "content": { "application/json": { "schema": { @@ -30817,8 +35607,18 @@ } } }, + "403": { + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to access another user's routine.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError403" + } + } + } + }, "404": { - "description": "Job not found, not in COMPLETE state, or result is no longer available (results are retained for 14 days).", + "description": "Routine not found or has been deleted.", "content": { "application/json": { "schema": { @@ -30828,44 +35628,61 @@ } } } - } - }, - "/api/v1/ai/jobs/{jobId}/vis": { - "get": { - "description": "Render the visualization from a completed AI job as a PNG image. The endpoint extracts the visualization configuration from the job result, loads Arrow IPC data, and renders it server-side using Vega. For style-only follow-ups (e.g., \"make it a bar chart\"), the endpoint walks back through previous jobs in the conversation to find the original query data.", - "operationId": "aiJobVisualization", - "summary": "Render AI job visualization", + }, + "patch": { + "description": "Update a routine. All request fields are optional, and only supplied fields are changed. Supplying `destination` replaces the full recipient configuration.", + "operationId": "routineUpdate", + "summary": "Update a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "The unique identifier of the AI job", - "example": "123e4567-e89b-12d3-a456-426614174000" + "description": "The UUID of the routine." }, "required": true, - "description": "The unique identifier of the AI job", - "name": "jobId", + "description": "The UUID of the routine.", + "name": "id", "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutineUpdateBody" + } + } + } + }, "responses": { "200": { - "description": "Visualization rendered as a PNG image. The Content-Type header is image/png.", + "description": "Updated routine details.", "content": { - "image/png": { + "application/json": { "schema": { - "format": "binary", - "type": "string" + "$ref": "#/components/schemas/RoutineResponse" } } } }, "400": { - "description": "Invalid job ID format. Must be a valid UUID.", + "description": "Invalid routine ID, request body, recipient configuration, schedule, or timezone.", "content": { "application/json": { "schema": { @@ -30884,104 +35701,46 @@ } } }, - "404": { - "description": "Job not found, not in COMPLETE state, or the apiAiVis feature flag is not enabled.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError404" - } - } - } - }, - "422": { - "description": "The job completed but cannot be rendered as a visualization. Common causes: no visualization action in the job result, no Arrow IPC data available, missing summary fields, or the chart type is not renderable as an image (e.g., tables, KPIs).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiError422" - } - } - } - } - } - } - }, - "/api/v1/ai/branding": { - "get": { - "description": "Returns the organization's AI helper branding — display name, optional custom logo URL, and copy used on AI helper landing surfaces (headline, body, prompt placeholder). Falls back to Omni's defaults when the organization hasn't configured custom branding, so the response is always populated. Used by client apps (iOS, embeds) to render the AI helper with the org's chosen identity.", - "operationId": "aiBranding", - "summary": "Get AI helper branding", - "tags": [ - "AI" - ], - "responses": { - "200": { - "description": "AI branding retrieved successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiBrandingResponse" - } - } - } - }, - "401": { - "description": "Missing or invalid API key.", + "403": { + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to update another user's routine.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError401" + "$ref": "#/components/schemas/ApiError403" } } } }, - "403": { - "description": "AI access is required to view AI helper branding (no model in the org grants USE_AI to the caller).", + "404": { + "description": "Routine not found or has been deleted.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ApiError403" + "$ref": "#/components/schemas/ApiError404" } } } } } - } - }, - "/api/v1/ai/conversations": { - "get": { - "description": "List the user's recent AI conversations, ordered by most-recent activity. Each record includes the conversation id (pass it back as `conversationId` on subsequent /api/v1/ai/jobs submissions to continue the thread), an optional name, and a one-line summary of the most recent prompt for display. Paginated via opaque `pageInfo.nextCursor` — pass it back as `cursor` to fetch the next page.", - "operationId": "aiConversationsList", - "summary": "List AI conversations", + }, + "delete": { + "description": "Delete a routine. It stops running immediately and no longer appears in list or get responses.", + "operationId": "routineDelete", + "summary": "Delete a routine", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", - "description": "Cursor for pagination (from previous response nextCursor)", - "example": "eyJpZCI6IjEyMzQ1In0" - }, - "required": false, - "description": "Cursor for pagination (from previous response nextCursor)", - "name": "cursor", - "in": "query" - }, - { - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20, - "description": "Number of results per page (1-100, integer)", - "example": 20 + "format": "uuid", + "description": "The UUID of the routine." }, - "required": false, - "description": "Number of results per page (1-100, integer)", - "name": "pageSize", - "in": "query" + "required": true, + "description": "The UUID of the routine.", + "name": "id", + "in": "path" }, { "schema": { @@ -30997,11 +35756,21 @@ ], "responses": { "200": { - "description": "Paginated list of conversations.", + "description": "Routine deleted successfully.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiConversationsListResponse" + "$ref": "#/components/schemas/RoutineDeleteResponse" + } + } + } + }, + "400": { + "description": "Invalid routine ID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" } } } @@ -31017,7 +35786,7 @@ } }, "403": { - "description": "Insufficient permissions.", + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to delete another user's routine.", "content": { "application/json": { "schema": { @@ -31025,36 +35794,69 @@ } } } + }, + "404": { + "description": "Routine not found or has already been deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError404" + } + } + } } } } }, - "/api/v1/ai/conversations/{conversationId}": { - "get": { - "description": "Return a conversation with its full message history (alternating user / assistant turns). Used by clients (iOS app, embed widgets) to restore a prior conversation in their UI.", - "operationId": "aiConversationDetail", - "summary": "Get AI conversation with messages", + "/api/v1/ai/routines/{id}/trigger": { + "post": { + "description": "Run a routine immediately, in addition to its schedule. The run executes once using the routine owner's permissions and delivers the AI response to every configured recipient — it is not a private preview. Returns once the run has started; the result is delivered asynchronously. Organization API keys can pass `?userId=` to act on behalf of a specific organization member.", + "operationId": "routineTrigger", + "summary": "Run a routine now", "tags": [ - "AI" + "AI Routines" ], "parameters": [ { "schema": { "type": "string", - "format": "uuid" + "format": "uuid", + "description": "The UUID of the routine." }, "required": true, - "name": "conversationId", + "description": "The UUID of the routine.", + "name": "id", "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" } ], "responses": { - "200": { - "description": "Conversation with messages in chronological order.", + "202": { + "description": "The run has started.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiConversationDetailResponse" + "$ref": "#/components/schemas/RoutineTriggerResponse" + } + } + } + }, + "400": { + "description": "Invalid routine ID, or the routine cannot run as configured (e.g. its model, branch, or owner is no longer accessible).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError400" } } } @@ -31070,7 +35872,7 @@ } }, "403": { - "description": "AI access is required to view chat conversations (no model in the org grants USE_AI to the caller).", + "description": "AI routines or AI query generation are not enabled for the organization, or a user-scoped API key tried to run another user's routine.", "content": { "application/json": { "schema": { @@ -31080,7 +35882,7 @@ } }, "404": { - "description": "Conversation not found. User-scoped keys also get 404 (not 403) when the conversation exists but belongs to a different user — existence of another user's conversations is not disclosed.", + "description": "The routine does not exist or cannot be triggered (deleted, paused, or disabled by Omni).", "content": { "application/json": { "schema": { @@ -31088,6 +35890,16 @@ } } } + }, + "409": { + "description": "A run is already in progress for this routine.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError409" + } + } + } } } } @@ -32434,7 +37246,7 @@ "string", "null" ], - "description": "dbt version to use. Supported: Auto, 1.10, 1.11", + "description": "dbt version to use. Supported: Auto, 1.11, 1.12", "example": "1.11" }, "enableSemanticLayer": { @@ -34531,7 +39343,7 @@ }, "put": { "deprecated": true, - "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Removal scheduled per the `Sunset` response header.\n\nUpdates a document with the specified identifier. This endpoint performs a full resource replacement — all required fields must be provided and existing query presentations are replaced entirely. Only dashboard documents are supported; analysis documents and documents without an associated dashboard return 400. For published documents, the update goes through a draft/publish workflow automatically; if a draft already exists, the request returns 409 unless `clearExistingDraft` is set to `true`.", + "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Scheduled for removal on July 31, 2026 (see the `Sunset` response header).\n\nUpdates a document with the specified identifier. This endpoint performs a full resource replacement — all required fields must be provided and existing query presentations are replaced entirely. Only dashboard documents are supported; analysis documents and documents without an associated dashboard return 400. For published documents, the update goes through a draft/publish workflow automatically; if a draft already exists, the request returns 409 unless `clearExistingDraft` is set to `true`.", "operationId": "documentsPut", "summary": "Replace document (full replacement)", "tags": [ @@ -34562,6 +39374,37 @@ "responses": { "200": { "description": "Document replaced successfully", + "headers": { + "Deprecation": { + "schema": { + "type": "string", + "enum": [ + "true" + ], + "description": "Marks the endpoint as deprecated." + }, + "required": true, + "description": "Marks the endpoint as deprecated." + }, + "Link": { + "schema": { + "type": "string", + "description": "Points to the v2 successor resource.", + "example": "; rel=\"successor-version\"" + }, + "required": true, + "description": "Points to the v2 successor resource." + }, + "Sunset": { + "schema": { + "type": "string", + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594).", + "example": "Fri, 31 Jul 2026 00:00:00 GMT" + }, + "required": true, + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594)." + } + }, "content": { "application/json": { "schema": { @@ -34589,7 +39432,7 @@ }, "patch": { "deprecated": true, - "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Removal scheduled per the `Sunset` response header.\n\nUpdates a document's name, description, and/or identifier. This is a partial update — only provided fields are modified, and at least one of `name`, `description`, or `identifier` must be supplied. When `identifier` is changed, the previous identifier is retained in the document identifier history and continues to redirect. For published documents, the update goes through a draft/publish workflow automatically.", + "description": "**Deprecated** — use `PATCH /api/v2/documents/{identifier}/draft` (and the related `/draft` routes). Scheduled for removal on July 31, 2026 (see the `Sunset` response header).\n\nUpdates a document's name, description, and/or identifier. This is a partial update — only provided fields are modified, and at least one of `name`, `description`, or `identifier` must be supplied. When `identifier` is changed, the previous identifier is retained in the document identifier history and continues to redirect. For published documents, the update goes through a draft/publish workflow automatically.", "operationId": "documentsUpdate", "summary": "Rename document", "tags": [ @@ -34620,6 +39463,37 @@ "responses": { "200": { "description": "Document updated successfully", + "headers": { + "Deprecation": { + "schema": { + "type": "string", + "enum": [ + "true" + ], + "description": "Marks the endpoint as deprecated." + }, + "required": true, + "description": "Marks the endpoint as deprecated." + }, + "Link": { + "schema": { + "type": "string", + "description": "Points to the v2 successor resource.", + "example": "; rel=\"successor-version\"" + }, + "required": true, + "description": "Points to the v2 successor resource." + }, + "Sunset": { + "schema": { + "type": "string", + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594).", + "example": "Fri, 31 Jul 2026 00:00:00 GMT" + }, + "required": true, + "description": "Date the endpoint will be removed, in RFC 1123 form (RFC 8594)." + } + }, "content": { "application/json": { "schema": { @@ -35819,7 +40693,7 @@ }, "/api/v2/documents": { "post": { - "description": "Create a brand-new document and publish it live. Accepts creation metadata (`modelId`, `name`, optional `identifier` / `description` / `folderId`) plus the same content slice as the PATCH body — `queryPresentations`, `controls`, `settings`, `containers`. The server mints internal tile identifiers, so callers omit `miniUuid`. Tiles in `queryPresentations` are merged by key over the single empty seed tile at key `\"1\"`; write to `\"1\"` (or send it as `null`) to replace the seed.\n\nWhen `containers` is omitted, every dashboard-eligible tile is auto-placed in a default layout. When `containers` is present, it fully defines the layout — tiles it does not reference are stored but not rendered.\n\nThe new document is published live before the response returns. As a first publish of brand-new content it is not subject to the org’s `requirePullRequestToPublish` policy (which gates edits to existing content).", + "description": "Create a brand-new document and publish it live. Accepts creation metadata (`modelId`, `name`, optional `identifier` / `description` / `folderId`) plus the same content slice as the PATCH body — `queryPresentations`, `controls`, `settings`, `containers`. The server mints internal tile identifiers, so callers omit `miniUuid`. Tiles in `queryPresentations` are merged by key over the single empty seed tile at key `\"1\"`; write to `\"1\"` (or send it as `null`) to replace the seed.\n\nWhen `containers` is omitted, every dashboard-eligible tile is auto-placed in a default layout. When `containers` is present, it fully defines the layout — tiles it does not reference are stored but not rendered. Send `containers: null` to create a workbook-only document with no dashboard (`controls` and `settings` must then be omitted); an empty `containers: []` is rejected.\n\nThe new document is published live before the response returns. As a first publish of brand-new content it is not subject to the org’s `requirePullRequestToPublish` policy (which gates edits to existing content).", "operationId": "documentsV2Create", "summary": "Create document", "tags": [ @@ -35883,6 +40757,22 @@ "description": "Document identifier — either the URL slug (e.g. `abc123`) or the canonical workbook UUID.", "name": "identifier", "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless." + }, + "required": false, + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless.", + "name": "pretty", + "in": "query" } ], "responses": { @@ -36006,6 +40896,22 @@ "description": "Published document identifier.", "name": "identifier", "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "0", + "1", + "true", + "false" + ], + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless." + }, + "required": false, + "description": "Set `true` or `1` to pretty-print (2-space indent) the response; `false` / `0` (the default) is compact. Key ordering is deterministic regardless.", + "name": "pretty", + "in": "query" } ], "responses": { @@ -36349,6 +41255,16 @@ } } } + }, + "422": { + "description": "Prompt count exceeds the organization's per-set cap (default 25, higher for orgs with the `ai-eval-extra-prompts` flag).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvalApiError422" + } + } + } } } } @@ -36511,7 +41427,7 @@ } }, "422": { - "description": "A `prompts[].id` in the request does not belong to this prompt set.", + "description": "A `prompts[].id` in the request does not belong to this prompt set, or the prompt count exceeds the organization's per-set cap (default 25, higher for orgs with the `ai-eval-extra-prompts` flag).", "content": { "application/json": { "schema": { @@ -37841,84 +42757,483 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LabelsUpdateBody" + "$ref": "#/components/schemas/LabelsUpdateBody" + } + } + } + }, + "responses": { + "200": { + "description": "Label updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LabelsUpdateResponse" + } + } + } + }, + "400": { + "description": "Invalid request body" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Permission denied - cannot modify verified/homepage labels without admin permissions" + }, + "404": { + "description": "Label not found" + }, + "409": { + "description": "Label with new name already exists" + } + } + }, + "delete": { + "operationId": "labelsDelete", + "summary": "Delete a label", + "tags": [ + "Labels" + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Label name", + "example": "verified" + }, + "required": true, + "description": "Label name", + "name": "name", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Target user membership ID (for org-scoped API keys)" + }, + "required": false, + "description": "Target user membership ID (for org-scoped API keys)", + "name": "userId", + "in": "query" + } + ], + "responses": { + "204": { + "description": "Label deleted successfully" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Permission denied - cannot delete verified/homepage labels without admin permissions" + }, + "404": { + "description": "Label not found" + }, + "409": { + "description": "Cannot delete label that is applied to documents" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions": { + "get": { + "description": "Lists AI-generated model suggestions for a shared model, filtered by dismissal status. Requires organization admin permissions.", + "operationId": "modelSuggestionsList", + "summary": "List model suggestions", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestions belong to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestions belong to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "Cursor for pagination: the `nextCursor` from the previous response (the last suggestion id)." + }, + "required": false, + "description": "Cursor for pagination: the `nextCursor` from the previous response (the last suggestion id).", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Number of results per page (1-100, integer)", + "example": 20 + }, + "required": false, + "description": "Number of results per page (1-100, integer)", + "name": "pageSize", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "active", + "ignored", + "all" + ], + "default": "active", + "description": "Which suggestions to return: `active` (default, not dismissed), `ignored` (dismissed only), or `all`.", + "example": "active" + }, + "required": false, + "description": "Which suggestions to return: `active` (default, not dismissed), `ignored` (dismissed only), or `all`.", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Paginated list of suggestions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelSuggestionsListResponse" + } + } + } + }, + "400": { + "description": "Invalid query parameters or malformed `modelId`" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Model not found in this organization" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/schedule": { + "put": { + "description": "Enables the daily schedule that generates suggestions for the shared model. Idempotent — re-enabling leaves an existing schedule untouched. Requires organization admin permissions.", + "operationId": "modelSuggestionsScheduleEnable", + "summary": "Enable the suggestion schedule", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestions belong to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestions belong to", + "name": "modelId", + "in": "path" + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduleSuggestionsBody" } } } }, "responses": { "200": { - "description": "Label updated successfully", + "description": "The schedule is enabled", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LabelsUpdateResponse" + "$ref": "#/components/schemas/ScheduleSuggestionsResponse" } } } }, "400": { - "description": "Invalid request body" + "description": "Invalid timezone or malformed `modelId`" }, "401": { "description": "Authentication required" }, "403": { - "description": "Permission denied - cannot modify verified/homepage labels without admin permissions" + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" }, "404": { - "description": "Label not found" + "description": "Model not found in this organization" }, - "409": { - "description": "Label with new name already exists" + "405": { + "description": "Method not allowed" } } }, "delete": { - "operationId": "labelsDelete", - "summary": "Delete a label", + "description": "Disables the daily generation schedule for the shared model. Idempotent. Requires organization admin permissions.", + "operationId": "modelSuggestionsScheduleDisable", + "summary": "Disable the suggestion schedule", "tags": [ - "Labels" + "AI Model Suggestions" ], "parameters": [ { "schema": { "type": "string", - "description": "Label name", - "example": "verified" + "format": "uuid", + "description": "UUID of the shared model the suggestions belong to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, "required": true, - "description": "Label name", - "name": "name", + "description": "UUID of the shared model the suggestions belong to", + "name": "modelId", "in": "path" + } + ], + "responses": { + "200": { + "description": "The schedule is disabled", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } }, + "400": { + "description": "Malformed `modelId`" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Model not found in this organization" + }, + "405": { + "description": "Method not allowed" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/{suggestionId}/ignore": { + "post": { + "description": "Dismisses (ignores) a suggestion, optionally with a reason. Requires organization admin permissions.", + "operationId": "modelSuggestionsIgnore", + "summary": "Ignore a suggestion", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ { "schema": { "type": "string", "format": "uuid", - "description": "Target user membership ID (for org-scoped API keys)" + "description": "UUID of the shared model the suggestion belongs to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, - "required": false, - "description": "Target user membership ID (for org-scoped API keys)", - "name": "userId", - "in": "query" + "required": true, + "description": "UUID of the shared model the suggestion belongs to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the suggestion", + "example": "b2c3d4e5-f6a7-8901-bcde-f12345678901" + }, + "required": true, + "description": "UUID of the suggestion", + "name": "suggestionId", + "in": "path" } ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IgnoreSuggestionBody" + } + } + } + }, "responses": { - "204": { - "description": "Label deleted successfully" + "200": { + "description": "The suggestion was dismissed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "400": { + "description": "Invalid body or malformed id" }, "401": { "description": "Authentication required" }, "403": { - "description": "Permission denied - cannot delete verified/homepage labels without admin permissions" + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" }, "404": { - "description": "Label not found" + "description": "Suggestion or model not found in this organization" }, - "409": { - "description": "Cannot delete label that is applied to documents" + "405": { + "description": "Method not allowed" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/{suggestionId}/restore": { + "post": { + "description": "Restores a previously dismissed suggestion back to the active list. Requires organization admin permissions.", + "operationId": "modelSuggestionsRestore", + "summary": "Restore a suggestion", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestion belongs to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestion belongs to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the suggestion", + "example": "b2c3d4e5-f6a7-8901-bcde-f12345678901" + }, + "required": true, + "description": "UUID of the suggestion", + "name": "suggestionId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The suggestion was restored", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "400": { + "description": "Malformed id" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Suggestion or model not found in this organization" + }, + "405": { + "description": "Method not allowed" + } + } + } + }, + "/api/v1/models/{modelId}/suggestions/{suggestionId}": { + "delete": { + "description": "Permanently deletes a suggestion. Requires organization admin permissions.", + "operationId": "modelSuggestionsDelete", + "summary": "Delete a suggestion", + "tags": [ + "AI Model Suggestions" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the shared model the suggestion belongs to", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + }, + "required": true, + "description": "UUID of the shared model the suggestion belongs to", + "name": "modelId", + "in": "path" + }, + { + "schema": { + "type": "string", + "format": "uuid", + "description": "UUID of the suggestion", + "example": "b2c3d4e5-f6a7-8901-bcde-f12345678901" + }, + "required": true, + "description": "UUID of the suggestion", + "name": "suggestionId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The suggestion was deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "400": { + "description": "Malformed id" + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Feature not enabled, AI disabled, model is not a shared model, or caller lacks organization admin permissions" + }, + "404": { + "description": "Suggestion or model not found in this organization" + }, + "405": { + "description": "Method not allowed" } } } @@ -39999,10 +45314,11 @@ { "schema": { "type": "string", - "description": "Optional branch ID to validate" + "format": "uuid", + "description": "Optional branch ID to validate against. Non-UUID values return 400." }, "required": false, - "description": "Optional branch ID to validate", + "description": "Optional branch ID to validate against. Non-UUID values return 400.", "name": "branch_id", "in": "query" }, @@ -41017,6 +46333,14 @@ "description": "IANA timezone for the schedule", "example": "America/New_York" }, + "timezoneOverride": { + "type": [ + "string", + "null" + ], + "description": "Optional IANA timezone applied to query execution at render time. Distinct from `timezone` (which controls *when* the schedule fires). Omit or pass null for no override.", + "example": "Europe/Paris" + }, "webhookUrl": { "type": "string", "format": "uri", @@ -43026,6 +48350,47 @@ } } } + }, + "/api/v1/whoami": { + "get": { + "description": "Returns the authenticated caller's own identity, API key scope, organization role, and resolved per-model permissions. Self-scoped and available to non-admins: it lets a caller decide whether an action is permitted without attempting it. Pass `modelId` to scope `rolesByModel` to specific models.", + "operationId": "whoami", + "summary": "Get current identity and permissions (whoami)", + "tags": [ + "Whoami" + ], + "parameters": [ + { + "schema": { + "type": "string", + "description": "Optional model filter. A single model id or a comma-separated list. When provided, `rolesByModel` contains only these models. When omitted, models the caller can access are returned (up to a limit; see `rolesByModelTruncated`).", + "example": "550e8400-e29b-41d4-a716-446655440000" + }, + "required": false, + "description": "Optional model filter. A single model id or a comma-separated list. When provided, `rolesByModel` contains only these models. When omitted, models the caller can access are returned (up to a limit; see `rolesByModelTruncated`).", + "name": "modelId", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Caller's identity, key scope, org role, and per-model permissions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhoamiResponse" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "404": { + "description": "One or more requested `modelId`s do not exist or are not accessible to the caller" + } + } + } } }, "webhooks": {}