API

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

| View as Markdown
1 min read
# api # org-api # export # transcripts # analytics # rest # cron # snowflake # attribution

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.

ℹ️

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:

plaintext
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:

ScopeGrants
apps:readList the org’s apps
transcripts:readRead chat sessions and messages
usage:readRead 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/<sessionId>/messages" \
  -H "Authorization: Bearer $CHIPP_ORG_KEY"

# With tool markers
curl -s "https://dino-mullet.chipp.ai/api/v1/org/sessions/<sessionId>/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": "..." } }:

HTTPWhen
401Missing/invalid/expired key
403Key lacks the required scope
404Session not found (or not in your org)
429Rate 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_*)
ScopeOne appAll apps in org
AuthPer-app keyOrg-level key
ScriptingManual per-app loopSingle paginated pull
ZDRReturns empty messagesReturns empty messages
Tool markersRaw tool_calls / tool_results blobsCompact tools markers (no code)

For the Builder API’s per-app sessions and messages endpoints, see Sessions.