> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://documents.pyramid-ai.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://documents.pyramid-ai.com/_mcp/server.

# Streaming & Async Patterns

## Why streaming?

Some API calls return immediately (creating a project takes milliseconds). Others — like asking the AI a complex question — can take several seconds. Streaming lets you show results **as they're generated** instead of waiting for the complete response.

## Chat streaming

The Chat endpoint (`POST /api/v2/chat`) supports **Server-Sent Events (SSE)** for real-time streaming. Set `stream: true` in your request:

```bash
curl -X POST https://api.pyramid-ai.com/api/v2/chat \
  -H "Authorization: Bearer pai_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "...",
    "messages": [{"role": "user", "content": "What are the safety requirements?"}],
    "stream": true
  }'
```

The response arrives as a stream of events:

```
data: {"type": "text", "content": "Based on"}
data: {"type": "text", "content": " the project"}
data: {"type": "text", "content": " safety plan,"}
data: {"type": "text", "content": " all workers"}
...
data: {"type": "done", "sources": [...], "usage": {...}}
```

Each event contains a small piece of the response. Your UI can display each piece as it arrives — creating a typing effect.

### When to use streaming

| Use case                                            | Streaming?               |
| --------------------------------------------------- | ------------------------ |
| Chatbot UI showing responses in real time           | Yes                      |
| Server-to-server integration logging full responses | No (use `stream: false`) |
| Mobile app showing AI typing indicator              | Yes                      |
| Automated pipeline processing answers               | No                       |

### Handling SSE in JavaScript

```javascript
const response = await fetch('/api/v2/chat', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer pai_live_YOUR_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    project_id: '...',
    messages: [{ role: 'user', content: 'What are the safety requirements?' }],
    stream: true,
  }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value);
  // Parse SSE events and update UI
  console.log(text);
}
```

## Asynchronous operations

Some operations run in the background because they take too long for a synchronous response:

| Operation           | Pattern         | How to check status                       |
| ------------------- | --------------- | ----------------------------------------- |
| Document processing | Fire-and-forget | `GET /documents/{id}` — poll `status`     |
| Batch queries       | Background job  | `GET /agent/batches/{id}` — poll `status` |
| Compliance runs     | Background job  | `GET /check/runs/{id}` — poll `status`    |

### Polling pattern

For background operations, poll the status endpoint until the job completes:

```python
import time
import requests

def wait_for_completion(url, headers, max_wait=300):
    """Poll until status is terminal (completed/failed)."""
    start = time.time()

    while time.time() - start < max_wait:
        response = requests.get(url, headers=headers)
        data = response.json()["data"]

        if data["status"] in ("completed", "failed", "partial"):
            return data

        time.sleep(5)  # Check every 5 seconds

    raise TimeoutError("Operation did not complete in time")
```

### Recommended polling intervals

| Operation           | Poll interval    | Typical duration                            |
| ------------------- | ---------------- | ------------------------------------------- |
| Document processing | Every 5 seconds  | 10–60 seconds                               |
| Batch queries       | Every 10 seconds | 1–30 minutes (depends on batch size)        |
| Compliance runs     | Every 10 seconds | 1–10 minutes (depends on requirement count) |

> **Note**
>
> Don't poll more frequently than every 2 seconds. Excessive polling wastes your rate limit quota without meaningfully improving responsiveness.

## Synchronous vs asynchronous at a glance

| Endpoint                        | Sync/Async                      | Response time      |
| ------------------------------- | ------------------------------- | ------------------ |
| `POST /projects`                | Sync                            | Instant            |
| `POST /documents` (reserve)     | Sync                            | Instant            |
| `POST /documents/process`       | Async (triggers background job) | Seconds to minutes |
| `POST /agent/query`             | Sync                            | 3–15 seconds       |
| `POST /chat`                    | Sync (streamable)               | 3–30 seconds       |
| `POST /agent/batches`           | Async                           | Minutes to hours   |
| `POST /check/verify`            | Sync                            | 5–30 seconds       |
| `POST /check/runs` (create)     | Sync                            | Instant            |
| `POST /check/runs/{id}/trigger` | Async                           | Minutes            |