Visualization Spec (viz_spec) Reference

Visualization Spec (viz_spec) Reference

Visualization assets are configured through a single JSON column on the asset record: viz_spec. This page is the authoritative reference for that column.

viz_spec stores a Plotly figure JSON — the same shape produced by Spartera's visual chart editor and consumed by the server-side renderer. It replaces the legacy viz_chart_type, viz_dep_var_col_name, viz_indep_var_col_name, and related per-field columns with one self-contained spec.

The legacy viz_* columns are read-only metadata. The legacy viz_chart_type, viz_dep_var_col_name, viz_indep_var_col_name, and related columns still exist on the asset record and are returned by the API — existing integrations that read these fields don't break. They are no longer used for visualization rendering. Only viz_spec drives the renderer; visualization assets must populate viz_spec.


Shape

A viz_spec is a JSON object with up to three top-level keys:

{
  "data":   [ /* one or more trace objects */ ],
  "layout": { /* layout options (title, axes, legend, etc.) */ },
  "frames": [ /* optional, for animations — rendered as static */ ]
}
  • data — array of trace objects. Each trace describes one series on the chart.
  • layout — object configuring chart-wide presentation (axis ranges, titles, legend position, etc.).
  • frames — optional array of animation frames. The renderer produces static images, so any animation collapses to the first frame.

All three keys follow Plotly's figure schema. See plotly.com/javascript/reference for the full per-attribute documentation.


Required structure

The model enforces a small set of structural rules. Anything else is passed through to Plotly, which is the schema authority.

RuleReason
viz_spec is a JSON object (dict)Other types are rejected at validation time
data (if present) is a list of trace objectsPlotly traces
Each trace has a type string (e.g., "bar", "scatter", "pie")Plotly defaults to scatter if missing; we require it explicitly to avoid surprises
Every *src key value is a stringThese reference column names — see "Column references" below
layout (if present) is an objectLayout config

Anything else — colors, axis configs, marker sizes, hover templates, etc. — is passed through unchanged. Malformed Plotly content fails at render time, not at save time.


Column references — the *src convention

A spec authored by the visual editor does not embed your data as inline arrays. Instead, each trace points at columns in your seller-side dataset using *src keys. At render time (when a buyer purchases or previews the asset), Spartera fetches the latest data from your connection and hydrates the spec.

The renderer currently hydrates these reference keys:

*src keyReplacesUsed by
xsrcxMost cartesian traces
ysrcyMost cartesian traces
zsrczHeatmaps, contour, 3D
labelssrclabelsPie, sunburst, treemap
valuessrcvaluesPie, sunburst, treemap
textsrctextHover text, annotations
rsrcrPolar traces
thetasrcthetaPolar traces
parentssrcparentsTreemap, sunburst
idssrcidsAny trace

A trace setting xsrc: "age" tells the renderer: "for x, use whatever the column named age returns from the seller's connection at render time." This keeps the stored spec portable and small, and lets the chart reflect updated data without re-saving.

Important: nested *src keys are not supported. Plotly supports *src references at nested paths — for example, the visual editor emits error_x.arraysrc, error_y.arraysrc, marker.colorsrc, marker.sizesrc, and line.colorsrc when you configure error bars or per-point styling. Spartera does not pull live data for these. A chart that uses nested *src will render from whatever values were captured at authoring time, which drifts from your live data as the underlying table changes.

Use only the top-level *src keys in the table above. If a chart needs per-point colors, sizes, or error bars driven from live data, expose those as additional columns in your source view (e.g., pre-compute per-row color or error values) and reference them via top-level *src keys where Plotly's schema allows.


Inline data arrays (editor-authored specs only)

When you author a chart in the visual editor, the saved spec contains both *src column references and inline data arrays (a snapshot of sample data the editor had at authoring time). The renderer overwrites the inline values with live data from your connection at execution time, so inline arrays do not affect what buyers see — they exist only so the editor can reopen the chart for further editing without re-querying your data source.

The editor also writes meta.columnNames.<attr> entries to restore human-readable column labels in its UI when a spec is reopened.

If you author viz_spec via API, omit inline data arrays and meta.columnNames. Set only the *src references. The renderer hydrates them at execution time.


