curl --request GET \
--url https://control.ergoly.ai/api/v1/channels \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://control.ergoly.ai/api/v1/channels', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://control.ergoly.ai/api/v1/channels"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"data": [
{
"id": "C0A1B2C3D4E",
"name": "acme-support",
"isPrivate": false,
"isArchived": false,
"memberWorkerIds": [
"8f1c2d3e-9a4b-4c5d-8e6f-1a2b3c4d5e6f"
],
"seenAt": "2026-02-28T16:44:12.000Z"
}
],
"page": {
"limit": 50,
"hasMore": true,
"nextCursor": null
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}List Slack channels
Every public room in the workspace — including the ones none of your Workers is in, which is what makes it a directory rather than a roster — plus the private rooms a Worker has been invited into. ?query= ranks by name with an exact match first, so “#ops” never resolves to “#ops-archive”. memberWorkerIds is empty for a room no bot is in; that is the normal state of a public channel and the reason inviteWorkerBot exists. seenAt says when Slack last confirmed the room: the directory is re-read from Slack when it goes stale, so the answer is current rather than live. A workspace that has never been read and cannot be reached now is a 502, never an empty list — “we could not find out” and “you have no channels” are opposite answers.
curl --request GET \
--url https://control.ergoly.ai/api/v1/channels \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://control.ergoly.ai/api/v1/channels', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://control.ergoly.ai/api/v1/channels"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"data": [
{
"id": "C0A1B2C3D4E",
"name": "acme-support",
"isPrivate": false,
"isArchived": false,
"memberWorkerIds": [
"8f1c2d3e-9a4b-4c5d-8e6f-1a2b3c4d5e6f"
],
"seenAt": "2026-02-28T16:44:12.000Z"
}
],
"page": {
"limit": 50,
"hasMore": true,
"nextCursor": null
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"docs": "<string>"
}
}Authorizations
An organization-scoped API key, minted in control under Settings → API keys. Every failure to authenticate answers one identical 401: "Provide a valid organization-scoped API key or an active session." Each operation names the one scope it is filed under, <resource>:<read|write>, where the resource is the first path segment after /api/v1/; a key minted today carries all 22 of them. Scopes are RECORDED — on the key and on every audit row — and are not yet enforced: no request is refused for want of one.
Query Parameters
Narrow to rooms whose name matches. A leading # is ignored and case is folded. An exact match always comes first, then names that start with the text, then names that contain it. Omit it to list the whole workspace.
How many rooms to return. Defaults to 50, clamped at 200. A value that is not a positive integer is a 400.

