---
title: "Render AI job visualization"
method: GET
path: "/v1/ai/jobs/{jobId}/vis"
tags: ["AI"]
---

# Render AI job visualization

`GET /v1/ai/jobs/{jobId}/vis`

## Request

- Base URL: `https://blobsrus.omniapp.co/api`
- URL: `https://blobsrus.omniapp.co/api/v1/ai/jobs/{jobId}/vis`
- Auth: HTTP bearer

Render the visualization from a completed AI job as a PNG image.

## Path parameters

- `jobId` string, uuid, required

## Query parameters

- `auto_vis` string
- `debug` string

## Response `200`

Visualization rendered as `image/png`, or `application/json` when debug is enabled or when the underlying data is unavailable. Debug JSON contains the compiled visualization and query details, or diagnostic information when the data is unavailable.

- union
  - object
    - `compiledVegaLiteSpec` object, required: Compiled Vega specification, or `null` if compilation produced no specification.
    - `dataSource` 'arrow', required
    - `hasQuery` true, required
    - `hasResultFields` true, required
    - `query` OmniQuery, required
      - `aiGenerated` boolean: True when AI generated this query’s SQL; the AI SQL is shown in the advanced SQL box.
      - `branch_id` string: Branch model ID when querying against a model branch.
      - `calculations` object[], required: Table calculations attached to this query.
        - `allow_refs_to_unselected_fields` boolean: Set by the Kotlin parser when this calc references fields not selected at the top level (AI SQL-gen produces these; UI-authored calcs do not).
        - `anchor_row` integer: The 1-indexed row the Excel-style formula was authored against, so row-relative references resolve to the intended offsets — e.g. a percent-of-previous "=B4/B5" authored on row 5. Only used when sql_expression is omitted; defaults to 1 (B1 = this row).
        - `calc_name` string, required: Internal identifier for the calculation, used as the column alias.
        - `description` string: Description of the calculation.
        - `format` string: Number/date format string (e.g. "#,##0.00").
        - `label` string: Display label shown in the UI.
        - `original_formula` string: The calculation formula. Prefer referencing selected fields by name (e.g. "=SUM(orders.revenue)") — name references are stable under column reordering and have no row semantics. Excel-style references also work: column letters map to the query's fields in table order (A = the first field) with rows anchored at row 1 (set anchor_row to author row-relative references against a different row), and calc columns can only be referenced by letter. When sql_expression is omitted, the server parses this formula on write and rejects the request if it does not parse.
        - `outside_pivot` boolean: When true, the calculation is evaluated outside the pivot grouping.
        - `pushdown` boolean: Per-calc override for whether to evaluate before the row limit. `null` defers to the model-level default.
        - `sql` string: Compiled SQL string produced from the formula.
        - `sql_expression` unknown
        - `swallow_errors` boolean: When `true`, a calculation that errors shows `#ERROR!` in its cells instead of failing the whole query, and spreadsheet-style calculations are evaluated by Omni rather than pushed to the database. Set `false` to fail the query on a calculation error instead. Defaults to `true` when omitted on write.
      - `column_limit` number: Max number of pivot columns to return.
      - `column_totals` object, required: Column-level aggregation totals, keyed by field name.
      - `controls` union[]: Interactive controls (field selectors, PoP controls) attached to this query.
        - union
          - object
            - `description` string
            - `hidden` boolean
            - `id` string, required
            - `label` string
            - `display` 'SELECT' | 'BUTTON_TOGGLE'
            - `field` string, required
            - `kind` 'FIELD' | 'TIMEFRAME', required
            - `options` object[], required
              - …
            - `type` 'FIELD_SELECTION', required
          - object
            - `description` string
            - `hidden` boolean
            - `id` string, required
            - `label` string
            - `display` 'SELECT' | 'BUTTON_TOGGLE'
            - `options` object[], required
              - …
            - `selectionMap` object, required
            - `type` 'MULTI_FIELD_SELECTION', required
            - `value` string
          - object
            - `computations` object[], required
              - …
            - `filterFieldName` string, required
            - `filterId` string
            - `id` string, required
            - `type` 'PERIOD_OVER_PERIOD', required
          - object
            - `description` string
            - `hidden` boolean
            - `id` string, required
            - `label` string
            - `fieldOrder` 'query' | 'control'
            - `options` object[], required
              - …
            - `type` 'FIELD_PICKER', required
            - `values` string[]
          - object
            - `description` string
            - `hidden` boolean
            - `id` string, required
            - `label` string
            - `conjunction` 'OR', required
            - `filters` object[], required
              - …
            - `type` 'MULTI_FIELD_FILTER', required
          - object
            - `description` string
            - `hidden` boolean
            - `id` string, required
            - `label` string
            - `fieldSelection` union, required
              - …
            - `includeViewNameInLabels` boolean
            - `type` 'DYNAMIC_FILTER', required
          - object
            - `description` string
            - `hidden` boolean
            - `id` string, required
            - `label` string
            - `defaultValue` integer, required
            - `field` string, required
            - `max` integer
            - `min` integer
            - `type` 'TOP_N', required
            - `value` integer, required
      - `cube_metadata` object: Cube-specific metadata (topic name, cube name, hash key) for cube-backed queries.
        - `cube_name` string, required
        - `hash_key` string, required
        - `topic_name` string, required
      - `custom_summary_types` object: Per-field custom summary functions (e.g. SUM, AVG, COUNT).
      - `dbtFileName` string: dbt file name when this query is backed by a dbt model.
      - `dbtMode` boolean: Whether this query is in dbt mode.
      - `default_group_by` boolean: When true, all dimensions are implicitly included in GROUP BY, unless the model's default_group_by setting (never or primary_key) suppresses it.
      - `dimensionIndex` number: Index of the primary dimension used for result ordering.
      - `executableSQL` string: Server-compiled SQL string (read-only, set by the backend).
      - `fields` string[], required: Model field names selected for the query (dimensions + measures).
      - `fill_fields` string[], required: Fields whose missing date/time values should be filled with nulls to create continuous series.
      - `filters` object, required: Query filters keyed by filter ID.
      - `filtersUsedInSql` object: Filters that were embedded directly in raw SQL (read-only).
      - `imported_model_ids` string[]: Model IDs (UUIDs) the query reaches through its composite topic's imports — the topic's import_models entries, which must be model ids, not names. Authorization requires the run gate on each; omit only for queries that do not touch imported topics.
      - `join_paths_from_topic_name` string: Name of the topic the query runs on; it also sets join path precedence.
      - `join_via_map` object: Virtual topic join path overrides, keyed by view name to ordered join path.
      - `limit` number: Row limit for the query.
      - `manualSort` boolean: When true, the user has explicitly set a custom sort order.
      - `metadata` object: Per-field display metadata (format, label, order).
      - `modelId` string, required: The model ID this query runs against.
      - `model_extension_id` string: ID of a model extension layered on top of the base model.
      - `offset` number: Row offset for pagination.
      - `parsed` boolean: Whether this raw SQL query has been parsed into a semantic query.
      - `periodOverPeriodTransposed` boolean: Whether the period-over-period comparison columns are transposed.
      - `period_over_period_computations` object[]: Period-over-period comparison configurations.
        - `date_filter_field_name` string, required: The date dimension field this comparison is anchored to.
        - `date_filter_id` string: ID of the date filter being offset (if backed by a dashboard filter).
        - `is_dynamic_previous_period` boolean: When true, the previous period is calculated dynamically relative to the current filter range.
        - `periods_ago` number, nullable, required: How many periods back to compare (e.g. 1 = previous period). Null if not set.
        - `time_unit_name` string, nullable, required: Time grain for the offset (e.g. "month", "year"). Null if not set.
      - `pivots` string[], required: Field names to pivot on (columns become values).
      - `rewriteSql` boolean: When true, the backend should rewrite/optimize the SQL.
      - `row_totals` object, required: Row-level aggregation totals, keyed by field name.
      - `sorts` object[], required: Sort clauses applied to the query result set.
        - `column_name` string, required: The model field name to sort by.
        - `is_column_sort` boolean: When true, this sort targets a pivoted column rather than a row dimension.
        - `pivot_value_map` object: Maps pivot field names to specific pivot values, scoping the sort to a single pivot column.
        - `sort_descending` boolean, required: When true, sort order is descending.
        - `subtotal_sort` string: Field name whose subtotal row should be used as the sort key.
      - `sqlSortsEnabled` boolean: Whether user-created sorts are enabled on a raw SQL query.
      - `staticQueryReferences` object: Pre-computed query references for AI chat context. Inner shape is `OmniQuery & { model_id: string }`; left as `unknown` to avoid a recursive zod schema. Validated structurally by consumers when they execute the referenced sub-queries.
      - `table` string, required: Base view (table) name in the model.
      - `transposed_measures` string[]: Measure field names that have been transposed into rows.
      - `userEditedSQL` string, required: User-authored raw SQL (empty string when not in SQL mode).
      - `version` number: Schema version for migration support.
    - `resultFields` object[], required
      - `label` string, required
      - `name` string, required
      - `type` object: Serialized Apache Arrow data type, when available.
        - `typeId` integer, required
    - `rowCount` integer, required
    - `visConfig` object, required
      - `chartType` 'auto' | 'area' | 'areaStacked' | 'areaStackedPercentage' | 'bar' | 'barLine' | 'barGrouped' | 'barStacked' | 'barStackedPercentage' | 'boxplot' | 'code' | 'column' | 'columnGrouped' | 'columnStacked' | 'columnStackedPercentage' | 'heatmap' | 'kpi' | 'line' | 'lineColor' | 'map' | 'regionMap' | 'markdown' | 'omni-ai-summary-markdown' | 'pie' | 'funnel' | 'sankey' | 'point' | 'pointColor' | 'pointSize' | 'pointSizeColor' | 'singleRecord' | 'omni-spreadsheet' | 'summaryValue' | 'svgMap' | 'table' | 'treemap'
      - `visualization` object
  - object
    - `error` string, required: Human-readable error message describing what went wrong.
    - `hasQuery` false, required
    - `jobId` string, uuid, required

## Other responses

- `204`: No visualization exists, and `auto_vis=false` disabled automatic chart selection. The response has no body.
- `400`: Invalid job ID format. Must be a valid UUID.
- `401`: Unauthorized Possible error messages: - `Missing or invalid API key`
- `403`: Insufficient permissions. AI query generation must be enabled for the organization, and the caller must have permission to use AI on the job's model.
- `404`: Job not found, not in `COMPLETE` state, or this endpoint isn't enabled for the organization.
- `422`: The job completed but can't be rendered as a visualization. Common causes: no visualization action in the job result, no underlying data available, missing summary fields, or the chart type isn't renderable as an image (for example, tables or KPIs).
- `429`: Too Many Requests - Rate limit exceeded (60 requests/minute)
- `500`: Internal Server Error

## Changes

- **2026-11-03** `43290040f88f`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/omni/apis/omni-api/changes/v1/ai/jobs/:jobId/vis/get.md)

---

[API](https://skmtc.dev/omni/apis/omni-api.md) - [All operations](https://skmtc.dev/omni/apis/omni-api/llms.txt) - [OpenAPI document](https://skmtc.dev/omni/apis/omni-api/revisions/43290040f88f?raw)