Layout: titles and axis labels

Chart titles and axis labels live in layout, following Plotly's schema:

"layout": {
  "title": { "text": "Monthly Recurring Revenue" },
  "xaxis": { "title": { "text": "Month" } },
  "yaxis": { "title": { "text": "MRR (USD)" } }
}

Plotly accepts both the object form ({"text": "..."}, which the visual editor emits) and the bare-string form ("title": "Month"). Prefer the object form for new API-authored specs so they match what the editor produces.

Defaults when titles are omitted

You do not have to set any titles. When a title is missing or blank, the renderer fills it in at render time:

Layout fieldDefault when omitted
layout.titleThe asset's name
layout.xaxis.titleThe column referenced by the trace's xsrc, humanized (rushing_yards → "Rushing Yards")
layout.yaxis.titleThe column referenced by the trace's ysrc, humanized. For histograms with no ysrc, defaults to "Count"

Rules to know:

  • Explicit always wins. A title you set is never overwritten — defaults only fill blanks. A whitespace-only title counts as blank and is replaced by the default, so there is currently no supported way to render a cartesian chart with an intentionally empty title or axis label.
  • Non-cartesian charts (pie, doughnut, sunburst, treemap, polar, etc.) receive only the plot-title default — they have no x/y axes to label.
  • Render-time only. Defaults are applied server-side when the chart renders. The visual editor's live preview shows its own placeholder text ("Click to enter Plot title"), so a chart with no titles looks unlabeled in the editor but renders with the defaults in the output PNG. The difference is expected.

So a minimal spec like {"data": [{"type": "bar", "xsrc": "region", "ysrc": "revenue"}]} renders with a title (the asset name), an X label ("Region"), and a Y label ("Revenue") even though none were specified.


Supported trace types

