{
  "openapi": "3.1.0",
  "info": {
    "title": "ClawLink Agent API",
    "version": "1.0.0",
    "summary": "Tool discovery and execution for AI agents",
    "description": "ClawLink connects AI agents to 100+ external apps (Gmail, Slack, Notion, GitHub, and more) through one hosted account. This document describes the agent-facing surface: tool discovery, tool execution, the MCP endpoint, and the two credential registration flows. How authentication works, including the human-approval requirement, is documented at https://claw-link.dev/auth.md. All other routes on claw-link.dev (dashboard, billing, connect pages) are browser-facing and not part of this API.",
    "contact": {
      "name": "ClawLink",
      "url": "https://claw-link.dev"
    }
  },
  "externalDocs": {
    "description": "ClawLink documentation",
    "url": "https://docs.claw-link.dev"
  },
  "servers": [
    {
      "url": "https://claw-link.dev"
    }
  ],
  "security": [
    { "ApiKeyHeader": [] },
    { "BearerAuth": [] }
  ],
  "tags": [
    { "name": "discovery", "description": "List, search, and describe the tools available to the authenticated user" },
    { "name": "execution", "description": "Run a tool against the user's connected apps" },
    { "name": "mcp", "description": "Model Context Protocol endpoint (JSON-RPC 2.0)" },
    { "name": "registration", "description": "Obtain a cllk_live_ API key. Every flow requires a signed-in human to approve in a browser. Do not probe these endpoints during passive scans; they create sessions and mint credentials." }
  ],
  "paths": {
    "/api/tools": {
      "get": {
        "tags": ["discovery"],
        "operationId": "listTools",
        "summary": "List available tools",
        "description": "Returns a capped page of tools (default 50, max 200), sorted by integration then name. Schemas are not included; call describeTool for the one tool you plan to run. When the list is truncated, the final array entry is a notice object with \"name\": null describing how many tools were omitted.",
        "parameters": [
          {
            "name": "integration",
            "in": "query",
            "description": "Filter to one integration slug, for example \"gmail\".",
            "schema": { "type": "string" }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Default 50, maximum 200.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 200 }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool page",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["tools", "count", "total", "truncated"],
                  "properties": {
                    "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } },
                    "count": { "type": "integer", "description": "Number of tools in this page." },
                    "total": { "type": "integer", "description": "Total tools available to this user." },
                    "truncated": { "type": "boolean" },
                    "integration": { "type": ["string", "null"] }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/tools/search": {
      "get": {
        "tags": ["discovery"],
        "operationId": "searchTools",
        "summary": "Search tools by keyword",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "Keyword to match against tool names and descriptions.",
            "schema": { "type": "string" }
          },
          {
            "name": "integration",
            "in": "query",
            "schema": { "type": "string" }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer" }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["tools", "count", "query"],
                  "properties": {
                    "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } },
                    "count": { "type": "integer" },
                    "query": { "type": "string" },
                    "integration": { "type": ["string", "null"] }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/tools/{name}": {
      "get": {
        "tags": ["discovery"],
        "operationId": "describeTool",
        "summary": "Describe one tool, including its input schema",
        "description": "The list and search responses omit input schemas. Call this for the tool you plan to execute; the response includes its hydrated JSON Schema.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Tool name as returned by listTools or searchTools."
          }
        ],
        "responses": {
          "200": {
            "description": "Tool description",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["tool"],
                  "properties": {
                    "tool": { "$ref": "#/components/schemas/Tool" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/tools/{name}/execute": {
      "post": {
        "tags": ["execution"],
        "operationId": "executeTool",
        "summary": "Execute a tool",
        "description": "Runs the named tool against the user's connected app. Write and destructive tools are refused with HTTP 412 unless the request sets \"confirmed\": true. When the user has several connections for one app, pass \"connectionId\" to pick one; omitting it uses the default connection. On failure, read \"error.message\" and \"hint\": they are written for agent self-correction.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "arguments": {
                    "type": "object",
                    "description": "Tool arguments matching the input schema from describeTool. If \"arguments\" is omitted, all top-level body fields except connectionId, confirmed, and files are treated as the arguments.",
                    "additionalProperties": true
                  },
                  "connectionId": {
                    "type": ["integer", "string"],
                    "description": "Connection id from the user's account. Omit to use the default connection for the tool's integration."
                  },
                  "confirmed": {
                    "type": "boolean",
                    "description": "Required as true for write and destructive tools."
                  },
                  "files": {
                    "type": "array",
                    "description": "File attachments for tools with file-uploadable fields. Total decoded size is capped at 100 MB per request (HTTP 413 above it).",
                    "items": { "$ref": "#/components/schemas/FileAttachment" }
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Execution succeeded",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } }
            }
          },
          "400": { "description": "Invalid arguments or malformed files envelope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "401": { "description": "Missing or invalid API key, or the connection needs re-authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "402": { "description": "Billing limit reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "403": { "description": "The connection is missing OAuth scopes the tool needs", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "404": { "description": "Tool not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "409": { "description": "No connection for the integration, or several connections and no connectionId given", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "412": { "description": "Confirmation required: re-send with \"confirmed\": true after the user approves", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "413": { "description": "Files envelope over the 100 MB cap", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "Integration configuration problem", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "429": { "description": "Provider rate limit", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "503": { "description": "Upstream provider unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } },
          "default": { "description": "Execution failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecutionResult" } } } }
        }
      }
    },
    "/api/mcp": {
      "get": {
        "tags": ["mcp"],
        "operationId": "describeMcpSurface",
        "security": [
          { "ApiKeyHeader": [] },
          { "BearerAuth": [] },
          { "OAuth2": ["openid"] }
        ],
        "summary": "Describe the MCP surface",
        "description": "Returns the tool, prompt, and resource listing of the MCP server without a JSON-RPC handshake.",
        "responses": {
          "200": {
            "description": "MCP surface description",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "transport": { "type": "string" },
                    "endpoint": { "type": "string" },
                    "tools": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
                    "prompts": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
                    "resources": { "type": "array", "items": { "type": "object", "additionalProperties": true } }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["mcp"],
        "operationId": "mcpJsonRpc",
        "security": [
          { "ApiKeyHeader": [] },
          { "BearerAuth": [] },
          { "OAuth2": ["openid"] }
        ],
        "summary": "MCP JSON-RPC 2.0 endpoint",
        "description": "Model Context Protocol over plain JSON-RPC 2.0 request/response (no SSE stream). Supports initialize, tools/list, and tools/call. The server exposes 10 tools namespaced clawlink.* covering discovery (clawlink.search, clawlink.list_actions, clawlink.get_action), connections (clawlink.list_integrations, clawlink.get_connection, clawlink.connect_app), and execution (clawlink.execute, clawlink.get_execution). Write tools need \"confirm\": true in clawlink.execute arguments.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["jsonrpc", "method"],
                "properties": {
                  "jsonrpc": { "const": "2.0" },
                  "id": { "type": ["integer", "string", "null"] },
                  "method": { "type": "string", "examples": ["initialize", "tools/list", "tools/call"] },
                  "params": { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC 2.0 response (errors are carried in the JSON-RPC \"error\" member)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["jsonrpc"],
                  "properties": {
                    "jsonrpc": { "const": "2.0" },
                    "id": { "type": ["integer", "string", "null"] },
                    "result": { "type": "object", "additionalProperties": true },
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": { "type": "integer" },
                        "message": { "type": "string" },
                        "data": {}
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/mcp/server-card": {
      "get": {
        "tags": ["discovery"],
        "operationId": "getMcpServerCard",
        "summary": "MCP Server Card",
        "security": [],
        "description": "Static pre-connection description of the MCP server (SEP-2127): identity, transport endpoint, and supported protocol versions. Public and unauthenticated, so a client can read it before it holds a key. Primitives (tools, prompts, resources) are deliberately absent because the tool list is per-user; list them at runtime instead. The same document is served at /.well-known/mcp/server-card.json.",
        "responses": {
          "200": {
            "description": "MCP Server Card",
            "content": {
              "application/mcp-server-card+json": {
                "schema": {
                  "type": "object",
                  "required": ["$schema", "name", "version", "description"],
                  "properties": {
                    "$schema": { "type": "string" },
                    "name": { "type": "string" },
                    "version": { "type": "string" },
                    "description": { "type": "string" },
                    "title": { "type": "string" },
                    "websiteUrl": { "type": "string" },
                    "remotes": {
                      "type": "array",
                      "items": { "type": "object", "additionalProperties": true }
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "304": { "description": "Not modified (matched If-None-Match)" }
        }
      }
    },
    "/api/openclaw/pair/start": {
      "post": {
        "tags": ["registration"],
        "operationId": "startPairing",
        "summary": "Start a pairing session",
        "description": "Starts the pairing flow used by the OpenClaw plugin and the CLI. The response includes a pairUrl for the human to open while signed in, and a verifier that must stay on the calling machine: it is the proof that lets this process, and no one else, claim the minted key.",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "deviceLabel": { "type": "string", "description": "Human-readable device name shown on the approval page and in the dashboard." },
                  "client": { "type": "string", "description": "Set to \"cli\" for CLI installs; omit for the OpenClaw plugin." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pairing session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["sessionToken", "pairUrl", "verifier", "expiresAt"],
                  "properties": {
                    "sessionToken": { "type": "string" },
                    "displayCode": { "type": "string", "description": "Short code shown to the user to match device and browser." },
                    "deviceLabel": { "type": ["string", "null"] },
                    "status": { "type": "string" },
                    "expiresAt": { "type": "string" },
                    "pollIntervalMs": { "type": "integer" },
                    "pairUrl": { "type": "string", "description": "Approval URL (https://claw-link.dev/openclaw/pair/<token>). Show it to the user; they approve while signed in." },
                    "verifier": { "type": "string", "description": "Keep secret on the calling machine. Required by the exchange call." }
                  }
                }
              }
            }
          },
          "default": { "description": "Session could not be created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/openclaw/pair/sessions/{token}/exchange": {
      "post": {
        "tags": ["registration"],
        "operationId": "exchangePairing",
        "summary": "Exchange an approved pairing session for an API key",
        "description": "After the human approves the pairUrl, present the verifier from startPairing to receive the cllk_live_ key. The session token alone is not sufficient. Store the key locally; the server keeps only its SHA-256 hash and cannot show it again.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "sessionToken from startPairing."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["verifier"],
                "properties": {
                  "verifier": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "API key issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["apiKey"],
                  "properties": {
                    "session": { "type": "object", "additionalProperties": true },
                    "apiKey": { "type": "string", "description": "The cllk_live_ key. This is the only time it is transmitted." },
                    "apiKeyId": { "type": ["integer", "string"] }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "default": { "description": "Session invalid, expired, or not yet approved", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/hermes/bootstrap-sessions": {
      "post": {
        "tags": ["registration"],
        "operationId": "createBootstrapSession",
        "summary": "Start a Hermes bootstrap session",
        "description": "Registration flow used by the Hermes plugin installer. The human opens approval_url while signed in; the installer polls poll_url and receives the install config, containing the key, after approval. Sessions expire after 15 minutes.",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "type": "object", "additionalProperties": true }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bootstrap session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["session_id", "approval_url", "poll_url", "expires_at"],
                  "properties": {
                    "session_id": { "type": "string" },
                    "status": { "type": "string" },
                    "approval_url": { "type": "string" },
                    "poll_url": { "type": "string" },
                    "expires_at": { "type": "string" },
                    "poll_interval_ms": { "type": "integer" },
                    "display": {
                      "type": "object",
                      "properties": {
                        "title": { "type": "string" },
                        "summary": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": { "description": "Session could not be created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-clawlink-api-key",
        "description": "A cllk_live_ API key obtained through one of the registration flows."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The same cllk_live_ key as a bearer token. Both header forms are accepted on every authenticated endpoint."
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "For remote MCP clients calling /api/mcp. Discovery starts at https://claw-link.dev/.well-known/oauth-protected-resource; client registration follows the authorization server metadata.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://clerk.claw-link.dev/oauth/authorize",
            "tokenUrl": "https://clerk.claw-link.dev/oauth/token",
            "refreshUrl": "https://clerk.claw-link.dev/oauth/token",
            "scopes": {
              "openid": "Identify the authorizing user",
              "email": "Read the user's email address",
              "profile": "Read basic profile information"
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "BadRequest": {
        "description": "Invalid request",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Not found",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" }
        }
      },
      "Tool": {
        "type": "object",
        "description": "A tool the authenticated user can run. List and search responses omit inputSchema; describeTool includes it. A truncation notice entry has \"name\": null and is not callable.",
        "properties": {
          "integration": { "type": ["string", "null"], "description": "Integration slug, for example \"gmail\"." },
          "name": { "type": ["string", "null"], "description": "Tool name to pass to executeTool." },
          "description": { "type": "string" },
          "inputSchema": {
            "type": ["object", "null"],
            "description": "JSON Schema for the arguments object. Null or a placeholder in list responses; hydrated by describeTool.",
            "additionalProperties": true
          },
          "canonical": {
            "type": "object",
            "description": "Canonical summary: integration_id, action, and default connection state.",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "FileAttachment": {
        "type": "object",
        "required": ["pointer", "name", "mimetype", "md5", "dataBase64"],
        "properties": {
          "pointer": { "type": "string", "description": "JSON pointer to the file-uploadable field in the arguments this attachment fills." },
          "name": { "type": "string", "description": "File name." },
          "mimetype": { "type": "string" },
          "md5": { "type": "string", "description": "MD5 hex digest of the raw bytes." },
          "dataBase64": { "type": "string", "description": "Base64-encoded file bytes." }
        }
      },
      "ExecutionResult": {
        "type": "object",
        "description": "Execution envelope returned for both success and failure. On failure, \"error.message\" and \"hint\" are written for agent self-correction; \"details\" may carry additional lines.",
        "required": ["ok"],
        "properties": {
          "ok": { "type": "boolean" },
          "result": { "description": "Tool output on success. Large outputs may be replaced by a compact offload envelope for clients that opt in." },
          "data": { "description": "Alias of result carried for compatibility." },
          "error": {
            "type": ["object", "null"],
            "properties": {
              "code": { "type": "string", "description": "Machine-readable code, for example needs_connection, confirmation_required, tool_not_found." },
              "type": { "type": "string", "description": "Error class, for example validation, auth, missing_scopes, rate_limit." },
              "message": { "type": "string" }
            },
            "additionalProperties": true
          },
          "hint": { "type": "string", "description": "Actionable next step for the agent." },
          "details": { "type": "array", "items": { "type": "string" } },
          "requiresConfirmation": { "type": "boolean" },
          "canonical": { "type": "object", "description": "Canonical execution summary.", "additionalProperties": true }
        },
        "additionalProperties": true
      }
    }
  }
}
