{
  "openapi": "3.1.0",
  "info": {
    "title": "Kaiva Bridge Management API",
    "version": "1.0.0",
    "summary": "Create, read and publish managed MCP servers from an OpenAPI spec, a GraphQL API, a database, documents or a remote MCP server.",
    "description": "The REST API behind the Kaiva Bridge console and the @kaiva/bridge CLI. It creates managed MCP servers from an OpenAPI or Swagger spec, a GraphQL endpoint, a PostgreSQL, MySQL, MariaDB or SQL Server database, uploaded documents or a remote MCP server, chooses which tools are exposed, publishes the server and creates the keys AI clients use.\n\nEvery request carries a management key: `Authorization: Bearer kv_mgmt_...`. An owner or admin creates one in the console under Keys & access (type Management), or `npx @kaiva/bridge login` approves a machine and saves one. A management key acts on its own workspace only, and it cannot create another management key. Gateway keys (`kv_live_...`) are for calling an MCP server and are refused here.\n\nA published server is an MCP endpoint (Streamable HTTP) at `https://api.kaiv.ai/api/bridge/mcp/<slug>`. AI clients call it with MCP, not with this API. Claude, ChatGPT, Cursor, VS Code and Claude Code can sign in to it with OAuth 2.1 instead of a key.\n\nLimits: 120 requests a minute per address; 60 document uploads a minute and 12 credential tests a minute per workspace.\n\nThe CLI's source is public: https://github.com/ai-kaiva/bridge",
    "contact": { "name": "Kaiva support", "email": "support@kaiv.ai", "url": "https://kaiv.ai/bridge/docs" },
    "termsOfService": "https://kaiv.ai/terms"
  },
  "externalDocs": { "description": "Kaiva Bridge docs", "url": "https://kaiv.ai/bridge/docs" },
  "servers": [{ "url": "https://api.kaiv.ai/api/bridge/v1", "description": "Production" }],
  "security": [{ "managementKey": [] }],
  "tags": [
    { "name": "Account", "description": "Who the key belongs to, and signing out." },
    { "name": "Servers", "description": "Create a managed MCP server from a source, choose its tools and publish it." },
    { "name": "Credentials", "description": "The credential Bridge sends to your API or MCP server. Stored encrypted, never shown to an AI client." },
    { "name": "Changes", "description": "Tool changes found when a source is read again, held until they are accepted." },
    { "name": "Documents", "description": "A server that makes uploaded documents searchable." },
    { "name": "Keys", "description": "Gateway keys for AI clients that use a key instead of signing in." },
    { "name": "Policies", "description": "Rules for which tools a key may call." },
    { "name": "Activity", "description": "Tool-call records and usage figures." },
    { "name": "No account", "description": "A trial MCP server from a public spec, with no account." }
  ],
  "paths": {
    "/whoami": {
      "get": {
        "tags": ["Account"], "operationId": "whoami", "summary": "Show the workspace and person a management key belongs to",
        "responses": {
          "200": { "description": "The key's workspace, the person who created it and the key's label.", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "workspace": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" } } },
              "user": { "type": "object", "properties": { "email": { "type": ["string", "null"] } } },
              "key": { "type": "object", "properties": { "label": { "type": "string" }, "prefix": { "type": ["string", "null"] }, "createdAt": { "type": ["string", "null"], "format": "date-time" } } }
            }
          } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/session": {
      "delete": {
        "tags": ["Account"], "operationId": "signOut", "summary": "Revoke the management key sent with this request",
        "description": "Revokes only the key presented. Other machines stay signed in.",
        "responses": {
          "200": { "description": "Whether the key was revoked.", "content": { "application/json": { "schema": { "type": "object", "properties": { "revoked": { "type": "boolean" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/servers": {
      "get": {
        "tags": ["Servers"], "operationId": "listServers", "summary": "List the workspace's MCP servers",
        "responses": {
          "200": { "description": "Every server in the workspace.", "content": { "application/json": { "schema": { "type": "object", "properties": { "servers": { "type": "array", "items": { "$ref": "#/components/schemas/Server" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Servers"], "operationId": "createServer", "summary": "Create an MCP server from an OpenAPI spec, a GraphQL API, a database or a remote MCP server",
        "description": "Creates a draft server. Read its tools with `POST /servers/{id}/introspect`, choose the tools, then publish. Every tool starts switched off; database tools only read.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["sourceType"],
          "properties": {
            "name": { "type": "string", "description": "A short name. It becomes part of the server's address." },
            "sourceType": { "type": "string", "enum": ["openapi", "graphql", "postgres", "mysql", "sqlserver", "mcp"] },
            "sourceUrl": { "type": "string", "description": "The spec's link (openapi), the GraphQL endpoint (graphql), the remote MCP server's address (mcp), or the connection string (postgres, mysql, sqlserver)." },
            "sourceContent": { "type": "string", "description": "An OpenAPI or Swagger spec as text, instead of sourceUrl. Send apiBase with it." },
            "apiBase": { "type": "string", "format": "uri", "description": "The API's address, when the spec does not name one or names the wrong one." },
            "auth": { "$ref": "#/components/schemas/Credential" }
          }
        } } } },
        "responses": {
          "201": { "description": "The draft server.", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": { "$ref": "#/components/schemas/Server" } }, "additionalProperties": true } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      }
    },
    "/servers/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "get": {
        "tags": ["Servers"], "operationId": "getServer", "summary": "Get one server with its tools and their input schemas",
        "responses": {
          "200": { "description": "The server and its tools.", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": { "$ref": "#/components/schemas/Server" }, "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } } }, "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["Servers"], "operationId": "deleteServer", "summary": "Delete a server",
        "description": "Its address stops working at once.",
        "responses": {
          "200": { "description": "Whether a server was deleted.", "content": { "application/json": { "schema": { "type": "object", "properties": { "deleted": {} } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/servers/{id}/spec": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "put": {
        "tags": ["Servers"], "operationId": "replaceSpec", "summary": "Replace a pasted OpenAPI spec",
        "description": "For a server created from spec text. Read the tools again afterwards with `POST /servers/{id}/introspect`.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["sourceContent"], "properties": { "sourceContent": { "type": "string" }, "apiBase": { "type": "string", "format": "uri" } } } } } },
        "responses": {
          "200": { "description": "The server.", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": { "$ref": "#/components/schemas/Server" } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/servers/{id}/introspect": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "post": {
        "tags": ["Servers"], "operationId": "introspectServer", "summary": "Read the source and register its tools",
        "description": "Reads the spec, schema, database or remote server and creates one tool per operation, query or table. On a live server, a tool whose meaning changed is held until the change is accepted (see Changes). Tools that were exposed stay exposed.",
        "responses": {
          "200": { "description": "The tools found.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "502": { "$ref": "#/components/responses/SourceError" }
        }
      }
    },
    "/servers/{id}/tls": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "patch": {
        "tags": ["Servers"], "operationId": "setDatabaseTls", "summary": "Set how a database's certificate is checked",
        "description": "Check the certificate against a CA you provide (tested before it is saved), or connect without checking it (the connection is still encrypted), which needs `acknowledge: true`.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["mode"], "properties": { "mode": { "type": "string", "enum": ["verify", "skip"] }, "caPem": { "type": "string", "description": "The CA certificate in PEM, for verify." }, "acknowledge": { "type": "boolean", "description": "Required to skip the check." } } } } } },
        "responses": {
          "200": { "description": "The server.", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": { "$ref": "#/components/schemas/Server" } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/servers/{id}/api-base": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "patch": {
        "tags": ["Servers"], "operationId": "setApiBase", "summary": "Change the address of the API a server calls",
        "description": "A saved credential for a different host is removed, so it is never sent to an address it was not given for.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["apiBase"], "properties": { "apiBase": { "type": "string", "format": "uri" } } } } } },
        "responses": {
          "200": { "description": "The server and what changed.", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": { "$ref": "#/components/schemas/Server" }, "changes": {} } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/servers/{id}/tools": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "patch": {
        "tags": ["Servers"], "operationId": "setToolsExposed", "summary": "Expose or hide several tools at once",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["toolIds", "exposed"], "properties": { "toolIds": { "type": "array", "items": { "type": "string" } }, "exposed": { "type": "boolean" } } } } } },
        "responses": {
          "200": { "description": "The result.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/servers/{id}/tools/{toolId}": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }, { "$ref": "#/components/parameters/ToolId" }],
      "patch": {
        "tags": ["Servers"], "operationId": "setToolExposed", "summary": "Expose or hide one tool",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["exposed"], "properties": { "exposed": { "type": "boolean" } } } } } },
        "responses": {
          "200": { "description": "The tool.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/servers/{id}/publish": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "post": {
        "tags": ["Servers"], "operationId": "publishServer", "summary": "Publish a server so its MCP address goes live",
        "description": "The server answers at `https://api.kaiv.ai/api/bridge/mcp/<slug>` with the tools that are exposed. The plan's server count is checked here.",
        "responses": {
          "200": { "description": "The published server.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "description": "The plan's server limit is reached.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/servers/{id}/upstream-auth": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "put": {
        "tags": ["Credentials"], "operationId": "setServerCredential", "summary": "Save or clear the credential sent to your API or MCP server",
        "description": "For openapi, graphql and mcp servers. Stored encrypted and sent only to the server's own source. Send `auth: null` to clear it.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["auth"], "properties": { "auth": { "oneOf": [{ "$ref": "#/components/schemas/Credential" }, { "type": "null" }] } } } } } },
        "responses": {
          "200": { "description": "The saved state and the server.", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": {} }, "additionalProperties": true } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/servers/{id}/upstream-auth/test": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "post": {
        "tags": ["Credentials"], "operationId": "testServerCredential", "summary": "Test the saved credential with one read call to your API",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "toolId": { "type": ["string", "null"], "description": "A read tool to call. Bridge picks one when this is left out." } } } } } },
        "responses": {
          "200": { "description": "What the call showed about the API and the credential.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/servers/{id}/revisions": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "get": {
        "tags": ["Changes"], "operationId": "listRevisions", "summary": "List a server's tool changes, newest first",
        "parameters": [{ "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } }],
        "responses": {
          "200": { "description": "The changes.", "content": { "application/json": { "schema": { "type": "object", "properties": { "revisions": { "type": "array", "items": { "type": "object", "additionalProperties": true } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/servers/{id}/revisions/{revId}/promote": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }, { "$ref": "#/components/parameters/RevisionId" }],
      "post": {
        "tags": ["Changes"], "operationId": "promoteRevision", "summary": "Accept a held tool change so AI clients get it",
        "responses": {
          "200": { "description": "The result and the server.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/servers/{id}/revisions/{revId}/reject": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }, { "$ref": "#/components/parameters/RevisionId" }],
      "post": {
        "tags": ["Changes"], "operationId": "rejectRevision", "summary": "Reject a held tool change and keep the current tools",
        "responses": {
          "200": { "description": "The result and the server.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/servers/{id}/auto-promote": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "post": {
        "tags": ["Changes"], "operationId": "setAutoPromote", "summary": "Accept tool changes automatically, or hold them",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["enabled"], "properties": { "enabled": { "type": "boolean" } } } } } },
        "responses": {
          "200": { "description": "The result and the server.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/servers/{id}/tools/{toolId}/invoke": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }, { "$ref": "#/components/parameters/ToolId" }],
      "post": {
        "tags": ["Servers"], "operationId": "invokeTool", "summary": "Call one tool to test it",
        "description": "Runs the same path an AI client's call takes: the saved credential, the plan's answer size and the tool-call record. Recorded as a test call, so it never counts as an AI client's call. With `readOnly: true` only a tool that reads is run.",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "arguments": { "type": "object", "additionalProperties": true }, "readOnly": { "type": "boolean" } } } } } },
        "responses": {
          "200": { "description": "The tool's answer.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/documents/servers": {
      "post": {
        "tags": ["Documents"], "operationId": "createDocumentsServer", "summary": "Create a documents server",
        "description": "It goes live with its first document that is read.",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" } } } } } },
        "responses": {
          "201": { "description": "The documents server.", "content": { "application/json": { "schema": { "type": "object", "properties": { "server": { "$ref": "#/components/schemas/Server" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/documents/servers/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "get": {
        "tags": ["Documents"], "operationId": "listDocuments", "summary": "List a documents server's files and how each is getting on",
        "description": "Each document is queued, being read (page n of m), ready or failed.",
        "responses": {
          "200": { "description": "The server's documents.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/documents/servers/{id}/documents": {
      "parameters": [{ "$ref": "#/components/parameters/ServerId" }],
      "post": {
        "tags": ["Documents"], "operationId": "uploadDocument", "summary": "Upload one document",
        "description": "A PDF, Word (.docx), PNG, JPEG, text or Markdown file, up to 25 MB. A file with the same name replaces that document once the new version is read.",
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": ["file"], "properties": { "file": { "type": "string", "format": "binary" } } } } } },
        "responses": {
          "202": { "description": "Accepted; it is read in the background.", "content": { "application/json": { "schema": { "type": "object", "properties": { "document": { "type": "object", "additionalProperties": true } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "413": { "description": "The file is larger than 25 MB.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/keys": {
      "get": {
        "tags": ["Keys"], "operationId": "listKeys", "summary": "List the workspace's keys",
        "responses": {
          "200": { "description": "Keys, without their secret (only a prefix is kept for display).", "content": { "application/json": { "schema": { "type": "object", "properties": { "keys": { "type": "array", "items": { "type": "object", "additionalProperties": true } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Keys"], "operationId": "createGatewayKey", "summary": "Create a gateway key for an AI client that uses a key",
        "description": "A gateway key calls the MCP servers it names. The key is returned once; Bridge keeps only its hash. This API never creates a management key.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["label", "serverIds"],
          "properties": {
            "label": { "type": "string" },
            "serverIds": { "type": "array", "items": { "type": "string" }, "description": "The servers this key may call." },
            "policyId": { "type": "string", "description": "A policy that limits which tools the key may call." }
          }
        } } } },
        "responses": {
          "201": { "description": "The new key, shown once.", "content": { "application/json": { "schema": { "type": "object", "properties": { "key": { "type": "object", "properties": { "key": { "type": "string", "description": "kv_live_..., shown only in this answer." } }, "additionalProperties": true } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/keys/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
      "delete": {
        "tags": ["Keys"], "operationId": "revokeKey", "summary": "Revoke a key",
        "description": "Requests with it are refused at once.",
        "responses": {
          "200": { "description": "Revoked.", "content": { "application/json": { "schema": { "type": "object", "properties": { "revoked": {} } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/policies": {
      "get": {
        "tags": ["Policies"], "operationId": "listPolicies", "summary": "List policies",
        "responses": {
          "200": { "description": "The workspace's policies.", "content": { "application/json": { "schema": { "type": "object", "properties": { "policies": { "type": "array", "items": { "type": "object", "additionalProperties": true } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Policies"], "operationId": "createPolicy", "summary": "Create a policy",
        "description": "Rules allow, hold for review or deny tool calls by tool name and, optionally, an argument value. A tool no rule allows is denied.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["name", "rules"],
          "properties": {
            "name": { "type": "string" },
            "subjects": { "type": "array", "items": { "type": "string" }, "description": "Who the policy applies to: key names, or the email addresses of people who sign in (*@yourcompany.com covers a domain). Empty means everyone." },
            "rules": { "type": "array", "items": { "type": "object", "properties": { "effect": { "type": "string", "enum": ["allow", "review", "deny"] }, "tool": { "type": "string", "description": "A tool name, or a pattern such as get_*." } }, "additionalProperties": true } }
          }
        } } } },
        "responses": {
          "201": { "description": "The policy.", "content": { "application/json": { "schema": { "type": "object", "properties": { "policy": { "type": "object", "additionalProperties": true } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/audit": {
      "get": {
        "tags": ["Activity"], "operationId": "listToolCalls", "summary": "List tool calls, newest first",
        "description": "Which key called which tool, when, the decision, how long it took and the size of the answer. Arguments are kept only in redacted form.",
        "parameters": [
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } },
          { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0 } }
        ],
        "responses": {
          "200": { "description": "Tool calls and the total.", "content": { "application/json": { "schema": { "type": "object", "properties": { "entries": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "total": { "type": "integer" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/metrics": {
      "get": {
        "tags": ["Activity"], "operationId": "getMetrics", "summary": "Get tool-call figures for the workspace",
        "parameters": [{ "name": "range", "in": "query", "required": false, "schema": { "type": "string", "enum": ["1h", "24h", "7d", "30d"], "default": "24h" } }],
        "responses": {
          "200": { "description": "Calls, errors and latency for the range.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/claimable": {
      "servers": [{ "url": "https://api.kaiv.ai/api/bridge", "description": "Production, no account" }],
      "post": {
        "tags": ["No account"], "operationId": "createTrialServer", "summary": "Create a trial MCP server from a public OpenAPI spec, with no account",
        "description": "For AI agents. Returns a live MCP server with the spec's GET operations as tools, a key for that one server, and a link a person opens to keep it. Unclaimed servers are deleted after 72 hours or 500 tool calls. Never send a credential; the spec must be public. Full rules: https://kaiv.ai/bridge/auth.md",
        "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "specUrl": { "type": "string", "format": "uri", "description": "A link to a public OpenAPI or Swagger spec. Send this or spec, not both." },
            "spec": { "type": "string", "description": "The spec's text (900 KB at most)." },
            "name": { "type": "string" }
          }
        } } } },
        "responses": {
          "201": { "description": "The trial server.", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "mcpUrl": { "type": "string", "format": "uri", "description": "The server's MCP address." },
              "apiKey": { "type": "string", "description": "Shown once. Send as Authorization: Bearer <apiKey>." },
              "claimUrl": { "type": "string", "format": "uri", "description": "For the person. Whoever opens it can take the server." },
              "expiresAt": { "type": "string", "format": "date-time" },
              "tools": { "type": "array", "items": { "type": "object", "additionalProperties": true } }
            },
            "additionalProperties": true
          } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "413": { "description": "The spec text is too large; send its link.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "The spec has no GET operations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "description": "Busy or paused; try again later, or sign up.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "managementKey": {
        "type": "http", "scheme": "bearer", "bearerFormat": "kv_mgmt_",
        "description": "A management key (kv_mgmt_...). Create one in the console under Keys & access, or run `npx @kaiva/bridge login`."
      }
    },
    "parameters": {
      "ServerId": { "name": "id", "in": "path", "required": true, "description": "The server's id.", "schema": { "type": "string" } },
      "ToolId": { "name": "toolId", "in": "path", "required": true, "description": "The tool's id.", "schema": { "type": "string" } },
      "RevisionId": { "name": "revId", "in": "path", "required": true, "description": "The change's id.", "schema": { "type": "string" } }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string", "description": "A short code, or a sentence for errors in your request." }, "message": { "type": "string" } }
      },
      "Credential": {
        "description": "What Bridge sends to your API or MCP server. Stored encrypted; never shown to an AI client.",
        "oneOf": [
          { "type": "object", "title": "Bearer token", "required": ["type", "token"], "properties": { "type": { "const": "bearer" }, "token": { "type": "string" } } },
          { "type": "object", "title": "Username and password", "required": ["type", "username"], "properties": { "type": { "const": "basic" }, "username": { "type": "string" }, "password": { "type": "string" } } },
          { "type": "object", "title": "Header", "required": ["type", "name", "value"], "properties": { "type": { "const": "header" }, "name": { "type": "string", "examples": ["X-API-Key"] }, "value": { "type": "string" } } },
          { "type": "object", "title": "Query parameter", "required": ["type", "name", "value"], "properties": { "type": { "const": "query" }, "name": { "type": "string", "examples": ["api_key"] }, "value": { "type": "string" } } }
        ]
      },
      "Server": {
        "type": "object",
        "description": "An MCP server. A published server's MCP address is https://api.kaiv.ai/api/bridge/mcp/<slug>.",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "source_type": { "type": "string", "enum": ["openapi", "graphql", "postgres", "mysql", "sqlserver", "mcp", "dataset", "documents"] },
          "state": { "type": "string", "description": "draft, live and so on." }
        },
        "additionalProperties": true
      },
      "Tool": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "method": { "type": "string" },
          "exposed": { "type": "boolean" },
          "input_schema": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": true
      }
    },
    "responses": {
      "BadRequest": { "description": "The request is missing something or has a value Bridge cannot use; the answer says which.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "No key, an unknown key, or a gateway key (kv_live_) where a management key is needed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "No such item in this workspace.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Conflict": { "description": "A name already in use, or the same request a moment ago.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "Too many requests; wait a minute.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "SourceError": { "description": "Your source could not be read; the answer says what to fix.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
