Every API here is available over the APIs.io API and to AI agents over MCP.
{
"openapi": "3.0.3",
"info": {
"title": "Willow API",
"description": "The Willow API allows you to programmatically manage MCP servers and other resources in your organization.\n\n## Authentication\n\nAll API endpoints require authentication using a Bearer token. You can generate API tokens from the Admin Settings in your Willow dashboard.\n\nInclude your token in the `Authorization` header:\n```\nAuthorization: Bearer wxt_xxxxx\n```\n\nSee [API Tokens](/docs/admin/api-tokens) for more details on generating and managing tokens.\n\n## Versioning and deprecation\n\nThis is version `1` of the Admin API. Every endpoint accepts an optional\n`Willow-API-Version` header; omitting it means version `1`. New endpoints and\nfields are added without a version change. Breaking changes ship only under a\nnew version, and an operation is removed no sooner than 6 months after it is\ndeprecated. Deprecated operations are marked `deprecated: true` in this spec\nand return `Deprecation` and `Sunset` response headers.\n\nSee [API Versioning and Deprecation](/docs/developers/api-versioning) for the full policy.\n",
"version": "1.0.0",
"contact": {
"name": "Willow Support",
"url": "https://withwillow.ai"
}
},
"servers": [
{
"url": "https://app.withwillow.ai",
"description": "Your Willow instance"
}
],
"tags": [
{
"name": "MCP Servers",
"description": "Create and manage MCP servers programmatically"
},
{
"name": "Connectors",
"description": "Browse the catalog of available connectors (built-in MCPs) and check whether a connector is already in use in your organization"
},
{
"name": "Groups",
"description": "Manage groups and their integrations"
},
{
"name": "Users",
"description": "Manage users in your organization"
},
{
"name": "Toolkits",
"description": "Create and manage toolkits"
},
{
"name": "Skills",
"description": "Create and manage skills"
},
{
"name": "Guards",
"description": "Create and manage guards (content evaluation rules with checks, thresholds, and actions)"
},
{
"name": "Commands",
"description": "Create and manage commands"
},
{
"name": "Plugins",
"description": "Create and manage plugins"
},
{
"name": "Background Agents",
"description": "Create, sync, and invoke background agents, and inspect their platform sessions"
},
{
"name": "Auth Exchange",
"description": "Exchange external JWT tokens for Willow Connect credentials"
},
{
"name": "AI Discovery",
"description": "Inspect AI tools discovered across your organization by the Willow Scan Agent — MCP servers, skills, agent memory, AI agents, and the devices reporting them"
},
{
"name": "AI Discovery Policy",
"description": "Read and manage the AI Discovery enforcement policy — the default actions and ordered rules that allow, warn, or block discovered MCP servers and skills"
}
],
"security": [
{
"bearerAuth": []
}
],
"paths": {
"/api/mcp-servers": {
"parameters": [
{
"$ref": "#/components/parameters/ApiVersion"
}
],
"get": {
"operationId": "listMcpServers",
"summary": "List MCP Servers",
"description": "Returns a list of all MCP servers in your organization.\n\nThe response includes all configured MCP servers with their current status, configuration, and metadata.\n",
"tags": [
"MCP Servers"
],
"parameters": [
{
"name": "x-willow-group",
"in": "header",
"required": false,
"description": "Scope the returned list to the MCP servers accessible to a specific group, by group name (case-insensitive), instead of the full org-wide list. An unknown group resolves to an empty list rather than an error.\n",
"schema": {
"type": "string"
},
"example": "engineering"
},
{
"name": "status",
"in": "query",
"required": false,
"description": "Filter MCP servers by status.",
"schema": {
"type": "string",
"enum": [
"active",
"draft"
]
},
"example": "active"
},
{
"name": "search",
"in": "query",
"required": false,
"description": "Case-insensitive search against MCP server name and description.",
"schema": {
"type": "string"
},
"example": "slack"
},
{
"name": "include_tools",
"in": "query",
"required": false,
"description": "Include enabled tools for each server. Deprecated in favor of `fields=tools(...)`.",
"schema": {
"type": "boolean"
},
"example": true
},
{
"name": "fields",
"in": "query",
"required": false,
"description": "Comma-separated list of fields to include in the response. When omitted, all fields are returned.\nSupports nested field selection using parenthesized syntax — e.g. `tools(name,slug,description)`.\n",
"schema": {
"type": "string"
},
"example": "slug,name,status,tools(name,slug,description)"
},
{
"name": "cursor",
"in": "query",
"required": false,
"description": "Zero-based cursor offset for pagination.",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "limit",
"in": "query",
"required": false,
"description": "Maximum number of MCP servers to return (1-200).",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 200,
"default": 100
},
"example": 100
},
{
"name": "includeTotal",
"in": "query",
"required": false,
"description": "Include the total number of matching MCP servers in `totalCount`.",
"schema": {
"type": "boolean"
},
"example": true
}
],
"responses": {
"200": {
"description": "List of MCP servers retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListMcpServersResponse"
},
"example": {
"data": [
{
"slug": "my-custom-server",
"name": "My Custom Server",
"description": "Custom MCP server for internal tools",
"status": "active",
"logo_url": "https://icons.withwillow.ai/connectors/slack.svg",
"auth_type": "proxy-oauth",
"tools_count": 12,
"is_local_mcp": false,
"connection": {
"url": "https://gateway.example.com/mcp?mcp=my-custom-server",
"transport": "both"
},
"tools": []
}
],
"pagination": {
"cursor": 0,
"limit": 100,
"hasMore": false,
"nextCursor": null
},
"totalCount": 1
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerError"
},
"example": {
"error": "Failed to retrieve MCP servers"
}
}
}
}
}
},
"post": {
"operationId": "createMcpServer",
"summary": "Create MCP Server",
"description": "Creates a new MCP server integration in your organization.\n\nThe server will be created with the specified configuration and will be immediately available for use by your organization's users.\n\nIf no `slug` is provided, one will be automatically generated from the `name`. If the slug already exists, a numeric suffix will be appended to ensure uniqueness.\n",
"tags": [
"MCP Servers"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMcpServerRequest"
},
"examples": {
"basic": {
"summary": "Basic MCP Server",
"description": "Create a simple MCP server with minimal configuration",
"value": {
"name": "My Custom Server",
"mcp_proxy": "{\"type\":\"http\",\"url\":\"https://my-mcp-server.example.com\"}"
}
},
"withAuth": {
"summary": "Server with OAuth Authentication",
"description": "Create an MCP server that requires OAuth authentication",
"value": {
"name": "GitHub Integration",
"slug": "github-custom",
"mcp_proxy": "{\"type\":\"http\",\"url\":\"https://github-mcp.example.com\"}",
"description": "Custom GitHub MCP server integration",
"status": "active",
"auth_settings": {
"authType": "proxy-oauth",
"clientId": "your-client-id",
"authUrl": "https://github.com/login/oauth/authorize",
"tokenUrl": "https://github.com/login/oauth/access_token",
"client": {
"scope": "repo user"
}
}
}
},
"withApiKey": {
"summary": "Server with API Key Authentication",
"description": "Create an MCP server that requires an API key from users",
"value": {
"name": "Custom API Server",
"mcp_proxy": "{\"type\":\"http\",\"url\":\"https://api-server.example.com\"}",
"auth_settings": {
"authType": "proxy-api-key",
"howtoMarkdown": "Go to [Settings](https://example.com/settings) to generate your API key."
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "MCP server created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMcpServerResponse"
},
"example": {
"data": {
"slug": "my-custom-server"
}
}
}
}
},
"400": {
"description": "Invalid request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
},
"examples": {
"missingName": {
"summary": "Missing required field",
"value": {
"code": "INVALID_FORMAT",
"message": "name: name is required"
}
},
"invalidJson": {
"summary": "Invalid MCP proxy JSON",
"value": {
"code": "INVALID_FORMAT",
"message": "mcp_proxy: mcp_proxy must be valid JSON"
}
},
"invalidSlug": {
"summary": "Invalid slug format",
"value": {
"code": "INVALID_FORMAT",
"message": "slug: slug must be lowercase alphanumeric with hyphens, no leading/trailing hyphens"
}
}
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerError"
},
"example": {
"error": "Failed to create MCP server"
}
}
}
}
}
},
"patch": {
"operationId": "updateMcpServer",
"summary": "Update MCP Server",
"description": "Updates an existing MCP server by slug.\n\nOnly the fields provided in the request body will be updated. Omitted fields retain their current values.\n",
"tags": [
"MCP Servers"
],
"parameters": [
{
"name": "slug",
"in": "query",
"required": true,
"description": "The slug of the MCP server to update",
"schema": {
"type": "string"
},
"example": "my-custom-server"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMcpServerRequest"
},
"examples": {
"updateName": {
"summary": "Update Name",
"value": {
"name": "Updated Server Name"
}
},
"updateStatus": {
"summary": "Update Status",
"value": {
"status": "draft"
}
},
"updateAuth": {
"summary": "Update Auth Settings",
"value": {
"auth_settings": {
"authType": "proxy-api-key",
"howtoMarkdown": "Visit [Settings](https://example.com/settings) to get your key."
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "MCP server updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateMcpServerResponse"
},
"example": {
"data": {
"slug": "my-custom-server"
}
}
}
}
},
"400": {
"description": "Invalid request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
},
"example": {
"code": "INVALID_FORMAT",
"message": "slug: slug must be lowercase alphanumeric with hyphens"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"404": {
"description": "MCP server not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundError"
},
"example": {
"error": "MCP server not found"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerError"
},
"example": {
"error": "Failed to update MCP server"
}
}
}
}
}
},
"delete": {
"operationId": "deleteMcpServer",
"summary": "Delete MCP Server",
"description": "Deletes an MCP server by slug.\n\nThis operation is permanent and cannot be undone. All associated configurations will be removed.\n",
"tags": [
"MCP Servers"
],
"parameters": [
{
"name": "slug",
"in": "query",
"required": true,
"description": "The slug of the MCP server to delete",
"schema": {
"type": "string"
},
"example": "my-custom-server"
}
],
"responses": {
"200": {
"description": "MCP server deleted successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"example": {
"message": "MCP server deleted successfully"
}
}
}
},
"400": {
"description": "Missing slug query parameter",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
},
"example": {
"code": "INVALID_FORMAT",
"message": "slug query parameter is required"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"404": {
"description": "MCP server not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundError"
},
"example": {
"error": "MCP server not found"
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerError"
},
"example": {
"error": "Failed to delete MCP server"
}
}
}
}
}
}
},
"/api/connectors": {
"parameters": [
{
"$ref": "#/components/parameters/ApiVersion"
}
],
"get": {
"operationId": "listConnectors",
"summary": "List Connectors",
"description": "Returns the catalog of connectors (available MCPs) that can be used to create MCP servers in your organization.\n\nThe catalog merges two sources, distinguished by the `source` field on each entry:\n- `connector` — built-in, first-party connectors (such as Slack, GitHub, or Gmail) with their supported authentication methods and the tools they expose.\n- `registry` — entries from the public MCP registry directory (community/third-party servers) that are not part of the connector database.\n\nUse the `source` query parameter to return only one of the two catalogs. Results are paginated. Use the `id` of an entry to check whether it is already in use in your organization via [Check Connector Usage](#tag/Connectors/operation/getConnectorUsage).\n",
"tags": [
"Connectors"
],
"parameters": [
{
"name": "source",
"in": "query",
"required": false,
"description": "Return only built-in connectors or only registry entries. When omitted, both are returned.",
"schema": {
"type": "string",
"enum": [
"connector",
"registry"
]
},
"example": "connector"
},
{
"name": "status",
"in": "query",
"required": false,
"description": "Filter connectors by availability status. Registry entries are always `active`.",
"schema": {
"type": "string",
"enum": [
"active",
"beta",
"soon"
]
},
"example": "active"
},
{
"name": "search",
"in": "query",
"required": false,
"description": "Case-insensitive search against connector id, name, and description.",
"schema": {
"type": "string"
},
"example": "slack"
},
{
"name": "include_tools",
"in": "query",
"required": false,
"description": "Include a summary of the tools each built-in connector exposes.",
"schema": {
"type": "boolean"
},
"example": false
},
{
"name": "cursor",
"in": "query",
"required": false,
"description": "Zero-based cursor offset for pagination.",
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
},
"example": 0
},
{
"name": "limit",
"in": "query",
"required": false,
"description": "Maximum number of entries to return (1-200).",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 200,
"default": 100
},
"example": 100
}
],
"responses": {
"200": {
"description": "List of connectors retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListConnectorsResponse"
},
"example": {
"data": [
{
"id": "slack",
"source": "connector",
"name": "Slack",
"description": "Send messages and manage channels in Slack",
"logo_url": "https://icons.withwillow.ai/connectors/slack.svg",
"website": "https://slack.com",
"category": "Communication",
"status": "active",
"auth_types": [
"proxy-oauth"
],
"tools_count": 4
},
{
"id": "context7",
"source": "registry",
"name": "Context7",
"description": "Up-to-date code docs for any prompt",
"logo_url": "https://context7.com/favicon.ico",
"website": null,
"category": null,
"status": "active",
"auth_types": [
"none"
],
"tools_count": 0,
"transport": "http",
"url": "https://mcp.context7.com/mcp",
"repo": "https://github.com/upstash/context7",
"official": true
}
],
"pagination": {
"cursor": 0,
"limit": 100,
"hasMore": true,
"nextCursor": 100
},
"totalCount": 1804
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerError"
},
"example": {
"error": "Failed to list connectors"
}
}
}
}
}
}
},
"/api/connectors/{connector_id}/usage": {
"parameters": [
{
"$ref": "#/components/parameters/ApiVersion"
}
],
"get": {
"operationId": "getConnectorUsage",
"summary": "Check Connector Usage",
"description": "Reports whether a catalog entry is already in use in your organization.\n\nAn entry is considered \"in use\" when at least one MCP server in your organization was created from it:\n- Built-in connectors are matched by their `connector_id`.\n- Registry MCPs are matched by comparing the registry entry's remote endpoint against the URL each installed MCP server points at.\n\nWhen the id exists in both catalogs (or you don't know which it is), omit `source` and both strategies run. The response includes the matching MCP servers so you can see exactly where the entry is configured.\n",
"tags": [
"Connectors"
],
"parameters": [
{
"name": "connector_id",
"in": "path",
"required": true,
"description": "The catalog entry id (for example `slack`), as returned by [List Connectors](#tag/Connectors/operation/listConnectors).",
"schema": {
"type": "string"
},
"example": "slack"
},
{
"name": "source",
"in": "query",
"required": false,
"description": "Restrict matching to a built-in connector or a registry entry. When omitted, both are checked.",
"schema": {
"type": "string",
"enum": [
"connector",
"registry"
]
},
"example": "connector"
}
],
"responses": {
"200": {
"description": "Connector usage retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorUsageResponse"
},
"example": {
"data": {
"connector_id": "slack",
"source": "connector",
"in_use": true,
"count": 2,
"mcp_servers": [
{
"id": "6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"slug": "slack",
"name": "Slack",
"status": "active",
"needs_setup": false
},
{
"id": "7a2d3e4f-5b6c-7d8e-9f0a-1b2c3d4e5f60",
"slug": "slack-eng",
"name": "Slack (Engineering)",
"status": "draft",
"needs_setup": true
}
]
}
}
}
}
},
"400": {
"description": "Connector id is missing",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
},
"example": {
"error": "Connector id is required"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerError"
},
"example": {
"error": "Failed to check connector usage"
}
}
}
}
}
}
},
"/api/groups": {
"parameters": [
{
"$ref": "#/components/parameters/ApiVersion"
}
],
"get": {
"operationId": "listGroups",
"summary": "List Groups",
"description": "Returns a paginated list of groups in your organization.\n\nGroups control which integrations, toolkits, and skills are available to users.\n",
"tags": [
"Groups"
],
"parameters": [
{
"name": "skip",
"in": "query",
"required": false,
"description": "Number of records to skip for pagination",
"schema": {
"type": "number",
"default": 0
},
"example": 0
},
{
"name": "limit",
"in": "query",
"required": false,
"description": "Maximum number of records to return",
"schema": {
"type": "number",
"default": 100
},
"example": 100
},
{
"name": "search",
"in": "query",
"required": false,
"description": "Search term to filter groups by name",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "List of groups retrieved successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Group"
}
},
"pagination": {
"$ref": "#/components/schemas/AdminPagination"
}
}
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerError"
},
"example": {
"error": "Failed to retrieve groups"
}
}
}
}
}
},
"post": {
"operationId": "createGroup",
"summary": "Create Group",
"description": "Creates a new group in your organization.\n\nGroups are used to organize users and control their access to integrations, toolkits, and skills.\n",
"tags": [
# --- truncated at 32 KB (339 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/webrix/refs/heads/main/openapi/_original/webrix-api-openapi.json