E EMBAN / Docs

API Reference

Base URL: https://emban.sidelabs.dev

Authentication: Authorization: Bearer YOUR_API_KEY. Use ingest for events, and admin for dashboard management and embed sessions.

Reference conventions: expect 200 for reads, 201 for creates, 204 for deletes or publish-state changes, 401 for missing or invalid auth, 403 for scope or role violations, 402 for plan-limit enforcement, and 409 for conflict cases such as duplicate invites or already-registered users.

Auth and Error Conventions

CaseStatusMeaning
Read or success response200The request succeeded and returns JSON content.
Create201A new resource was created, such as an API key, invite, alert, or signed embed session.
Delete / publish-state mutation204The mutation succeeded and no response body is required.
Missing or invalid auth401The request did not authenticate or used an expired or invalid token.
Scope or role violation403The caller authenticated but does not have the required admin scope, role, or permission.
Plan limit exceeded402The org hit a plan gate such as published dashboards, API keys, advanced embed permissions, or usage caps.
Conflict409The request collides with existing state, such as duplicate registration or an already pending invite.
Missing resource or invalid path data404 / 400The resource does not exist, or a route parameter or payload shape is invalid.

Plan-limit response shape

{
  "error": "published dashboard limit reached for this plan",
  "code": "published_dashboards_limit",
  "plan": "free",
  "upgrade_to": "starter",
  "limit": 1,
  "used": 1
}

Auth

POST /v1/auth/register

Create a user, org, owner membership, default environments, and default admin API key. Returns a session cookie for the admin console.

POST /v1/auth/login

Authenticate an existing user and set the emban_session httpOnly cookie.

POST /v1/auth/logout

Delete the current session cookie and server-side session row.

GET /v1/auth/me

Return the authenticated user, org, role, and scope resolved from the session or API key.

POST /v1/auth/accept-invite

Accept a pending team invite using the signed invite token.

Events

POST /v1/events

Ingest events. Accepts admin or ingest-scoped keys.

{
  "events": [{
    "tenant_id": "string (required)",
    "event_name": "string (required)",
    "timestamp": "ISO 8601 (optional, defaults to server time)",
    "event_id": "string (optional, auto-generated)",
    "user_id": "string (optional)",
    "string_props": {"key": "value"},
    "numeric_props": {"key": 123.4}
  }]
}

Response: {"accepted": 1, "status": "ok"}

Dashboards

GET /v1/dashboards

List all dashboards.

GET /v1/dashboards/:id

Get dashboard by ID.

POST /v1/dashboards

Create dashboard. Body: {"name": "...", "widgets": [...]}

Each widget needs: id, kind, title, query, viz, layout. See Dashboards for widget structure.

PATCH /v1/dashboards/:id

Update dashboard name or config.

DELETE /v1/dashboards/:id

Delete dashboard.

POST /v1/dashboards/:id/publish

Publish current draft config to live embed.

POST /v1/dashboards/:id/unpublish

Unpublish — embed shows a "not published" message instead of the dashboard.

POST /v1/dashboards/:id/restore-published

Replace the draft config with the last published snapshot. Admin access required.

POST /v1/dashboards/:id/widgets

Add an inline widget to a dashboard draft. Admin access required.

PATCH /v1/dashboards/:id/widgets/:widgetId

Update a widget's title, viz, or layout.

DELETE /v1/dashboards/:id/widgets/:widgetId

Delete a widget.

Widgets

GET /v1/widgets

List standalone widgets in the current org.

GET /v1/widgets/:id

Get one standalone widget.

POST /v1/widgets

Create a standalone widget. Admin access required.

PATCH /v1/widgets/:id

Update a standalone widget. Admin access required.

POST /v1/widgets/:id/publish

Publish a standalone widget snapshot. Admin access required.

DELETE /v1/widgets/:id

Delete a standalone widget. Admin access required.

POST /v1/widgets/preview/data

Evaluate an inline widget config without saving it. Used by the builder preview path.

Embed Sessions

POST /v1/embed-sessions

Create a signed embed session. Returns a short-lived URL for iframe/SDK. Admin scope required.

{
  "tenant_id": "string (required)",
  "dashboard_id": "string (required)",
  "expires_in": 3600,
  "permissions": {
    "allow_drill_down": true,
    "locked_filters": {"plan": "pro"},
    "hidden_widgets": ["internal_notes"],
    "allowed_dimensions": ["model"],
    "allowed_periods": ["7d", "30d"],
    "max_date_range_days": 30
  }
}

Response: {
  "token": "jwt...",
  "embed_url": "https://emban.sidelabs.dev/embed/dash_id?token=...",
  "expires_at": "2026-03-29T13:00:00Z"
}

GET /v1/embed-sessions

List active signed embed sessions for the current org. Admin access required. The list is derived from recent embed_session.create audit rows and includes tenant_id, resource, scope, actor, issued time, expiry time, and remaining TTL.

