# Org Export API Pull conversations, usage, and session metadata across every app in your organization with a single API key -- designed for warehouse sync and scheduled scripts --- The Org Export API is a read-only REST API scoped to your **entire organization** rather than a single app. Use it to pull all your chat transcripts, usage data, and session metadata into a warehouse (Snowflake, BigQuery, Postgres) or to run attribution analysis on a schedule. > **Note:** The Builder API (`chipp_` keys) covers one app at a time. The Org Export API (`chipp_org_*` keys) covers all apps in your org with one key and one pull loop. ## Authentication Mint a key from the Chipp dashboard under **Settings** (org-level) or ask the Alchemist: > *"Create an org read API key with the transcripts and usage scopes."* Send the key as a Bearer token on every request: ``` Authorization: Bearer chipp_org_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` **Base URL:** `https://dino-mullet.chipp.ai/api/v1/org` Rate limit: 120 requests/minute per key. A `429` response includes `Retry-After`. ### Scopes Each key carries one or more read scopes. Requests outside a key's scopes return `403`: | Scope | Grants | |-------|--------| | `apps:read` | List the org's apps | | `transcripts:read` | Read chat sessions and messages | | `usage:read` | Read token usage and cost rows | ## Pagination List endpoints (`/sessions`, `/usage`) are cursor-paginated, newest first: - `?limit=` (default 50, max 100) - Follow `pagination.next_cursor` until `pagination.has_more` is `false` - For incremental syncs, filter by time (`started_after`, `since`) and keep the max `started_at` / `created_at` seen as the checkpoint for the next run ```json "pagination": { "next_cursor": "eyJ...", "has_more": true, "limit": 50 } ``` ## Endpoints ### `GET /apps` List the org's apps (non-deleted, newest first). ```bash curl -s "https://dino-mullet.chipp.ai/api/v1/org/apps" \ -H "Authorization: Bearer $CHIPP_ORG_KEY" ``` ```json { "data": [ { "id": "uuid", "name": "Ask Max!", "slug": "ask-max-8jw2", "created_at": "2026-04-19T02:33:55.787Z", "updated_at": "2026-06-04T23:55:47.228Z" } ] } ``` --- ### `GET /sessions` Chat sessions across all your org's apps, newest first. **Query params:** `limit`, `cursor`, `app_id` (filter to one app), `started_after` (ISO 8601), `started_before` (ISO 8601). ```bash curl -s "https://dino-mullet.chipp.ai/api/v1/org/sessions?limit=100&started_after=2026-06-01T00:00:00Z" \ -H "Authorization: Bearer $CHIPP_ORG_KEY" ``` ```json { "data": [ { "id": "uuid", "app_id": "uuid", "consumer_id": "uuid|null", "consumer_email": "patient@clinic.com|null", "title": "Onboarding chat", "source": "APP", "external_id": "patient-abc123|null", "query_params": [ { "name": "rp_token", "value": "abc123" }, { "name": "ab_variant", "value": "B" } ], "started_at": "2026-06-15T14:30:00Z", "ended_at": "2026-06-15T14:45:00Z", "last_activity_at": "2026-06-15T14:45:00Z" } ], "pagination": { "next_cursor": "...", "has_more": true, "limit": 100 } } ``` **`external_id`** holds the value passed via `?chipp_external_id=` (web sessions), or a channel-namespaced key like `whatsapp:+15551234567` (channel sessions). `null` when not set. **`query_params`** is the `[{ name, value }]` array of URL query params the consumer arrived with. These are captured at session creation and never change. Chipp control params (`bt`, `session`, `share`) are excluded. Use this for A/B arm attribution, referral token analysis, and campaign tracking -- with no side-channel joins required. --- ### `GET /sessions/{sessionId}/messages` Messages for one session (must belong to your org). **Query params:** `include` (comma-separated, optional). **Include values:** - `tool_details` -- adds a compact `tools` array to each message that invoked a tool or rendered a component. ```bash # Basic curl -s "https://dino-mullet.chipp.ai/api/v1/org/sessions//messages" \ -H "Authorization: Bearer $CHIPP_ORG_KEY" # With tool markers curl -s "https://dino-mullet.chipp.ai/api/v1/org/sessions//messages?include=tool_details" \ -H "Authorization: Bearer $CHIPP_ORG_KEY" ``` ```json { "data": { "session": { "id": "uuid", "app_id": "uuid", "consumer_id": "uuid|null", "consumer_email": "patient@clinic.com|null", "title": "Onboarding chat", "source": "APP", "external_id": "patient-abc123", "query_params": [{ "name": "rp_token", "value": "abc123" }], "started_at": "2026-06-15T14:30:00Z" }, "messages": [ { "id": "msg-001", "role": "user", "content": "Hi, I just signed up.", "created_at": "2026-06-15T14:30:01Z" }, { "id": "msg-002", "role": "assistant", "content": "Welcome! Let me walk you through the next steps.", "created_at": "2026-06-15T14:30:05Z", "tools": [ { "id": "toolu_01XYZ", "name": "render_completion_screen", "type": "custom_component", "component": "CompletionScreen", "success": true } ] } ], "zdr": false } } ``` Each `tools` marker has: `{ id?, name?, type?, component?, success? }`. Heavy blobs (`code`, `input`, `output`) are excluded. **Detect completion:** `messages.some(m => m.tools?.some(t => t.name === "render_completion_screen"))` > If an app has Zero Data Retention (ZDR) enabled, message bodies are ephemeral and not stored. `messages` is empty and `zdr` is `true`. --- ### `GET /usage` Per-call token usage and cost across the org's apps, newest first. **Query params:** `limit`, `cursor`, `since` (ISO 8601). ```bash curl -s "https://dino-mullet.chipp.ai/api/v1/org/usage?since=2026-06-01T00:00:00Z&limit=100" \ -H "Authorization: Bearer $CHIPP_ORG_KEY" ``` ```json { "data": [ { "id": 123, "app_id": "uuid", "session_id": "uuid|null", "consumer_id": "uuid|null", "model": "claude-sonnet-4-6", "source": "consumer", "input_tokens": 1200, "output_tokens": 340, "cache_read_input_tokens": 800, "cache_creation_input_tokens": 0, "cost_usd": "0.012500", "created_at": "2026-06-05T14:39:07.415Z" } ], "pagination": { "next_cursor": "...", "has_more": true, "limit": 100 } } ``` `session_id` is `null` for sessionless usage (Alchemist copilot, heartbeat). `consumer_id` is `null` for builder/anonymous sessions. Consumer email is on the `/sessions` endpoint (behind the `transcripts:read` scope) to keep this `usage:read` endpoint PII-free. Join `consumer_id` / `session_id` between the two endpoints. ## Errors `{ "error": { "code": "...", "message": "..." } }`: | HTTP | When | |------|------| | 401 | Missing/invalid/expired key | | 403 | Key lacks the required scope | | 404 | Session not found (or not in your org) | | 429 | Rate limited (see `Retry-After`) | ## Cron / script pull recipe This loop pulls new transcripts since the last sync and writes them to a local file. Adapt for Snowflake, BigQuery, or any datastore. ```javascript const ORG_KEY = process.env.CHIPP_ORG_KEY; const BASE = "https://dino-mullet.chipp.ai/api/v1/org"; async function pull(sinceIso) { // 1. List apps once to build the name map. const { data: apps } = await fetch(`${BASE}/apps`, { headers: { Authorization: `Bearer ${ORG_KEY}` } }).then(r => r.json()); const appNames = Object.fromEntries(apps.map(a => [a.id, a.name])); // 2. Page through sessions. let cursor, maxStartedAt = sinceIso; const sessions = []; do { const url = new URL(`${BASE}/sessions`); url.searchParams.set("started_after", sinceIso); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const { data, pagination } = await fetch(url, { headers: { Authorization: `Bearer ${ORG_KEY}` } }).then(r => r.json()); for (const s of data) { // 3. Fetch messages with compact tool markers. const msgUrl = `${BASE}/sessions/${s.id}/messages?include=tool_details`; const { data: { messages } } = await fetch(msgUrl, { headers: { Authorization: `Bearer ${ORG_KEY}` } }).then(r => r.json()); sessions.push({ session_id: s.id, app_name: appNames[s.app_id], external_id: s.external_id, query_params: s.query_params, // [{ name, value }] -- self-attributing completed: messages.some(m => m.tools?.some(t => t.name === "render_completion_screen") ), messages, }); if (s.started_at > maxStartedAt) maxStartedAt = s.started_at; } cursor = pagination.next_cursor; } while (cursor); return { sessions, nextSince: maxStartedAt }; } ``` **Attribution pattern:** Because `query_params` includes the arrival link's params (e.g. `rp_token`, `ab_variant`), each row in `sessions` is already attributed -- no join to an external mapping table required. ## Relationship to the Builder API | | Builder API (`chipp_`) | Org Export API (`chipp_org_*`) | |--|------------------------|-------------------------------| | Scope | One app | All apps in org | | Auth | Per-app key | Org-level key | | Scripting | Manual per-app loop | Single paginated pull | | ZDR | Returns empty messages | Returns empty messages | | Tool markers | Raw `tool_calls` / `tool_results` blobs | Compact `tools` markers (no code) | For the Builder API's per-app sessions and messages endpoints, see [Sessions](/docs/builder-api/sessions).