Visualization Spec (viz_spec) Reference
viz_spec) ReferenceVisualization 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 legacyviz_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. Onlyviz_specdrives the renderer; visualization assets must populateviz_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.
| Rule | Reason |
|---|---|
viz_spec is a JSON object (dict) | Other types are rejected at validation time |
data (if present) is a list of trace objects | Plotly 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 string | These reference column names — see "Column references" below |
layout (if present) is an object | Layout 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
*src conventionA 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 key | Replaces | Used by |
|---|---|---|
xsrc | x | Most cartesian traces |
ysrc | y | Most cartesian traces |
zsrc | z | Heatmaps, contour, 3D |
labelssrc | labels | Pie, sunburst, treemap |
valuessrc | values | Pie, sunburst, treemap |
textsrc | text | Hover text, annotations |
rsrc | r | Polar traces |
thetasrc | theta | Polar traces |
parentssrc | parents | Treemap, sunburst |
idssrc | ids | Any 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
*srckeys are not supported. Plotly supports*srcreferences at nested paths — for example, the visual editor emitserror_x.arraysrc,error_y.arraysrc,marker.colorsrc,marker.sizesrc, andline.colorsrcwhen you configure error bars or per-point styling. Spartera does not pull live data for these. A chart that uses nested*srcwill 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
*srckeys 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*srckeys 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 field | Default when omitted |
|---|---|
layout.title | The asset's name |
layout.xaxis.title | The column referenced by the trace's xsrc, humanized (rushing_yards → "Rushing Yards") |
layout.yaxis.title | The 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 label | type value | Notes |
|---|---|---|
| Scatter | scatter | Defaults to mode: "markers" |
| Bar | bar | Defaults to orientation: "v" |
| Line | scatter | Synthetic UI type — emits {type: "scatter", mode: "lines", stackgroup: null}. There is no line Plotly trace type. |
| Area | scatter | Synthetic UI type — emits {type: "scatter", mode: "lines", stackgroup: 1} |
| Heatmap | heatmap | |
| Table | table | Data lives under nested header.values / cells.values |
| Contour | contour | |
| Pie | pie | Uses labels / values (and labelssrc / valuessrc) |
Distributions
| Editor label | type value |
|---|---|
| Box | box |
| Violin | violin |
| Histogram | histogram |
| 2D Histogram | histogram2d |
| 2D Contour Histogram | histogram2dcontour |
3D
| Editor label | type value | Notes |
|---|---|---|
| 3D Scatter | scatter3d | Defaults to mode: "markers" |
| 3D Line | scatter3d | Synthetic UI type — emits {type: "scatter3d", mode: "lines"} |
| 3D Surface | surface | |
| 3D Mesh | mesh3d | |
| Cone | cone | |
| Streamtube | streamtube |
Maps
| Editor label | type value | Notes |
|---|---|---|
| Tile Map | scattermapbox | Mapbox-based; satellite tiles require a Mapbox token configured at the editor level |
| Atlas Map | scattergeo | |
| Choropleth Tile Map | choroplethmapbox | |
| Choropleth Atlas Map | choropleth | |
| Density Tile Map | densitymapbox |
Financial
| Editor label | type value | Notes |
|---|---|---|
| Candlestick | candlestick | Uses open / high / low / close (and matching *src keys) |
| OHLC | ohlc | Same data shape as candlestick |
| Waterfall | waterfall | Defaults to orientation: "v" |
| Funnel | funnel | |
| Funnel Area | funnelarea |
Specialized
| Editor label | type value |
|---|---|
| Polar Scatter | scatterpolar |
| Polar Bar | barpolar |
| Ternary Scatter | scatterternary |
| Sunburst | sunburst |
| Treemap | treemap |
| Sankey | sankey |
Authoring
viz_specprogrammatically. If you skip the editor and POST a spec directly, do not usetype: "line"ortype: "area"— those are editor-UI labels, not Plotly types. Usetype: "scatter"with the appropriatemodeandstackgroup(see the synthetic mappings above). The same applies totype: "line3d"→ usetype: "scatter3d"withmode: "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
viz_specWhen you POST a visualization asset, the body must include:
| Field | Required | Purpose |
|---|---|---|
name | Yes | Display name for the asset |
company_id | Yes | Company that will own the asset (must match your JWT) |
user_id | Yes | User creating the asset (must match your JWT) |
asset_type | Yes | Must be "VISUALIZATION" |
connection_id | Yes | The connection the renderer hydrates data from |
source_schema_name | Yes | Database schema containing the source table |
source_table_name | Yes | Source table for the columns referenced by *src keys |
viz_spec | Yes | The Plotly figure JSON (see Shape above) |
viz_data_limit | No | Optional 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:
| Operation | Meaning |
|---|---|
= | 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:
| Operation | Meaning |
|---|---|
[] () [) (] | 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: falseare skipped entirely - Filters with no
targetsrcor novalueare ignored - A filter's
targetsrccolumn is auto-fetched even if it's not referenced by any*srcon the trace — you don't need to add the column to anxsrc/ysrcjust 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
*srcvalue across all traces (xsrc,ysrc,labelssrc, etc.) - Every
targetsrcon afilter/aggregate/sorttransform - Every
groupssrcon agroupby/aggregatetransform
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 value | Effective row cap |
|---|---|
Unset, 0, or null | 10,000 (platform default) |
1 to 10,000 | The value you set |
Above 10,000 | Rejected 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:
- Applies them to the underlying SQL query (out of scope for this document; see Connections and the endpoint API docs)
- Renders them as an
Active Filters: region=west | year=2024annotation 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
dictvalue - Trace-attribute validity (whether
marker.sizeaccepts a number or an array, whether a giventypesupportsorientation, etc.) is not validated server-side - The renderer ignores type-mismatched attributes (matching Plotly's
skip_invalid=truebehavior) — for example, amodeattribute left over on a trace whosetypewas changed fromscattertobaris silently ignored, matchingplotly.jsclient-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:
| Parameter | Values | Purpose |
|---|---|---|
_download_type | json (default), image, base64, download | What to return — see response shapes below. |
_bool | true, false (default) | If true, return only a validation result — no rendering. |
_format | png (default), svg | Image 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 /testwith 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
/testendpoint 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=1to the URL. Cache contents auto-invalidate when youPATCHthe 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
/testis 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 toviz_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:
- Create the asset with your initial
viz_spec(POST /assets) - Hit
/test(default_download_type=json) to render the chart and get back a signed URL plus metadata - Adjust
viz_specandPATCHthe asset (see below) - Re-test
- 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/...)
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"
}| HTTP | message | When |
|---|---|---|
400 | Missing required fields: {...} | Body is missing company_id, user_id, or name. |
400 | Cannot create asset (with error field) | Schema validation failed — e.g., viz_spec not a dict, data not a list, a trace missing type. |
400 | Invalid or missing update data | PATCH body is empty or unparseable. |
400 | Cannot update asset (with details field) | Schema validation failed on the partial update. |
400 | Cannot delete asset | Delete operation failed at the data layer. |
403 | Cannot perform operation for asset; user does not have permissions, ... | Auth / permission / billing problem. |
401 | Error (with data: "NOT FOUND") | Attempted to CRUD a Spartera-internal entity. |
500 | Internal server error (with details) | Unexpected server-side failure. |
Test endpoint errors (GET|POST /assets/{asset_id}/test)
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_type | error_source | Typical cause |
|---|---|---|
ASSET_NOT_FOUND | SPARTERA | The asset_id doesn't exist or your JWT can't see it. |
COMPANY_NOT_FOUND | SPARTERA | The company_id in the URL doesn't match a real company. |
COMPANY_INACTIVE | SPARTERA | The buyer or seller company is deactivated. |
CONNECTION_NOT_FOUND | SPARTERA | The asset's connection has been deleted or is inaccessible. |
MISSING_REQUIRED_FIELDS | SPARTERA | The asset is missing fields needed for processing — usually a misconfigured asset. |
BILLING_INACTIVE | SPARTERA | The buyer's company doesn't have an active billing setup. |
ACCESS_DENIED | BUYER | The user has no email on file, or their email domain is restricted from this asset. |
QUOTA_EXCEEDED | BUYER | The buyer's company has exceeded its monthly request allotment. |
INSUFFICIENT_CREDITS | BUYER | The buyer's company doesn't have enough credits to pay for this asset. |
INVALID_QUERY | BUYER | The buyer-supplied URL params produced an invalid query (wrong types, malformed values, etc.). |
INVALID_PARAMETERS | BUYER | Required URL parameters are missing or malformed. |
NO_DATA_FOUND | BUYER | The query returned zero rows for these test parameters — usually filters too restrictive. Returns HTTP 404. |
INVALID_REQUEST | BUYER | Request shape is invalid for this asset type (e.g., _download_type=base64 on a non-visualization). |
INVALID_ASSET_TYPE | SPARTERA | The asset's asset_type value is unrecognized. |
DATA_ACCESS_ERROR | SELLER | The data source returned an access error (permissions, table not found, etc.). |
CONNECTION_ERROR | SELLER | The seller's connection is unreachable, refused, or timed out. |
TIMEOUT_ERROR | SELLER | The query took longer than the renderer's timeout (default 30s). |
CALCULATION_ERROR | SPARTERA / SELLER | Generic processing failure. Check data.message for specifics. |
VISUALIZATION_ERROR | SPARTERA | Spec missing/malformed, renderer returned empty, or image upload failed. |
DATA_STRUCTURE_ERROR | SELLER | The columns referenced in viz_spec don't match what the source query returned. |
UNEXPECTED_ERROR | BUYER / SELLER / SPARTERA | Catch-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_source | HTTP status |
|---|---|
BUYER | 400 Bad Request (or 404 Not Found for NO_DATA_FOUND, 403 Forbidden for ACCESS_DENIED) |
SELLER | 502 Bad Gateway |
SPARTERA | 500 Internal Server Error |
Route-level errors (any endpoint)
| HTTP | When |
|---|---|
401 | Authentication missing or invalid (no/bad bearer token). |
403 | Authenticated but not authorized for this operation. |
404 | Asset (or company) doesn't exist or you can't see it. |
500 | Unhandled 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
datetimeobjects, 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:
- Visual editor (recommended) — the Spartera chart editor produces
viz_specautomatically and persists it on save. Inline data snapshots are included for editor reopen support. - API —
POSTorPATCHthe asset endpoint withviz_specset 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
- Creating Assets — Full asset creation flow
- Plotly figure reference (external) — Authoritative per-attribute documentation for every trace type
- Testing and Validation — Preview execution for visualization assets
- Publishing to Marketplace — Going live