Browser Runtime

After your backend creates a signed embed_url, the host page can drive the mounted dashboard through a small browser contract. This layer is separate from product data ingestion.

Boundary: POST /v1/embed-sessions mints the signed session on the server. Everything below happens after the browser mounts that signed embed. For the higher-level mental model, see Embed Runtime.

Host -> embed commands

The host page can update filters, adjust theme, or force a refresh.

{
  "_emban": true,
  "type": "setFilters",
  "filters": {
    "period": "7d"
  }
}

{
  "_emban": true,
  "type": "setTheme",
  "theme": {
    "primaryColor": "#0f9d58",
    "backgroundColor": "#f7f9f7",
    "cardBackground": "#ffffff",
    "cardBorder": "#d8e2d9",
    "textColor": "#101513",
    "mutedColor": "#5d6c63"
  }
}

{
  "_emban": true,
  "type": "reload"
}
CommandPayloadDescription
setFilters{ period, dimensions?, numericRanges?, dateRanges? }Updates active period and filter state inside the mounted dashboard.
setTheme{ primaryColor, backgroundColor, cardBackground, cardBorder, textColor, mutedColor }Applies host-driven theme tokens to the published embed.
reload{}Forces a fresh data load without minting a new signed session.

Embed -> host events

The embedded dashboard reports lifecycle, state changes, and user interaction back to the host page.

{
  "_emban": true,
  "type": "ready",
  "dashboardId": "dash_123"
}

{
  "_emban": true,
  "type": "resize",
  "height": 712
}

{
  "_emban": true,
  "type": "filter",
  "period": "7d",
  "filters": { "model": "gpt-4" },
  "numericRanges": {},
  "dateRanges": {}
}

{
  "_emban": true,
  "type": "period-change",
  "previousPeriod": "30d",
  "period": "7d"
}

{
  "_emban": true,
  "type": "drill",
  "dimension": "endpoint",
  "value": "/chat",
  "action": "apply"
}

{
  "_emban": true,
  "type": "error",
  "message": "Couldn't refresh this dashboard"
}
EventPayloadDescription
ready{ dashboardId }Fires when the embed runtime is ready and the dashboard is connected.
resize{ height }Reports the current iframe height for host-side auto-resize.
filter{ period, filters, numericRanges, dateRanges }Reports the full active filter state after a change or refresh.
period-change{ previousPeriod, period }Reports period transitions from inside the embedded dashboard.
drill{ dimension, value, action }Reports drill apply, clear, or detail actions.
error{ message }Reports a failed refresh or other runtime error.
Helper-specific event: session-swapped is emitted by the browser helper on the host side when setEmbedUrl(nextSignedUrl) replaces the current signed URL. It is not posted by the iframe runtime itself.

Discovery

GET /v1/discover

Scan events and return discovered event types with their properties.

POST /v1/discover/auto-create

Auto-generate a dashboard from discovered events.

Response: {
  "dashboard_id": "uuid",
  "name": "Auto-generated Dashboard",
  "widgets": 6,
  "events_found": 2
}

Query

GET /v1/query

Run an analytics query.

?event_name=api.call
&tenant_id=acme
&metric=count|count_unique|count_unique_approx|sum|avg|p95|p99
&period=1h|24h|7d|30d|90d
&granularity=hour|day|week|month
&group_by=model
&f_model=gpt-4
&numeric_prop=tokens (required for sum|avg|p95|p99)

tenant_id, event_name, metric, and period are required. The raw query endpoint accepts simple exact string filters via f_<dimension>=value, for example f_model=gpt-4 or f_status=success.

The response meta can also include planner proof fields when Emban routes a query through ClickHouse rollups:

{
  "data": [...],
  "meta": {
    "query_time_ms": 12,
    "rows_scanned": 0,
    "event_name": "api.call",
    "metric": "count",
    "period": "30d",
    "source_table": "events_daily_dim_rollup",
    "planned_by": "daily_rollup",
    "rollup_granularity": "day"
  }
}

Today this planner path is intentionally conservative: safe count queries can use hourly or daily rollups, including filtered and group_by rollups for common SaaS dimensions like model, endpoint, status, plan, and source. Exact count_unique stays bucket-matching only, count_unique_approx can merge HLL rollup states on safe paths, including filtered and grouped HLL rollups for those same common dimensions, and p95 / p99 still run against raw events.

POST /v1/query/adhoc

Run a read-only ClickHouse SQL query from the Query Builder. Admin access required. Only SELECT and WITH queries are accepted, writes/DDL are rejected before execution, and ClickHouse runs with readonly, timeout, row limit, and org/environment table filters.

{
  "sql": "SELECT event_name, count() FROM events GROUP BY event_name LIMIT 20"
}

