API
Every team on this shelf is readable as JSON. No key, no account, no rate limit worth mentioning. CORS is open, so a browser or an agent can call it directly. 140 teams and 59 bots today.
Two shapes, two collections. A team is a recipe with a group chat: two to six named Bots, standing routines, and the connectors the account needs first. A bot is a recipe with one Bot and no group chat. They never appear in the same list, because adding them up and calling the total teams is not true. The installer prompt ships in both payloads, so a client never has to scrape this site to get it.
Endpoints
- GET /api/teams
- Teams only: two to six Bots in one group chat. Items arrive under a teams key.
- GET /api/bots
- Bots only: one Bot, no group chat. Same filters and cursor contract. Items arrive under a bots key.
- GET /openapi.json
- The same contract as OpenAPI 3.1, built from the types the routes return.
There is no per-item endpoint. /api/teams/<slug> and /api/bots/<slug> both return 404 by design. Filter the collection instead.
Machine contract
OpenAPI 3.1, generated from the same types this endpoint returns rather than written alongside them, so it cannot drift from the response. Defaults on this page and in the document come from one list.
Download OpenAPI 3.1 (JSON)YAML
Default limit 25, maximum 100. Values outside the range clamp rather than error.
Parameters
q | Free text. Matches name, tagline, category, slug, connector, Bot name, contributor, and the installer prompt. |
|---|---|
category | Exact category, case-insensitive. One of the sections listed on the shelf. |
integration | Exact connector, case-insensitive and alias-aware. Calendar and Google Calendar match the same teams. |
page | Page number, 1-based. Ignored in cursor mode. |
limit | Teams per page. Values outside the range clamp rather than error. |
sort | Ordering. Ignored in cursor mode, which is always oldest first. |
cursor | Pass start to begin an append-safe sync, then follow sync.nextCursor. Switches the response envelope from pagination to sync. |
Examples
# five teams that expect Stripe
curl "https://botteams.ai/api/teams?integration=Stripe&limit=5"
# one-Bot recipes for sales
curl "https://botteams.ai/api/bots?category=Sales"
# search, alphabetical
curl "https://botteams.ai/api/teams?q=inbox&sort=name"
# one category
curl "https://botteams.ai/api/teams?category=Engineering"
# every team, one page
curl "https://botteams.ai/api/teams?limit=100"Sync
Cursor mode walks oldest first, so a team added after your last sync always lands after your cursor. A page never shifts underneath you. Start at cursor=start, store sync.nextCursor, and keep the same filters on every call.
curl "https://botteams.ai/api/teams?cursor=start&limit=100"
{
"version": 1,
"teams": [ ... ],
"sync": { "returned": 26, "hasMore": false, "nextCursor": null },
"filters": { "q": null, "category": null, "integration": null, "sort": "oldest" },
"links": { "self": "...", "next": null, "previous": null }
}Team shape
{
"slug": "founder-os",
"name": "Founder OS",
"tagline": "Money, inbox, and a chief of staff in one founder room.",
"category": "Founder OS",
"kind": "team",
"status": "installable",
"bots": 3,
"addedAt": "2026-08-21T03:57:51.000Z",
"connectors": ["Stripe", "Gmail", "Calendar", "Ramp", "Notion"],
"agents": [{ "name": "...", "persona": "...", "connectors": [...] }],
"rooms": [{ "name": "Founder HQ", "members": [...] }], // always [] on a bot
"routines": [{ "name": "...", "owner": "...", "schedule": "...", "prompt": "..." }],
"installer": "# Grok Bot Teams installer ...",
"contributor": null,
"contributorUrl": null,
"scoutedBy": null,
"sourceUrl": null,
"url": null,
"detailUrl": "https://botteams.ai/teams/founder-os"
}addedAt is the date stated in the team file and nothing else. It is never inferred and never invented. A file that does not state one reports null and sorts last.