Spartera supports the trace types Plotly defines, organized below into the six categories the visual editor uses. The type value in each row is what to put in viz_spec.data[].type. (Plotly's full schema also defines parcoords, splom, pointcloud, carpet, scattercarpet, contourcarpet, isosurface, and heatmapgl — these are not surfaced in the visual editor but will render if you set them programmatically.)

Simple

Editor labeltype valueNotes
ScatterscatterDefaults to mode: "markers"
BarbarDefaults to orientation: "v"
LinescatterSynthetic UI type — emits {type: "scatter", mode: "lines", stackgroup: null}. There is no line Plotly trace type.
AreascatterSynthetic UI type — emits {type: "scatter", mode: "lines", stackgroup: 1}
Heatmapheatmap
TabletableData lives under nested header.values / cells.values
Contourcontour
PiepieUses labels / values (and labelssrc / valuessrc)

Distributions

Editor labeltype value
Boxbox
Violinviolin
Histogramhistogram
2D Histogramhistogram2d
2D Contour Histogramhistogram2dcontour

3D

Editor labeltype valueNotes
3D Scatterscatter3dDefaults to mode: "markers"
3D Linescatter3dSynthetic UI type — emits {type: "scatter3d", mode: "lines"}
3D Surfacesurface
3D Meshmesh3d
Conecone
Streamtubestreamtube

Maps

Editor labeltype valueNotes
Tile MapscattermapboxMapbox-based; satellite tiles require a Mapbox token configured at the editor level
Atlas Mapscattergeo
Choropleth Tile Mapchoroplethmapbox
Choropleth Atlas Mapchoropleth
Density Tile Mapdensitymapbox

Financial

Editor labeltype valueNotes
CandlestickcandlestickUses open / high / low / close (and matching *src keys)
OHLCohlcSame data shape as candlestick
WaterfallwaterfallDefaults to orientation: "v"
Funnelfunnel
Funnel Areafunnelarea

Specialized

Editor labeltype value
Polar Scatterscatterpolar
Polar Barbarpolar
Ternary Scatterscatterternary
Sunburstsunburst
Treemaptreemap
Sankeysankey

Authoring viz_spec programmatically. If you skip the editor and POST a spec directly, do not use type: "line" or type: "area" — those are editor-UI labels, not Plotly types. Use type: "scatter" with the appropriate mode and stackgroup (see the synthetic mappings above). The same applies to type: "line3d" → use type: "scatter3d" with mode: "lines".

Transforms

The visual editor exposes four transform types — filter, groupby, aggregate, and sort — but only on a subset of traces: scatter, scattergl, box, violin, bar, ohlc, candlestick, histogram, histogram2d, waterfall. Transforms are stored under trace.transforms[] and reference columns via targetsrc (which column to operate on) and groupssrc (for grouping). These are fully supported — see the Filter transforms section below for filter-specific behavior.


Minimal examples

Four minimal specs covering common chart shapes. Replace the column names with your own. The exact JSON shapes below match what the visual editor produces when you pick each chart type from its dropdown and bind columns via the data-source picker.

Bar chart

{
  "data": [
    {
      "type": "bar",
      "xsrc": "product_name",
      "ysrc": "total_revenue_usd",
      "orientation": "v"
    }
  ],
  "layout": {
    "title": "Top Products by Revenue"
  }
}

Line chart

The editor's "Line" entry emits a scatter trace with mode: "lines" (or "lines+markers") — there is no Plotly line trace type. See the synthetic-mappings note in the trace-type tables above.

{
  "data": [
    {
      "type": "scatter",
      "mode": "lines+markers",
      "xsrc": "month_date",
      "ysrc": "mrr_usd"
    }
  ],
  "layout": {
    "title": "Monthly Recurring Revenue",
    "xaxis": { "title": "Month" },
    "yaxis": { "title": "MRR (USD)" }
  }
}

Area chart

Same pattern as line — Plotly has no area trace type. The editor's "Area" entry emits a scatter trace with mode: "lines" and stackgroup set, which is what triggers the filled-area rendering.

{
  "data": [
    {
      "type": "scatter",
      "mode": "lines",
      "stackgroup": "1",
      "xsrc": "month_date",
      "ysrc": "active_users"
    }
  ],
  "layout": {
    "title": "Active Users by Month"
  }
}

Pie chart

{
  "data": [
    {
      "type": "pie",
      "labelssrc": "segment_name",
      "valuessrc": "revenue_usd"
    }
  ],
  "layout": {
    "title": "Revenue by Segment"
  }
}

For other trace types — heatmap, sunburst, treemap, scatter with markers, polar, geo/mapbox, financial (candlestick / OHLC / waterfall / funnel), 3D — see the "Editor-supported trace types" tables above for the canonical type value, then consult the Plotly trace reference for that trace type's specific attributes.


Required asset fields when using viz_spec

When you POST a visualization asset, the body must include:

FieldRequiredPurpose
nameYesDisplay name for the asset
company_idYesCompany that will own the asset (must match your JWT)
user_idYesUser creating the asset (must match your JWT)
asset_typeYesMust be "VISUALIZATION"
connection_idYesThe connection the renderer hydrates data from
source_schema_nameYesDatabase schema containing the source table
source_table_nameYesSource table for the columns referenced by *src keys
viz_specYesThe Plotly figure JSON (see Shape above)
viz_data_limitNoOptional row cap (1–10,000). Defaults to the platform max of 10,000 when unset or 0. See Column fetching and record counts.

company_id and user_id are read from your JWT regardless of what you put in the body, but the body fields must still be present (the create endpoint validates their presence before letting the request through).

The column names you reference via *src must exist in the table identified by source_schema_name.source_table_name on the specified connection.


Filter transforms

Plotly defines filter transforms that go on a trace's transforms[] array. Spartera supports them with one performance distinction worth knowing.

Fast operations are applied at the data source — only matching rows are fetched. These are the ones to prefer:

OperationMeaning
=Equals
!=Not equals
< <= > >=Numeric / date comparison
{}In set (value is an array)
}{Not in set (value is an array)

Slower operations are applied after data is fetched — your full result set is loaded and then filtered in the chart. These work, but they're inefficient for large tables:

OperationMeaning
[] () [) (]Range (inclusive / exclusive endpoints)
][ )( ]( )[Exclude range

Other rules:

  • Multiple filters on the same trace combine with AND, matching Plotly's chained-filter semantics
  • Filters with enabled: false are skipped entirely
  • Filters with no targetsrc or no value are ignored
  • A filter's targetsrc column is auto-fetched even if it's not referenced by any *src on the trace — you don't need to add the column to an xsrc/ysrc just to filter on it

Example: a filter on a third column

{
  "type": "scatter",
  "mode": "markers",
  "xsrc": "ad_spend",
  "ysrc": "conversions",
  "transforms": [
    {
      "type": "filter",
      "targetsrc": "region",
      "operation": "=",
      "value": "west"
    }
  ]
}

region doesn't appear in xsrc or ysrc, but the filter still works — Spartera fetches it along with the chart's data columns.


Column fetching and record counts

The data fetched for a chart is determined directly from the spec's column references:

  • Every *src value across all traces (xsrc, ysrc, labelssrc, etc.)
  • Every targetsrc on a filter/aggregate/sort transform
  • Every groupssrc on a groupby/aggregate transform

Columns are unioned across traces, so multi-trace specs referencing different columns work transparently. The data comes from the table identified by your asset's connection_id, source_schema_name, and source_table_name.

Record counts. Visualization queries are capped to protect performance:

viz_data_limit valueEffective row cap
Unset, 0, or null10,000 (platform default)
1 to 10,000The value you set
Above 10,000Rejected at save time

You can lower the cap by setting viz_data_limit on your asset (e.g., 100 for a focused summary chart, 1,000 for a typical trend line). The cap applies after any pushable filter transforms and after the source view's own filtering, so the limit applies to rows that would have reached the renderer, not to the underlying table.

If you need more than 10,000 rows, that's a sign the chart should aggregate further at the source (group by week instead of day, etc.) — chart legibility falls off well before 10K points.

If your chart needs to display only top-N or bottom-N rows, encode that in the source view — e.g., a LIMIT 100 with an ORDER BY clause — or rely on pushable filter transforms. The renderer renders whatever rows are returned.


Buyer-supplied URL filters

Buyers can parameterize a chart at request time by passing query parameters on the asset endpoint URL (e.g., ?region=west&year=2024). Spartera does two things with these:

  1. Applies them to the underlying SQL query (out of scope for this document; see Connections and the endpoint API docs)
  2. Renders them as an Active Filters: region=west | year=2024 annotation at the bottom of the chart, so buyers can see what filter values produced the output

The annotation is added by the renderer, not by the spec. Authors don't need to configure it. Annotations the chart author added in the editor are preserved alongside it.

System params (any param starting with _) are excluded from the annotation.


Validation behavior

viz_spec validation is intentionally permissive:

  • Structural rules (table above) are enforced at the model layer
  • The Marshmallow schema accepts any dict value
  • Trace-attribute validity (whether marker.size accepts a number or an array, whether a given type supports orientation, etc.) is not validated server-side
  • The renderer ignores type-mismatched attributes (matching Plotly's skip_invalid=true behavior) — for example, a mode attribute left over on a trace whose type was changed from scatter to bar is silently ignored, matching plotly.js client-side behavior

This means: if a spec saves successfully, it's structurally valid; whether it renders the chart you intended is a question for Plotly's reference and a preview execution.


Testing your visualization

Before publishing a visualization asset, you can render it against live data to verify the chart looks right. Use the asset test endpoint:

GET /companies/{company_id}/assets/{asset_id}/test

Query parameters:

ParameterValuesPurpose
_download_typejson (default), image, base64, downloadWhat to return — see response shapes below.
_booltrue, false (default)If true, return only a validation result — no rendering.
_formatpng (default), svgImage format. SVG falls back to PNG in current build.

Any other query parameters you include are treated as buyer-style URL filters and applied to the chart's underlying data — useful for testing parameterized charts (see Buyer-supplied URL filters below).

Successful response (_download_type=json — the default and most useful for programmatic clients). A JSON envelope with the signed URL plus full metadata:

{
  "message": "Success",
  "data": {
    "asset_id": "a6e682c1-02dd-4990-90c4-197708cfa225",
    "asset_value": "https://storage.googleapis.com/.../chart.png?X-Goog-Signature=...",
    "asset_type": "VISUALIZATION",
    "format": "png_url",
    "storage_path": "test/2026/05/12/.../chart.png",
    "expires_in": 3600,
    "size_bytes": 48291,
    "asset_source": "SERVER",
    "request_id": "req_abc123",
    "last_updated": "2026-05-12T14:30:00.000000",
    "runtime": 1.247,
    "is_purchased": false,
    "test_mode": true
  }
}

data.asset_value is a signed URL — GET it directly to retrieve the PNG bytes. The URL expires after expires_in seconds (typically 3600 = 1 hour); after that, hit /test again to get a fresh one. asset_source is SERVER for fresh renders, CACHE for cache hits, VALIDATION for _bool=true calls.

Successful response (_download_type=base64). PNG bytes embedded inline as base64 — useful when you want to display the chart immediately without a second HTTP call:

{
  "message": "Success",
  "data": {
    "asset_id": "a6e682c1-02dd-4990-90c4-197708cfa225",
    "asset_value": "iVBORw0KGgoAAAANSUhEUgAA...",
    "asset_type": "VISUALIZATION",
    "format": "png_base64",
    "asset_source": "SERVER",
    "request_id": "req_abc123",
    "last_updated": "2026-05-12T14:30:00.000000",
    "runtime": 1.247,
    "is_purchased": false,
    "test_mode": true
  }
}

Decode with base64.b64decode(data["asset_value"]) to get raw PNG bytes.

Successful response (_download_type=image or download). A lighter response with just the URL — when you only need the signed URL for follow-up:

{
  "message": "PNG URL available",
  "data": {
    "url": "https://storage.googleapis.com/.../chart.png?X-Goog-Signature=...",
    "download_type": "image"
  }
}

(At the buyer-facing runtime endpoint outside test mode, _download_type=image returns a 302 Found redirect to the signed URL instead of a JSON body — ideal for serving charts directly in browsers.)

Successful response (_bool=true). A validation-only response — no rendering, no data fetched beyond what's needed to confirm the query is valid:

{
  "message": "Success",
  "data": {
    "asset_id": "a6e682c1-02dd-4990-90c4-197708cfa225",
    "asset_value": {
      "is_valid": true,
      "asset_type": "VISUALIZATION",
      "test_mode": true,
      "validation_only": true,
      "data_summary": {
        "type": "tabular_for_viz",
        "row_count": 847
      }
    },
    "asset_source": "VALIDATION",
    "request_id": "req_abc123",
    "last_updated": "2026-05-12T14:30:00.000000",
    "runtime": 0.082,
    "asset_type": "VISUALIZATION",
    "is_purchased": false,
    "test_mode": true
  }
}

data_summary gives you a quick read on what your chart will render. row_count is the number of rows the source query returned (after sampling and any filters) — useful for spotting "my chart should have ~1000 points but the validator says 12" early.

When validation fails, data.asset_value.is_valid is false and additional diagnostic fields appear inside asset_value: validation_error (human-readable summary), error_type, error_source, suggested_fix, and technical_details (when error_source isn't BUYER).

Testing with parameters. The /test endpoint accepts buyer-style parameter values two ways:

  • Query string (GET /test?region=west&year=2024) — preferred for simple values
  • POST body (POST /test with JSON body {"region": "west", "year": 2024}) — preferred when values are complex or numeric typing matters

Both patterns produce identical results; pick whichever fits your client. System parameters (any param starting with _, like _download_type, _bool, _cachebuster, _format) are stripped before being applied as filters.

Caching. Rendered chart results are cached to improve performance:

  • The /test endpoint always skips the cache — every test request produces a fresh render. This is what you want during development.
  • The buyer-facing runtime endpoint (when buyers actually fetch your published asset) uses the cache. If your asset has cached: true, owned-by-buyer requests serve from cache when available.
  • To force a fresh render at the buyer-facing endpoint (e.g., after updating viz_spec), append ?_cachebuster=1 to the URL. Cache contents auto-invalidate when you PATCH the asset.

You don't need to do anything special during development — /test always renders fresh.

Test mode samples your data, doesn't fetch it all. To keep /test responsive on large source tables, the test endpoint fetches a ~10% random sample (RAND() < 0.1) of the rows that would otherwise be queried, capped by viz_data_limit. This means:

  • The chart you see at /test is shaped like what your buyers will see, but with fewer points
  • The point count may vary between test runs (random sampling)
  • If your chart's correctness depends on seeing every row (e.g., a "last 7 days" filter where exact dates matter), test with explicit URL parameters that narrow the data first
  • At the buyer-facing runtime endpoint (not /test), the full dataset is fetched up to viz_data_limit

Discovering what columns are available. Before writing *src references, you can list the columns in your source table:

GET /companies/{company_id}/assets/{asset_id}/infoschema

This returns the column names and types from the table identified by your asset's connection_id, source_schema_name, and source_table_name. Use it to confirm column names match what you reference in *src.

Discovering distinct values in a column. Useful for picking sensible filter-transform values:

POST /companies/{company_id}/assets/{asset_id}/scan_column
Body: { "column": "region", "limit": 100 }

Returns the distinct values present in that column. Helps you write filter transforms like {operation: "{}", value: ["west", "east"]} against real data.

Typical iteration loop:

  1. Create the asset with your initial viz_spec (POST /assets)
  2. Hit /test (default _download_type=json) to render the chart and get back a signed URL plus metadata
  3. Adjust viz_spec and PATCH the asset (see below)
  4. Re-test
  5. Publish to the marketplace when satisfied

See Testing and Validation for the broader asset testing workflow.


Editing a visualization

To modify an existing visualization asset, send a PATCH request with only the fields you want to update:

PATCH /companies/{company_id}/assets/{asset_id}

The body is a partial asset payload — fields you don't include are left unchanged. Most commonly you'll update viz_spec (and optionally name, description, tags):

{
  "viz_spec": {
    "data": [
      {
        "type": "bar",
        "xsrc": "product_name",
        "ysrc": "total_revenue_usd",
        "orientation": "h"
      }
    ],
    "layout": { "title": "Top Products by Revenue (Horizontal)" }
  }
}

Response on success:

{
  "message": "success",
  "data": { "asset_id": "a6e682c1-02dd-4990-90c4-197708cfa225" }
}

After updating, re-test with /test to confirm the rendered output matches what you expected.


Error responses

Spartera uses two error envelope shapes depending on which endpoint family fails. Both share message as a top-level human-readable summary; they differ in how diagnostic detail is structured.

CRUD endpoint errors (POST / PATCH / DELETE /assets/...)

Returned by the create, update, and delete endpoints. The diagnostic detail (when present) lives at the top level alongside message:

{
  "message": "Cannot create asset",
  "error": "Validation failed: viz_spec.data must be a list"
}
HTTPmessageWhen
400Missing required fields: {...}Body is missing company_id, user_id, or name.
400Cannot create asset (with error field)Schema validation failed — e.g., viz_spec not a dict, data not a list, a trace missing type.
400Invalid or missing update dataPATCH body is empty or unparseable.
400Cannot update asset (with details field)Schema validation failed on the partial update.
400Cannot delete assetDelete operation failed at the data layer.
403Cannot perform operation for asset; user does not have permissions, ...Auth / permission / billing problem.
401Error (with data: "NOT FOUND")Attempted to CRUD a Spartera-internal entity.
500Internal server error (with details)Unexpected server-side failure.

Test endpoint errors (GET|POST /assets/{asset_id}/test)

Returned by the test endpoint when rendering or its prerequisites fail. The diagnostic detail lives inside a structured data object:

{
  "message": "This asset is marked as using viz_spec but the spec is missing or has no traces. Re-author the chart in the editor and save.",
  "data": {
    "error_type": "VISUALIZATION_ERROR",
    "error_source": "SPARTERA",
    "request_id": "req_abc123",
    "asset_id": "a6e682c1-02dd-4990-90c4-197708cfa225",
    "asset_type": "VISUALIZATION",
    "is_purchased": false,
    "test_mode": true,
    "last_updated": "2026-05-12T14:30:00.000000"
  }
}

When the failure is a render-layer issue (rather than orchestration), data may also include technical_details and suggested_fix. In non-production environments only, data.query_params is included for debugging.

error_source semantics. Always one of three values:

  • BUYER — the caller's URL params / inputs produced the failure. Surface the message back to your user; they need to fix their request.
  • SELLER — the asset's own configuration is wrong (spec references a missing column, data source unreachable, etc.). The asset owner needs to fix the spec.
  • SPARTERA — something failed on the platform side. Retry; if it persists, contact support.

error_type values you might see — these come from two layers in the pipeline:

error_typeerror_sourceTypical cause
ASSET_NOT_FOUNDSPARTERAThe asset_id doesn't exist or your JWT can't see it.
COMPANY_NOT_FOUNDSPARTERAThe company_id in the URL doesn't match a real company.
COMPANY_INACTIVESPARTERAThe buyer or seller company is deactivated.
CONNECTION_NOT_FOUNDSPARTERAThe asset's connection has been deleted or is inaccessible.
MISSING_REQUIRED_FIELDSSPARTERAThe asset is missing fields needed for processing — usually a misconfigured asset.
BILLING_INACTIVESPARTERAThe buyer's company doesn't have an active billing setup.
ACCESS_DENIEDBUYERThe user has no email on file, or their email domain is restricted from this asset.
QUOTA_EXCEEDEDBUYERThe buyer's company has exceeded its monthly request allotment.
INSUFFICIENT_CREDITSBUYERThe buyer's company doesn't have enough credits to pay for this asset.
INVALID_QUERYBUYERThe buyer-supplied URL params produced an invalid query (wrong types, malformed values, etc.).
INVALID_PARAMETERSBUYERRequired URL parameters are missing or malformed.
NO_DATA_FOUNDBUYERThe query returned zero rows for these test parameters — usually filters too restrictive. Returns HTTP 404.
INVALID_REQUESTBUYERRequest shape is invalid for this asset type (e.g., _download_type=base64 on a non-visualization).
INVALID_ASSET_TYPESPARTERAThe asset's asset_type value is unrecognized.
DATA_ACCESS_ERRORSELLERThe data source returned an access error (permissions, table not found, etc.).
CONNECTION_ERRORSELLERThe seller's connection is unreachable, refused, or timed out.
TIMEOUT_ERRORSELLERThe query took longer than the renderer's timeout (default 30s).
CALCULATION_ERRORSPARTERA / SELLERGeneric processing failure. Check data.message for specifics.
VISUALIZATION_ERRORSPARTERASpec missing/malformed, renderer returned empty, or image upload failed.
DATA_STRUCTURE_ERRORSELLERThe columns referenced in viz_spec don't match what the source query returned.
UNEXPECTED_ERRORBUYER / SELLER / SPARTERACatch-all for uncategorized failures. The system classifies by pattern-matching the error message; report if you see one whose error_source looks wrong.

HTTP status mapping. Status codes follow error_source systematically:

error_sourceHTTP status
BUYER400 Bad Request (or 404 Not Found for NO_DATA_FOUND, 403 Forbidden for ACCESS_DENIED)
SELLER502 Bad Gateway
SPARTERA500 Internal Server Error

Route-level errors (any endpoint)

HTTPWhen
401Authentication missing or invalid (no/bad bearer token).
403Authenticated but not authorized for this operation.
404Asset (or company) doesn't exist or you can't see it.
500Unhandled server error — please report.

Working with dates

Time-series charts are common. To make a date column render correctly on a chart axis, your source view should return either:

  • ISO 8601 date strings ("2024-01-15", "2024-01-15T14:30:00Z") — most reliable across all chart types and connection engines, and
  • Native date / timestamp types that the SQL connector returns as Python datetime objects, which serialize cleanly to ISO strings on the way to the renderer.

What to avoid: epoch numbers, locale-formatted strings ("01/15/2024"), or mixed types within a single column. These will render as categorical strings instead of a time axis.

If a chart's X axis isn't behaving like a time axis even though your column "looks like dates," cast it to a real date type in your source view (e.g., CAST(month_str AS DATE) AS month_date in BigQuery / Postgres / Snowflake — syntax varies by engine).


Authoring paths

There are two ways a viz_spec value ends up on an asset:

  1. Visual editor (recommended) — the Spartera chart editor produces viz_spec automatically and persists it on save. Inline data snapshots are included for editor reopen support.
  2. APIPOST or PATCH the asset endpoint with viz_spec set to a JSON object matching the shape above. No inline arrays needed for API-authored specs.

Visualization assets that don't have a populated viz_spec won't render — viz_spec is required for the renderer.


Related Pages