{
  "openapi": "3.1.0",
  "info": {
    "title": "botteams.ai API",
    "version": "1.0.0",
    "summary": "Read the public shelf of Grok Bot teams as JSON.",
    "description": "A team is a recipe, not a bot: named Bots, one group chat where the recipe has one, standing routines, and the connectors the account needs first.\n\nNo key, no account, no auth. CORS is open, so a browser or an agent can call this directly.\n\nThere is no per-team endpoint by design. /api/teams/<slug> returns 404. Filter the collection instead.\n\nConnectors in Grok Bot are account-wide. Nothing in this payload is a permission boundary.",
    "contact": {
      "name": "Ellelion LLC",
      "email": "info@ellelion.com",
      "url": "https://botteams.ai"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "servers": [
    {
      "url": "https://botteams.ai",
      "description": "Production"
    }
  ],
  "security": [],
  "paths": {
    "/api/teams": {
      "get": {
        "operationId": "listTeams",
        "summary": "List teams",
        "description": "Teams only: two to six Bots in one group chat. A one-Bot recipe is a bot and is never returned here. Filtered and paginated. Passing cursor switches to append-safe sync mode, which walks oldest first so a team added after your last sync always lands after your cursor.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free text. Matches name, tagline, category, slug, connector, Bot name, contributor, and the installer prompt.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Exact category, case-insensitive. One of the sections listed on the shelf.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "integration",
            "in": "query",
            "required": false,
            "description": "Exact connector, case-insensitive and alias-aware. Calendar and Google Calendar match the same teams.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number, 1-based. Ignored in cursor mode.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Teams per page. Values outside the range clamp rather than error.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Ordering. Ignored in cursor mode, which is always oldest first.",
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "name"
              ],
              "default": "newest"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Pass start to begin an append-safe sync, then follow sync.nextCursor. Switches the response envelope from pagination to sync.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of teams. The envelope carries pagination, or sync when cursor was passed.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/TeamPage"
                    },
                    {
                      "$ref": "#/components/schemas/TeamSync"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The cursor did not match any team, so the sync position cannot be trusted. Start again from cursor=start.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/bots": {
      "get": {
        "operationId": "listBots",
        "summary": "List bots",
        "description": "Bots only: one Bot doing one job, with no group chat. A team is never returned here. Filtered and paginated. Passing cursor switches to append-safe sync mode, which walks oldest first so a bot added after your last sync always lands after your cursor.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free text. Matches name, tagline, category, slug, connector, Bot name, contributor, and the installer prompt.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Exact category, case-insensitive. One of the sections listed on the shelf.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "integration",
            "in": "query",
            "required": false,
            "description": "Exact connector, case-insensitive and alias-aware. Calendar and Google Calendar match the same teams.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number, 1-based. Ignored in cursor mode.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Teams per page. Values outside the range clamp rather than error.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Ordering. Ignored in cursor mode, which is always oldest first.",
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "name"
              ],
              "default": "newest"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Pass start to begin an append-safe sync, then follow sync.nextCursor. Switches the response envelope from pagination to sync.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of bots. The envelope carries pagination, or sync when cursor was passed.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BotPage"
                    },
                    {
                      "$ref": "#/components/schemas/BotSync"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The cursor did not match any bot, so the sync position cannot be trusted. Start again from cursor=start.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Team": {
        "type": "object",
        "description": "A team: two to six Bots in one group chat.",
        "required": [
          "slug",
          "name",
          "tagline",
          "category",
          "kind",
          "status",
          "fromXai",
          "featured",
          "bots",
          "addedAt",
          "connectors",
          "agents",
          "rooms",
          "routines",
          "installer",
          "contributor",
          "contributorUrl",
          "scoutedBy",
          "sourceUrl",
          "url",
          "detailUrl"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "Stable identifier. Matches the filename under teams/ and the last path segment of detailUrl."
          },
          "name": {
            "type": "string",
            "description": "Display name of the team."
          },
          "tagline": {
            "type": "string",
            "description": "One line saying what the team does."
          },
          "category": {
            "type": "string",
            "description": "Section the team files under, and the value the category filter matches."
          },
          "kind": {
            "const": "team",
            "description": "Always team on this collection."
          },
          "status": {
            "type": "string",
            "enum": [
              "installable",
              "example"
            ],
            "description": "installable is a recipe to use. example is a format demonstration."
          },
          "fromXai": {
            "type": "boolean",
            "description": "True when the recipe is our write-up of a job published in xAI's own Grok Bot use-case gallery. Sourcing, not endorsement: xAI does not review or certify anything on this shelf."
          },
          "featured": {
            "type": "boolean",
            "description": "True when this team is pinned first on the public index."
          },
          "bots": {
            "type": "integer",
            "minimum": 1,
            "description": "Number of Bots. Always equal to agents.length, and always 1 on a bot."
          },
          "addedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Date stated in the team file. Never inferred. Null when the file does not state one, and those sort last."
          },
          "connectors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Connectors the account must already have. Connectors are account-wide in Grok Bot."
          },
          "agents": {
            "type": "array",
            "description": "The Bots this team creates. One entry per Bot.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "persona",
                "connectors"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Bot name, used exactly."
                },
                "persona": {
                  "type": "string",
                  "description": "The Bot's job, in a sentence."
                },
                "connectors": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Which connectors this Bot is expected to use. Not a permission: every Bot on an account can reach every connected tool."
                }
              }
            }
          },
          "rooms": {
            "type": "array",
            "description": "Group chats. A group chat holds two to six Bots. A bot has none, so this is empty on every item from /api/bots.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "members"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Group chat name."
                },
                "members": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 2,
                  "maxItems": 6,
                  "description": "Bot names in the group chat."
                }
              }
            }
          },
          "routines": {
            "type": "array",
            "description": "Standing routines. Each is owned by one Bot and needs a human to confirm the save.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "owner",
                "schedule",
                "prompt"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Routine name."
                },
                "owner": {
                  "type": "string",
                  "description": "Bot that owns it."
                },
                "schedule": {
                  "type": "string",
                  "description": "When it runs, in words."
                },
                "prompt": {
                  "type": "string",
                  "description": "The prompt a human confirms."
                }
              }
            }
          },
          "installer": {
            "type": "string",
            "description": "The full installer prompt, ready to paste into Grok Bot. This is the product: a client never has to scrape the site for it."
          },
          "contributor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Handle of whoever contributed the team."
          },
          "contributorUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Profile URL for the contributor."
          },
          "scoutedBy": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who found the team, when that is not the contributor."
          },
          "sourceUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The post the team was based on."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canonical homepage for the team, when it has one."
          },
          "detailUrl": {
            "type": "string",
            "description": "Page for this team on the shelf."
          }
        }
      },
      "Bot": {
        "type": "object",
        "description": "A bot: one Bot doing one job, with no group chat. Never Verified, because Verified is a claim about a group chat.",
        "required": [
          "slug",
          "name",
          "tagline",
          "category",
          "kind",
          "status",
          "fromXai",
          "featured",
          "bots",
          "addedAt",
          "connectors",
          "agents",
          "rooms",
          "routines",
          "installer",
          "contributor",
          "contributorUrl",
          "scoutedBy",
          "sourceUrl",
          "url",
          "detailUrl"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "Stable identifier. Matches the filename under teams/ and the last path segment of detailUrl."
          },
          "name": {
            "type": "string",
            "description": "Display name of the team."
          },
          "tagline": {
            "type": "string",
            "description": "One line saying what the team does."
          },
          "category": {
            "type": "string",
            "description": "Section the team files under, and the value the category filter matches."
          },
          "kind": {
            "const": "bot",
            "description": "Always bot on this collection."
          },
          "status": {
            "type": "string",
            "enum": [
              "installable",
              "example"
            ],
            "description": "installable is a recipe to use. example is a format demonstration."
          },
          "fromXai": {
            "type": "boolean",
            "description": "True when the recipe is our write-up of a job published in xAI's own Grok Bot use-case gallery. Sourcing, not endorsement: xAI does not review or certify anything on this shelf."
          },
          "featured": {
            "type": "boolean",
            "description": "True when this team is pinned first on the public index."
          },
          "bots": {
            "const": 1,
            "description": "A bot is one Bot."
          },
          "addedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Date stated in the team file. Never inferred. Null when the file does not state one, and those sort last."
          },
          "connectors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Connectors the account must already have. Connectors are account-wide in Grok Bot."
          },
          "agents": {
            "type": "array",
            "description": "The Bots this team creates. One entry per Bot.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "persona",
                "connectors"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Bot name, used exactly."
                },
                "persona": {
                  "type": "string",
                  "description": "The Bot's job, in a sentence."
                },
                "connectors": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Which connectors this Bot is expected to use. Not a permission: every Bot on an account can reach every connected tool."
                }
              }
            }
          },
          "rooms": {
            "type": "array",
            "maxItems": 0,
            "description": "Always empty. A bot has no group chat."
          },
          "routines": {
            "type": "array",
            "description": "Standing routines. Each is owned by one Bot and needs a human to confirm the save.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "owner",
                "schedule",
                "prompt"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Routine name."
                },
                "owner": {
                  "type": "string",
                  "description": "Bot that owns it."
                },
                "schedule": {
                  "type": "string",
                  "description": "When it runs, in words."
                },
                "prompt": {
                  "type": "string",
                  "description": "The prompt a human confirms."
                }
              }
            }
          },
          "installer": {
            "type": "string",
            "description": "The full installer prompt, ready to paste into Grok Bot. This is the product: a client never has to scrape the site for it."
          },
          "contributor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Handle of whoever contributed the team."
          },
          "contributorUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Profile URL for the contributor."
          },
          "scoutedBy": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who found the team, when that is not the contributor."
          },
          "sourceUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The post the team was based on."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canonical homepage for the team, when it has one."
          },
          "detailUrl": {
            "type": "string",
            "description": "Page for this team on the shelf."
          }
        }
      },
      "Filters": {
        "type": "object",
        "description": "The filters the server actually applied, echoed back.",
        "required": [
          "q",
          "category",
          "integration",
          "sort"
        ],
        "properties": {
          "q": {
            "type": [
              "string",
              "null"
            ],
            "description": "The free text that was applied, or null."
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "The category that was applied, or null."
          },
          "integration": {
            "type": [
              "string",
              "null"
            ],
            "description": "The connector that was applied, or null."
          },
          "sort": {
            "type": "string",
            "description": "The sort actually used. Cursor mode always reports oldest."
          }
        }
      },
      "Links": {
        "type": "object",
        "required": [
          "self",
          "next",
          "previous"
        ],
        "properties": {
          "self": {
            "type": "string",
            "description": "This request."
          },
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "Next page, or null at the end."
          },
          "previous": {
            "type": [
              "string",
              "null"
            ],
            "description": "Previous page, or null at the start. Always null in cursor mode."
          }
        }
      },
      "TeamPage": {
        "type": "object",
        "description": "Page envelope for /api/teams. Returned when cursor was not passed.",
        "required": [
          "version",
          "teams",
          "pagination",
          "filters",
          "links"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "description": "Envelope version. Bumped only for a breaking change."
          },
          "teams": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Team"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "page",
              "limit",
              "total",
              "totalPages",
              "hasNext",
              "hasPrevious"
            ],
            "properties": {
              "page": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              },
              "total": {
                "type": "integer",
                "description": "Items matching the filters, across all pages."
              },
              "totalPages": {
                "type": "integer"
              },
              "hasNext": {
                "type": "boolean"
              },
              "hasPrevious": {
                "type": "boolean"
              }
            }
          },
          "filters": {
            "$ref": "#/components/schemas/Filters"
          },
          "links": {
            "$ref": "#/components/schemas/Links"
          }
        }
      },
      "TeamSync": {
        "type": "object",
        "description": "Sync envelope for /api/teams. Returned when cursor was passed.",
        "required": [
          "version",
          "teams",
          "sync",
          "filters",
          "links"
        ],
        "properties": {
          "version": {
            "type": "integer"
          },
          "teams": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Team"
            }
          },
          "sync": {
            "type": "object",
            "required": [
              "returned",
              "hasMore",
              "nextCursor"
            ],
            "properties": {
              "returned": {
                "type": "integer",
                "description": "Items in this response."
              },
              "hasMore": {
                "type": "boolean"
              },
              "nextCursor": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Pass as cursor on the next call. Null at the end."
              }
            }
          },
          "filters": {
            "$ref": "#/components/schemas/Filters"
          },
          "links": {
            "$ref": "#/components/schemas/Links"
          }
        }
      },
      "BotPage": {
        "type": "object",
        "description": "Page envelope for /api/bots. Returned when cursor was not passed.",
        "required": [
          "version",
          "bots",
          "pagination",
          "filters",
          "links"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "description": "Envelope version. Bumped only for a breaking change."
          },
          "bots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Bot"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "page",
              "limit",
              "total",
              "totalPages",
              "hasNext",
              "hasPrevious"
            ],
            "properties": {
              "page": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              },
              "total": {
                "type": "integer",
                "description": "Items matching the filters, across all pages."
              },
              "totalPages": {
                "type": "integer"
              },
              "hasNext": {
                "type": "boolean"
              },
              "hasPrevious": {
                "type": "boolean"
              }
            }
          },
          "filters": {
            "$ref": "#/components/schemas/Filters"
          },
          "links": {
            "$ref": "#/components/schemas/Links"
          }
        }
      },
      "BotSync": {
        "type": "object",
        "description": "Sync envelope for /api/bots. Returned when cursor was passed.",
        "required": [
          "version",
          "bots",
          "sync",
          "filters",
          "links"
        ],
        "properties": {
          "version": {
            "type": "integer"
          },
          "bots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Bot"
            }
          },
          "sync": {
            "type": "object",
            "required": [
              "returned",
              "hasMore",
              "nextCursor"
            ],
            "properties": {
              "returned": {
                "type": "integer",
                "description": "Items in this response."
              },
              "hasMore": {
                "type": "boolean"
              },
              "nextCursor": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Pass as cursor on the next call. Null at the end."
              }
            }
          },
          "filters": {
            "$ref": "#/components/schemas/Filters"
          },
          "links": {
            "$ref": "#/components/schemas/Links"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "version",
          "error",
          "cursor"
        ],
        "properties": {
          "version": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "description": "What went wrong, in words."
          },
          "cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "The cursor that was rejected."
          }
        }
      }
    }
  }
}