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.
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.Use this page in two passes: first mint the signed session on the server, then validate the browser runtime contract that starts after mount.
Auth and Error Conventions
| Case | Status | Meaning |
|---|---|---|
| Read or success response | 200 | The request succeeded and returns JSON content. |
| Create | 201 | A new resource was created, such as an API key, invite, alert, or signed embed session. |
| Delete / publish-state mutation | 204 | The mutation succeeded and no response body is required. |
| Missing or invalid auth | 401 | The request did not authenticate or used an expired or invalid token. |
| Scope or role violation | 403 | The caller authenticated but does not have the required admin scope, role, or permission. |
| Plan limit exceeded | 402 | The org hit a plan gate such as published dashboards, API keys, advanced embed permissions, or usage caps. |
| Conflict | 409 | The request collides with existing state, such as duplicate registration or an already pending invite. |
| Missing resource or invalid path data | 404 / 400 | The 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.
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"
}
| Command | Payload | Description |
|---|---|---|
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"
}
| Event | Payload | Description |
|---|---|---|
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. |
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.