Crew has a small read-only API for connecting your own tools: a staff page, a spreadsheet, a bot. It never changes anything, and it never returns contact details, notes, disciplinary records or leave reasons.
Warning: The API sends your organization's data to systems you control. Your organization is responsible for keeping keys safe and for what those systems do with the data.
Creating an API key
Super Users open Settings → Integrations → API keys and create a key with:
- a name, so you know what it's for (for example Website staff page);
- one or more scopes: what the key may read.
| Scope | Gives access to |
|---|---|
employees:read |
/v1/employees (members, no contact details) |
departments:read |
/v1/departments |
requests:read |
/v1/requests |
leave:read |
/v1/leave (who is away, no reasons) |
/v1/organization works with any key.
The key looks like crew_<server ID>_<secret> and is shown once. Store it somewhere safe, such as
your server's environment variables, never in a website's public code. The list shows each key's
last four characters and when it was last used. Revoke a key at any time; it stops working
immediately. An organization can have up to 10 active keys.
Making requests
Send the key as a bearer token:
curl https://crew.noyralabs.com/v1/employees \
-H "Authorization: Bearer crew_123456789012345678_yourSecretHere"
- All endpoints are
GETand return JSON. - The API is for servers, not browsers: there's no CORS, so a web page can't call it directly. Call it from your backend and pass on what you need.
- Each key can make 240 requests a minute. Above that you get
429withRetry-After: 60. - Responses aren't cached (
Cache-Control: no-store). Cache on your side if you call often.
Endpoints
GET /v1/organization
{ "guildId": "123456789012345678", "name": "Lumen Collective", "memberCount": 48 }
memberCount counts everyone who isn't terminated.
GET /v1/employees
Scope employees:read. Every member, as { "data": [ … ] }:
{
"data": [
{
"id": "1001",
"name": "Maya Chen",
"title": "Team Lead",
"departmentId": "3f6b2c1e-8a4d-4c2e-9b1f-2d7e5a9c0b41",
"unitId": null,
"managerId": "1000",
"employmentStatus": "active",
"workerType": "internal",
"hireDate": "2026-01-15",
"discordId": "234567890123456789"
}
]
}
employmentStatus is active, on_leave or terminated; workerType is internal, external or
contractor. Terminated members are included, so filter them out if you only want current staff.
GET /v1/employees/{id}
Scope employees:read. One member, same fields, or 404.
GET /v1/departments
Scope departments:read. Departments with their units, sorted by name:
{
"data": [
{
"id": "3f6b2c1e-8a4d-4c2e-9b1f-2d7e5a9c0b41",
"name": "Support",
"units": [{ "id": "9c1d7e2a-5b3f-4e8a-a6c2-7f0e1d4b8a53", "name": "Weekend team" }]
}
]
}
GET /v1/requests
Scope requests:read. The 500 most recent requests, newest first:
{
"data": [
{
"id": "b2e4f6a8-1c3d-4e5f-8a9b-0c1d2e3f4a5b",
"number": "REQCREW000042",
"type": "leave",
"status": "approved",
"employeeId": "1001",
"submittedAt": "2026-10-20T09:12:00.000Z",
"decidedAt": "2026-10-20T15:40:00.000Z"
}
]
}
No request details or reasons are included. Decided requests disappear after 7 days, like in the app.
GET /v1/leave
Scope leave:read. Approved leave that ended in the last 30 days, is happening now or is coming up:
{
"data": [
{
"id": "e7d9c1b3-2a4f-4b6d-9e8c-1f3a5b7d9c2e",
"employeeId": "1001",
"startDate": "2026-11-02",
"endDate": "2026-11-06"
}
]
}
Errors
Errors are JSON with an error message:
| Status | Meaning |
|---|---|
401 |
The key is missing, malformed or revoked. |
403 |
The key lacks the scope for this endpoint, or the organization is archived. |
404 |
No such endpoint or record. |
429 |
Too many requests. Wait a minute. |
500 |
Something went wrong on Crew's side. Try again later. |
Prefer pushing over polling? See Webhooks.