botteams.ai

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

qFree text. Matches name, tagline, category, slug, connector, Bot name, contributor, and the installer prompt.
categoryExact category, case-insensitive. One of the sections listed on the shelf.
integrationExact connector, case-insensitive and alias-aware. Calendar and Google Calendar match the same teams.
pagePage number, 1-based. Ignored in cursor mode.
limitTeams per page. Values outside the range clamp rather than error.
sortOrdering. Ignored in cursor mode, which is always oldest first.
cursorPass 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.