# List badge progress and awards
Source: https://developers.scoutworks.app/api-reference/badges/list-badge-progress-and-awards
/api-reference/openapi.json get /badges
One record per young person per badge (and stage), from planned to awarded, for the group’s current members. A record belongs to the young person, so it is listed whichever group awarded it. Adult volunteers’ awards are not included.
# List events
Source: https://developers.scoutworks.app/api-reference/events/list-events
/api-reference/openapi.json get /events
Meetings, trips and camps, soonest first. Leader admin meetings and leaders-only events are not included. To keep a calendar in sync, bound the list with `from` and `to`: an event rescheduled while you page can otherwise move across a page boundary.
# List who is going to an event
Source: https://developers.scoutworks.app/api-reference/events/list-who-is-going-to-an-event
/api-reference/openapi.json get /events/{eventId}/attendees
This group’s young people invited to or answering the event, with their RSVP and, once the register is taken, their attendance.
# API reference
Source: https://developers.scoutworks.app/api-reference/introduction
Every endpoint, parameter and field, with a “Try it” button on each page.
This reference is built from the API’s own [OpenAPI file](https://api.scoutworks.app/api/v1/openapi.json), so it always matches what the API does.
## Try it
Each endpoint page has a **Try it** button. To use it:
1. Choose the **Demo** server.
2. Paste the [sandbox key](/guides/sandbox) into **Authorization**:
```text theme={null}
sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab
```
3. Choose **Send**.
The request goes straight from your browser to Scoutworks. It does not pass through anyone else’s server. You can use your own key on the **Live** server in the same way.
## Servers
| Server | Address |
| --------------------------------- | ---------------------------------------- |
| Live | `https://api.scoutworks.app/api/v1` |
| Demo (made-up data, reset weekly) | `https://api-demo.scoutworks.app/api/v1` |
## The basics
* Send your key as `Authorization: Bearer sw_live_…`. See [Keys and scopes](/guides/authentication).
* Each endpoint page names the scope it needs.
* Lists come back as `{ data, nextCursor }`. See [Pagination](/guides/pagination).
* Errors come back as `{ error: { code, message } }`. See [Errors](/guides/errors).
* Each key can make 120 requests a minute. See [Rate limits](/guides/rate-limits).
* Times are UTC in ISO 8601 (`2026-09-24T18:30:00.000Z`). Dates are `YYYY-MM-DD`.
# The group and key this API key belongs to
Source: https://developers.scoutworks.app/api-reference/keys/the-group-and-key-this-api-key-belongs-to
/api-reference/openapi.json get /me
Needs no scope. Use it to check a key works and to see which scopes it carries. Scopes removed from the API since the key was created are not listed.
# List members
Source: https://developers.scoutworks.app/api-reference/members/list-members
/api-reference/openapi.json get /members
Young people in the group, longest-standing records first. With `members.contacts:read` too, each member also carries `dateOfBirth` and `parents`.
# List the waiting list
Source: https://developers.scoutworks.app/api-reference/waiting-list/list-the-waiting-list
/api-reference/openapi.json get /waiting-list
Applications, oldest first, with their target section and queue position. With `waiting-list.contacts:read` too, each entry also carries `dateOfBirth` and `parent`.
# Changelog
Source: https://developers.scoutworks.app/changelog
Every change to the Scoutworks API. The newest is at the top.
Each entry names the version of the [OpenAPI file](https://api.scoutworks.app/api/v1/openapi.json) (`info.version`) that it describes.
`GET /me` tells you more on your first call.
* **`environment`** says which Scoutworks deployment answered: `production`, `staging`, `demo` or `development`. Demo holds only made-up data.
* **`links.docs`** points to these docs, and **`links.try`** suggests a first list request that your key’s scopes allow.
* The description of `expiresAt` now says that `null` means the key never expires.
* Browsers can now read the `RateLimit-*` and `Retry-After` headers, so a web page can show how many requests are left.
* The developer docs and the Try it playground can call the API from the browser.
* A public [sandbox key](/guides/sandbox) for the demo group.
The first public version of the Scoutworks API.
* Read endpoints: `GET /me`, `/members`, `/events`, `/events/{eventId}/attendees`, `/badges` and `/waiting-list`.
* Scopes for each area, plus `.contacts` scopes that add dates of birth and parent details.
* Cursor pagination, a fixed error shape and rate-limit headers.
# Keys and scopes
Source: https://developers.scoutworks.app/guides/authentication
How to send a key, what each scope unlocks, and when a key stops working.
## Sending your key
Send the key in the `Authorization` header of every request:
```text theme={null}
Authorization: Bearer sw_live_…
```
Every key starts with `sw_live_`. A key belongs to **one group** and reads only that group’s data. The API never takes a group ID from the URL.
Treat a key like a password. Keep it in your tool’s secret store (for example Script Properties in Google Sheets, or Credentials in n8n). Never put it in a cell, a shared document or a public repository.
## Scopes
A scope opens one area of your group’s data. When you create a key, choose only what the key needs.
| Scope | What it gives |
| ---------------------------- | -------------------------------------------------------------------- |
| `members:read` | Members: names, sections, patrols and status. |
| `members.contacts:read` | Adds dates of birth and parent names and emails to members. |
| `events:read` | Events, and who is going to each one. |
| `badges:read` | Badge progress and awards. |
| `waiting-list:read` | The waiting list: names, sections, status and position. |
| `waiting-list.contacts:read` | Adds dates of birth and parent names and emails to the waiting list. |
A `.contacts` scope adds fields to responses you can already read. It does nothing on its own, so you must also choose its area scope.
A field that your key may not see is **left out** of the response. It is not sent as `null`. So check whether a field exists before you read it.
The API never shares health information, bank details or free-text notes, whatever the scopes.
If a key calls an endpoint without the right scope, the API answers `403` with the code `insufficient_scope`. A group admin can add a scope to an existing key in **Settings → Integrations → API keys → Edit**. The key itself does not change.
## When a key stops working
A key stops working when:
* a group admin **revokes** it;
* it reaches its **expiry date** (a key with `expiresAt: null` never expires); or
* the admin who **created** it is no longer an active group admin of the group.
The last one catches people out. If the admin who made the key steps down, the key stops. It works again if they become a group admin again. To avoid this, a current admin can create a new key.
A key that stops working gets `401` with the code `invalid_api_key`.
## Check a key
`GET /me` tells you which group and scopes a key has. It needs no scope, so it is the best first call.
It also says when the key expires (`expiresAt`, or `null` if it never does) and which Scoutworks answered (`environment`: `production`, `staging`, `demo` or `development`). A key works only where it was created, so a live key gets `401` on the demo server, and the sandbox key gets `401` on the live one.
```bash theme={null}
curl https://api-demo.scoutworks.app/api/v1/me \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
# Errors
Source: https://developers.scoutworks.app/guides/errors
Every error has the same shape. Branch on the code, never on the message.
Every error comes back in this shape:
```json theme={null}
{
"error": {
"code": "insufficient_scope",
"message": "This API key does not have the \"events:read\" scope. A group admin can add it in Settings → Integrations → API keys."
}
}
```
**Branch on `code`.** The `message` is written for people. We may change its wording at any time.
## Codes
| Status | `code` | What it means | What to do |
| ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| 400 | `invalid_request` | A query parameter or cursor is wrong: a typo, an unknown parameter or a value out of range. | Check the parameter against the [API reference](/api-reference/introduction). |
| 401 | `invalid_api_key` | No key, or a key that is unknown, revoked or expired. It also happens when the admin who created the key is no longer a group admin. | Check the key. See [When a key stops working](/guides/authentication#when-a-key-stops-working). |
| 403 | `insufficient_scope` | The key does not have the scope this endpoint needs. | Add the scope to the key in Scoutworks. |
| 404 | `not_found` | No such record in your group. | Check the ID. IDs from another group are never found. |
| 429 | `rate_limited` | Too many requests. | Wait `retryAfter` seconds, then try again. See [Rate limits](/guides/rate-limits). |
| 500 | `internal_error` | Something went wrong on our side. | Try again later. If it keeps happening, send us the `errorRef`. |
## Extra fields
Some errors carry more:
* **`retryAfter`** on a `429`: the number of seconds to wait.
* **`errorRef`** on a `500`: a short reference. Quote it when you [contact us](https://scoutworks.app/contact), and we can find the exact request.
New codes may appear in future. Treat a code you do not know as a general failure.
# Pagination
Source: https://developers.scoutworks.app/guides/pagination
Every list comes back in pages. Follow nextCursor until it is null.
Every list endpoint returns one page at a time:
```json theme={null}
{
"data": [ ... ],
"nextCursor": "eyJ2IjoxLCJhdCI6IjIwMjYtMDkt…"
}
```
To get the next page, send the same request again with `?cursor=` set to `nextCursor`. Stop when `nextCursor` is `null`.
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/members?limit=200&cursor=PASTE_NEXT_CURSOR_HERE" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## Rules
* **`limit`** is 1 to 200. The default is 50. Use 200 to make fewer requests.
* **Pass the cursor back unchanged.** It is not a page number. Do not read it, build it or change it. Its format can change at any time.
* **Keep the same filters and limit.** A cursor belongs to one query. If you change `status` or `sectionId` between pages, the pages you get are not reliable. To change a filter, start again without a cursor.
* **A cursor you changed gets `400`.** The message tells you to start again without one.
* **Spell parameters exactly.** The API rejects a parameter it does not know with `400 invalid_request`, so a typo fails loudly instead of being ignored.
## A loop that reads every page
```python Python theme={null}
import requests
API_KEY = "sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
url = "https://api-demo.scoutworks.app/api/v1/members?limit=200"
members, cursor = [], None
while True:
params = {"cursor": cursor} if cursor else {}
page = requests.get(url, params=params, headers={"Authorization": f"Bearer {API_KEY}"})
page.raise_for_status()
body = page.json()
members += body["data"]
cursor = body["nextCursor"]
if cursor is None:
break
print(len(members), "members")
```
```javascript JavaScript theme={null}
const API_KEY = 'sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab';
const members = [];
let cursor = null;
do {
const url = new URL('https://api-demo.scoutworks.app/api/v1/members?limit=200');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${API_KEY}` } });
if (!res.ok) throw new Error((await res.json()).error.code);
const body = await res.json();
members.push(...body.data);
cursor = body.nextCursor;
} while (cursor);
console.log(members.length, 'members');
```
# Getting started
Source: https://developers.scoutworks.app/guides/quickstart
Make your first call with the sandbox key, then create a key for your own group.
## 1. Make your first call
Open a terminal and paste this. It uses the public [sandbox key](/guides/sandbox), so it works straight away.
```bash theme={null}
curl https://api-demo.scoutworks.app/api/v1/me \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
You get back the group, the key and where to go next:
```json theme={null}
{
"data": {
"group": { "id": "…", "name": "1st Demo Group" },
"key": {
"id": "30d82fa2-7e41-4d52-97ff-5be1a0b909a3",
"name": "Public docs sandbox (read-only)",
"last4": "b6ab",
"scopes": ["members:read", "members.contacts:read", "events:read", "badges:read", "waiting-list:read", "waiting-list.contacts:read"],
"expiresAt": null
},
"environment": "demo",
"links": {
"docs": "https://developers.scoutworks.app",
"try": "/api/v1/members?limit=5"
}
}
}
```
* **`expiresAt: null`** means the key never expires.
* **`environment`** says which Scoutworks answered. `demo` holds only made-up people. Your own key says `production`.
* **`links.try`** is a good second call. It is always a list that your key may read.
The answer arrives on one line. Add `| jq` to the end of the command to make it readable, if you have [jq](https://jqlang.org).
No terminal? Open [GET /me in the API reference](/api-reference/introduction), paste the sandbox key into the **Authorization** box, choose the **Demo** server and press **Send**.
## 2. Read a list
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/members?limit=5" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
Every list comes back as `{ "data": [...], "nextCursor": "..." }`. To get the next page, see [Pagination](/guides/pagination).
## 3. Create your own key
The API is in beta. A group admin turns it on in **Settings → Beta programme**, or [asks us to join](https://scoutworks.app/contact).
In Scoutworks, go to **Settings → Integrations → API keys**. Only a group admin can see this page.
Give the key a name that says what it is for, for example “Membership spreadsheet”. Choose only the [scopes](/guides/authentication#scopes) it needs. You can set an expiry date.
Scoutworks shows the key **once**. Copy it into your tool straight away. If you lose it, revoke it and create a new one.
In any recipe, replace the sandbox key with your key, and `api-demo.scoutworks.app` with `api.scoutworks.app`. The **Test connection** button on the key page checks that the key works.
## Next
Google Sheets, Excel, n8n, Zapier, Python and curl.
What each scope unlocks, and when a key stops working.
# Rate limits
Source: https://developers.scoutworks.app/guides/rate-limits
120 requests a minute for each key. Every response tells you how many you have left.
Each key can make **120 requests a minute**. Each IP address can also make 600 requests a minute, across all its keys.
For most groups this is far more than enough. A sheet that reads 1,000 members at 200 a page needs 5 requests.
## Headers
Every response carries three headers:
| Header | Meaning |
| --------------------- | --------------------------------------------- |
| `RateLimit-Limit` | The number of requests allowed in the window. |
| `RateLimit-Remaining` | The number left in the current window. |
| `RateLimit-Reset` | Seconds until the window resets. |
## When you go over
The API answers `429` with the code `rate_limited`. The response has a `Retry-After` header, and `retryAfter` in the body. Both give the number of seconds to wait.
```json theme={null}
{
"error": {
"code": "rate_limited",
"message": "This API key has made more than 120 requests in a minute. Please slow down and try again shortly."
},
"retryAfter": 23
}
```
Wait that long, then send the same request again. Do not retry at once in a loop. Every retry counts against the limit.
## Tips
* Use `limit=200` to read a list in fewer requests.
* Refresh a spreadsheet once a day or once an hour, not every minute.
* One key per tool makes it easy to see which tool is busy, and to revoke only that one.
# Sandbox key
Source: https://developers.scoutworks.app/guides/sandbox
A public key for a demo group full of made-up Scouts. Use it to try every recipe before you create your own key.
Use this key to try the API. It reads **1st Demo Group**, which holds only made-up people, so it is safe to paste anywhere.
```text Sandbox key theme={null}
sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab
```
It works only on the demo server:
```text Demo server theme={null}
https://api-demo.scoutworks.app/api/v1
```
Try it now:
```bash theme={null}
curl https://api-demo.scoutworks.app/api/v1/me \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## What the sandbox key can do
* It has **every scope**, including contact details, so you see the full shape of every response.
* It is **read-only**, like every API key.
* It has the normal limit of 120 requests a minute, counted for **your** address only. Other people using the sandbox do not use up your limit.
## What changes each week
The demo group is rebuilt every **Monday at 04:00 UTC** (05:00 in British Summer Time). For a few minutes during the rebuild, calls may fail. Names, events and IDs change, so do not save an ID from the demo and expect it next week. The key itself does not change.
## Moving to your own group
When a recipe works, make two changes:
1. Replace the sandbox key with [your own key](/guides/quickstart#create-your-own-key).
2. Replace `api-demo.scoutworks.app` with `api.scoutworks.app`.
Your own key reads real children’s data. Keep it secret. Never paste it into a shared document, a public page or a chat.
# Versioning and changes
Source: https://developers.scoutworks.app/guides/versioning
What we try to keep stable, and the risk you accept by building on a beta API.
**The API can change without notice.** We aim to keep it stable, and we will try to warn you before a change that could break your tool. We cannot promise that. Build your tool so that a change fails loudly and you notice it.
## What we aim for
We aim to change v1 only by **adding** things:
* new endpoints;
* new optional query parameters;
* new fields in a response;
* new values in a list of allowed values (an enum).
None of these should break a tool that follows the rules below.
## How to build a tool that survives change
* **Ignore fields you do not know.** A response can gain fields at any time.
* **Treat an unknown enum value as “other”.** For example, a new event `type` should not crash your tool.
* **Check for a field before you read it.** Fields that a key’s scopes do not allow are left out.
* **Check the status code of every response.** Fail loudly on an error. Do not quietly write an empty sheet.
* **Branch on the error `code`, never on the `message`.**
## Where changes are listed
Every change to the API is in the [changelog](/changelog). The version of the API document (`info.version` in the [OpenAPI file](https://api.scoutworks.app/api/v1/openapi.json)) goes up with each change.
## Beta
The API is in beta. While it is in beta, changes may come more often. Tell us what you build and what breaks: [contact us](https://scoutworks.app/contact).
# Your group’s data, where you need it
Source: https://developers.scoutworks.app/index
Get your members, events, badges and waiting list into a spreadsheet, an automation or your own code. Every recipe works in five minutes with our sandbox key.
The Scoutworks API lets your group read its own data from other tools. You do not need to be a developer. Most groups use it to keep a spreadsheet up to date without copying and pasting.
Pick what you want to do. Each recipe works straight away with the [sandbox key](/guides/sandbox), which reads a demo group full of made-up Scouts. When it works, swap in your own key.
## Spreadsheets
A sheet that refreshes itself every morning. Copy one script, press Run.
Pull your events into Excel with Power Query. No code at all.
## Automations
Send new waiting-list entries to Slack, email or anywhere else.
Connect Scoutworks to 7,000 apps with Webhooks by Zapier.
## Code
Read every page of a list in twelve lines.
Try any endpoint from your terminal.
## What next
Create a key and make your first call.
Every endpoint, with a “Try it” button.
Point Claude, ChatGPT or Cursor at these docs.
The API reads data. It cannot change anything in Scoutworks. It never shares health information, bank details or free-text notes.
# curl
Source: https://developers.scoutworks.app/recipes/curl
Try every endpoint from a terminal. Each command works as it is, with the sandbox key.
Every command below uses the public [sandbox key](/guides/sandbox) and the demo server, so you can paste it as it is. Add `| jq` to the end to make the output easier to read, if you have [jq](https://jqlang.org).
## Your key
```bash theme={null}
curl https://api-demo.scoutworks.app/api/v1/me \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## Members
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/members?limit=20" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
Members who have left:
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/members?status=archived" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## Events
Published events from 1 September 2026:
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/events?from=2026-09-01T00:00:00Z&limit=20" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
Who is going to one event. Replace `EVENT_ID` with an `id` from the list above:
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/events/EVENT_ID/attendees" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## Badges
Badges that have been awarded:
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/badges?status=awarded&limit=20" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## Waiting list
People who have been offered a place:
```bash theme={null}
curl "https://api-demo.scoutworks.app/api/v1/waiting-list?status=offered" \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## See the rate-limit headers
`-i` prints the response headers too:
```bash theme={null}
curl -i https://api-demo.scoutworks.app/api/v1/me \
-H "Authorization: Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab"
```
## The API description
The OpenAPI file needs no key:
```bash theme={null}
curl https://api-demo.scoutworks.app/api/v1/openapi.json
```
## Switch to your group
Change `api-demo.scoutworks.app` to `api.scoutworks.app`, and replace the sandbox key with your own.
# Events in Excel
Source: https://developers.scoutworks.app/recipes/excel
Pull your events into Excel with Power Query. Refresh with one click. No code to write.
This recipe loads your events into an Excel table with **Power Query**. It works in Excel for Microsoft 365 and Excel 2016 or later on Windows, and in Excel for Microsoft 365 on Mac.
## Quick version: the first 200 events
Choose **Data → Get Data → From Other Sources → From Web**. Choose **Advanced**.
In **URL parts**, enter:
```text theme={null}
https://api-demo.scoutworks.app/api/v1/events?limit=200
```
In **HTTP request header parameters**, type `Authorization` in the first box. In the second box, type `Bearer ` followed by the [sandbox key](/guides/sandbox):
```text theme={null}
Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab
```
Choose **OK**.
If Excel asks how to connect, choose **Anonymous**, then **Connect**. The key in the header does the signing in.
In the Power Query editor, select **data: List**, then **To Table**, then the expand button in the column header. Choose the columns you want. Choose **Close & Load**.
To refresh, choose **Data → Refresh All**.
## Full version: every event, every page
A list comes back 200 rows at a time. To load all of them, paste this into a blank query. Choose **Data → Get Data → From Other Sources → Blank Query**, then **Advanced Editor**.
```text Power Query (M) theme={null}
let
ApiKey = "sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab",
GetPage = (cursor as nullable text) =>
Json.Document(
Web.Contents(
"https://api-demo.scoutworks.app/api/v1/events",
[
Query = if cursor = null then [limit = "200"] else [limit = "200", cursor = cursor],
Headers = [Authorization = "Bearer " & ApiKey]
]
)
),
Pages = List.Generate(
() => GetPage(null),
each _ <> null,
each if [nextCursor] = null then null else GetPage([nextCursor]),
each [data]
),
Events = List.Combine(Pages),
Table = Table.FromRecords(
Events,
{"title", "type", "startsAt", "endsAt", "location", "hosted", "attendeeCount"},
MissingField.UseNull
)
in
Table
```
Choose **Done**, then **Close & Load**.
## Switch to your group
Change `api-demo.scoutworks.app` to `api.scoutworks.app`, and replace the sandbox key with your own. Your key needs the `events:read` scope.
Excel saves the key inside the workbook. Anyone with a copy of the file can read it. Keep the workbook private, and create a key with only the scope it needs. If the file is shared by mistake, revoke the key in Scoutworks.
## Other lists
Change the address in `Web.Contents` and the column names in `Table.FromRecords`:
| List | Address | Scope |
| ------------ | ----------------------------------------------------- | ------------------- |
| Members | `https://api-demo.scoutworks.app/api/v1/members` | `members:read` |
| Badges | `https://api-demo.scoutworks.app/api/v1/badges` | `badges:read` |
| Waiting list | `https://api-demo.scoutworks.app/api/v1/waiting-list` | `waiting-list:read` |
The [API reference](/api-reference/introduction) lists every field.
# Your members in Google Sheets
Source: https://developers.scoutworks.app/recipes/google-sheets
A sheet that fills itself with your members and refreshes every morning. One script, no add-ons.
This recipe adds a **Members** tab to a Google Sheet and fills it from Scoutworks. A timer refreshes it every morning. It takes about five minutes.
## 1. Add the script
In your Google Sheet, choose **Extensions → Apps Script**.
Delete what is there and paste the script below. Choose **Save**.
```javascript Code.gs theme={null}
// Scoutworks → Google Sheets. https://developers.scoutworks.app/recipes/google-sheets
function importMembers() {
const apiKey = PropertiesService.getScriptProperties().getProperty('SCOUTWORKS_API_KEY');
if (!apiKey) throw new Error('Add SCOUTWORKS_API_KEY in Project Settings → Script Properties.');
const rows = [['First name', 'Last name', 'Sections', 'Patrol', 'Joined']];
let cursor = null;
do {
let url = 'https://api-demo.scoutworks.app/api/v1/members?limit=200';
if (cursor) url += '&cursor=' + encodeURIComponent(cursor);
const body = fetchScoutworks(url, apiKey);
body.data.forEach(function (m) {
rows.push([
safe(m.firstName),
safe(m.lastName),
safe(m.sections.map(function (s) { return s.name; }).join(', ')),
safe(m.patrol ? m.patrol.name : ''),
m.joinedAt.slice(0, 10),
]);
});
cursor = body.nextCursor;
} while (cursor);
const book = SpreadsheetApp.getActive();
const sheet = book.getSheetByName('Members') || book.insertSheet('Members');
sheet.clearContents();
sheet.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
}
// Calls the API. Waits and tries again if the rate limit is reached.
function fetchScoutworks(url, apiKey) {
for (let attempt = 0; attempt < 3; attempt++) {
const res = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + apiKey },
muteHttpExceptions: true,
});
const body = JSON.parse(res.getContentText());
if (res.getResponseCode() === 200) return body;
if (res.getResponseCode() === 429) {
Utilities.sleep((body.retryAfter || 30) * 1000);
continue;
}
throw new Error(body.error.code + ': ' + body.error.message);
}
throw new Error('Scoutworks is still busy. Try again in a minute.');
}
// Stops a name that starts with = + - or @ from running as a formula.
function safe(value) {
const text = String(value);
return /^\s*[=+\-@]/.test(text) ? "'" + text : text;
}
```
Keep the `safe()` function. Names on the waiting list come from a public form, so anyone can type one. Without `safe()`, a name like `=IMPORTXML(…)` would run as a formula in your sheet.
## 2. Add your key
In the script editor, choose **Project Settings** (the cog), then **Script Properties → Add script property**.
Property: `SCOUTWORKS_API_KEY`. Value: the [sandbox key](/guides/sandbox) to try it, or your own key. Choose **Save script properties**.
The key lives in Script Properties, not in a cell. People you share the sheet with cannot see it.
## 3. Run it
Choose **importMembers** in the toolbar and press **Run**. Google asks for permission the first time: it needs to reach an external service and to edit this sheet. Then open the **Members** tab.
## 4. Refresh it every morning
In the script editor, choose **Triggers** (the clock), then **Add Trigger**.
Function: `importMembers`. Event source: **Time-driven**. Type: **Day timer**. Time: **6am to 7am**. Choose **Save**.
If a refresh fails, Google emails you the error. The trigger’s **Failure notification settings** choose how often.
## 5. Switch to your group
In the script, change `api-demo.scoutworks.app` to `api.scoutworks.app`. In Script Properties, replace the sandbox key with your own. Your key needs the `members:read` scope.
## Other lists
Change the URL and the columns to read another list:
| List | URL | Scope |
| ------------ | ------------------------------------------------------------------------ | ------------------- |
| Events | `https://api-demo.scoutworks.app/api/v1/events?limit=200` | `events:read` |
| Badges | `https://api-demo.scoutworks.app/api/v1/badges?limit=200&status=awarded` | `badges:read` |
| Waiting list | `https://api-demo.scoutworks.app/api/v1/waiting-list?limit=200` | `waiting-list:read` |
The [API reference](/api-reference/introduction) lists every field.
# n8n
Source: https://developers.scoutworks.app/recipes/n8n
Use the n8n HTTP Request node to read any Scoutworks list, with pagination and a stored credential.
This recipe reads the waiting list every hour. You can send the result to Slack, email, a sheet or any other n8n node.
## 1. Store the key as a credential
In n8n, open **Credentials → Add credential** and choose **Header Auth**.
Name: `Authorization`. Value: `Bearer ` followed by the key, for example:
```text theme={null}
Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab
```
Call the credential **Scoutworks**. Choose **Save**.
## 2. Build the workflow
Set it to run **every hour**.
* **Method:** GET
* **URL:** `https://api-demo.scoutworks.app/api/v1/waiting-list?limit=200`
* **Authentication:** Generic Credential Type → **Header Auth** → **Scoutworks**
Under **Options → Add option → Pagination**:
* **Pagination mode:** Update a Parameter in Each Request
* **Type:** Query. **Name:** `cursor`. **Value:** `{{ $response.body.nextCursor }}`
* **Pagination Complete When:** Other. **Complete Expression:** `{{ $response.body.nextCursor === null }}`
Add a **Split Out** node. **Field to split out:** `data`. Each waiting-list entry is now one item.
Connect the output to any node you like.
## 3. Switch to your group
In the HTTP Request node, change `api-demo.scoutworks.app` to `api.scoutworks.app`. In the credential, replace the sandbox key with your own. Your key needs the `waiting-list:read` scope.
## Tips
* **Only new entries.** Keep the IDs you have already seen in a data table, and filter them out with a **Remove Duplicates** node set to “Remove items processed in previous executions”.
* **Writing to Google Sheets.** In the Google Sheets node, set **Cell Format** to **Use Format From n8n** (RAW), so that a name starting with `=` is stored as text, not run as a formula.
* **Errors.** Turn on **Options → Response → Include Response Headers and Status**, and branch on `error.code` if the status is not 200. See [Errors](/guides/errors).
# Python
Source: https://developers.scoutworks.app/recipes/python
Read every page of a list with requests, handle the rate limit, and save a CSV.
This script reads every member and saves them to `members.csv`. It needs Python 3.9 or later and the `requests` package (`pip install requests`).
```python members_to_csv.py theme={null}
import csv
import os
import time
import requests
# The sandbox key reads a demo group. Set SCOUTWORKS_API_KEY to use your own.
API_KEY = os.environ.get(
"SCOUTWORKS_API_KEY",
"sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab",
)
session = requests.Session()
session.headers["Authorization"] = f"Bearer {API_KEY}"
def get(url, params):
"""GET one page. Waits and tries again if the rate limit is reached."""
while True:
res = session.get(url, params=params, timeout=30)
if res.status_code == 429:
time.sleep(res.json().get("retryAfter", 30))
continue
if not res.ok:
error = res.json()["error"]
raise SystemExit(f"{res.status_code} {error['code']}: {error['message']}")
return res.json()
def read_all(url):
"""Every item in a list, following nextCursor to the last page."""
items, params = [], {"limit": 200}
while True:
page = get(url, params)
items += page["data"]
if page["nextCursor"] is None:
return items
params["cursor"] = page["nextCursor"]
def safe(value):
"""Stops a spreadsheet running a cell that starts with = + - or @."""
text = str(value)
return "'" + text if text.lstrip().startswith(("=", "+", "-", "@")) else text
members = read_all("https://api-demo.scoutworks.app/api/v1/members")
with open("members.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["First name", "Last name", "Sections", "Patrol", "Joined"])
for m in members:
writer.writerow([
safe(m["firstName"]),
safe(m["lastName"]),
safe(", ".join(s["name"] for s in m["sections"])),
safe(m["patrol"]["name"] if m["patrol"] else ""),
m["joinedAt"][:10],
])
print(f"Saved {len(members)} members to members.csv")
```
Run it:
```bash theme={null}
python members_to_csv.py
```
## Switch to your group
Change `api-demo.scoutworks.app` to `api.scoutworks.app`, and set your key in the environment instead of the code:
```bash theme={null}
export SCOUTWORKS_API_KEY="sw_live_your_own_key"
python members_to_csv.py
```
Your key needs the `members:read` scope.
## Notes
* A field that the key’s scopes do not allow is **left out** of the response. Use `m.get("dateOfBirth")`, not `m["dateOfBirth"]`, for the `.contacts` fields.
* The `safe()` function matters if anyone opens the CSV in Excel or Sheets. See [Google Sheets](/recipes/google-sheets) for why.
* For other lists, change the URL. The [API reference](/api-reference/introduction) lists every endpoint and field.
# Zapier
Source: https://developers.scoutworks.app/recipes/zapier
Start a Zap when someone joins the waiting list, using Webhooks by Zapier. No custom app needed.
This recipe starts a Zap each time a new application arrives on your waiting list. It uses **Webhooks by Zapier**, which needs a paid Zapier plan.
## 1. Set up the trigger
Create a Zap. For the trigger, choose **Webhooks by Zapier**, then **Retrieve Poll**.
* **URL:** `https://api-demo.scoutworks.app/api/v1/waiting-list?status=received&limit=200`
* **Key:** `data` (the list inside the response)
* **Deduplication Key:** `id` (so each entry starts the Zap once)
* **Headers:** `Authorization` → `Bearer ` followed by the key, for example:
```text theme={null}
Bearer sw_live_473adcc6c7236916021f0f24559db36576a53bab071d0f0c08a025adb510b6ab
```
Choose **Test trigger**. Zapier shows entries from the demo group.
Zapier checks the list every few minutes, depending on your plan. It starts the Zap once for each entry it has not seen before.
## 2. Add an action
Add any action. For example, **Slack → Send Channel Message**, with the message:
```text theme={null}
New waiting-list entry: [First Name] [Last Name] for [Target Section Name]
```
Each `[…]` is a field you insert from the list Zapier shows. The [API reference](/api-reference/introduction) describes each one.
## 3. Switch to your group
In the trigger, change `api-demo.scoutworks.app` to `api.scoutworks.app`, and replace the sandbox key with your own. Your key needs the `waiting-list:read` scope. Add `waiting-list.contacts:read` if the Zap needs parent contact details.
## Limits
* **Retrieve Poll reads one page.** The list comes oldest first, so the recipe asks only for applications still at `received`, the first stage. Once a leader moves an application on, it leaves that list. If more than 200 applications sit at `received`, Zapier cannot see the newest ones. To copy a whole list, use [n8n](/recipes/n8n), [Google Sheets](/recipes/google-sheets) or [Python](/recipes/python).
* **Google Sheets actions.** If a Zap writes names into a sheet, a name that starts with `=` can run as a formula. Add a **Formatter** step that puts `'` in front of values that start with `=`, `+`, `-` or `@`.
* A ready-made Scoutworks app for Zapier and n8n is on our list. Until then, this recipe does the same job.
# Use these docs with AI
Source: https://developers.scoutworks.app/tools/ai-assistants
Give Claude, ChatGPT, Cursor or any other assistant the whole API in one step, so it writes working code first time.
An AI assistant writes much better code when it can read the real docs. There are three ways to give it them.
## 1. Copy a page
Every page has a menu at the top right. Choose **Copy page** to copy it as Markdown, or **Open in ChatGPT** or **Open in Claude** to start a chat with the page loaded.
## 2. Give it the whole API: llms.txt
These two files hold the docs as plain text, ready for an AI to read:
| File | What it holds |
| ------------------------------------------------------------------- | ---------------------------- |
| [`/llms.txt`](https://developers.scoutworks.app/llms.txt) | A short index of every page. |
| [`/llms-full.txt`](https://developers.scoutworks.app/llms-full.txt) | Every page in one file. |
Paste the address into a chat, or save the file and attach it. For example:
```text theme={null}
Read https://developers.scoutworks.app/llms-full.txt, then write a Google Apps Script
that copies our Scoutworks waiting list into a sheet called "Waiting list".
```
## 3. Connect it as an MCP server
These docs are also an **MCP server**. An assistant that supports MCP can search them as it works, so it always has the current version.
```text MCP server address theme={null}
https://developers.scoutworks.app/mcp
```
* **Claude Code:** `claude mcp add --transport http scoutworks-docs https://developers.scoutworks.app/mcp`
* **Cursor or VS Code:** open the page menu and choose **Connect to Cursor** or **Connect to VS Code**.
* **Claude.ai or ChatGPT:** add the address as a custom connector in settings.
This server searches the **documentation**. It cannot read your group’s data.
Never paste your own API key into an AI chat. The key reads real children’s data. Use the [sandbox key](/guides/sandbox) while you build, and put your real key into the finished tool yourself.
# Postman, Bruno and Insomnia
Source: https://developers.scoutworks.app/tools/postman-and-bruno
Load every endpoint into your API client in one step.
Every API client can read our OpenAPI file. It describes every endpoint, parameter and response, and it needs no key:
```text theme={null}
https://api.scoutworks.app/api/v1/openapi.json
```
## Postman
Open our public Postman workspace, then fork the **Scoutworks API** collection. It comes with a **Demo sandbox** environment that already holds the sandbox key, so the first request works straight away.
Fork the collection and the Demo sandbox environment.
To use your own group, fork the environment, change `baseUrl` to `https://api.scoutworks.app/api/v1` and set `apiKey` to your key. Store the key as a **secret** variable, so Postman hides it.
You can also import the file yourself: **Import → Link**, then paste the OpenAPI address.
## Bruno
Choose **Import Collection → OpenAPI V3 Spec**, and paste the OpenAPI address.
Open the collection settings. Under **Auth**, choose **Bearer Token** and paste the [sandbox key](/guides/sandbox) or your own.
Bruno keeps collections as files in a folder. Do not commit a real key to git: put it in a Bruno environment marked **secret**.
## Insomnia
Choose **Import → From URL**, and paste the OpenAPI address. Then set a **Bearer Token** on the collection.
## Other tools
Most tools that speak OpenAPI can use the same file. For example, you can generate a typed client with [OpenAPI Generator](https://openapi-generator.tech) or [openapi-typescript](https://openapi-ts.dev). The file is updated with every change to the API. See the [changelog](/changelog).