API Reference
All requests and responses are JSON unless noted. API keys use X-API-Key; only public pk_ tracking keys may use the key query parameter. Browser project APIs use the wl_session cookie.
Public Endpoints
No authentication required.
| Method | Path | Description |
|---|---|---|
GET | / | Landing page |
GET | /login | Login page |
GET | /auth/github | GitHub OAuth initiation |
GET | /auth/github/cb | GitHub OAuth callback |
POST | /auth/logout | Destroy session |
GET | /public/* | Static assets (Script Tag, CSS) |
GET | /llms.txt | LLM-readable site summary |
API Key Endpoints
POST /track
Ingest events. Single or batch.
Auth: pk_, sk_, or aat_ with track scope.
Single event:
{ "event_type": "page_view", "user_id": "u123", "device_id": "d456", "session_id": "s789", "time": "2026-01-15T10:30:00Z", "event_properties": {"page": "/home"}, "user_properties": {"plan": "pro"}, "insert_id": "optional-dedup-id"}Batch:
{ "events": [ {"event_type": "click", "event_properties": {"button": "signup"}}, {"event_type": "page_view", "event_properties": {"page": "/pricing"}} ]}Response:
{"accepted": 2}Invalid events in a batch are silently skipped.
POST /identify
Bind a device to a user. Update user profile properties.
Auth: pk_, sk_, or aat_ with track scope.
{ "user_id": "alice@acme.org", "device_id": "dev_123", "user_properties": {"email": "alice@acme.org", "plan": "pro"}, "user_property_ops": { "$set": {"plan": "pro"}, "$set_once": {"signup_source": "ads"}, "$add": {"login_count": 1}, "$unset": ["legacy_flag"] }}Response:
{"ok": true}POST /query
Run a pipe DSL query.
Auth: sk_ or aat_ with query scope.
{ "q": "page_view | where _path in [\"/\",\"/pricing\",\"/docs\"] | last 7d | count by _browser", "format": "llm", "limit": 100, "offset": 0}| Field | Required | Default | Notes |
|---|---|---|---|
q | Yes | — | Pipe DSL query string |
format | No | "llm" | "llm" (Markdown), "json", or "csv" |
limit | No | 100 | Max 10,000 |
offset | No | 0 | Pagination offset |
Response: Markdown table, JSON array, or CSV depending on format.
For format: "json" aggregate-style rows, canonical metric fields are included:
value: numeric metric outputmetric: metric identifier (count,unique,sum,avg, etc.)
Legacy metric keys (count, unique_count, total, …) are preserved for compatibility.
rows_scanned reports ClickHouse source rows read while executing the query, not result rows returned.
Mode-specific metadata is additive and optional. Funnel JSON responses include:
{ "mode": "funnel", "metadata": { "funnel_steps": ["landing", "signup", "activate"] }}PUT /api/dashboard-sync/{dashboardKey}
Sync validated dashboard YAML into the API credential’s project. The route accepts no project ID; tenant scope comes only from the authenticated key.
Omitted visibility defaults from the credential only when creating a dashboard. Re-sync preserves existing visibility unless personal or project is sent explicitly.
Auth: sk_ or aat_ with dashboards scope. pk_ and ak_ are rejected.
{ "source_yaml": "version: 1\nid: product-growth\ntitle: Product Growth\n...", "visibility": "personal"}The server strictly validates YAML and stores immutable content versions. The response includes changed for a new version and metadata_changed for a visibility-only update.
Session-Authenticated API
Session routes require the wl_session cookie (set by GitHub OAuth login). Missing sessions return JSON 401, and unsafe cross-origin session requests are denied.
Organizations
GET /api/orgs — List user’s orgs.
POST /api/orgs — Create org.
{"name": "Acme Corp"}GET /api/orgs/{orgID} — Get org details.
POST /api/orgs/{orgID}/rotate-admin-key — Owner only. Rotate the org admin key (ak_).
{"admin_key": "ak_..."}Members, Invitations, and Domains
Owners can manage team access:
GET /api/orgs/{orgID}/membersPATCH /api/orgs/{orgID}/members/{userID}with{"role":"owner|admin|member"}DELETE /api/orgs/{orgID}/members/{userID}POST /api/orgs/{orgID}/invitationswith{"email":"person@example.com","role":"member|admin"}POST /api/invitations/{token}/acceptPOST /api/orgs/{orgID}/domainswith{"domain":"example.com"}POST /api/orgs/{orgID}/domains/{domainID}/verify
Domain verification expects TXT _wirelog-verify.<domain> with value wirelog-verification=<token>.
Projects
GET /api/orgs/{orgID}/projects — List org projects.
POST /api/orgs/{orgID}/projects — Admin/owner. Create project.
{"name": "My App"}Returns project with public_key (pk_). secret_key (sk_) is omitted for members.
GET /api/projects/{projectID} — Get project. Includes role/capabilities; secret_key is omitted for members.
POST /api/projects/{projectID}/query — Session-authenticated query route for members/admins/owners.
GET /api/projects/{projectID}/dashboards — List shared dashboards plus the current user’s personal dashboards.
GET /api/projects/{projectID}/dashboards/{dashboardID} — Get one visible dashboard’s validated current config.
DELETE /api/projects/{projectID} — Owner only. Delete project permanently.
{ "project_name": "My App", "project_name_confirm": "My App"}Both fields must match exactly. Deletes project and all associated data.
POST /api/projects/{projectID}/rotate-secret-key — Admin/owner. Rotate sk_ key. Public key unchanged.
Access Tokens
POST /api/projects/{projectID}/tokens — Create scoped access token.
{ "name": "cli-dashboard", "kind": "personal", "scopes": ["query", "dashboards"], "expires_in": "720h"}Members can create personal tokens with query and/or dashboards. Admins/owners can create service tokens; owner role is required for admin scope. Returns the raw token once. It cannot be retrieved again.
GET /api/projects/{projectID}/tokens — Members see their own token metadata. Admins/owners see project token metadata.
DELETE /api/projects/{projectID}/tokens/{tokenID} — Revoke own token, or any project token as admin/owner.
GET /api/me/tokens — List your personal tokens.
DELETE /api/me/tokens/{tokenID} — Revoke your personal token.
Choices
Choices use the normal ingest and query APIs. SDKs emit wirelog.exposure
events through /track; results are queried through /query.
{"q":"choice landing_h1 | results signup | window 7d | unit user_id","format":"json"}Choice result packets include choice_key, conversion_event, variants,
comparisons, diagnostics, warnings, decision_status, method, and
generated_at.
Usage
GET /api/projects/{projectID}/usage — Daily usage stats.
Query params: ?from=YYYY-MM-DD&to=YYYY-MM-DD. Defaults to current month.
Admin API
Auth: org admin key (ak_) via X-API-Key header. All routes under /api/admin/*.
| Method | Path | Description |
|---|---|---|
GET | /api/admin/projects | List org projects |
POST | /api/admin/projects | Create project |
GET | /api/admin/projects/{projectID} | Get project (includes keys) |
DELETE | /api/admin/projects/{projectID} | Delete project (same confirmation body as session API) |
POST | /api/admin/projects/{projectID}/rotate-secret-key | Rotate sk_ key |
GET | /api/admin/projects/{projectID}/usage | Daily usage stats |
GDPR
Auth: sk_ or aat_ with admin scope via X-API-Key header.
DELETE /api/gdpr/users/{userID} — Delete all user data: events, identity mappings, and profile rows. Logged to audit trail.
GET /api/gdpr/users/{userID}/export — Stream all user events as NDJSON. Logged to audit trail.
API Key Types
| Prefix | Type | Scopes | Client-safe? |
|---|---|---|---|
pk_ | Public key | track | Yes |
sk_ | Secret key | track, query, admin | No |
ak_ | Admin key | org admin | No |
aat_ | Access token | custom per-token | Depends on scopes |
pk_keys are safe to expose in client-side code. They can ingest events.sk_keys have full project access. Never expose in browsers or public repos.ak_keys have org-level admin access. Never expose in client code.aat_tokens are hashed before storage. The raw token is shown once at creation.
Error Format
All errors return:
{"error": "message"}| Code | Meaning |
|---|---|
400 | Bad request |
401 | Missing or invalid authentication |
403 | Insufficient scope or limit reached |
404 | Not found |
413 | Payload too large |
429 | Rate limited (includes Retry-After: 60 header) |
503 | Feature disabled |
504 | Query timeout |