> ## Documentation Index
> Fetch the complete documentation index at: https://docs.causeloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Get a token and make your first API calls in under five minutes.

This guide walks you from zero to real API calls against the pipeline. You'll exchange a WorkOS access token for a Causeloop token, ingest an issue, list issues, and fetch a pattern.

<Note>
  You need a Causeloop workspace and a WorkOS access token from your frontend's AuthKit session. If you're testing locally, run the backend with `AUTH_PROVIDER=none` (and `ENVIRONMENT` not set to `production`) — `POST /v1/auth/exchange` then accepts any non-empty `subject_token` and returns a valid scoped JWT backed by seed data. You can also skip the exchange entirely for local scripting and mint a token directly with `python -m scripts.mint_dev_token <workspace_id> <user_id>` — see [Local development](/deploy-security/local-development).
</Note>

<Steps>
  <Step title="Exchange your WorkOS access token for a Causeloop token">
    Call `POST /v1/auth/exchange` with your WorkOS access token.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.causeloop.ai/v1/auth/exchange \
        -H "Content-Type: application/json" \
        -d '{"subject_token": "<your_workos_access_token>"}'
      ```

      ```javascript JavaScript theme={null}
      const response = await fetch('https://api.causeloop.ai/v1/auth/exchange', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ subject_token: '<your_workos_access_token>' }),
      });
      const { access_token } = await response.json();
      console.log('Token:', access_token);
      ```

      ```python Python theme={null}
      import requests

      resp = requests.post(
          'https://api.causeloop.ai/v1/auth/exchange',
          json={'subject_token': '<your_workos_access_token>'},
      )
      resp.raise_for_status()
      access_token = resp.json()['access_token']
      print('Token:', access_token)
      ```
    </CodeGroup>

    You'll receive a response like:

    ```json theme={null}
    {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": "clp_refresh_...",
      "token_type": "bearer",
      "expires_in": 259200,
      "scope": "issues:read issues:write patterns:read ...",
      "workspace_id": "ws_01abc123"
    }
    ```

    Save the `access_token`. It's valid for 72 hours. Set it as an environment variable for the next steps:

    ```bash theme={null}
    export CAUSELOOP_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    ```
  </Step>

  <Step title="Ingest an issue">
    `POST /v1/ingest/events` accepts a single raw signal. Two fields matter most: `narrative` (the finding, renamed from the old `body` field) and `occurred_at` (when the failure actually happened — this is what the hazard model's point process is built on). This path requires an `Idempotency-Key` header on every call — see [Idempotency](/api-reference/idempotency#mandatory-idempotency-keys).

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.causeloop.ai/v1/ingest/events \
        -H "Authorization: Bearer $CAUSELOOP_TOKEN" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: $(uuidgen)" \
        -d '{
          "external_id": "INC-4471",
          "title": "Payment gateway timeout on checkout",
          "narrative": "Health check timed out after 30s on 3/5 replicas during the 14:00 UTC deploy window.",
          "occurred_at": "2026-07-01T14:32:00Z",
          "severity": "p2",
          "source": "pagerduty"
        }'
      ```

      ```javascript JavaScript theme={null}
      const resp = await fetch('https://api.causeloop.ai/v1/ingest/events', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': crypto.randomUUID(),
        },
        body: JSON.stringify({
          external_id: 'INC-4471',
          title: 'Payment gateway timeout on checkout',
          narrative: 'Health check timed out after 30s on 3/5 replicas during the 14:00 UTC deploy window.',
          occurred_at: '2026-07-01T14:32:00Z',
          severity: 'p2',
          source: 'pagerduty',
        }),
      });
      const ack = await resp.json();
      console.log(ack.issue_id, ack.engine_status_summary);
      ```

      ```python Python theme={null}
      import uuid
      import requests

      resp = requests.post(
          'https://api.causeloop.ai/v1/ingest/events',
          headers={
              'Authorization': f'Bearer {access_token}',
              'Idempotency-Key': str(uuid.uuid4()),
          },
          json={
              'external_id': 'INC-4471',
              'title': 'Payment gateway timeout on checkout',
              'narrative': 'Health check timed out after 30s on 3/5 replicas during the 14:00 UTC deploy window.',
              'occurred_at': '2026-07-01T14:32:00Z',
              'severity': 'p2',
              'source': 'pagerduty',
          },
      )
      resp.raise_for_status()
      ack = resp.json()
      print(ack['issue_id'], ack['engine_status_summary'])
      ```
    </CodeGroup>

    **Response — `202 Accepted`**

    ```json theme={null}
    {
      "accepted": 1,
      "rejected": [],
      "issue_id": "iss_01abc123",
      "job_id": "ing_job_01xyz789",
      "engine_status_summary": {
        "eligible": 1,
        "blocked_no_narrative": 0,
        "ineligible": 0
      }
    }
    ```

    `engine_status_summary` tells you what happened without a second call: `eligible` means the issue has a narrative and will enter clustering on the next engine run; `blocked_no_narrative` means an engine-eligible issue arrived with no narrative — it's accepted (never rejected) but parked with a review-queue item until someone supplies one, since narrative-less issues never enter clustering. Batch ingest (`POST /v1/ingest/batch`) returns the same shape with per-record `rejected: [{index, errors[]}]` — one bad record never fails the rest of the batch.
  </Step>

  <Step title="List your issues">
    `GET /v1/issues` returns a cursor-paginated list of issues in your workspace, newest first.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.causeloop.ai/v1/issues \
        -H "Authorization: Bearer $CAUSELOOP_TOKEN"
      ```

      ```javascript JavaScript theme={null}
      const resp = await fetch('https://api.causeloop.ai/v1/issues', {
        headers: { 'Authorization': `Bearer ${accessToken}` },
      });
      const { data, page } = await resp.json();
      console.log(`${data.length} issues (${page.total} total)`);
      ```

      ```python Python theme={null}
      import requests

      resp = requests.get(
          'https://api.causeloop.ai/v1/issues',
          headers={'Authorization': f'Bearer {access_token}'},
      )
      resp.raise_for_status()
      body = resp.json()
      print(f"{len(body['data'])} issues ({body['page']['total']} total)")
      ```
    </CodeGroup>

    **Example response**

    ```json theme={null}
    {
      "data": [
        {
          "id": "iss_01abc123",
          "title": "Payment gateway timeout on checkout",
          "status": "open",
          "severity": "high",
          "pattern_id": "pat_01xyz789",
          "created_at": "2024-01-15T09:00:00Z"
        }
      ],
      "page": {
        "next_cursor": "eyJvZmZzZXQiOiAyNX0",
        "has_more": true,
        "total": 87
      }
    }
    ```

    To page through results, pass `cursor=<next_cursor>` on the next request. See [Pagination & Filtering](/api-reference/pagination-filtering) for full details.
  </Step>

  <Step title="Fetch a pattern">
    Patterns are clusters of related issues. Use the `pattern_id` from an issue (or list all patterns) to get details.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.causeloop.ai/v1/patterns/pat_01xyz789 \
        -H "Authorization: Bearer $CAUSELOOP_TOKEN"
      ```

      ```javascript JavaScript theme={null}
      const patternId = 'pat_01xyz789';
      const resp = await fetch(
        `https://api.causeloop.ai/v1/patterns/${patternId}`,
        { headers: { 'Authorization': `Bearer ${accessToken}` } },
      );
      const pattern = await resp.json();
      console.log(pattern.name, '— risk score:', pattern.risk_score);
      ```

      ```python Python theme={null}
      pattern_id = 'pat_01xyz789'
      resp = requests.get(
          f'https://api.causeloop.ai/v1/patterns/{pattern_id}',
          headers={'Authorization': f'Bearer {access_token}'},
      )
      resp.raise_for_status()
      p = resp.json()
      print(p['name'], '— risk score:', p['risk_score'])
      ```
    </CodeGroup>

    **Example response**

    ```json theme={null}
    {
      "id": "pat_01xyz789",
      "name": "Payment gateway timeout under load",
      "status": "active",
      "risk_score": 82,
      "issue_count": 14,
      "last_seen_at": "2024-01-15T09:00:00Z",
      "fingerprint": {
        "domain": "payments",
        "root_cause": "third_party_timeout"
      }
    }
    ```
  </Step>

  <Step title="Next steps">
    <CardGroup cols={2}>
      <Card title="Pagination & Filtering" icon="filter" href="/api-reference/pagination-filtering">
        Page through large result sets and apply filters and sorting.
      </Card>

      <Card title="Real-time events" icon="bolt" href="/api-reference/realtime-websocket">
        Subscribe to live issue and pattern updates via WebSocket.
      </Card>

      <Card title="Rate limits" icon="gauge" href="/api-reference/rate-limits">
        Understand limits and implement retry logic.
      </Card>

      <Card title="Error handling" icon="circle-exclamation" href="/api-reference/errors">
        Handle errors consistently across all endpoints.
      </Card>
    </CardGroup>
  </Step>
</Steps>
