Skip to main content

vibedasher — Python SDK

Official Python SDK for Vibedasher — the home for the dashboards your AI built. Your coding agent (Claude Code, Codex, Cursor, Gemini) writes the dashboard as React + SQL; Vibedasher hosts it at a URL behind your login, with per-viewer row filtering, embeds, refresh schedules, alerts and subscriptions. Managed cloud (EU, Frankfurt) or self-hosted.

This SDK is the programmatic half of that. The method you'll use most is client.query(...) — it runs SQL across your managed datasets and returns typed rows, so you can also render a dashboard natively from your own backend.

Install

pip install vibedasher

Own the code — start here

Vibedasher hosts the dashboard and the data behind it (ETL, datasets, query). A dashboard is never locked in: eject it into your own repo whenever you want the frontend back, and wire it to your data with a single call:

from vibedasher import Vibedasher

# Server-side (API key held on your server, never shipped to a browser).
client = Vibedasher(api_key="sk_...")

result = client.query(
    sql="SELECT region, AVG(lead_price_usd) AS avg_price FROM sales GROUP BY region",
    params={"region": "Caribe"},     # bound/escaped server-side, never interpolated
)

for col in result.columns:
    print(col.name, col.type)        # e.g. region string / avg_price number
for row in result.rows:
    print(row["region"], row["avg_price"])
  • dataset_ids is optional (plural). Omit it and the server infers the dataset set from the aliases your sql references. Pass it to pin an exact set explicitly: client.query(sql=..., dataset_ids=[12, 47], params=...) — each id resolves server-side to that dataset's alias under RLS, and a dataset not in the set is rejected (deny-don't-drop). A mistyped/unknown alias raises 400 query_dataset_unresolved.
  • sql is inline and alias-only — it references dataset aliases, never a physical table.
  • Results are typed columnar — result.columns are QueryColumn(name, type) with type ∈ string | number | boolean | date | timestamp | json, and result.rows are dicts keyed by column name.
  • Transport is hidden — inline vs presigned object storage, MessagePack decoding, and transient retries all happen inside query(). One call in, typed rows out.

query() is the one hand-written method (_query.py); everything else is generated (see below).

Where the query runs: type="wasm"

By default the server executes your SQL and returns rows. Pass type="wasm" to get a plan instead — the injected SQL plus one presigned Parquet per dataset — and execute it yourself (in DuckDB-WASM in a browser, or plain DuckDB locally):

result = client.query(sql="SELECT region, COUNT(*) FROM sales GROUP BY region", type="wasm")

if result.mode == "wasm":
    for src in result.plan.sources:
        print(src.alias, src.url, src.bytes)   # register each under `alias`
    print(result.plan.sql)                     # then run this
else:
    print(result.rows, result.fallback_reason) # ran server-side, and why

Authorization does not move. The server still authorizes, validates alias-only SQL, and injects row filters — in that order, before the capability check. Asking for wasm skips no gate; you get a plan only for data you were already allowed to read.

Asking for wasm does not guarantee getting it. If any participating dataset isn't wasm-capable — no current extract, an extract over the size cap, or a mandatory row-level filter — the query runs server-side and the response says so via mode: "backend" and a fallback_reason. It is never a silent downgrade, so always branch on result.mode, never on what you requested.

VibedasherEmbedClient (scoped-token) has no wasm lane and raises ValueError rather than quietly serving rows.

Embed vs eject

  • Embed (iframe, zero-code): paste a snippet; we host and render. Nothing to build.
  • Eject (this SDK, own-your-code): pull the dashboard's code into your stack and feed it data via client.query(...). Your app owns the rendering; only the data crosses the wire. See a runnable host app in examples/nextjs-embed (TS), the same contract as here.

Client-side (no backend) auth

For a pure-frontend app, mint a short-TTL scoped token and use the same method via the token-pinned client:

from vibedasher import VibedasherEmbedClient

client = VibedasherEmbedClient(token=scoped_token)
result = client.query(sql=sql, params=params)
# The token pins the viz + datasets — dataset_ids are accepted for symmetry but the
# server enforces the token's bound set.

Concurrency: a 503 is a limit, not an outage

The query engine has a finite, shared concurrency ceiling. A 503 Service Unavailable from query() means the request was throttled, not that the service is down — the same SQL run serially succeeds. query() retries throttled requests (429/502/503/504) with jittered backoff and honours Retry-After.

httpx.Client is synchronous, so one client is already one query at a time and there is no in-flight gate here — that lives in the TypeScript SDK, where a browser dashboard fans out on mount. If you thread this client yourself, keep the width modest.

Errors raise QueryHttpError (a httpx.HTTPStatusError subclass, so existing except clauses keep working) carrying status_code, the API's own message, and request_id — quote that in a bug report.

Everything else: resource methods (generated)

Datasets, uploads, viz metadata, API keys, etc. are resource-grouped on the same client, generated from the OpenAPI spec with flat, fully typed signatures: path params positional (in path order), then the request body, then the query params as keyword-only kwargs. No **kwargs, no bare Any; a non-2xx raises VibedasherApiError (status_code, detail, request_id).

from vibedasher import Vibedasher, VibedasherApiError
from vibedasher.models import DatasetUpdate

client = Vibedasher(api_key="sk_...")
page = client.datasets.list(limit=50, folder_id=7)               # -> DatasetListResponse
ds = client.datasets.update(42, DatasetUpdate(name="renamed"))   # -> DatasetResponse
client.viz.delete(7)                                             # 204 -> None
try:
    client.viz.get(999999)
