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

# Streaming

> Export an entire result set in one NDJSON request

```
GET https://app.gethuntd.com/api/v1/public/people/stream
GET https://app.gethuntd.com/api/v1/public/companies/stream
```

One request returns the **entire** result set as **NDJSON** — one JSON object per line, each the
same shape as a `data[]` entry from the paginated endpoint.

The same [filters](/public-api/conventions#filtering) apply. Paging parameters (`page`, `limit`,
`skip_count`) are ignored.

## Paginated vs stream

|                                           | Paginated                   | Stream                            |
| ----------------------------------------- | --------------------------- | --------------------------------- |
| **Requests for a full backfill**          | \~86 at `limit=1000`        | 1                                 |
| **Rate-limit cost**                       | 1 per request               | **1 total**, however long it runs |
| **Knows the total up front**              | Yes — `totalPages`          | No                                |
| **Resumable after a dropped connection**  | Yes — re-request the page   | No — restart                      |
| **Can miss rows if data shifts mid-walk** | Yes, offset paging can skip | No                                |
| **Client requirement**                    | Any JSON client             | **Streaming line reader**         |

<Tip>
  Use the stream for backfills and full exports. Use pagination for interactive queries where you
  want a count, or where you only need the first page.
</Tip>

## Reading the stream

<Warning>
  You must use a **streaming line reader**. `fetch().json()`, `requests.get().json()` and
  PowerShell's `Invoke-RestMethod` all buffer the entire body into memory before returning, which
  defeats the point of streaming and can exhaust memory on large exports.
</Warning>

<CodeGroup>
  ```bash cURL theme={null}
  # Each line is a complete JSON object
  curl -N "https://app.gethuntd.com/api/v1/public/people/stream?is_user=true" \
    -H "X-API-Key: hntd_abc12345_yoursecretkey"
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    'https://app.gethuntd.com/api/v1/public/people/stream?is_user=true',
    { headers: { 'X-API-Key': process.env.HUNTD_API_KEY } }
  );

  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

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

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop(); // keep the trailing partial line

    for (const line of lines) {
      if (!line.trim()) continue;
      const row = JSON.parse(line);

      // A trailing object carrying `error` means the stream was truncated
      if (row.error) throw new Error(row.error.code);

      handle(row);
    }
  }
  ```

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

  with requests.get(
      'https://app.gethuntd.com/api/v1/public/people/stream',
      params={'is_user': 'true'},
      headers={'X-API-Key': os.environ['HUNTD_API_KEY']},
      stream=True,            # required — without this requests buffers the body
  ) as res:
      for line in res.iter_lines():
          if not line:
              continue
          row = json.loads(line)

          # A trailing object carrying `error` means the stream was truncated
          if 'error' in row:
              raise RuntimeError(row['error']['code'])

          handle(row)
  ```
</CodeGroup>

## Mid-stream errors

Once the response has started, the HTTP status line has already been sent — so a failure
**cannot** be signalled as a status code. Instead we emit a final line and close the connection:

```json theme={null}
{"success":false,"error":{"code":"STREAM_FAILED","message":"Stream failed before completion."}}
```

<Warning>
  Treat a trailing object containing `error` as a **truncated result, not data**. A stream that
  ends this way is incomplete — discard what you collected or mark it partial, then retry.
  Ignoring this line means silently importing half an export as if it were the whole thing.
</Warning>

Because the stream is not resumable, a retry restarts from the beginning. For very large exports,
consider narrowing with `discovered_from` / `discovered_to` and streaming one window at a time.
