Create a custom view (experimental)

⚠️ **Experimental** — this endpoint may change or be removed without notice and is not subject to v2 backwards-compatibility guarantees. Do not build production-critical integrations against it yet. ⚠️ Only call this when the user has EXPLICITLY asked to save, create, or publish the view. After generating or previewing a dashboard, do NOT automatically save a draft — show it to the user and wait for them to ask you to save it. Saves named analytics queries + a self-contained HTML render bundle as a DRAFT custom view. **Call getCustomViewAuthoringGuide (GET /experimental/views/authoring-guide) first** — it returns the full guide and a copy-paste interactive template. Key rules: (1) Do NOT add a customer filter to the SQL — the database scopes every query to the viewing customer at embed time. (2) Each query's SQL must be SELECT-only; return clearly-named columns. Compute metric VALUES in SQL (e.g. (count()*2)/5 AS custom_metric) — derive a number in the render bundle only when it depends on user interaction (toggle/filter/hover) or is pure formatting of a value a query already returns. (3) The render bundle must be SELF-CONTAINED — inline all CSS/JS/charting, NO external loads or fetch (the sandbox has connect-src 'none'); it must listen for the `paid:data` message (data keyed by query id) and re-render on each one. (4) Make it INTERACTIVE — mousemove hover tooltips and at least one addEventListener-wired control that re-renders (a static chart feels broken). (5) The render bundle is the single source of truth — preview the EXACT bundle you save (call getCustomViewPreviewHarness with your bundle + sample data and render the HTML it returns) or review it in the Paid preview; do NOT build a separate chart, and only show numbers that come from a declared query. (6) A view is a FULL dashboard — include as many charts/KPIs as the analysis has. Keep every element derived from the single viewing customer (KPIs, trends, type mix); drop only cross-customer comparisons (rankings, share-of-total, 'N customers'). Don't simplify to one chart. (7) To make the date range adjustable (e.g. the user says 'last month'), write the date boundary as `{period_start:DateTime}` / `{period_end:DateTime}` placeholders in the SQL and pass a default `period` (relative like {kind:'relative',unit:'month',amount:1}, or absolute start/end). The org user can then change it in Paid without re-authoring. A query using the placeholders REQUIRES a period. Do NOT add your own date-range picker to the render bundle — Paid owns the timeframe and the bundle receives already-filtered data; a second in-bundle picker cannot re-run the SQL. (8) Check your draft with validateCustomView (POST /experimental/views/validate) BEFORE asking the user to save — it runs these same gates without persisting and reports every problem at once. The response returns a `previewUrl` — give it to the user so they can open the new view in Paid.

Authentication

AuthorizationBearer
API Key authentication using Bearer token

Request

Body
namestringRequired1-255 characters

Human-readable view name (shown in preview + audit log).

querieslist of objectsRequired

One or more named queries. Each becomes a separately-keyed result set in the embed.

renderBundlestringRequired>=1 character

Self-contained HTML document that renders the result sets. Inline ALL CSS/JS/charting libraries — the render sandbox has no network access. It receives the keyed result sets via a message event (data[<queryId>]) and must not fetch anything itself.

descriptionstringOptional<=2048 characters
Optional longer description of what the view shows.
periodobjectOptional

Optional default date range. Required if any query uses the {period_start:DateTime} / {period_end:DateTime} placeholders. Can be changed later in Paid without re-authoring.

scopeenumOptional

‘customer’ (default): data is scoped to one viewing customer and the view is embeddable per-customer. ‘organization’: data is org-wide; the view is internal-only (visible to org members in Paid, never embeddable).

Response

201
displayIdstring

Stable public identifier used in preview/embed URLs.

namestring
statusenum

A view is DRAFT until explicitly published; only PUBLISHED views are served to end-customers.

previewUrlstring or nullformat: "uri"
Absolute URL to open this view in the Paid app for preview. Surface it to the user after creating or publishing a view. Null when the app URL is not configured.
scopeenum

‘customer’: data is scoped to one viewing customer and the view is embeddable per-customer. ‘organization’: data is org-wide and the view is internal-only.

Errors

400
Bad Request Error
403
Forbidden Error
500
Internal Server Error