Response: {
  "columns": [{"name": "event_name", "type": "String"}],
  "rows": [["api.call", 120]],
  "stats": {"wall_ms": 18, "rows_returned": 1, "truncated": false}
}

Datasets

GET /v1/datasets

List ClickHouse tables and materialized views visible to the org's admin console, including engine, approximate rows/bytes, update time, and columns. Admin access required.

GET /v1/datasets/:name/ddl

Return the ClickHouse CREATE TABLE or view statement for one dataset. Admin access required.

Saved Queries

GET /v1/saved-queries

List saved SQL snippets for the current org. Any authenticated admin-console user can read them.

POST /v1/saved-queries

Create a saved SQL query. Admin access required. Body: {"name": "...", "sql": "...", "dataset": "events"}

PATCH /v1/saved-queries/:id

Update name, SQL, dataset, or starred state. Admin access required.

DELETE /v1/saved-queries/:id

Delete a saved query. Admin access required. Returns 204.

Alerts

GET /v1/alerts

List alert rules for the current org.

GET /v1/alerts/:alertId

Get one alert rule by numeric ID.

GET /v1/alerts/:alertId/events

List the most recent firing or resolution events for one alert.

POST /v1/alerts

Create an alert rule. Admin access required.

{
  "dashboard_id": "dash_abc",
  "widget_id": "w_latency_p95",
  "name": "Latency p95 over threshold",
  "condition_type": "threshold",
  "operator": "gt",
  "threshold_value": 1200,
  "lookback_minutes": 60,
  "email_to": ["ops@example.com"],
  "webhook_url": "https://example.com/emban-alerts"
}

condition_type must be one of threshold, delta, or no_data. If lookback_minutes is omitted or <= 0, Emban defaults it to 60.

PATCH /v1/alerts/:alertId

Update mutable fields such as name, enabled, operator, threshold_value, lookback_minutes, email_to, or webhook_url.

DELETE /v1/alerts/:alertId

Delete an alert rule. Returns 204.

Scheduled Reports

GET /v1/reports

List scheduled report rules for the current org.

GET /v1/reports/:reportId

Get one report rule by numeric ID.

GET /v1/reports/:reportId/runs

List recent report runs, including delivery status and error summary.

POST /v1/reports

Create a scheduled report. Admin access required. Reports can deliver dashboard exports by email and/or webhook depending on the rule configuration.

PATCH /v1/reports/:reportId

Update mutable report fields such as schedule, format, recipients, webhook URL, or enabled state. Admin access required.

DELETE /v1/reports/:reportId

Delete a report rule. Admin access required. Returns 204.

API Keys

GET /v1/api-keys

List API keys (prefix only, not full key).

POST /v1/api-keys

Create API key. Body: {"label": "...", "scope": "admin|ingest"}

Response includes key field — shown only once.

DELETE /v1/api-keys/:id

Revoke an API key.

Team

GET /v1/members

List org members.

POST /v1/members/invite

Invite a member. Body: {"email": "...", "role": "viewer|editor|admin"}

Response includes token for invite link.

GET /v1/members/invites

List pending invites for the current org.

PATCH /v1/members/:id

Change member role (owner only). Body: {"role": "..."}

DELETE /v1/members/:id

Remove member.

Settings

GET /v1/settings/org

Get org details.

PATCH /v1/settings/org

Update org. Body: {"name": "...", "email": "..."}

DELETE /v1/settings/org

Delete org (owner only). Irreversible.

Other

GET /v1/demo/embed-url

Public demo-only helper route used by the website live demo. Returns a seeded signed embed session. Optional query param: ?tenant_id=demo_workspace.

GET /v1/usage

Usage stats. ?period=30d

GET /v1/billing

Current plan, billing status, usage counters, limits, and available plan specs. Admin scope required.

GET /v1/billing/plans

List standard plan specs and feature flags.

GET /v1/billing/checkout-config

Returns Paddle.js initialization data (environment, client token, price IDs, org ID) so the admin app can open the hosted checkout overlay. Returns 503 when Paddle is not configured. Auth required.

GET /v1/billing/portal

Exchanges the org's Paddle customer ID for a hosted customer portal URL (payment method, plan changes, invoices, cancellation). Returns 404 if the org has no Paddle customer yet, 503 if Paddle is not configured. Admin scope required.

POST /webhooks/paddle

Public Paddle Billing webhook endpoint. HMAC-authenticated via the Paddle-Signature header against PADDLE_NOTIFICATION_KEY. Idempotent: duplicate event IDs are acknowledged with 200 OK without re-running side effects. Body capped at 1 MiB.

GET /v1/audit

Audit log. Last 100 entries.

GET /v1/onboarding/status

Onboarding progress: sample_only, real_events count.

GET /v1/webhooks

Read-only webhook delivery observability for alert and report rules: 24h success/fail counts, last status code, last error, last response excerpt, and duration. Admin access required.