except VibedasherApiError as e:
    print(e.status_code, e.detail, e.request_id)

The low-level generated modules remain available for anything the facade does not expose (vibedasher.api.<tag>.<operation_id> with .sync(), .sync_detailed(), .asyncio(), .asyncio_detailed()), driven by create_client(api_key).

Auth is an API key sent as X-Api-Key (mint one via POST /v1/api-keys). The base URL defaults to https://api.vibedasher.com/api, one hostname that reads the credential and routes to the region owning the account — so there is no region to look up. region="eu-central-1" still works and pins the client to that region's own host; base_url overrides both.

Paging a list endpoint: iter_all()

Every cursor-paginated list endpoint returns one page plus a nextKey:

{ "datasets": [ ... ], "nextKey": 812 }

nextKey: null means that was the last page — it is the only end-of-list signal. Rather than hand-rolling that loop, iterate:

for dataset in client.datasets.iter_all():
    print(dataset.name)                    # pages transparently, rows streamed

for dataset in client.datasets.iter_all(page_size=50, max_pages=10, folder_id=7):
    ...                                    # cap the work; extra kwargs are forwarded

for page in client.datasets.iter_pages():
    print(page.next_key)                   # page-at-a-time, e.g. to checkpoint

Nested collections take the method name:

for upload in client.datasets.iter_all(method="list_uploads", dataset_id=4):
    ...

Notes:

  • Bounded memory — rows are yielded as each page arrives, and break stops the requests immediately. Nothing is prefetched or accumulated.
  • max_pages caps the work: iterating an unbounded table to exhaustion is a footgun, so the ceiling is one keyword away.
  • A repeated cursor raises CursorNotAdvancingError instead of looping forever. That is a server bug in the endpoint's nextKey; the SDK surfaces it rather than absorbing it as a short read.
  • For a paginated endpoint that is not on the resource, the standalone form works on any list callable: from vibedasher import iter_all.

A bare 403 {"message":"Forbidden"} is always the credential

The edge rejects a request before the application sees it, so its refusal body is one word and nothing else. That one word stands for three different mistakes, and the response cannot tell you which:

what happened what to do
no credential sent set X-Api-Key
the key was sent as Authorization: Bearer move it to X-Api-Key — Authorization is read only as a login session token, so a key placed there is never seen
the key was sent in X-Api-Key and refused it is unknown, revoked or expired; mint a new one

Two conclusions this response does not support, and both are the ones usually drawn from it:

  • It is not a path problem. With an accepted credential, an unknown path answers 404 and names the path in the body. A bare Forbidden never means the URL was wrong.
  • It is not a permissions problem. Roles and resource access are decided by the application, which answers with a reason. The edge only ever asks whether a credential is valid, so a bare Forbidden is never "ask your human for access".

The single most common first-contact mistake is the second row: Authorization: Bearer <key> returns 403 on every route, including plain reads, with a perfectly good key.

Fix the header first, then read the body — once a credential is accepted, the API distinguishes all four conditions on its own.

Layout

  • vibedasher/_query.py — the hand-written query() (transport, decode, retry, typed rows).
  • vibedasher/_pagination.py — the hand-written iter_all() / iter_pages() cursor loop.
  • vibedasher/_facade.py — GENERATED resource classes with the flat signatures + VibedasherApiError.
  • tests/test_facade_surface.py — the blocking lint on _facade.py (no **kwargs, no bare Any, snake_case only).
  • vibedasher/api/<tag>/<operation>.py — one module per API operation.
  • vibedasher/models/ — request/response models (attrs classes).
  • vibedasher/factory.py — create_client(api_key, region=..., base_url=...); region is optional (one hostname routes by credential).

Generated code — do not edit vibedasher/ by hand. Regenerate with make py from packages/sdk/. Source of truth: ../openapi.json. query() and the pagination iterators are the deliberate exceptions — hand-written in packages/sdk/py/{_query,_pagination}.py and re-copied after each generation.

Metadata

Release files for vibedasher 4.3.19

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vibedasher 4.3.19
File Size Uploaded
vibedasher-4.3.19.tar.gz 399.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vibedasher 4.3.19
File Interpreter ABI Platform
vibedasher-4.3.19-py3-none-any.whl Python 3 none any Details

Total release size: 1.4 MB

Release files / vibedasher-4.3.19.tar.gz

Download URL vibedasher-4.3.19.tar.gz
Size 399.9 kB
Tags Source
SHA-256 checksum
How to use checksums
055b744593a5dd947cec1b4e3d736b9189ed8865dedebc966b5569bb8ed1f0f0
BLAKE2b-256 checksum
How to use checksums
05b74126351e8178e3192b263fba0c607eed30ecced5dbb9980fa40601e6c2b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / vibedasher-4.3.19-py3-none-any.whl

Download URL vibedasher-4.3.19-py3-none-any.whl
Size 998.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5dccab64970b7059b68b607301487d193c51a7932a73b51fe9a4d0fc8d95e336
BLAKE2b-256 checksum
How to use checksums
36fac44006a8cee885250b7b500846ef61fdcf9e8018acdb7fb3bcfd586900ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

4.3.20

2 release files

This release

4.3.19 This release

2 release files

4.3.10

2 release files

4.3.9

2 release files

4.3.8

2 release files

4.3.6

2 release files

4.3.5

2 release files

4.3.4

2 release files

4.3.3

2 release files

4.3.2

2 release files

4.3.1

2 release files

4.3.0

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.1.0

2 release files

3.0.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page