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

# JavaScript and Node.js

> Read every page of a list with fetch, handle the rate limit, and save a CSV. No packages to install.

This script reads the waiting list and saves it to `waiting-list.csv`. It needs Node.js 18 or later, which has `fetch` built in. There is nothing to install.

```javascript waiting-list.mjs theme={null}
import { writeFile } from 'node:fs/promises';

const BASE = 'https://api-demo.scoutworks.app/api/v1';
// The sandbox key reads a demo group. Set SCOUTWORKS_API_KEY to use your own.
const API_KEY =
  process.env.SCOUTWORKS_API_KEY ??
  'sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab';

/** GET one page. Waits and tries again if the rate limit is reached. */
async function get(url) {
  for (;;) {
    const res = await fetch(url, { headers: { Authorization: `Bearer ${API_KEY}` } });
    const body = await res.json();
    if (res.status === 429) {
      await new Promise((resolve) => setTimeout(resolve, (body.retryAfter ?? 30) * 1000));
      continue;
    }
    if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
    return body;
  }
}

/** Every item in a list, following nextCursor to the last page. */
async function readAll(path, filters = {}) {
  const url = new URL(BASE + path);
  url.search = new URLSearchParams({ limit: '200', ...filters }).toString();
  const items = [];
  for (;;) {
    const page = await get(url);
    items.push(...page.data);
    if (page.nextCursor === null) return items;
    url.searchParams.set('cursor', page.nextCursor);
  }
}

/** One CSV cell. Stops a spreadsheet running a cell that starts with = + - or @. */
function cell(value) {
  let text = String(value ?? '');
  if (/^\s*[=+\-@]/.test(text)) text = `'${text}`;
  return /[",\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
}

const entries = await readAll('/waiting-list', { status: 'received' });

const rows = [
  ['First name', 'Last name', 'Section', 'Position', 'Applied'],
  ...entries.map((w) => [
    w.firstName,
    w.lastName,
    w.targetSection?.name,
    w.position,
    w.appliedAt.slice(0, 10),
  ]),
];
await writeFile('waiting-list.csv', rows.map((r) => r.map(cell).join(',')).join('\r\n') + '\r\n');
console.log(`Saved ${entries.length} applications to waiting-list.csv`);
```

Run it:

```bash theme={null}
node waiting-list.mjs
```

## Switch to your group

Change `api-demo.scoutworks.app` to `api.scoutworks.app`, and set your key in the environment instead of the code. `read -s` asks for the key without showing it, so it stays out of your shell history:

```bash theme={null}
read -rs -p "Scoutworks API key: " SCOUTWORKS_API_KEY && export SCOUTWORKS_API_KEY
node waiting-list.mjs
```

Your key needs the `waiting-list:read` scope.

## TypeScript

The [OpenAPI file](https://api.scoutworks.app/api/v1/openapi.json) describes every response. Generate types from it with [openapi-typescript](https://openapi-ts.dev):

```bash theme={null}
npx openapi-typescript https://api.scoutworks.app/api/v1/openapi.json -o scoutworks.d.ts
```

Then type a response like this:

```typescript theme={null}
import type { components } from './scoutworks';

type Member = components['schemas']['V1Member'];
```

## Notes

* **Only in a server or a script.** Do not call the API from a web page that other people open. The key would be in the page, and anyone could read it.
* **Fields that may be missing.** A field that the key’s scopes do not allow is left out. Use `w.parent?.email`, not `w.parent.email`, for the `.contacts` fields.
* **Other lists.** Change `/waiting-list` and the filters. The [API reference](/api-reference/introduction) lists every endpoint and field.
