{ "openapi": "3.1.0", "info": { "title": "MCPJam API", "version": "1.0.0-preview", "description": "Programmatic access to MCP servers saved in your MCPJam projects \u2014 live diagnostics (validate, inspect, export) and operations: call tools, render prompts, run eval suites asynchronously and poll their results, and import OAuth tokens.\n\n**The API is in preview**: the surface may change while we finish the design. Error `code` values are stable; error `message` strings are not. Write clients that ignore unknown response fields.", "contact": { "name": "MCPJam", "url": "https://github.com/MCPJam/inspector/issues" } }, "servers": [ { "url": "https://app.mcpjam.com/api/v1", "description": "Hosted MCPJam" } ], "security": [ { "bearerAuth": [] } ], "tags": [ { "name": "Hosts", "description": "Project hosts: named model + capability profiles you run chats and eval suites against." }, { "name": "Environments", "description": "Project environments: named, live-editable execution bundles (one host, an optional standalone server group, optionally pinned skills and plugin versions) that eval suites and journeys run against. Distinct from Sandbox images, which are Computer base images. Reads require project membership; every write requires project admin." }, { "name": "Plugins", "description": "Agent Plugins imported into a project \u2014 read-only inventory and version detail." }, { "name": "Sandbox images", "description": "Custom Computer images: a digest-pinned Dockerfile built into an immutable image your project's computers boot from." }, { "name": "Server diagnostics", "description": "Connect-level health checks against a saved MCP server." }, { "name": "Primitives", "description": "The server's MCP primitives: tools, prompts, and resources." }, { "name": "Export", "description": "Full-server snapshots for diffing and CI." }, { "name": "Execution", "description": "Run the server's primitives: call tools, render prompts." }, { "name": "Eval runs", "description": "Asynchronous eval suite runs: create with 202, poll status, iterations, and traces." }, { "name": "Server connections", "description": "Connect an MCP server URL to a project, authorizing in a browser when the server requires it." }, { "name": "OAuth", "description": "Bring-your-own OAuth: import externally obtained tokens for a server." }, { "name": "Chatboxes", "description": "Read-only access to the chatboxes published from a project: listing, settings, attached servers, and share links." }, { "name": "Catalog", "description": "Discover the resources the other routes operate on: your account, projects, servers, eval suites, and chat sessions." }, { "name": "Tunnels", "description": "Relay tunnels that expose local MCP servers through a public URL, registered as first-class project servers (the `mcpjam tunnel` CLI flow)." }, { "name": "Agent", "description": "Headless agent turns over the public API: send a message history, the server runs one assistant turn with project-scoped workspace tools (eval reads + suite creation) on a pinned hosted model, and returns the reply plus created-resource references." }, { "name": "Swarms", "description": "Personas, journeys and swarm containers \u2014 the authoring half of Swarms \u2014 plus the model-backed generation that drafts them." }, { "name": "Swarm runs", "description": "Launching journeys and reading what they produced. Launching SPENDS \u2014 see the per-operation notes." }, { "name": "Swarm insights", "description": "What a swarm run revealed. The scorecard and findings are deterministic and free; requesting wave insights runs models and draws on your shared daily ledger." }, { "name": "User testing", "description": "Publishing an environment for real visitors, and controlling who can reach it. Several of these NARROW access and take effect immediately." } ], "paths": { "/host-catalog": { "get": { "operationId": "getHostCompatCatalog", "tags": [ "Catalog" ], "summary": "Get the host-compat catalog", "description": "The versioned host-compatibility catalog backing `mcpjam compat` verdicts. Public and unauthenticated: static host facts and creation config with no project or user scope. Always returns a catalog \u2014 `source` is `live` when the backend publish was reachable and `bundled` when serving the SDK's built-in fallback.", "security": [], "responses": { "200": { "description": "The latest catalog envelope.", "content": { "application/json": { "schema": { "type": "object", "required": [ "schemaVersion", "version", "contentHash", "publishedAt", "catalog", "source" ], "properties": { "schemaVersion": { "type": "integer", "description": "Catalog document schema version (currently 2)." }, "version": { "type": "integer", "description": "Monotonic backend publish version; 0 when `source` is `bundled`." }, "contentHash": { "type": "string", "description": "SHA-256 of the canonical catalog JSON; empty when `source` is `bundled`." }, "publishedAt": { "type": "integer", "description": "Publish time (ms epoch); 0 when `source` is `bundled`." }, "source": { "type": "string", "enum": [ "live", "bundled" ], "description": "Whether this envelope came from the backend publish or the SDK's bundled fallback." }, "catalog": { "type": "object", "required": [ "hostsById" ], "properties": { "hostsById": { "type": "object", "description": "Canonical host catalog keyed by host style. Each entry is the full host object: compare/display facts plus HostConfigInputV2 creation fields.", "additionalProperties": { "type": "object", "required": [ "id", "label", "provenance", "rendersMcpApps", "hostStyle" ], "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "provenance": { "type": "string", "enum": [ "observed", "probe", "vendor-doc", "assumed" ] }, "rendersMcpApps": { "type": "boolean" }, "supportedProtocolVersions": { "type": "array", "items": { "type": "string" } }, "verifiedAt": { "type": "integer", "description": "When this host's facts were last verified (ms epoch)." }, "imageSupport": { "type": "object", "additionalProperties": true }, "hostStyle": { "type": "string" }, "mcpProfile": { "type": "object", "description": "Host MCP profile. MCP Apps capabilities and OpenAI compat live here.", "additionalProperties": true } }, "additionalProperties": true } } } } } } } } } } } }, "/harness/{harnessId}/builtin-tools": { "get": { "operationId": "listHarnessBuiltinTools", "tags": [ "Harness" ], "summary": "List a harness's native built-in tools", "description": "The native tools an agent harness (e.g. `claude-code`) runs INSIDE its sandbox \u2014 Bash, Read, Edit, Glob, Grep, WebSearch, and the like. Display-only: these execute via the harness's own agent loop and are NOT callable through MCPJam. Static published-package metadata; no project scope.", "parameters": [ { "name": "harnessId", "in": "path", "required": true, "schema": { "type": "string", "enum": [ "claude-code" ] }, "description": "The harness id." } ], "responses": { "200": { "description": "The harness's built-in tools.", "content": { "application/json": { "schema": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "type": "object", "required": [ "key", "name" ], "properties": { "key": { "type": "string" }, "name": { "type": "string" }, "commonName": { "type": "string" }, "toolUseKind": { "type": "string" }, "description": { "type": "string" }, "inputSchema": { "type": "object", "additionalProperties": true } } } } } } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/images": { "get": { "operationId": "listSandboxImages", "tags": [ "Sandbox images" ], "summary": "List a project's sandbox images", "description": "The custom Computer images (digest-pinned Dockerfiles built into immutable images) saved in the project \u2014 your own personal drafts plus the project-shared ones.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's sandbox images.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImagePage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createSandboxImage", "tags": [ "Sandbox images" ], "summary": "Create a sandbox image", "description": "Create a personal-draft sandbox image from a blueprint and respond `201` with its detail. Build it (`POST .../build`) before a computer can boot from it. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageCreateRequest" }, "example": { "name": "scraper", "blueprint": "base: debian:bookworm-slim@sha256:\ninitialize:\n - name: Install tooling\n run: apt-get update && apt-get install -y git\nknowledge:\n - name: Test notes\n contents: Run `make test` before pushing.\n" } } } }, "responses": { "201": { "description": "The sandbox image was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}": { "get": { "operationId": "getSandboxImage", "tags": [ "Sandbox images" ], "summary": "Get a sandbox image", "description": "One sandbox image's blueprint, sharing, and latest build. An image that exists but belongs to a different project reads as `404 NOT_FOUND`.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "responses": { "200": { "description": "The sandbox image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateSandboxImage", "tags": [ "Sandbox images" ], "summary": "Update a sandbox image", "description": "Edit a sandbox image's name and/or Dockerfile. Re-build it for the changes to take effect on a computer. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageUpdateRequest" }, "example": { "name": "scraper (v2)" } } } }, "responses": { "200": { "description": "The updated sandbox image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteSandboxImage", "tags": [ "Sandbox images" ], "summary": "Delete a sandbox image", "description": "Permanently delete a sandbox image. Computers booted from it fall back to the base image. Deleting a project-shared image requires project admin. Bodyless \u2014 any field is rejected. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "responses": { "200": { "description": "The sandbox image was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageDeleted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/builds": { "get": { "operationId": "listSandboxImageBuilds", "tags": [ "Sandbox images" ], "summary": "List a sandbox image's builds", "description": "A sandbox image's builds, newest first, each with its status and capped log preview.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "responses": { "200": { "description": "The sandbox image's builds.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBuildPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/build": { "post": { "operationId": "buildSandboxImage", "tags": [ "Sandbox images" ], "summary": "Build a sandbox image", "description": "Trigger a build of the sandbox image's image and respond `202`. The build runs asynchronously \u2014 poll the builds list for status. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "202": { "description": "The build was accepted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBuildStarted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/promote": { "post": { "operationId": "promoteSandboxImage", "tags": [ "Sandbox images" ], "summary": "Share a sandbox image with the project", "description": "Promote a personal-draft sandbox image to a project-shared one. Requires project admin. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "The now-shared sandbox image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/images/{imageId}/use": { "post": { "operationId": "useSandboxImage", "tags": [ "Sandbox images" ], "summary": "Boot your computer from a sandbox image", "description": "Attach the sandbox image to the caller's computer, which re-provisions it from the pinned image (installed files are wiped). The image must have a `ready` build, and the computer provider must match the build's. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/imageId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "The sandbox image was attached; the computer is re-provisioning.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ComputerAttached" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/computer/reset": { "post": { "operationId": "resetComputer", "tags": [ "Sandbox images" ], "summary": "Reset your computer to its image", "description": "Reset the caller's computer back to its current image, wiping mutable state. Bodyless. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "The reset was requested.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ComputerReset" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/hosts": { "get": { "operationId": "listHosts", "tags": [ "Hosts" ], "summary": "List a project's hosts", "description": "The hosts saved in the project \u2014 the named model + capability profiles you attach to chats and eval suites. Returns the `id`s the host detail/update/delete routes take.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's hosts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createHost", "tags": [ "Hosts" ], "summary": "Create a host (from a template or a full config)", "description": "Creates a host in the project and responds `201` with the new host's detail. Seed the host config one of two ways: pass `template` (a built-in template id, optional `theme`) to seed server-side from the live backend host catalog, falling back to the bundled SDK catalog snapshot if the live catalog is unavailable, OR pass `config` (a full host config v2). Exactly one of `template` or `config` is required.\n\nGuest callers are denied (host creation is a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostCreateRequest" }, "example": { "name": "Claude", "template": "claude", "theme": "dark" } } } }, "responses": { "201": { "description": "The host was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/hosts/{hostId}": { "get": { "operationId": "getHost", "tags": [ "Hosts" ], "summary": "Get a host", "description": "One host's full settings, including its resolved host config. A host that exists but belongs to a different project reads as `404 NOT_FOUND`.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "responses": { "200": { "description": "The host settings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateHost", "tags": [ "Hosts" ], "summary": "Update a host", "description": "Edit a host's display name and/or its host config. Only the fields you pass change. Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostUpdateRequest" }, "example": { "name": "Claude (renamed)" } } } }, "responses": { "200": { "description": "The updated host settings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteHost", "tags": [ "Hosts" ], "summary": "Delete a host", "description": "Permanently delete a host from the project. Pass `{ \"force\": true }` to delete a host that is still referenced (e.g. by an eval suite). Guest callers are denied (a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "force": { "type": "boolean", "description": "Delete even if the host is still referenced." } } } } } }, "responses": { "200": { "description": "The host was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDeleted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/servers/{serverId}/validate": { "post": { "operationId": "validateServer", "tags": [ "Server diagnostics" ], "summary": "Validate a server", "description": "Connects to the server, initializes the MCP session, and returns a connection snapshot (server info, negotiated capabilities). The same check the hosted inspector runs when you connect a server.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Server connected and initialized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidateResult" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/doctor": { "post": { "operationId": "runDoctor", "tags": [ "Server diagnostics" ], "summary": "Run the doctor", "description": "Runs the full doctor workflow \u2014 probe \u2192 connect \u2192 initialize \u2192 capabilities \u2192 primitives \u2014 and returns a step-by-step report. The richest signal for \"is this server healthy, and why not.\"", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Doctor report. `status` summarizes the outcome; per-step detail is in `checks`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DoctorReport" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/check-oauth": { "post": { "operationId": "checkOAuth", "tags": [ "Server diagnostics" ], "summary": "Check OAuth requirement", "description": "Lightweight, no MCP connection: reports whether the server is configured to require an OAuth grant.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "OAuth requirement for the server.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckOAuthResult" }, "example": { "useOAuth": true, "serverUrl": "https://mcp.example.com/mcp" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/servers/{serverId}/tools": { "post": { "operationId": "listTools", "tags": [ "Primitives" ], "summary": "List tools", "description": "Returns the server's tools as a collection page. Each item is the MCP tool definition (`name`, `description`, `inputSchema`, ...).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "One page of tools.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ToolPage" }, "example": { "items": [ { "name": "create_issue", "description": "Create a new issue in the tracker", "inputSchema": { "type": "object", "properties": { "title": { "type": "string" } } } } ] } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/prompts": { "post": { "operationId": "listPrompts", "tags": [ "Primitives" ], "summary": "List prompts", "description": "Returns the server's prompts as a collection page.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "One page of prompts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PromptPage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/resources": { "post": { "operationId": "listResources", "tags": [ "Primitives" ], "summary": "List resources", "description": "Returns the server's resources as a collection page.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "One page of resources.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResourcePage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/resources/read": { "post": { "operationId": "readResource", "tags": [ "Primitives" ], "summary": "Read a resource", "description": "Reads a single resource by URI and returns its contents.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "uri" ], "properties": { "uri": { "type": "string", "description": "The resource URI, exactly as returned by the resources list.", "example": "file:///readme.md" } } } } } }, "responses": { "200": { "description": "The resource contents.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResourceContents" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/export": { "post": { "operationId": "exportServer", "tags": [ "Export" ], "summary": "Export a server snapshot", "description": "Lists tools, resources, and prompts in one call and returns a single JSON snapshot \u2014 handy for diffing a server's surface over time in CI.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Full server snapshot.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExportSnapshot" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/tools/call": { "post": { "operationId": "callTool", "tags": [ "Execution" ], "summary": "Call a tool", "description": "Executes a tool on the server and returns the MCP `CallToolResult` directly. Tool-level failures (`isError: true` in the result) are **successful calls** \u2014 the server answered; only transport/auth errors use the error envelope.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "toolName" ], "properties": { "toolName": { "type": "string", "description": "Tool name, exactly as returned by the tools list." }, "parameters": { "type": "object", "description": "Tool arguments matching the tool's `inputSchema`. Defaults to `{}`.", "additionalProperties": true } } }, "example": { "toolName": "create_issue", "parameters": { "title": "Bug: login fails" } } } } }, "responses": { "200": { "description": "The MCP `CallToolResult`. Check `isError` for tool-level failures.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CallToolResult" }, "example": { "content": [ { "type": "text", "text": "Issue created: #42" } ] } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/servers/{serverId}/prompts/get": { "post": { "operationId": "getPrompt", "tags": [ "Execution" ], "summary": "Render a prompt", "description": "Renders a prompt with arguments and returns the MCP `GetPromptResult` directly (`description?`, `messages`).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "promptName" ], "properties": { "promptName": { "type": "string", "description": "Prompt name, exactly as returned by the prompts list." }, "arguments": { "type": "object", "description": "Prompt arguments. String, number, and boolean values.", "additionalProperties": { "type": [ "string", "number", "boolean" ] } } } }, "example": { "promptName": "summarize", "arguments": { "style": "bullet" } } } } }, "responses": { "200": { "description": "The MCP `GetPromptResult`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetPromptResult" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-runs": { "post": { "operationId": "createEvalRun", "tags": [ "Eval runs" ], "summary": "Create an eval run (async)", "description": "Creates a suite run from an existing `suiteId` (rerun) and/or inline `tests`, then **detaches execution and responds `202` immediately** with the `runId`. Validation and quota errors surface on this request; poll `GET /eval-runs/{runId}` for progress. The run appears live in the hosted UI Runs tab, tagged `source: \"api\"`.\n\nA bare `suiteId` with no inline tests reruns the suite as configured. Per-organization concurrency is capped (default 2 concurrent runs); exceeding it returns `429` with `details.reason: \"CONCURRENT_RUN_LIMIT\"`.\n\nFor a suite with attached project environments, pass `environmentId` to choose which one the run uses; the `202` echoes the resolved `environment` triple, and `GET /eval-runs/{runId}` reports the same triple for the life of the run.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunCreateRequest" }, "example": { "suiteName": "smoke", "serverIds": [ "srv_abc123" ], "tests": [ { "title": "echo works", "runs": 1, "model": "anthropic/claude-haiku-4.5", "provider": "anthropic", "steps": [ { "id": "s1", "kind": "prompt", "prompt": "Use the echo tool to say hi" }, { "id": "s2", "kind": "assert", "assertion": { "type": "toolCalledWith", "toolName": "echo", "args": { "args": { "text": "hi" } } } } ] } ] } } } }, "responses": { "202": { "description": "Run created; execution continues in the background.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunCreated" }, "example": { "runId": "run_abc123", "suiteId": "suite_def456", "status": "running", "caseUpsert": { "committed": [ { "name": "echo works" } ], "failed": [] }, "environment": null } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-runs/{runId}": { "get": { "operationId": "getEvalRun", "tags": [ "Eval runs" ], "summary": "Get run status", "description": "Run status, result, and summary. Poll until `status` is terminal (`completed`, `failed`, or `cancelled`).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "responses": { "200": { "description": "The run.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRun" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/iterations": { "get": { "operationId": "listEvalRunIterations", "tags": [ "Eval runs" ], "summary": "List run iterations", "description": "Per-iteration results: actual tool calls, structured token usage, and latency. Cursor-paginated.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" }, { "name": "limit", "in": "query", "required": false, "description": "Page size, 1\u2013200. Defaults to 50.", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }, { "name": "cursor", "in": "query", "required": false, "description": "Opaque cursor from a previous response's `nextCursor`.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "One page of iterations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIterationPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/iterations/{iterationId}/trace": { "get": { "operationId": "getEvalIterationTrace", "tags": [ "Eval runs" ], "summary": "Get an iteration trace", "description": "Full trace envelope for one iteration: conversation messages, expected-vs-actual tool call analysis, and spans. The shape is rich and may evolve \u2014 treat it as an open document. Returns `404` with `details.reason: \"TRACE_NOT_AVAILABLE\"` when the iteration finished without a stored trace.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" }, { "$ref": "#/components/parameters/iterationId" } ], "responses": { "200": { "description": "The trace envelope.", "content": { "application/json": { "schema": { "type": "object", "description": "Trace document: `messages`, `prompts` (per-prompt expected/actual tool-call analysis), `spans`, and more.", "additionalProperties": true } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/iterations/{iterationId}/steps": { "get": { "operationId": "getEvalRunSteps", "tags": [ "Eval runs" ], "summary": "Get an iteration's step results", "description": "One row per authored test step, in order, with `status` (`ok`/`fail`/`skipped`/`pending`), a `reason`, and any `evidence` (screenshots, replay-video offset, widget tool calls). The fastest way to see which step failed and why. Unlike `/trace`, a missing trace is not a `404` \u2014 step verdicts still return, just without evidence.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" }, { "$ref": "#/components/parameters/iterationId" } ], "responses": { "200": { "description": "The ordered step results as a page envelope.", "content": { "application/json": { "schema": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "description": "Authored steps in order with their verdict.", "items": { "type": "object", "required": [ "stepId", "stepIndex", "kind", "status" ], "properties": { "stepId": { "type": "string" }, "stepIndex": { "type": "integer" }, "kind": { "type": "string", "enum": [ "prompt", "toolCall", "interact", "assert" ] }, "status": { "type": "string", "enum": [ "ok", "fail", "skipped", "pending" ] }, "reason": { "type": [ "string", "null" ] }, "evidence": { "type": "object", "description": "Optional, omitted when the step produced none.", "additionalProperties": true } }, "additionalProperties": true } }, "nextCursor": { "type": "string" } }, "additionalProperties": true } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/cancel": { "post": { "operationId": "cancelEvalRun", "tags": [ "Eval runs" ], "summary": "Cancel a run", "description": "Request cancellation of an in-flight run; marks the run and its pending/running iterations `cancelled`. A no-op success when the run is already cancelled; returns `409` when the run already reached a terminal status (`completed`/`failed`/`timed_out`).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "responses": { "200": { "description": "The cancelled run.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRun" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "description": "The run already finished and cannot be cancelled." }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-suites/{suiteId}/runs": { "get": { "operationId": "listEvalSuiteRuns", "tags": [ "Eval runs" ], "summary": "List a suite's runs", "description": "Recent runs for a suite, newest first.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" }, { "name": "limit", "in": "query", "required": false, "description": "Maximum runs to return, 1\u2013100. Defaults to 25.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } } ], "responses": { "200": { "description": "Recent runs, newest first.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-suites/{suiteId}/schedule": { "patch": { "operationId": "updateEvalSuiteSchedule", "tags": [ "Eval runs" ], "summary": "Enable, repoint, or disable a suite's schedule", "description": "A schedule fires exactly ONE run per interval, so an environment-based suite must pin exactly one of its attached environments. Omitting `environmentId` on a single-environment suite means that environment; omitting it on a multi-environment suite is a VALIDATION_ERROR. Disabling preserves the existing pin. Responds with the full updated suite.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteScheduleRequest" } } } }, "responses": { "200": { "description": "The updated suite.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/servers/{serverId}/oauth/import-tokens": { "post": { "operationId": "importOAuthTokens", "tags": [ "OAuth" ], "summary": "Import OAuth tokens", "description": "Stores OAuth tokens you obtained yourself (e.g. via the SDK's `runOAuthLogin` \u2014 interactive loopback, headless, or client-credentials) for this server, scoped to your user, project, and server. Subsequent API calls against the server inject the stored access token automatically, and `401`s from the server trigger a server-side refresh \u2014 no further caller involvement.\n\nThis closes the loop after an `OAUTH_REQUIRED` error.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportTokensRequest" }, "example": { "serverUrl": "https://mcp.example.com/mcp", "clientInformation": { "clientId": "client_123" }, "tokens": { "access_token": "at_\u2026", "refresh_token": "rt_\u2026", "expires_in": 3600 } } } } }, "responses": { "200": { "description": "Tokens stored.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportTokensResult" }, "example": { "imported": true, "expiresAt": 1781142000000 } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/me": { "get": { "operationId": "getMe", "tags": [ "Catalog" ], "summary": "Get the authenticated account", "description": "The user behind the API key (keys act as their creator, scoped to one organization).", "responses": { "200": { "description": "The account.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Me" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/organizations": { "get": { "operationId": "listOrganizations", "tags": [ "Catalog" ], "summary": "List organizations", "description": "The organizations the caller belongs to — the source of the `organizationId` that `GET /projects` filters by and `POST /projects` accepts. An API key is bound to one organization and only ever sees that one. Read-only: organization, member, role and billing changes are not part of the public API.", "responses": { "200": { "description": "The caller's organizations.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects": { "get": { "operationId": "listProjects", "tags": [ "Catalog" ], "summary": "List projects", "description": "Projects the caller can access. API keys only ever see projects inside the key's organization.", "parameters": [ { "name": "organizationId", "in": "query", "required": false, "description": "Filter to one organization.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "The caller's projects.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createProject", "tags": [ "Projects" ], "summary": "Create a project", "description": "Creates a project in the caller's organization and responds `201` with its detail. Requires organization membership and counts against the plan's `maxProjects` limit. Guest callers are denied.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectCreateRequest" }, "example": { "name": "CI evals" } } } }, "responses": { "201": { "description": "The project was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Project" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/servers": { "get": { "operationId": "listProjectServers", "tags": [ "Catalog" ], "summary": "List a project's servers", "description": "The MCP servers saved in the project \u2014 the `serverId`s every other route takes. STDIO command/args/env and raw headers are never exposed.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's servers.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServerPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createProjectServer", "tags": [ "Servers" ], "summary": "Save a server into a project", "description": "Creates a saved MCP server and responds `201` with its detail. This is the provisioning entry point: every eval, host and environment route addresses servers that were created here. Names are unique per workspace, so a clash responds `409` rather than silently returning the existing row. Secret-bearing fields (`env`, `headers`, `clientSecret`) are encrypted at rest and never returned by any read.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServerCreateRequest" }, "example": { "name": "learn", "enabled": true, "transportType": "http", "url": "https://learn.mcpjam.com/mcp" } } } }, "responses": { "201": { "description": "The server was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServer" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/eval-suites": { "get": { "operationId": "listEvalSuites", "tags": [ "Catalog" ], "summary": "List a project's eval suites", "description": "Eval suites in the project with latest-run summaries and pass-rate trends \u2014 the `suiteId`s the eval-run routes take.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's eval suites.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuitePage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createEvalSuite", "tags": [ "Eval runs" ], "summary": "Create an eval suite (author-only, does not run)", "description": "Creates a runnable eval suite \u2014 the suite record plus its test cases \u2014 and responds `201` **synchronously**, WITHOUT executing anything. Use this to author a suite, then run it later with `POST /eval-runs` (passing the returned `suiteId`).\n\nThis is distinct from `POST /eval-runs`, which creates a run and **detaches execution**, responding `202` with a `runId`. There is no concurrency cap here (no run is started).\n\nThe body uses an ergonomic authoring shape: a suite-level default `model` (and optional `provider`) applies to every test unless the test overrides it; `provider` is derived from a `provider/model` id when neither is supplied. Each test's case body is an ordered `steps` array (prompt / toolCall / interact / assert).\n\nGuest callers are denied (suite creation is a write).", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteCreateRequest" }, "example": { "name": "smoke", "serverIds": [ "srv_abc123" ], "model": "anthropic/claude-haiku-4.5", "tests": [ { "title": "lists files", "steps": [ { "id": "s1", "kind": "prompt", "prompt": "List the files in the project root." }, { "id": "s2", "kind": "assert", "assertion": { "type": "toolCalledWith", "toolName": "list_files", "args": { "args": {} } } } ] } ] } } } }, "responses": { "201": { "description": "The suite was created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteCreated" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/eval-suites/{suiteId}": { "get": { "operationId": "getEvalSuite", "tags": [ "Eval runs" ], "summary": "Get one eval suite's configuration", "description": "The suite's full settings \u2014 attached environments, hosts, execution config, judge, schedule. The list route returns run rollups instead; this is the shape the edit routes read and write.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" } ], "responses": { "200": { "description": "The suite.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteDetail" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateEvalSuite", "tags": [ "Eval runs" ], "summary": "Update an eval suite's settings", "description": "Partial update \u2014 omitted fields are left as they are. `environmentIds` is tri-state: a non-empty array sets/replaces, `null` clears (reverting the suite to legacy config), and `[]` is rejected rather than silently treated as a clear. Responds with the full updated suite.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteUpdateRequest" } } } }, "responses": { "200": { "description": "The updated suite.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalSuiteDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteEvalSuite", "tags": [ "Eval runs" ], "summary": "Delete an eval suite", "description": "Deletes the suite and its cases. Past runs are not deleted.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" } ], "responses": { "200": { "description": "The suite was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalDeleted" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/eval-suites/{suiteId}/cases": { "get": { "operationId": "listEvalCases", "tags": [ "Eval runs" ], "summary": "List a suite's eval cases", "description": "Every case in the suite, in the public steps-first shape.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" } ], "responses": { "200": { "description": "The suite's cases.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCasePage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createEvalCase", "tags": [ "Eval runs" ], "summary": "Create an eval case", "description": "Adds one case to the suite. Both `title` and a non-empty `steps` array are required \u2014 `steps` is optional on the shared case shape so PATCH can be partial, but a case persisted without executable steps could never run. When `models` is omitted the suite's configured model is used.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCaseCreateRequest" }, "example": { "title": "lists files", "steps": [ { "id": "s1", "kind": "prompt", "prompt": "List the files in the project root." }, { "id": "s2", "kind": "assert", "assertion": { "type": "toolCalledWith", "toolName": "list_files", "args": { "args": {} } } } ] } } } }, "responses": { "201": { "description": "The created case.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCase" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/eval-suites/{suiteId}/cases/generate": { "post": { "operationId": "generateEvalCases", "tags": [ "Eval runs" ], "summary": "Generate eval cases from the suite's tools", "description": "Discovers the suite's server tools over a live MCP connection, generates cases against them, and persists them \u2014 the only edit route that connects to a server, and the only one that SPENDS ORG CREDITS. Synchronous: connect, generate, persist, disconnect, respond.\n\nAn environment-based suite generates against that environment's closed server set, so the cases match the tools its runs will actually see.\n\nPass `x-mcpjam-idempotency-key` to make a retry safe: drafts are recorded backend-side BEFORE any case is persisted, so a replay reuses them instead of spending credits again, and each case is persisted under a derived per-item key so the loop is resumable.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" }, { "name": "x-mcpjam-idempotency-key", "in": "header", "required": false, "schema": { "type": "string" }, "description": "Makes a retry replay the recorded drafts instead of spending credits again." } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCaseGenerateRequest" } } } }, "responses": { "200": { "description": "The generated cases.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCaseGenerated" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-suites/{suiteId}/cases/{caseId}": { "get": { "operationId": "getEvalCase", "tags": [ "Eval runs" ], "summary": "Get one eval case", "description": "A case that belongs to this suite and project. A case in another suite is a 404, not a 403.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" }, { "name": "caseId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Eval case id." } ], "responses": { "200": { "description": "The case.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCase" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateEvalCase", "tags": [ "Eval runs" ], "summary": "Update an eval case", "description": "Partial update \u2014 omitted fields are left as they are. Supplying `steps` REPLACES the case's definition rather than merging into it. `matchOptions` and `checks` accept `null` to clear the case-level override and fall back to the suite's.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" }, { "name": "caseId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Eval case id." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCaseUpdateRequest" } } } }, "responses": { "200": { "description": "The updated case.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalCase" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteEvalCase", "tags": [ "Eval runs" ], "summary": "Delete an eval case", "description": "Removes the case from the suite. Past runs that executed it are not affected.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/suiteId" }, { "name": "caseId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Eval case id." } ], "responses": { "200": { "description": "The case was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalDeleted" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/chat-sessions": { "get": { "operationId": "listChatSessions", "tags": [ "Catalog" ], "summary": "List chat sessions", "description": "Chat sessions visible to the caller (personal + project-shared merged). Top-level rather than project-nested because `projectId` is an optional filter, not an owning scope.", "parameters": [ { "name": "projectId", "in": "query", "required": false, "description": "Filter to one project.", "schema": { "type": "string" } }, { "name": "status", "in": "query", "required": false, "description": "`active` (default) or `archived`.", "schema": { "type": "string", "enum": [ "active", "archived" ], "default": "active" } }, { "name": "limit", "in": "query", "required": false, "description": "Page size, 1\u2013200. Defaults to 50.", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }, { "name": "before", "in": "query", "required": false, "description": "Cursor: the previous page's `nextCursor` (a `lastActivityAt` timestamp).", "schema": { "type": "string" } } ], "responses": { "200": { "description": "One page of chat sessions, newest activity first.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatSessionPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/chatboxes": { "get": { "operationId": "listChatboxes", "tags": [ "Chatboxes" ], "summary": "List a project's chatboxes", "description": "The chatboxes published from this project \u2014 name, access mode, attached servers, and share link. Read-only.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's chatboxes.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatboxPage" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/chatboxes/{chatboxId}": { "get": { "operationId": "getChatbox", "tags": [ "Chatboxes" ], "summary": "Get a chatbox", "description": "One chatbox's full read-only settings: model, system prompt, tool-approval policy, attached servers, and share link. A chatbox that exists but belongs to a different project reads as `404 NOT_FOUND`.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/chatboxId" } ], "responses": { "200": { "description": "The chatbox settings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatboxDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/tunnels": { "post": { "operationId": "createTunnel", "tags": [ "Tunnels" ], "summary": "Create (or revive) a relay tunnel for a named project server", "description": "Registers a server record named `name` if missing, mints a relay tunnel grant for it, and **persists the tunnel bearer URL (including the plaintext `?k=` secret) onto the server record's `url`** so evals and chatboxes can target the tunnel like any remote server. The plaintext persistence is a deliberate trade-off of the current tunnel MVP \u2014 the backend otherwise stores only a hash of the secret \u2014 mitigated by rotation: **every call rotates the secret, revokes the previous grant at the edge (disconnecting any live tunnel session for the server), and updates the stored URL**, so re-calling this route is also the rotation/recovery path.\n\nThe caller hosts the tunnel itself: connect a WebSocket to `relayWsUrl` (subprotocol `mcpjam-tunnel.v1`, `Authorization: Bearer `) and serve the relayed requests \u2014 this is what `mcpjam tunnel` does. When the host disconnects, the server record stays and calls to the public URL fail fast at the edge.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TunnelCreateRequest" }, "example": { "name": "everything" } } } }, "responses": { "201": { "description": "Tunnel grant minted and the server record updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TunnelGrant" }, "example": { "serverId": "srv_abc123", "name": "everything", "existed": false, "slug": "calm-otter", "url": "https://calm-otter.tunnels.mcpjam.com/api/mcp/adapter-http/srv_abc123?k=\u2026", "connectToken": "ct_\u2026", "connectTokenExpiresAt": 1767225600000, "relayWsUrl": "wss://tunnels.mcpjam.com/agent", "secretVersion": 3 } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/tunnels/{serverId}/close": { "post": { "operationId": "closeTunnel", "tags": [ "Tunnels" ], "summary": "Revoke a tunnel's live grant", "description": "Revokes the grant at the control plane and edge: the public URL stops working immediately and any live tunnel session is disconnected. The server record \u2014 including its now-dead `url` \u2014 is intentionally left untouched, so the next create revives the tunnel with the same slug.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "responses": { "200": { "description": "Grant revoked; the server record is unchanged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TunnelClosed" }, "example": { "serverId": "srv_abc123", "status": "closed" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments": { "get": { "operationId": "listEnvironments", "tags": [ "Environments" ], "summary": "List a project's environments", "description": "The project environments saved in the project. Archived environments are excluded unless `includeArchived=true` \u2014 you need that to find one to restore.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "name": "includeArchived", "in": "query", "required": false, "description": "Include archived environments.", "schema": { "type": "boolean", "default": false } } ], "responses": { "200": { "description": "The project's environments.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "post": { "operationId": "createEnvironment", "tags": [ "Environments" ], "summary": "Create an environment", "description": "Create a project environment from a host plus, optionally, a standalone server group, a pinned skill selection, and pinned plugin versions. The name must be unique among the project's live environments. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentCreateRequest" } } } }, "responses": { "201": { "description": "The created environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/capabilities": { "get": { "operationId": "getEnvironmentCapabilities", "tags": [ "Environments" ], "summary": "Check what this deployment's environment surface supports", "description": "Report which environment features this MCPJam deployment accepts. Call it before sending a model override: clients ship independently of the platform, and a field an older deployment does not know is a hard validation error there rather than a silently ignored one. A deployment too old to answer reports `false` for everything, which is the correct assumption.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The deployment's environment capabilities.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentCapabilities" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}": { "get": { "operationId": "getEnvironment", "tags": [ "Environments" ], "summary": "Show one environment", "description": "Read one environment, including the `revision` you pass back as `expectedRevision` when writing to it.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "responses": { "200": { "description": "The environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateEnvironment", "tags": [ "Environments" ], "summary": "Update an environment", "description": "Edit an environment. Only the fields you send change; send `null` for `serverAttachmentId`, `skillSelection`, or `pluginVersionIds` to clear them. Requires `expectedRevision` \u2014 the revision you last read. If the environment changed since, the write is rejected with 409 rather than overwriting the concurrent edit. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentUpdateRequest" } } } }, "responses": { "200": { "description": "The updated environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}/resolve": { "get": { "operationId": "resolveEnvironment", "tags": [ "Environments" ], "summary": "Preview what an environment resolves to", "description": "Resolve an environment to the exact execution inputs a run would use right now: the host's current config, the closed server set (including servers contributed by pinned plugin versions), and the resolved plugin versions. Returns 409 when the environment cannot currently produce a runnable configuration \u2014 for example a pinned plugin was disabled or deleted; `details.code` carries the specific reason (`ENV_PLUGIN_UNAVAILABLE`, `ENV_NO_SERVERS`, `ENV_HOST_MISSING`, \u2026).", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "responses": { "200": { "description": "The resolved execution inputs.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentResolved" } } } }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}/archive": { "post": { "operationId": "archiveEnvironment", "tags": [ "Environments" ], "summary": "Archive an environment", "description": "Archive an environment. It stops being selectable for runs and frees its name for a new one, but the row is kept and can be restored \u2014 this is why archive is a sub-action rather than a DELETE. Requires `expectedRevision` \u2014 the revision you last read. If the environment changed since, the write is rejected with 409 rather than overwriting the concurrent edit. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentRevisionRequest" } } } }, "responses": { "200": { "description": "The updated environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/environments/{environmentId}/restore": { "post": { "operationId": "restoreEnvironment", "tags": [ "Environments" ], "summary": "Restore an archived environment", "description": "Restore an archived environment. Returns 409 if another live environment took its name in the meantime. Plugin pins whose version row no longer exists at all are dropped on the way back to live \u2014 compare the returned `pluginVersionIds` against what you archived to detect that. Requires `expectedRevision` \u2014 the revision you last read. If the environment changed since, the write is rejected with 409 rather than overwriting the concurrent edit. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironmentRevisionRequest" } } } }, "responses": { "200": { "description": "The updated environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "409": { "$ref": "#/components/responses/Conflict" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/plugins": { "get": { "operationId": "listProjectPlugins", "tags": [ "Plugins" ], "summary": "List a project's plugins", "description": "The live (installed, non-uninstalled) Agent Plugins in the project, disabled ones included (marked `enabled: false`). Read-only: import, activation, enable/disable and uninstall are app flows, not API operations.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project's plugins.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PluginPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/plugin-versions/{pluginVersionId}": { "get": { "operationId": "getPluginVersion", "tags": [ "Plugins" ], "summary": "Get a plugin version", "description": "One imported plugin version with its component projections: declared MCP servers (placement, auth timing, materialized server id) and declared skills (namespaced model refs). Addressed by the version id alone \u2014 access is membership of the version's own project, and historical versions of uninstalled plugins stay readable because eval snapshots and stale environment pins reference them.", "parameters": [ { "name": "pluginVersionId", "in": "path", "required": true, "description": "A plugin's `activeVersionId` from the list endpoint, or a pinned id from an environment's `pluginVersionIds`.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "The plugin version.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PluginVersion" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/eval-ingest/artifacts/upload-url": { "post": { "operationId": "createEvalIngestUploadUrl", "tags": [ "Eval runs" ], "summary": "Get an upload URL for a run artifact", "description": "Mints a short-lived URL for uploading a widget blob or other artifact referenced by an ingested run.\n\nHow eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies.\n\nThe `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project \u2014 the zero-config CI case.\n\nSTATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope.", "parameters": [ { "name": "projectId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id, or the literal `default` for the key org's Default project." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestRequest" } } } }, "responses": { "200": { "description": "The backend's response, passed through.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestResponse" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-ingest/report": { "post": { "operationId": "reportEvalRun", "tags": [ "Eval runs" ], "summary": "Ingest a completed eval run in one call", "description": "The whole-run variant: submit a finished run and its iterations in a single request. This is what the SDK reporter uses by default.\n\nHow eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies.\n\nThe `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project \u2014 the zero-config CI case.\n\nSTATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope.", "parameters": [ { "name": "projectId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id, or the literal `default` for the key org's Default project." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestRequest" } } } }, "responses": { "200": { "description": "The backend's response, passed through.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestResponse" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-ingest/runs/finalize": { "post": { "operationId": "finalizeEvalIngestRun", "tags": [ "Eval runs" ], "summary": "Close an open ingestion run", "description": "Marks the run complete and computes its rollups. Until this is called the run stays open in the dashboard.\n\nHow eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies.\n\nThe `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project \u2014 the zero-config CI case.\n\nSTATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope.", "parameters": [ { "name": "projectId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id, or the literal `default` for the key org's Default project." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestRequest" } } } }, "responses": { "200": { "description": "The backend's response, passed through.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestResponse" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-ingest/runs/iterations": { "post": { "operationId": "ingestEvalRunIterations", "tags": [ "Eval runs" ], "summary": "Append iterations to an open run", "description": "Appends one batch of iteration results to a run opened by `runs/start`.\n\nHow eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies.\n\nThe `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project \u2014 the zero-config CI case.\n\nSTATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope.", "parameters": [ { "name": "projectId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id, or the literal `default` for the key org's Default project." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestRequest" } } } }, "responses": { "200": { "description": "The backend's response, passed through.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestResponse" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/eval-ingest/runs/start": { "post": { "operationId": "startEvalIngestRun", "tags": [ "Eval runs" ], "summary": "Open a run for incremental ingestion", "description": "Opens a run that iterations are appended to, for a long CI job that reports as it goes. Pair with `runs/iterations` and `runs/finalize`.\n\nHow eval runs executed OUTSIDE the platform (local dev, CI) reach the Evals dashboard. Authenticate like any other `/api/v1` route (typically an `sk_` key); the gateway swaps in a delegated org-scoped token so the backend's fail-closed org scoping applies.\n\nThe `{projectId}` segment declares where results land and always wins over any `projectId` in the body. The literal `default` resolves to the key org's Default project \u2014 the zero-config CI case.\n\nSTATUS AND BODY PASS THROUGH VERBATIM: success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses, not the v1 resource envelope.", "parameters": [ { "name": "projectId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id, or the literal `default` for the key org's Default project." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestRequest" } } } }, "responses": { "200": { "description": "The backend's response, passed through.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalIngestResponse" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" }, "504": { "$ref": "#/components/responses/Timeout" } } } }, "/projects/{projectId}/images/validate": { "post": { "operationId": "validateSandboxImageBlueprint", "summary": "Validate a blueprint", "description": "Lint blueprint YAML without saving it. Returns 200 with `ok: false` + structured errors for an invalid blueprint; the authoritative rejection still happens at create/update/build.", "tags": [ "Sandbox images" ], "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBlueprintValidateRequest" }, "example": { "blueprint": "base: debian:bookworm-slim@sha256:\ninitialize:\n - name: Install tooling\n run: apt-get update && apt-get install -y git\nknowledge:\n - name: Test notes\n contents: Run `make test` before pushing.\n" } } } }, "responses": { "200": { "description": "Lint result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxImageBlueprintValidateResult" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/agent": { "post": { "operationId": "runAgentTurn", "tags": [ "Agent" ], "summary": "Run one headless agent turn", "description": "Runs ONE assistant turn over the supplied message history and responds synchronously with the final assistant text, the operations it invoked, references to any resources it created, and any actions awaiting human approval.\n\nThe caller owns conversation state: resend the full history each turn. The model is pinned server-side (hosted catalog) and billed to the project.\n\nTools are TIERED. Read operations and non-spending writes execute directly. Three categories are PROPOSAL-ONLY: operations that SPEND once (running a suite or a case, generating cases, cancelling a run), operations whose spend RECURS (setting a suite's schedule), and operations whose effects MCPJam cannot describe or undo because they leave MCPJam entirely (`call_server_tool`, which runs a tool on the caller's own MCP server). For each, the turn validates against the operation's real schema, persists a proposal, and returns it in `proposedActions` for a person to approve; `confirmSeverity` says which hazard the approver should be warned about. MCPJam-side deletions are excluded from the surface entirely \u2014 a proposal makes spend deliberate, but it does not make a deletion recoverable. Proposals are only offered when the request supplies a `conversationId`; without somewhere to collect a click there is nothing to approve, so the tools are withheld rather than offered and then refused.\n\nEvery tool invocation is hard-clamped to the path `projectId`. Turns are capped at 4 concurrent per organization (`429 RATE_LIMITED`; enforced per server instance) and ~90s wall clock (`504 TIMEOUT`). Guest callers are denied. Requires a hosted MCPJam deployment (`422 FEATURE_NOT_SUPPORTED` otherwise).\n\nWhen a turn fails or times out AFTER work already persisted, the error body's `details.createdResources` and `details.proposedActions` carry what survived \u2014 check them before retrying.\n\n**Retry policy:** send `idempotencyKey` as a STABLE identity for the triggering event (not a fresh uuid per attempt). Every write the turn performs derives its own key from it, so a retried turn's mutations land on the rows the first attempt created instead of duplicating them. The key makes a retry SAFE; it does not make one free \u2014 dedupe at your own trigger so most retries never re-run the turn at all. A retry whose model authors materially different arguments hashes differently and is correctly treated as a different write.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentTurnRequest" }, "example": { "messages": [ { "role": "user", "content": "Create an eval suite for my weather server with one case that checks get_forecast is called for \"forecast for Paris\"." } ] } } } }, "responses": { "200": { "description": "The completed turn.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentTurnResponse" }, "example": { "reply": "Created the suite \"weather smoke\" with 1 case. It is ready to run.", "toolCalls": [ { "operation": "list_project_servers" }, { "operation": "create_eval_suite" } ], "createdResources": [ { "type": "eval_suite", "id": "ts_abc123", "name": "weather smoke", "url": "https://app.mcpjam.com/evals/suite/ts_abc123" } ], "usage": { "inputTokens": 2450, "outputTokens": 312 }, "proposedActions": [ { "actionId": "act_8f2c1d", "operation": "run_eval_suite", "description": "Run eval suite weather smoke", "buttonLabel": "Run it", "kind": "start" } ] } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "422": { "$ref": "#/components/responses/FeatureNotSupported" }, "429": { "$ref": "#/components/responses/RateLimited" }, "504": { "$ref": "#/components/responses/Timeout" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}": { "patch": { "operationId": "updateProject", "tags": [ "Projects" ], "summary": "Update project metadata", "description": "Updates name, description, icon or visibility. Requires project admin; a non-member gets `404` rather than `403`, so the response never confirms that a project exists. The project's server map is not updatable here \u2014 see the per-server routes.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectUpdateRequest" }, "example": { "name": "CI evals (staging)" } } } }, "responses": { "200": { "description": "The updated project.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Project" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteProject", "tags": [ "Projects" ], "summary": "Delete a project and everything scoped to it", "description": "IRREVERSIBLE, and it cascades: project access grants and invites, the mapped workspace and its memberships, every saved server in the project (each soft-deleted asynchronously, which also rotates the host configs that referenced it), and hosted tasks. Requires project admin. The request body must be empty.", "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "responses": { "200": { "description": "The project was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectDeleted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/servers/{serverId}": { "get": { "operationId": "getProjectServer", "tags": [ "Servers" ], "summary": "Get one saved server", "description": "The server's detail, projected toward the hosted shape. Secrets are never included \u2014 `hasClientSecret` reports presence only. A server id from another project reads as `404`.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "responses": { "200": { "description": "The server.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServer" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "patch": { "operationId": "updateProjectServer", "tags": [ "Servers" ], "summary": "Update a saved server", "description": "Sparse update: omitted fields are unchanged. Renaming re-checks per-workspace uniqueness (`409` on a clash). Editing a server shared as an OAuth connection requires project admin. `clientSecret` and `clearClientSecret` are mutually exclusive.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServerUpdateRequest" }, "example": { "enabled": false } } } }, "responses": { "200": { "description": "The updated server.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServer" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } }, "delete": { "operationId": "deleteProjectServer", "tags": [ "Servers" ], "summary": "Delete a saved server", "description": "Soft-deletes the server and schedules its cascade: MCP Apps views, hosted OAuth credentials, encrypted secret objects, and a rotation of every host config that referenced it. The cascade is ASYNCHRONOUS, so a read immediately after this call may still observe the row. The request body must be empty.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/serverId" } ], "responses": { "200": { "description": "The server was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectServerDeleted" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/hosts/{hostId}/servers": { "post": { "operationId": "setHostServers", "tags": [ "Hosts" ], "summary": "Replace a host's server attachments", "description": "Sets the host's required and optional server ids without round-tripping its whole config, and responds with the updated host detail. Preserves every other identity field (computer, harness, built-in tools) and trims per-server connection overrides to the kept set. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostServersRequest" }, "example": { "serverIds": [ "srv_1" ], "optionalServerIds": [] } } } }, "responses": { "200": { "description": "The updated host.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/projects/{projectId}/hosts/{hostId}/duplicate": { "post": { "operationId": "duplicateHost", "tags": [ "Hosts" ], "summary": "Duplicate a host", "description": "Copies the host and responds `201` with the new host's detail. The copy SHARES the source's content-addressed config row rather than cloning it, so both hosts continue to describe the same configuration. Requires project admin.", "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/hostId" } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDuplicateRequest" }, "example": { "name": "Claude (copy)" } } } }, "responses": { "201": { "description": "The duplicated host.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HostDetail" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/models": { "get": { "operationId": "listModels", "tags": [ "Catalog" ], "summary": "The MCPJam-provided model catalog", "description": "PUBLIC: this route carries no user or project data and requires no bearer token, matching the upstream catalog it proxies. Mounted before authentication for that reason.", "security": [], "responses": { "200": { "description": "The model catalog.", "content": { "application/json": { "schema": { "type": "object" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/trace-exports/otlp": { "get": { "operationId": "exportTracesOtlp", "tags": [ "Catalog" ], "summary": "Export project traces as OTLP/JSON", "description": "Returns an OTLP `ExportTraceServiceRequest`. Pagination rides in RESPONSE HEADERS rather than the body so the payload stays a valid OTLP document: read `x-mcpjam-next-cursor` and feed it back as `cursor`, and treat `x-mcpjam-export-complete: true` as the end of the stream.", "parameters": [ { "name": "projectId", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "sourceTypes", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Comma-separated source filter." }, { "name": "includeContent", "in": "query", "required": false, "schema": { "type": "boolean" }, "description": "Include message/tool payloads, which may contain sensitive data." } ], "responses": { "200": { "description": "A page of spans in OTLP/JSON form.", "content": { "application/json": { "schema": { "type": "object" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" }, "502": { "$ref": "#/components/responses/ServerUnreachable" } } } }, "/server-connections": { "post": { "operationId": "createServerConnection", "tags": [ "Server connections" ], "summary": "Connect an MCP server", "description": "Starts a connection request for an MCP server URL. Discovery, browser consent, and validation happen asynchronously; poll the returned `connectionRequestId` until the status is terminal. When the next step belongs to a person, the response carries a `handoffUrl` to open.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerConnectionCreateBody" } } } }, "responses": { "201": { "description": "Connection request created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerConnection" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/server-connections/{requestId}": { "get": { "operationId": "getServerConnection", "tags": [ "Server connections" ], "summary": "Get a connection request", "description": "Current state of one connection request. Metered on its own poll budget rather than the shared per-caller one, so polling on a short interval is expected and will not spend the budget your other calls need. A 429 here means the poll interval itself is too fast; honour Retry-After.", "parameters": [ { "name": "requestId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "The `scr_\u2026` connection request id." } ], "responses": { "200": { "description": "Current state.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerConnection" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/server-connections/{requestId}/cancel": { "post": { "operationId": "cancelServerConnection", "tags": [ "Server connections" ], "summary": "Cancel a connection request", "description": "Stops a request and invalidates its handoff link. Cancelling an already-terminal request is a no-op.", "parameters": [ { "name": "requestId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "The `scr_\u2026` connection request id." } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Request cancelled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerConnection" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/server-connections/{requestId}/retry-validation": { "post": { "operationId": "retryServerConnectionValidation", "tags": [ "Server connections" ], "summary": "Retry validation", "description": "Re-arms a stalled validation immediately instead of waiting out its backoff. Does not revive a terminal request \u2014 after `failed`, `expired`, or `cancelled`, start a new one.", "parameters": [ { "name": "requestId", "in": "path", "required": true, "schema": { "type": "string" }, "description": "The `scr_\u2026` connection request id." } ], "requestBody": { "$ref": "#/components/requestBodies/emptyBody" }, "responses": { "200": { "description": "Validation re-armed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerConnection" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/personas": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "get": { "operationId": "listPersonas", "tags": [ "Swarms" ], "summary": "List personas", "description": "The project's personas, newest first. Archived personas are excluded.", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "A page of personas.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonaPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "post": { "operationId": "createPersona", "tags": [ "Swarms" ], "summary": "Create a persona", "description": "Creates one persona. To draft several with a model first, use `POST /projects/{projectId}/personas/generate` \u2014 that route persists nothing.", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Retry-safe create key. Replaying the SAME key with the SAME body returns the original resource instead of creating a second one; reusing it with a DIFFERENT body is a 409.", "schema": { "type": "string" } } ], "requestBody": { "required": true, "description": "The persona to create.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonaCreateRequest" } } } }, "responses": { "201": { "description": "The created persona.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Persona" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/personas/{personaId}": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/personaId" } ], "get": { "operationId": "getPersona", "tags": [ "Swarms" ], "summary": "Get a persona", "description": "A persona that lives in a DIFFERENT project answers 404, never 403 \u2014 the route is not an existence oracle for a project you cannot see.", "responses": { "200": { "description": "The persona.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Persona" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "patch": { "operationId": "updatePersona", "tags": [ "Swarms" ], "summary": "Update a persona", "description": "Partial update. Journeys reference the persona by id, so renaming one does not disturb anything pointing at it.", "requestBody": { "required": true, "description": "Fields to change.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonaUpdateRequest" } } } }, "responses": { "200": { "description": "The updated persona.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Persona" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "operationId": "deletePersona", "tags": [ "Swarms" ], "summary": "Delete a persona", "description": "Refused with 409 while journeys still reference this persona \u2014 deleting it would strand them.", "responses": { "200": { "description": "The persona was deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonaDeleted" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/personas/generate": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "post": { "operationId": "generatePersonas", "tags": [ "Swarms" ], "summary": "Draft personas with a model", "description": "Returns DRAFTS and persists nothing. That separation is deliberate: generation is the step most likely to produce something nobody wanted, and a route that both invented and saved would make \"let me see what you'd suggest\" indistinguishable from \"fill my project with these\".\n\nIt is also why there is no idempotency key \u2014 a call with no effect has nothing to de-duplicate, and offering one would imply the drafts are stable across retries when they are not.\n\n**SPENDS.** This runs models on your organization's account and is metered two ways: a per-minute burst brake (429, retry in seconds) and your plan's daily budget (429, resets at UTC midnight). Both normally carry `Retry-After` \u2014 honor it when it is there, and back off on your own when it is not.", "requestBody": { "required": true, "description": "Grounding source and slate size.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PersonaGenerateRequest" } } } }, "responses": { "200": { "description": "Draft personas and journeys.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenerationDrafts" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journeys/generate": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "post": { "operationId": "generateJourneys", "tags": [ "Swarms" ], "summary": "Draft journeys for a persona", "description": "Returns DRAFTS and persists nothing \u2014 see `POST /projects/{projectId}/personas/generate` for why, and for what this spends.", "requestBody": { "required": true, "description": "Grounding source and the persona to draft for.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyGenerateRequest" } } } }, "responses": { "200": { "description": "Draft journeys.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenerationDrafts" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journeys/{journeyId}": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/journeyId" } ], "get": { "operationId": "getJourney", "tags": [ "Swarms" ], "summary": "Get a journey", "responses": { "200": { "description": "The journey.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Journey" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "patch": { "operationId": "updateJourney", "tags": [ "Swarms" ], "summary": "Update a journey", "description": "Editing a journey never disturbs a run already under way: a run pins its own immutable snapshot of the definition at launch.", "requestBody": { "required": true, "description": "Fields to change.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyUpdateRequest" } } } }, "responses": { "200": { "description": "The updated journey.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Journey" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "operationId": "archiveJourney", "tags": [ "Swarms" ], "summary": "Archive a journey", "description": "ARCHIVES rather than deletes, and the response says so. Existing runs keep pointing at the definition they executed, so their history stays readable.", "responses": { "200": { "description": "The journey was archived.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyArchived" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/swarms": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "get": { "operationId": "listSwarms", "tags": [ "Swarms" ], "summary": "List swarms", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "A page of swarms.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SwarmPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "post": { "operationId": "createSwarm", "tags": [ "Swarms" ], "summary": "Create a swarm", "description": "A swarm is an authoring container holding DEFAULTS for the journeys made under it. Creating one starts nothing.", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Retry-safe create key. Replaying the SAME key with the SAME body returns the original resource instead of creating a second one; reusing it with a DIFFERENT body is a 409.", "schema": { "type": "string" } } ], "requestBody": { "required": true, "description": "The swarm to create.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SwarmCreateRequest" } } } }, "responses": { "201": { "description": "The created swarm.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Swarm" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/swarms/{swarmId}": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/swarmId" } ], "get": { "operationId": "getSwarm", "tags": [ "Swarms" ], "summary": "Get a swarm", "responses": { "200": { "description": "The swarm.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Swarm" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "patch": { "operationId": "updateSwarm", "tags": [ "Swarms" ], "summary": "Update a swarm", "description": "Changes the container's DEFAULTS. Journeys already authored under it keep the values they were created with \u2014 this is not a cascade.", "requestBody": { "required": true, "description": "Fields to change.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SwarmUpdateRequest" } } } }, "responses": { "200": { "description": "The updated swarm.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Swarm" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "operationId": "archiveSwarm", "tags": [ "Swarms" ], "summary": "Archive a swarm", "description": "ARCHIVES the container. The journeys authored under it are not touched \u2014 they keep their own definitions and stay launchable.", "responses": { "200": { "description": "The swarm was archived.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SwarmArchived" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journeys": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "get": { "operationId": "listJourneys", "tags": [ "Swarms" ], "summary": "List journeys", "description": "The project's journeys, newest first. Archived journeys are excluded.", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "A page of journeys.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "post": { "operationId": "createJourney", "tags": [ "Swarms" ], "summary": "Create a journey", "description": "Creates one journey: a persona pursuing a `goal` against one or more environments. To draft several with a model first, use `POST /projects/{projectId}/journeys/generate` \u2014 that route persists nothing.\n\n`personaId` and `swarmId` must belong to THIS project. Naming one from another project is a `404`, not a silent cross-project reference.\n\nCreating a journey does not run it. Launch it with `POST /projects/{projectId}/journeys/{journeyId}/runs`.", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Retry-safe create key. Replaying the SAME key with the SAME body returns the original resource instead of creating a second one; reusing it with a DIFFERENT body is a 409.", "schema": { "type": "string" } } ], "requestBody": { "required": true, "description": "The journey to create.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyCreateRequest" } } } }, "responses": { "201": { "description": "The created journey.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Journey" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journeys/{journeyId}/runs": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/journeyId" } ], "get": { "operationId": "listJourneyRuns", "tags": [ "Swarm runs" ], "summary": "List a journey's runs", "description": "Newest first. Compact records \u2014 `attempts` is on the single-run read.", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "A page of runs.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyRunPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "post": { "operationId": "launchJourneyRun", "tags": [ "Swarm runs" ], "summary": "Launch a journey", "description": "Answers **202**: the run row exists and its fan-out has started. It is not finished \u2014 poll `GET /projects/{projectId}/journey-runs/{runId}` until `status` leaves `running`.\n\n**SPENDS.** A launch fans out into `targets \u00d7 sessionsPerTarget` chat sessions, every one of which runs a model on your organization's account.\n\nTwo different 429s can come back and they mean different waits: the per-minute burst brake (retry in seconds) and your plan's daily launch cap (resets at UTC midnight). Both normally carry `Retry-After` \u2014 honor it when it is there, and back off on your own when it is not, rather than blocking on a header that may not come. A retried `Idempotency-Key` consumes NO quota.", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "**Send one.** A launch spends model credits, so a retry of a dropped response must not run the journey twice. Replaying a key returns the ORIGINAL run with `deduped: true` and starts no second runner.\n\nOmitting the header is read as a request that declined to identify itself, and gets a FRESH run every time.", "schema": { "type": "string" } } ], "requestBody": { "required": false, "description": "Optional launch options. Omit the body entirely to run the journey as authored.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyRunLaunchRequest" } } } }, "responses": { "202": { "description": "The run was accepted and started.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyRunLaunched" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journey-runs/{runId}": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "get": { "operationId": "getJourneyRun", "tags": [ "Swarm runs" ], "summary": "Get a journey run", "description": "The full record, including per-session `attempts` and `targetSummaries`.\n\nRead `canceled` before reporting a failure: a stopped run carries `status: \"failed\"`.", "responses": { "200": { "description": "The run.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyRun" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journey-runs/{runId}/sessions": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "get": { "operationId": "listJourneyRunSessions", "tags": [ "Swarm runs" ], "summary": "List a run's sessions", "description": "One entry per chat session the run produced. Summaries, not transcripts \u2014 read a transcript through the chat-session routes using `id`.", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "A page of sessions.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyRunSessionPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journey-runs/{runId}/cancel": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "post": { "operationId": "cancelJourneyRun", "tags": [ "Swarm runs" ], "summary": "Cancel a journey run", "description": "Stops a run and settles its in-flight attempts. IDEMPOTENT: cancelling an already-cancelled run answers 200 with `alreadyCanceled: true` and `finalized: 0`, not a 409.\n\nAddressed entirely by the path `runId` \u2014 there is no body.\n\nDeliberately NOT behind the beta gate: cancelling REDUCES exposure and spend, so an organization that loses the flag can still stop a run that is under way.", "responses": { "200": { "description": "The run was cancelled (or already was).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JourneyRunCanceled" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journeys-overview": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "get": { "operationId": "getSwarmOverview", "tags": [ "Swarm insights" ], "summary": "Project swarm roll-up", "description": "Recent runs with their goal-completion rates and repeat-failure findings, plus a project-wide trend. Deterministic and free \u2014 start here.", "responses": { "200": { "description": "The roll-up.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SwarmOverview" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journey-runs/{runId}/scorecard": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "get": { "operationId": "getJourneyRunScorecard", "tags": [ "Swarm insights" ], "summary": "Get a run's rubric scorecard", "description": "The deterministic rubric result for one run. No model, no spend.\n\nDivide by `sessionsGraded`, never `sessionsTotal`, and keep `failedGradingCount` separate from `failCount` \u2014 a crashed judge is not a regression.", "responses": { "200": { "description": "The scorecard.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RunScorecard" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journey-findings": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "get": { "operationId": "listSwarmFindings", "tags": [ "Swarm insights" ], "summary": "List swarm findings", "description": "Criteria that keep failing, tracked across waves. One finding is one problem OVER TIME \u2014 `occurrenceCount` and `status` carry the history, so do not treat repeat appearances as separate problems.", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "A page of findings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SwarmFindingPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journey-findings/{findingId}/dismiss": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/findingId" } ], "post": { "operationId": "dismissSwarmFinding", "tags": [ "Swarm insights" ], "summary": "Dismiss a finding", "description": "Hides a finding from your view. ORTHOGONAL to `status`: dismissing does not claim the problem stopped happening, and a dismissed finding that fires again stays `recurring`.\n\nAddressed entirely by the path `findingId` \u2014 there is no body.", "responses": { "200": { "description": "The finding was dismissed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FindingDismissed" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/journey-findings/{findingId}/undismiss": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/findingId" } ], "post": { "operationId": "undismissSwarmFinding", "tags": [ "Swarm insights" ], "summary": "Undismiss a finding", "description": "Reverses a dismissal. Bodyless, like its twin.", "responses": { "200": { "description": "The finding was undismissed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FindingDismissed" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/waves/{waveId}/insights": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/waveId" } ], "get": { "operationId": "getWaveInsights", "tags": [ "Swarm insights" ], "summary": "Get wave insights", "description": "Poll this after requesting insights. **404 means nobody asked** \u2014 it is deliberately not an empty `status: \"none\"` body, so a caller in a polling loop cannot mistake \"never requested\" for \"asked and still working\".", "responses": { "200": { "description": "The wave's insights.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WaveInsights" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "post": { "operationId": "requestWaveInsights", "tags": [ "Swarm insights" ], "summary": "Request wave insights", "description": "Answers **202**: generation is scheduled, not done. Poll the GET above rather than re-requesting.\n\n**SPENDS.** This runs models on your organization's account and draws on the `insightsPerDay` ledger, which is SHARED with eval-run and user-testing insights \u2014 burning it here takes it from there too.\n\nThree refusals, and they mean different things:\n- `429` with `code: \"RATE_LIMITED\"` and a short `Retry-After` \u2014 the per-minute burst brake. Wait seconds.\n- `429` naming `insightsPerDay` in `details` \u2014 the daily ledger. `Retry-After` counts to UTC midnight.\n- `403` \u2014 the feature is not available to your organization. Waiting will never help; collapsing this into the 429s would send someone who hit today's cap shopping for a plan they already have.", "requestBody": { "required": false, "description": "Optional. Omit the body entirely for the common case.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WaveInsightsRequest" } } } }, "responses": { "202": { "description": "Generation was scheduled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WaveInsightsRequested" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "operationId": "cancelWaveInsights", "tags": [ "Swarm insights" ], "summary": "Cancel a pending wave-insights generation", "description": "Stops a generation that is still `pending`. A wave with no generation in progress answers 409 \u2014 there is nothing to cancel, and reporting success would tell a caller it stopped something it did not.", "responses": { "200": { "description": "The generation was cancelled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WaveInsightsCanceled" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/environments/{environmentId}/scenario": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/environmentId" } ], "put": { "operationId": "publishScenario", "tags": [ "User testing" ], "summary": "Publish an environment for user testing", "description": "PUT, not POST, because publishing is IDEMPOTENT: one scenario per environment, and publishing an already-published environment returns the existing one rather than minting a second. `created` says which happened.\n\nRequires project ADMIN.", "requestBody": { "required": false, "description": "Optional create-time overrides. A bodyless publish is the common case.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioPublishRequest" } } } }, "responses": { "200": { "description": "The scenario, new or existing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Scenario" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "operationId": "unpublishScenario", "tags": [ "User testing" ], "summary": "Unpublish an environment", "description": "Takes the scenario down. Deliberately NOT behind the beta gate that guards publishing: an organization that loses the flag must still be able to take a live scenario down.\n\nUnpublishing something already unpublished answers 200 with `deleted: false` \u2014 a no-op, not an error.", "responses": { "200": { "description": "The scenario was removed (or there was none).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioDeleted" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "patch": { "operationId": "updateUserTestingScenario", "tags": [ "User testing" ], "summary": "Update a scenario", "description": "Change a scenario's identity or its exposure \u2014 but not both in one call. **Send `mode` on its own.** Identity and exposure are separate operations upstream, and applying them in sequence could leave the scenario live in a mode you did not ask for.\n\nNarrowing `mode` bumps `accessVersion` upstream and invalidates sessions minted under the old one.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioUpdateRequest" } } } }, "responses": { "200": { "description": "The updated scenario.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserTestingScenario" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "get": { "operationId": "getUserTestingScenario", "tags": [ "User testing" ], "summary": "Get a scenario", "description": "The scenario, plus the insights envelope over its latest analyzed window when the caller may have it.\n\nProject members only. This route is deliberately outside the guest allowlist, and the envelope additionally needs workspace membership \u2014 share-link visitors can never read other visitors' evidence.", "responses": { "200": { "description": "The scenario.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserTestingScenarioDetail" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/sessions": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "get": { "operationId": "listUserTestingSessions", "tags": [ "User testing" ], "summary": "List a scenario's sessions", "description": "What visitors did, as summaries. Project members only \u2014 this route is deliberately absent from the guest allowlist, so a share-link visitor can never read other visitors' sessions.", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "A page of sessions.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserTestingSessionPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/sessions/{sessionId}": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" }, { "$ref": "#/components/parameters/sessionId" } ], "get": { "operationId": "getUserTestingSession", "tags": [ "User testing" ], "summary": "Get a session transcript", "description": "The projected transcript, paged. Tool payloads and blobs are dropped, and the stored blob URL is never returned.\n\nCheck `transcriptUnavailable` before trusting `messageCount`: an unreadable transcript reports `null`, never `0`.", "requestBody": { "$ref": "#/components/requestBodies/cursorBody" }, "responses": { "200": { "description": "The session and its messages.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserTestingSessionDetail" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/guest-execution": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "put": { "operationId": "setGuestExecution", "tags": [ "User testing" ], "summary": "Set the guest-execution caps", "description": "The spend dial for anonymous visitors. A FULL REPLACEMENT \u2014 send every field. These caps only mean something as a set, and a partial update that raised one while leaving another stale would produce a combination nobody chose.\n\nRequires project ADMIN: it is the one exposure control that genuinely does, because it authorizes strangers to spend.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GuestExecution" } } } }, "responses": { "200": { "description": "The caps now in force.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GuestExecution" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/rotate-link": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "post": { "operationId": "rotateScenarioLink", "tags": [ "User testing" ], "summary": "Rotate the share link", "description": "Mints a new share link and stops the old URL from granting access. Everyone you shared it with needs the new one.\n\n**Rotation does NOT evict anyone who already redeemed the old link.** Their grant is a stored row, not a property of the URL, and it survives rotation \u2014 `accessVersion` is deliberately not bumped here. So rotation alone does not undo a leak: rotate to close the door, then `DELETE .../members/{memberIdOrEmail}` for anyone who already walked through it.\n\nBodyless: the scenario is the whole request, and the new secret is minted server-side by definition.", "responses": { "200": { "description": "The new link.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioLinkRotated" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/members": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "put": { "operationId": "upsertScenarioMember", "tags": [ "User testing" ], "summary": "Invite a member", "description": "Upsert by email, so re-inviting somebody is not an error. Only meaningful while `mode` is `invited_only`.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioMemberRequest" } } } }, "responses": { "200": { "description": "The member list was updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserTestingScenario" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/members/{memberIdOrEmail}": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" }, { "$ref": "#/components/parameters/memberIdOrEmail" } ], "delete": { "operationId": "removeScenarioMember", "tags": [ "User testing" ], "summary": "Remove a member", "description": "Removal NARROWS access, so it bumps `accessVersion` and cuts off any session that member already had \u2014 it takes effect at once rather than at expiry.", "responses": { "200": { "description": "The member was removed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioMemberRemoved" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/rebind": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "post": { "operationId": "rebindScenario", "tags": [ "User testing" ], "summary": "Point a scenario at a different environment", "description": "Keeps the scenario's LINK, its members and its session history while changing what visitors actually reach. The alternative \u2014 unpublish and republish \u2014 mints a new link, which means re-sharing it with everyone.\n\nThe new environment must belong to THIS project; one that does not answers 404 rather than confirming it exists elsewhere.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioRebindRequest" } } } }, "responses": { "200": { "description": "The scenario now points at the new environment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Scenario" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/metrics": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "get": { "operationId": "getUserTestingMetrics", "tags": [ "User testing" ], "summary": "Session metrics", "description": "Volumes, completion and feedback rates over the scenario's sessions.\n\n`404` when the scenario has produced no sessions yet \u2014 an empty scenario has no metrics, which is different from metrics that are all zero.", "parameters": [ { "name": "population", "in": "query", "required": false, "description": "Restrict to `real` visitors or `synthetic` (swarm-driven) sessions. Omit for both. Any other value is a `400`.", "schema": { "type": "string", "enum": [ "real", "synthetic" ] } } ], "responses": { "200": { "description": "The metrics.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioMetrics" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/usage": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "get": { "operationId": "getUserTestingUsage", "tags": [ "User testing" ], "summary": "Usage breakdown", "description": "Which models, tools and servers the scenario's sessions used.\n\n**Check `scan.truncated` before quoting a rate.** `404` when there is no usage data yet.", "responses": { "200": { "description": "The breakdown.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioUsage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/signals": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "get": { "operationId": "getUserTestingSignals", "tags": [ "User testing" ], "summary": "Current window signals", "description": "What the miner extracted from the current window \u2014 and where you get the window id the insights read takes.\n\n`404` when no window has been analyzed yet.", "responses": { "200": { "description": "The signals.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioSignals" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/findings": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "get": { "operationId": "listUserTestingFindings", "tags": [ "User testing" ], "summary": "List scenario findings", "description": "Every finding raised over this scenario's windows, dismissed ones included \u2014 dismissal is a state on the finding, not a deletion.", "responses": { "200": { "description": "The findings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioFindingPage" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/windows/{windowId}/insights": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" }, { "$ref": "#/components/parameters/windowId" } ], "get": { "operationId": "getUserTestingWindowInsights", "tags": [ "User testing" ], "summary": "Read a window's insights", "description": "Poll this after requesting a generation.\n\n`404` means NOBODY ASKED \u2014 deliberately distinct from a `pending` status, so a caller looping on this cannot mistake \"never requested\" for \"requested and still working\".", "responses": { "200": { "description": "The window's insights.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioWindowInsights" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/insights": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" } ], "post": { "operationId": "requestUserTestingInsights", "tags": [ "User testing" ], "summary": "Request insights for the current window", "description": "**Spends.** Runs a model over the scenario's current window and draws on the organization's `insightsPerDay` ledger, which is SHARED with eval-run and swarm wave insights \u2014 spending it here takes it from there.\n\n`202`: scheduled, not done. Poll `GET .../windows/{windowId}/insights` with the returned `windowId` rather than re-requesting.\n\n`409` when the current window has not been mined yet: the request was well formed and retrying it verbatim will not help until sessions have been processed.", "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioInsightsRequest" } } } }, "responses": { "202": { "description": "Scheduled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioInsightsRequested" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } }, "delete": { "operationId": "cancelUserTestingInsights", "tags": [ "User testing" ], "summary": "Cancel a window's generation", "description": "Releases a generation that is stuck `pending`. Idempotent against an already-finished one.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioInsightsCancelRequest" } } } }, "responses": { "200": { "description": "Canceled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioInsightsCanceled" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/findings/{findingId}/dismiss": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" }, { "$ref": "#/components/parameters/findingId" } ], "post": { "operationId": "dismissUserTestingFinding", "tags": [ "User testing" ], "summary": "Dismiss a finding", "description": "Keyed on the finding's stable remediation id, so the same problem stays dismissed when insights regenerate. Bodyless: the path names the finding.", "responses": { "200": { "description": "The finding's new state.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioFindingDismissed" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/user-testing/scenarios/{scenarioId}/findings/{findingId}/undismiss": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/scenarioId" }, { "$ref": "#/components/parameters/findingId" } ], "post": { "operationId": "undismissUserTestingFinding", "tags": [ "User testing" ], "summary": "Restore a dismissed finding", "description": "Undoes a dismissal. Bodyless: the path names the finding.", "responses": { "200": { "description": "The finding's new state.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScenarioFindingDismissed" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/eval-runs/{runId}/insights": { "parameters": [ { "$ref": "#/components/parameters/projectId" }, { "$ref": "#/components/parameters/runId" } ], "post": { "operationId": "requestEvalRunInsights", "tags": [ "Evals" ], "summary": "Request insights for an eval run", "description": "**Spends.** Runs a model over the finished run and draws on the same shared `insightsPerDay` ledger as swarm and user-testing insights.\n\n`202`: scheduled, not done. Poll the run detail's `insights` envelope rather than re-requesting \u2014 the envelope's `status` distinguishes `pending` from `not_requested`, which a second POST would not.", "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunInsightsRequest" } } } }, "responses": { "202": { "description": "Scheduled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvalRunInsightsRequested" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/projects/{projectId}/capabilities": { "parameters": [ { "$ref": "#/components/parameters/projectId" } ], "get": { "operationId": "getProjectCapabilities", "tags": [ "Projects" ], "summary": "What the caller may do here", "description": "Role, gate state, plan limits and a set of derived booleans \u2014 so an agent on a static surface can check before it acts instead of attempting a write and reading the failure.\n\nDescriptive, never authoritative: see the schema.", "responses": { "200": { "description": "The caller's capabilities in this project.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectCapabilities" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" }, "500": { "$ref": "#/components/responses/InternalError" } } } } }, "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "description": "MCPJam API key (`sk_\u2026`). Create one at [Settings \u2192 API keys](https://app.mcpjam.com/settings/api-keys). Guest sessions cannot use the API, and API keys cannot manage other API keys." } }, "parameters": { "projectId": { "name": "projectId", "in": "path", "required": true, "description": "ID of the hosted project that contains the server.", "schema": { "type": "string" } }, "serverId": { "name": "serverId", "in": "path", "required": true, "description": "ID of the server inside the project.", "schema": { "type": "string" } }, "runId": { "name": "runId", "in": "path", "required": true, "description": "Eval run ID, as returned by `POST /eval-runs`.", "schema": { "type": "string" } }, "iterationId": { "name": "iterationId", "in": "path", "required": true, "description": "Iteration ID, as returned by the run's iterations list.", "schema": { "type": "string" } }, "suiteId": { "name": "suiteId", "in": "path", "required": true, "description": "Eval suite ID, as returned by `POST /eval-runs`.", "schema": { "type": "string" } }, "chatboxId": { "name": "chatboxId", "in": "path", "required": true, "description": "Chatbox ID, as returned by the project's chatbox list.", "schema": { "type": "string" } }, "hostId": { "name": "hostId", "in": "path", "required": true, "description": "Host ID, as returned by the project's host list.", "schema": { "type": "string" } }, "imageId": { "name": "imageId", "in": "path", "required": true, "description": "Sandbox image ID, as returned by the project's image list.", "schema": { "type": "string" } }, "environmentId": { "name": "environmentId", "in": "path", "required": true, "description": "Project environment ID, as returned by the project's environment list.", "schema": { "type": "string" } }, "personaId": { "name": "personaId", "in": "path", "required": true, "description": "Persona ID, as returned by the project's persona list.", "schema": { "type": "string" } }, "journeyId": { "name": "journeyId", "in": "path", "required": true, "description": "Journey ID, as returned by the project's journey list.", "schema": { "type": "string" } }, "swarmId": { "name": "swarmId", "in": "path", "required": true, "description": "Swarm ID, as returned by the project's swarm list.", "schema": { "type": "string" } }, "waveId": { "name": "waveId", "in": "path", "required": true, "description": "Wave ID \u2014 the opaque group id shared by every run of one co-launched swarm. Returned on every run in the wave.", "schema": { "type": "string" } }, "findingId": { "name": "findingId", "in": "path", "required": true, "description": "Finding ID. Stable across waves \u2014 that stability is what makes a recurring finding one finding rather than a new one each time.", "schema": { "type": "string" } }, "scenarioId": { "name": "scenarioId", "in": "path", "required": true, "description": "Scenario ID, as returned by publishing an environment or by the project's scenario reads.", "schema": { "type": "string" } }, "sessionId": { "name": "sessionId", "in": "path", "required": true, "description": "Chat session ID, as returned by the scenario's session list.", "schema": { "type": "string" } }, "windowId": { "name": "windowId", "in": "path", "required": true, "description": "Window group ID, as returned by the scenario's signals or insights reads. A window is the slice of a scenario's traffic that one analysis evaluated.", "schema": { "type": "string" } }, "memberIdOrEmail": { "name": "memberIdOrEmail", "in": "path", "required": true, "description": "Either the member's ID or their email address. Both are accepted deliberately: a caller who invited somebody by email should not have to look an ID up in order to revoke them.", "schema": { "type": "string" } } }, "requestBodies": { "emptyBody": { "required": false, "description": "Accepts an empty JSON object.", "content": { "application/json": { "schema": { "type": "object", "properties": {} }, "example": {} } } }, "cursorBody": { "required": false, "description": "Optional pagination cursor.", "content": { "application/json": { "schema": { "type": "object", "properties": { "cursor": { "type": "string", "description": "Opaque pagination cursor from a previous response's `nextCursor`. Don't parse it." } } }, "example": {} } } } }, "schemas": { "Error": { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "string", "description": "Stable, machine-readable error code. New codes may be added over time; treat unknown codes as non-retryable failures unless the HTTP status says otherwise.", "enum": [ "UNAUTHORIZED", "FORBIDDEN", "NOT_FOUND", "CONFLICT", "VALIDATION_ERROR", "RATE_LIMITED", "FEATURE_NOT_SUPPORTED", "SERVER_UNREACHABLE", "TIMEOUT", "OAUTH_REQUIRED", "INTERNAL_ERROR" ] }, "message": { "type": "string", "description": "Human-readable description. May change between releases \u2014 don't match on it." }, "details": { "type": "object", "description": "Optional, unstructured context bag.", "additionalProperties": true } } }, "EvalCase": { "type": "object", "required": [ "id", "title", "steps", "iterations", "isNegative", "models" ], "description": "A persisted eval case, in the public steps-first shape. Note this is NOT `EvalTestCase`, which is the INLINE authoring shape accepted by suite creation.", "properties": { "id": { "type": "string" }, "title": { "type": "string" }, "steps": { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/EvalTestStep" }, "description": "Ordered test steps. A `prompt` step is a model turn; a single model-free `toolCall` step is a render-check; `assert` steps hold the expectations." }, "expectedOutput": { "type": "string" }, "iterations": { "type": "integer", "minimum": 1, "maximum": 10 }, "isNegative": { "type": "boolean", "description": "When true, the case passes if NO tools are called." }, "scenario": { "type": "string" }, "models": { "type": "array", "items": { "type": "object", "required": [ "model" ], "properties": { "model": { "type": "string" }, "provider": { "type": "string" } } } }, "matchOptions": { "type": "object", "description": "Absent when the case sets none \u2014 omitted from the response rather than sent as `null`." }, "checks": { "type": "object", "description": "Absent when the case sets none \u2014 omitted from the response rather than sent as `null`.", "properties": { "mode": { "type": "string", "enum": [ "inherit", "replace", "extend" ] }, "list": { "type": "array", "items": { "type": "object" } } } }, "createdAt": { "type": [ "number", "null" ] }, "updatedAt": { "type": [ "number", "null" ] } } }, "EvalCaseCreateRequest": { "type": "object", "required": [ "title", "steps" ], "description": "Create one case. `title` and a non-empty `steps` array are both required \u2014 a case persisted without executable steps could never run.", "properties": { "title": { "type": "string", "minLength": 1 }, "steps": { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/EvalTestStep" }, "description": "REPLACES the case's test definition wholesale when provided." }, "expectedOutput": { "type": "string" }, "iterations": { "type": "integer", "minimum": 1, "maximum": 10 }, "isNegative": { "type": "boolean" }, "scenario": { "type": "string" }, "models": { "type": "array", "items": { "type": "object", "required": [ "model" ], "properties": { "model": { "type": "string" }, "provider": { "type": "string", "description": "Derived from a `provider/model` id when omitted." } } } }, "matchOptions": { "type": [ "object", "null" ], "description": "`null` clears the case override and inherits the suite's." }, "checks": { "type": [ "object", "null" ], "description": "`null` clears the case override.", "properties": { "mode": { "type": "string", "enum": [ "inherit", "replace", "extend" ] }, "list": { "type": "array", "items": { "type": "object" } } } } } }, "EvalCaseGenerateRequest": { "type": "object", "description": "AI-generate cases from the suite's server tools and persist them. SPENDS ORG CREDITS.", "properties": { "mode": { "type": "string", "enum": [ "normal", "negative" ], "description": "Superseded by `caseMix` when that is present." }, "servers": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Server ids or names to discover tools from. Ignored when the suite is environment-based." }, "environmentId": { "type": "string", "minLength": 1, "description": "Discover tools from this attached environment's closed server set, so generated cases are written against the tools the suite's runs will actually see." }, "caseModels": { "type": "array", "items": { "type": "object", "required": [ "model" ], "properties": { "model": { "type": "string" }, "provider": { "type": "string" } } } }, "caseMix": { "type": "object", "description": "Per-bucket case counts. Omitted buckets inherit the default mix; the backend bounds each bucket and the total.", "properties": { "simple": { "type": "integer", "minimum": 0, "maximum": 10 }, "multiTool": { "type": "integer", "minimum": 0, "maximum": 10 }, "multiTurn": { "type": "integer", "minimum": 0, "maximum": 10 }, "complex": { "type": "integer", "minimum": 0, "maximum": 10 }, "negative": { "type": "integer", "minimum": 0, "maximum": 10 } } }, "varyUserStyles": { "type": "boolean", "description": "Condition generated cases on a range of user styles so the queries read like different users wrote them." } } }, "EvalCaseGenerated": { "type": "object", "required": [ "generationModel", "created", "counts" ], "properties": { "generationModel": { "type": "string" }, "created": { "type": "array", "items": { "$ref": "#/components/schemas/EvalCase" } }, "counts": { "type": "object", "properties": { "normal": { "type": "integer" }, "negative": { "type": "integer" } } }, "skipped": { "type": "array", "description": "Drafts that were generated but failed to persist. Surfaced rather than silently dropped.", "items": { "type": "object" } } } }, "EvalCasePage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/EvalCase" } }, "nextCursor": { "type": [ "string", "null" ] } } }, "EvalCaseUpdateRequest": { "type": "object", "description": "Partial update. Every field is optional; omitted fields are left as they are. `steps` REPLACES the definition rather than merging into it.", "properties": { "title": { "type": "string", "minLength": 1 }, "steps": { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/EvalTestStep" }, "description": "REPLACES the case's test definition wholesale when provided." }, "expectedOutput": { "type": "string" }, "iterations": { "type": "integer", "minimum": 1, "maximum": 10 }, "isNegative": { "type": "boolean" }, "scenario": { "type": "string" }, "models": { "type": "array", "items": { "type": "object", "required": [ "model" ], "properties": { "model": { "type": "string" }, "provider": { "type": "string", "description": "Derived from a `provider/model` id when omitted." } } } }, "matchOptions": { "type": [ "object", "null" ], "description": "`null` clears the case override and inherits the suite's." }, "checks": { "type": [ "object", "null" ], "description": "`null` clears the case override.", "properties": { "mode": { "type": "string", "enum": [ "inherit", "replace", "extend" ] }, "list": { "type": "array", "items": { "type": "object" } } } } } }, "EvalDeleted": { "type": "object", "required": [ "id", "deleted" ], "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "EvalIngestRequest": { "type": "object", "description": "Forwarded VERBATIM to the backend's eval-ingestion surface, which owns the schema. The `projectId` path segment always wins: it overwrites any `projectId` in the body, and the literal `default` omits it so the key's org Default project is used. Bodies are capped at ~6 MiB here and 5 MiB by the backend.", "additionalProperties": true }, "EvalIngestResponse": { "type": "object", "description": "The backend's response, passed through verbatim along with its status code. Success shapes are the legacy `{ ok: true, ... }` envelopes the SDK reporter parses \u2014 deliberately NOT the v1 resource envelope. Errors are canonical v1 `{ code, message }`.", "additionalProperties": true }, "ValidateResult": { "type": "object", "required": [ "success", "status" ], "properties": { "success": { "type": "boolean", "const": true }, "status": { "type": "string", "const": "connected" }, "initInfo": { "type": [ "object", "null" ], "description": "MCP initialization snapshot: server info, negotiated protocol version, and capabilities. `null` if the server connected but initialization data was unavailable.", "additionalProperties": true } } }, "DoctorReport": { "type": "object", "description": "Step-by-step health report from the probe \u2192 connect \u2192 initialize \u2192 capabilities workflow.", "required": [ "generatedAt", "status", "connection", "checks" ], "properties": { "generatedAt": { "type": "string", "format": "date-time", "description": "When the report was generated." }, "status": { "type": "string", "enum": [ "ready", "oauth_required", "partial", "error" ], "description": "Overall outcome. `partial` means the server connected but some primitive listings failed." }, "probe": { "type": [ "object", "null" ], "description": "Transport-level probe result (HTTP reachability, protocol hints), or `null` when skipped.", "additionalProperties": true }, "connection": { "type": "object", "required": [ "status", "detail" ], "properties": { "status": { "type": "string", "enum": [ "connected", "error", "skipped" ] }, "detail": { "type": "string" } } }, "initInfo": { "type": [ "object", "null" ], "additionalProperties": true }, "capabilities": { "type": [ "object", "null" ], "description": "The server's negotiated MCP capabilities.", "additionalProperties": true }, "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } }, "resources": { "type": "array", "items": { "$ref": "#/components/schemas/Resource" } }, "resourceTemplates": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "prompts": { "type": "array", "items": { "$ref": "#/components/schemas/Prompt" } }, "checks": { "type": "object", "description": "Per-step status. Each check is `{ status: ok | error | skipped, detail }`.", "properties": { "probe": { "$ref": "#/components/schemas/DoctorCheck" }, "connection": { "$ref": "#/components/schemas/DoctorCheck" }, "initialization": { "$ref": "#/components/schemas/DoctorCheck" }, "capabilities": { "$ref": "#/components/schemas/DoctorCheck" }, "tools": { "$ref": "#/components/schemas/DoctorCheck" }, "resources": { "$ref": "#/components/schemas/DoctorCheck" }, "resourceTemplates": { "$ref": "#/components/schemas/DoctorCheck" }, "prompts": { "$ref": "#/components/schemas/DoctorCheck" } } }, "error": { "oneOf": [ { "type": "null" }, { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "details": {} } } ], "description": "Terminal error, or `null` when the workflow completed." } }, "additionalProperties": true }, "DoctorCheck": { "type": "object", "required": [ "status", "detail" ], "properties": { "status": { "type": "string", "enum": [ "ok", "error", "skipped" ] }, "detail": { "type": "string" } } }, "CheckOAuthResult": { "type": "object", "required": [ "useOAuth", "serverUrl" ], "properties": { "useOAuth": { "type": "boolean", "description": "Whether the server is configured to require an OAuth grant." }, "serverUrl": { "type": [ "string", "null" ], "description": "The server's URL, or `null` for non-HTTP transports." } } }, "Tool": { "type": "object", "description": "MCP tool definition, as returned by the server.", "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "inputSchema": { "type": "object", "description": "JSON Schema for the tool's arguments.", "additionalProperties": true }, "outputSchema": { "type": "object", "description": "JSON Schema for the tool's structured output, when declared.", "additionalProperties": true } }, "additionalProperties": true }, "Prompt": { "type": "object", "description": "MCP prompt definition, as returned by the server.", "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "arguments": { "type": "array", "items": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "required": { "type": "boolean" } }, "additionalProperties": true } } }, "additionalProperties": true }, "Resource": { "type": "object", "description": "MCP resource descriptor, as returned by the server.", "required": [ "uri" ], "properties": { "uri": { "type": "string" }, "name": { "type": "string" }, "description": { "type": "string" }, "mimeType": { "type": "string" } }, "additionalProperties": true }, "ResourceContents": { "type": "object", "description": "MCP read-resource result.", "required": [ "contents" ], "properties": { "contents": { "type": "array", "items": { "type": "object", "required": [ "uri" ], "properties": { "uri": { "type": "string" }, "mimeType": { "type": "string" }, "text": { "type": "string", "description": "Present for text resources." }, "blob": { "type": "string", "description": "Base64-encoded payload, present for binary resources." } }, "additionalProperties": true } } }, "additionalProperties": true }, "ToolPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "PromptPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Prompt" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "ResourcePage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Resource" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "CallToolResult": { "type": "object", "description": "MCP tool execution result, returned verbatim from the server.", "required": [ "content" ], "properties": { "content": { "type": "array", "description": "Content blocks (`text`, `image`, `resource`, ...).", "items": { "type": "object", "additionalProperties": true } }, "structuredContent": { "type": "object", "description": "Structured output matching the tool's `outputSchema`, when declared.", "additionalProperties": true }, "isError": { "type": "boolean", "description": "`true` when the **tool** reported a failure. The HTTP call still succeeds \u2014 the server answered." } }, "additionalProperties": true }, "GetPromptResult": { "type": "object", "description": "MCP prompt render result, returned verbatim from the server.", "required": [ "messages" ], "properties": { "description": { "type": "string" }, "messages": { "type": "array", "items": { "type": "object", "required": [ "role", "content" ], "properties": { "role": { "type": "string", "enum": [ "user", "assistant" ] }, "content": { "description": "MCP prompt message content block.", "additionalProperties": true } }, "additionalProperties": true } } }, "additionalProperties": true }, "EvalTestStep": { "type": "object", "description": "One authored test step (the unified test model). `kind` discriminates: `prompt` is a user message (model turn); `toolCall` is a deterministic, model-free tool call; `interact` is one pure widget action; `assert` is an assertion (a `Predicate` like `toolCalledWith` / `widgetRendered`, or a DOM `WidgetAssertion`).", "required": [ "id", "kind" ], "properties": { "id": { "type": "string", "minLength": 1 }, "kind": { "type": "string", "enum": [ "prompt", "toolCall", "interact", "assert" ] }, "prompt": { "type": "string", "description": "User message (`kind: prompt`)." }, "serverName": { "type": "string", "description": "Server that owns the tool (`kind: toolCall`)." }, "toolName": { "type": "string", "description": "Tool name (`kind: toolCall` / `interact`)." }, "arguments": { "type": "object", "description": "Tool-call arguments (`kind: toolCall`).", "additionalProperties": true }, "action": { "type": "object", "description": "Widget action (`kind: interact`).", "additionalProperties": true }, "assertion": { "type": "object", "description": "Predicate or widget assertion (`kind: assert`).", "additionalProperties": true } }, "additionalProperties": true }, "EvalTestCase": { "type": "object", "description": "Inline eval test case. The case body is an ordered `steps` array (the unified test model).", "required": [ "title", "runs", "model", "provider", "steps" ], "properties": { "title": { "type": "string" }, "steps": { "type": "array", "minItems": 1, "description": "Ordered test steps. The first `prompt` step is the case query; `toolCalledWith` asserts are the expected tool calls; a single model-free `toolCall` step is a render-check.", "items": { "$ref": "#/components/schemas/EvalTestStep" } }, "runs": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Iterations to execute for this case." }, "model": { "type": "string", "description": "Model ID. Hosted-catalog ids use `provider/name` form (e.g. `anthropic/claude-haiku-4.5`) and run on org credits; provider-native ids require a matching `modelApiKeys` entry (BYOK). Unknown models are rejected with VALIDATION_ERROR at create time." }, "provider": { "type": "string", "description": "Model provider, e.g. `anthropic`, `openai`." }, "isNegativeTest": { "type": "boolean", "description": "When `true`, the case passes if NO tools are called." }, "expectedOutput": { "type": "string" }, "advancedConfig": { "type": "object", "description": "Optional `system`, `temperature`, `toolChoice` overrides.", "additionalProperties": true } }, "additionalProperties": true }, "EvalSuiteCreateRequest": { "type": "object", "description": "Author-only suite-create body. A suite-level default `model` (and optional `provider`) applies to every test unless the test overrides it.", "required": [ "name", "serverIds", "model", "tests" ], "properties": { "name": { "type": "string", "minLength": 1, "description": "Suite name." }, "description": { "type": "string" }, "serverIds": { "type": "array", "minItems": 1, "description": "Servers (by canonical project ID) the suite's cases run against.", "items": { "type": "string" } }, "serverNames": { "type": "array", "description": "Optional display names, parallel to `serverIds`.", "items": { "type": "string" } }, "model": { "type": "string", "minLength": 1, "description": "Suite-level default model id (e.g. `anthropic/claude-haiku-4.5`). Used for any test that omits `model`." }, "provider": { "type": "string", "description": "Optional suite-level default provider. When omitted, the provider is derived from a `provider/model` id." }, "passCriteria": { "type": "object", "properties": { "minimumPassRate": { "type": "number" } } }, "tags": { "type": "array", "description": "Accepted for forward-compat; not persisted today (no-op).", "items": { "type": "string" } }, "tests": { "type": "array", "minItems": 1, "maxItems": 100, "description": "Test cases to create in the suite.", "items": { "type": "object", "required": [ "title", "steps" ], "properties": { "title": { "type": "string", "minLength": 1 }, "steps": { "type": "array", "minItems": 1, "description": "Ordered test steps (the unified test model). The first `prompt` step is the case query; `toolCalledWith` asserts are the expected tool calls; a single model-free `toolCall` step is a render-check.", "items": { "$ref": "#/components/schemas/EvalTestStep" } }, "runs": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Iterations to execute for this case. Defaults to 1." }, "model": { "type": "string", "description": "Per-case model override. Defaults to the suite-level `model`." }, "provider": { "type": "string", "description": "Per-case provider override." }, "expectedOutput": { "type": "string" }, "isNegativeTest": { "type": "boolean", "description": "When `true`, the case passes if NO tools are called." }, "scenario": { "type": "string" }, "advancedConfig": { "type": "object", "description": "Optional `system`, `temperature`, `toolChoice` overrides.", "additionalProperties": true } }, "additionalProperties": true } } }, "additionalProperties": false }, "EvalSuiteCreated": { "type": "object", "required": [ "suiteId", "name", "caseUpsert" ], "properties": { "suiteId": { "type": "string" }, "name": { "type": "string" }, "servers": { "type": "array", "description": "The servers attached to the suite. `name` is present when supplied.", "items": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "caseUpsert": { "type": "object", "description": "Per-case create outcomes. Partial failures don't abort the suite; an all-failed new suite is rejected with VALIDATION_ERROR.", "properties": { "committed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "failed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "error": { "type": "string" } } } } } } } }, "EvalSuiteDetail": { "type": "object", "required": [ "id", "environment", "hosts", "settings", "schedule" ], "description": "One eval suite's full configuration. Distinct from the `EvalSuite` summary returned by the list route, which carries run rollups instead of settings.", "properties": { "id": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] }, "projectId": { "type": [ "string", "null" ] }, "environment": { "type": "object", "description": "LEGACY server bag, kept as rollback data. Unrelated to `environmentIds`.", "properties": { "servers": { "type": "array", "items": { "type": "string" } } } }, "executionConfig": { "type": [ "object", "null" ], "properties": { "model": { "type": "string" }, "systemPrompt": { "type": "string" }, "temperature": { "type": "number" } } }, "hosts": { "type": "array", "items": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "servers": { "type": "array", "items": { "type": "string" } } } } }, "environmentIds": { "type": "array", "description": "Attached project environments, in attach order.", "items": { "type": "string" } }, "settings": { "type": "object", "properties": { "minimumAccuracy": { "type": [ "number", "null" ], "description": "Minimum pass rate for the suite to count as passing." }, "matchOptions": { "type": [ "object", "null" ] }, "checks": { "type": "array", "items": { "type": "object" } }, "judge": { "type": "object", "description": "Goal-completion judge. Absent config reads as enabled.", "properties": { "enabled": { "type": "boolean" }, "model": { "type": [ "string", "null" ] } } } } }, "schedule": { "$ref": "#/components/schemas/EvalSuiteSchedule" }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] } } }, "EvalRunCreateRequest": { "type": "object", "description": "Two valid shapes: `suiteId` (rerun an existing suite, optionally upserting inline `tests` into it) or `suiteName` + `tests` + `serverIds` (create a new suite and run it). Inline `tests` alone \u2014 without a `suiteId` or a `suiteName` \u2014 are rejected with `VALIDATION_ERROR`.\n\n`environmentId` requires `suiteId`: an environment is launchable only through a suite that has it attached (`environmentIds`, set via `PATCH /eval-suites/{suiteId}`), so an environment run on a not-yet-created suite could never be satisfied. `environmentId` and `serverIds` are mutually exclusive.", "anyOf": [ { "required": [ "suiteId" ] }, { "required": [ "suiteName", "tests", "serverIds" ], "properties": { "tests": { "minItems": 1 } } } ], "properties": { "suiteId": { "type": "string", "description": "Existing suite to rerun. A bare `suiteId` with no `tests` reruns the suite exactly as configured." }, "suiteName": { "type": "string", "description": "Name for a new suite. Required (non-empty) when no `suiteId` is given." }, "suiteDescription": { "type": "string" }, "tests": { "type": "array", "maxItems": 100, "description": "Inline test cases to upsert into the suite before running.", "items": { "$ref": "#/components/schemas/EvalTestCase" } }, "serverIds": { "type": "array", "minItems": 1, "description": "Servers (by ID) the run connects to. Required when creating a new suite; optional on reruns \u2014 when omitted, the run connects the suite's saved server selection (the set its snapshot references). A rerun of a suite with no saved selection is rejected with `VALIDATION_ERROR` (`details.reason: \"NO_SAVED_SERVER_SELECTION\"`). Rejected outright for a suite with attached environments (`details.reason: \"ENVIRONMENT_SERVERS_NOT_OVERRIDABLE\"`): the environment supplies a closed set that a server override cannot change, so accepting one would connect a different set than the run is stamped with.", "items": { "type": "string" } }, "modelApiKeys": { "type": "object", "description": "Optional per-provider model API keys (e.g. `{ \"anthropic\": \"sk-ant-\u2026\" }`). Falls back to your organization's configured providers when omitted.", "additionalProperties": { "type": "string" } }, "notes": { "type": "string" }, "passCriteria": { "type": "object", "properties": { "minimumPassRate": { "type": "number" } } }, "iterationOverride": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Override the per-case `runs` count for this run only." }, "environmentId": { "type": "string", "description": "Run against one of the suite's attached project environments. Requires `suiteId`, and must be a member of that suite's `environmentIds` \u2014 otherwise `400` with `details.reason: \"ENVIRONMENT_NOT_ATTACHED\"`, raised before any case is authored or any server connected.\n\nOmission is meaningful: a suite with no attached environments runs legacy; a suite with exactly ONE attached environment runs against it automatically (the response's `environment` says which); a suite with several returns `400` with `details.reason: \"ENVIRONMENT_REQUIRED\"`, naming the candidates.\n\nThe environment supplies the closed server set (so `serverIds` is not required, and is rejected alongside it), and the run is pinned to the revision resolved at launch \u2014 if the environment changes in between, the run is rejected with `409` rather than executing against a different configuration." } }, "additionalProperties": true }, "EvalRunCreated": { "type": "object", "required": [ "runId", "suiteId", "status", "caseUpsert" ], "properties": { "runId": { "type": "string" }, "suiteId": { "type": "string" }, "status": { "type": "string", "const": "running" }, "servers": { "type": "array", "description": "The servers the run connects to \u2014 explicit or derived from the suite's saved selection. `name` is present when known (always, on the derived path).", "items": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "caseUpsert": { "type": "object", "description": "Per-case upsert outcomes for inline `tests`. Partial failures don't abort the run.", "properties": { "committed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }, "failed": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "error": { "type": "string" } } } } } }, "environment": { "description": "The environment revision this run is pinned to. `null` on a legacy run that recorded none \u2014 always present, so a caller never has to distinguish absent from unpinned.", "oneOf": [ { "$ref": "#/components/schemas/EvalRunEnvironment" }, { "type": "null" } ] } } }, "EvalRun": { "type": "object", "required": [ "id", "suiteId", "status", "source", "createdAt" ], "properties": { "id": { "type": "string" }, "suiteId": { "type": "string" }, "runNumber": { "type": [ "integer", "null" ] }, "status": { "type": "string", "description": "Poll until terminal: `completed`, `failed`, or `cancelled`.", "enum": [ "pending", "running", "completed", "failed", "cancelled" ] }, "result": { "type": [ "string", "null" ], "description": "Pass/fail verdict once terminal.", "enum": [ "passed", "failed", null ] }, "summary": { "type": [ "object", "null" ], "properties": { "total": { "type": "integer" }, "passed": { "type": "integer" }, "failed": { "type": "integer" }, "passRate": { "type": "number" } } }, "source": { "type": "string", "description": "Run origin. API-created runs are `api`.", "enum": [ "ui", "api", "sdk" ] }, "notes": { "type": [ "string", "null" ] }, "createdAt": { "type": "number", "description": "Epoch milliseconds." }, "completedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds, `null` until terminal." }, "scoreIntegrity": { "type": [ "string", "null" ], "description": "Whether the run's score evidence verified at ingest. TRI-STATE, and the third state matters: `valid` means the backend checked and definitions and results agree; `invalid` means they do not; `null` (or absent) means NO VERDICT was produced, on a deployment that predates integrity checking. A score gate must treat `null` exactly like `invalid` — absent evidence is not valid evidence.", "enum": [ "valid", "invalid", null ] }, "environment": { "description": "The environment revision this run is pinned to. `null` on a legacy run that recorded none \u2014 always present, so a caller never has to distinguish absent from unpinned.", "oneOf": [ { "$ref": "#/components/schemas/EvalRunEnvironment" }, { "type": "null" } ] }, "insights": { "allOf": [ { "$ref": "#/components/schemas/InsightsEnvelope" } ], "description": "The common actionable-insights envelope. Present on the DETAIL response only \u2014 lists stay compact \u2014 and absent when the caller may not have it or the deployment cannot produce one. Treat absence exactly like `status: \"not_available\"`." } } }, "EvalRunPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/EvalRun" } } } }, "EvalIteration": { "type": "object", "required": [ "id", "iterationNumber", "status", "expectedToolCalls", "actualToolCalls" ], "properties": { "id": { "type": "string" }, "testCaseId": { "type": [ "string", "null" ] }, "title": { "type": [ "string", "null" ] }, "iterationNumber": { "type": "integer" }, "status": { "type": "string", "enum": [ "pending", "running", "completed", "failed", "cancelled" ] }, "result": { "type": [ "string", "null" ], "enum": [ "passed", "failed", null ] }, "model": { "type": [ "string", "null" ] }, "provider": { "type": [ "string", "null" ] }, "startedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "durationMs": { "type": [ "number", "null" ], "description": "Wall-clock duration; `null` until terminal." }, "tokensUsed": { "type": [ "number", "null" ] }, "usage": { "type": [ "object", "null" ], "description": "Structured token usage (input/output/cached/reasoning) when available.", "additionalProperties": true }, "actualToolCalls": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "expectedToolCalls": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "error": { "type": [ "string", "null" ] }, "scores": { "type": [ "array", "null" ], "description": "Per-scorer verdicts for this iteration. `null` when the run predates scoring, or when the stored payload failed validation at the boundary — partially-trusted score data is never projected.", "items": { "$ref": "#/components/schemas/ScoreResult" } }, "evaluationConfig": { "description": "The scorer definitions `scores` were produced under. Present exactly when `scores` is: results carry only a `definitionHash`, so without this a caller cannot tell a gating failure from an advisory one.", "oneOf": [ { "$ref": "#/components/schemas/EvaluationConfigSnapshot" }, { "type": "null" } ] }, "scoreIntegrity": { "type": [ "string", "null" ], "description": "Set when the backend downgraded this iteration's verdict because its gating score evidence was malformed.", "enum": [ "score_integrity_invalid", null ] } } }, "ScoreResult": { "type": "object", "description": "One scorer's verdict for one iteration. `passed` is DERIVED as `value >= passThreshold` — never asserted by the scorer or the model.", "required": [ "scorerId", "scorerVersion", "definitionHash", "status", "passThreshold", "deterministic" ], "properties": { "scorerId": { "type": "string", "maxLength": 128 }, "scorerVersion": { "type": "string" }, "definitionHash": { "type": "string", "description": "Joins this result to its definition in `evaluationConfig.definitions`." }, "status": { "type": "string", "description": "`error` is never a low score — a crashed judge is not a disagreeing judge. `not_applicable` never gates and is excluded from aggregation denominators, which is what distinguishes it from `skipped`.", "enum": [ "scored", "error", "skipped", "not_applicable" ] }, "value": { "type": "number", "minimum": 0, "maximum": 1, "description": "Present only when `status` is `scored`." }, "passThreshold": { "type": "number", "minimum": 0, "maximum": 1 }, "passed": { "type": "boolean", "description": "Derived as `value >= passThreshold`; present only when `status` is `scored`." }, "rationale": { "type": "string", "maxLength": 2000 }, "evidence": { "type": "array", "maxItems": 20, "items": { "type": "string", "maxLength": 300 } }, "deterministic": { "type": "boolean" }, "model": { "type": "string" }, "promptHash": { "type": "string" }, "error": { "type": "string", "maxLength": 500, "description": "Present only when `status` is `error`." }, "scope": { "type": "object", "description": "Absent means case-level; `{kind:\"turn\",promptIndex}` means the check was authored on a single turn.", "additionalProperties": true } } }, "ResolvedScoreDefinition": { "type": "object", "description": "A scorer definition with every semantic default resolved. This is the form that is hashed, so an omitted `onError` and an explicitly-configured default digest identically.", "required": [ "scorerId", "idSource", "scorerVersion", "implementationHash", "deterministic", "passThreshold", "role", "onError", "onSkipped" ], "properties": { "scorerId": { "type": "string", "maxLength": 128 }, "idSource": { "type": "string", "description": "`generated` ids are positional and UNSTABLE across config edits; only `explicit` ids may be referenced by a gate policy or tracked across runs.", "enum": [ "explicit", "generated" ] }, "scorerVersion": { "type": "string" }, "implementationHash": { "type": "string", "description": "Digest of what the scorer actually does — the canonicalized predicate, or the judge prompt plus template version. Required so two judges with different prompts cannot hash identically." }, "label": { "type": "string", "description": "Presentation only; deliberately excluded from the hash." }, "deterministic": { "type": "boolean" }, "passThreshold": { "type": "number", "minimum": 0, "maximum": 1 }, "role": { "type": "string", "description": "Only `gating` scorers decide the iteration's verdict.", "enum": [ "gating", "advisory" ] }, "onError": { "type": "string", "description": "What an `error` status does to a gating iteration. Defaults to `fail`.", "enum": [ "fail", "ignore" ] }, "onSkipped": { "type": "string", "description": "What a `skipped` status does to a gating iteration. Separate from `onError`: a crashed judge and a judge that never ran are different failures.", "enum": [ "fail", "ignore" ] }, "model": { "type": "string" }, "scope": { "type": "object", "additionalProperties": true } } }, "EvaluationConfigSnapshot": { "type": "object", "description": "The scorer definitions a run graded with, plus their hash. Changing scorer configuration changes this hash but NOT the case identity — a threshold edit must not fork a scenario's history.", "required": [ "hash", "definitions" ], "properties": { "hash": { "type": "string", "description": "`evaluationConfigHash` over the resolved definitions. Order-independent — the hash sorts internally." }, "definitions": { "type": "array", "items": { "$ref": "#/components/schemas/ResolvedScoreDefinition" } } } }, "EvalIterationPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/EvalIteration" } }, "nextCursor": { "type": "string", "description": "Opaque cursor for the next page. Omitted on the last page." } } }, "ImportTokensRequest": { "type": "object", "required": [ "serverUrl", "tokens" ], "properties": { "serverUrl": { "type": "string", "description": "The MCP server URL the tokens were obtained for." }, "oauthResourceUrl": { "type": "string", "description": "OAuth protected-resource URL, when it differs from `serverUrl`." }, "clientInformation": { "type": "object", "description": "OAuth client used to obtain the tokens. Optional, but without it server-side refresh cannot run \u2014 always include it when you provide a `refresh_token`.", "required": [ "clientId" ], "properties": { "clientId": { "type": "string" }, "clientSecret": { "type": "string", "format": "password" } } }, "tokens": { "type": "object", "required": [ "access_token" ], "properties": { "access_token": { "type": "string", "format": "password" }, "refresh_token": { "type": "string", "format": "password" }, "expires_in": { "type": "number", "description": "Seconds until the access token expires." }, "token_type": { "type": "string" }, "scope": { "type": "string" }, "id_token": { "type": "string", "format": "password" } } } } }, "Me": { "type": "object", "required": [ "id", "email", "name" ], "properties": { "id": { "type": "string" }, "email": { "type": "string" }, "name": { "type": "string" }, "imageUrl": { "type": [ "string", "null" ] }, "profilePictureUrl": { "type": [ "string", "null" ] }, "plan": { "type": [ "string", "null" ] }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] } } }, "Organization": { "type": "object", "required": [ "id", "name", "isCreator" ], "properties": { "id": { "type": "string", "description": "Pass as `organizationId` to the project routes." }, "name": { "type": "string" }, "plan": { "type": [ "string", "null" ], "description": "Billing plan slug: `free`, `team` or `enterprise`." }, "myRole": { "type": [ "string", "null" ], "description": "The caller's role in the organization." }, "isCreator": { "type": "boolean", "description": "Whether the caller created the organization." }, "logoUrl": { "type": [ "string", "null" ] }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." } } }, "OrganizationPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Organization" } } } }, "Project": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "icon": { "type": [ "string", "null" ] }, "organizationId": { "type": [ "string", "null" ] }, "visibility": { "type": [ "string", "null" ] }, "role": { "type": "string", "description": "Caller's role on the project, when resolved." }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] } } }, "ProjectPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } } } }, "ProjectServer": { "type": "object", "description": "A saved MCP server, projected toward the hosted (HTTP) shape. STDIO command/args/env and raw headers are never exposed.", "required": [ "id", "name", "enabled", "transportType", "useOAuth", "hasClientSecret" ], "properties": { "id": { "type": "string" }, "projectId": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "enabled": { "type": "boolean" }, "transportType": { "type": "string" }, "url": { "type": [ "string", "null" ], "description": "Endpoint for HTTP-transport servers; `null` for stdio." }, "useOAuth": { "type": "boolean" }, "hasClientSecret": { "type": "boolean" }, "oauthScopes": { "type": "array", "items": { "type": "string" } }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] } } }, "ProjectServerPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectServer" } } } }, "EvalSuite": { "type": "object", "required": [ "id", "totals", "passRateTrend" ], "properties": { "id": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "projectId": { "type": [ "string", "null" ] }, "createdAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ] }, "latestRun": { "type": [ "object", "null" ], "properties": { "id": { "type": [ "string", "null" ] }, "status": { "type": [ "string", "null" ] }, "passRate": { "type": [ "number", "null" ] }, "passed": { "type": [ "integer", "null" ] }, "failed": { "type": [ "integer", "null" ] }, "createdAt": { "type": [ "number", "null" ] } } }, "totals": { "type": "object", "properties": { "passed": { "type": "integer" }, "failed": { "type": "integer" }, "runs": { "type": "integer" } } }, "passRateTrend": { "type": "array", "description": "Recent runs' pass rates, oldest first.", "items": { "type": "number" } } } }, "EvalSuitePage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/EvalSuite" } } } }, "EvalSuiteSchedule": { "type": "object", "required": [ "enabled" ], "description": "Recurring-run schedule. A schedule fires exactly ONE run, so an environment-based suite pins the environment it runs against.", "properties": { "enabled": { "type": "boolean" }, "intervalMinutes": { "type": [ "integer", "null" ], "minimum": 5, "maximum": 10080, "description": "Minutes between runs. 5 minutes to 7 days." }, "environmentId": { "type": [ "string", "null" ], "description": "The single attached environment scheduled runs use. Null on a legacy (non-environment) suite." } } }, "EvalSuiteScheduleRequest": { "type": "object", "required": [ "enabled" ], "description": "Enable, repoint, or disable a suite's recurring runs. Disabling PRESERVES the existing environment pin \u2014 send `enabled: true` with a new `environmentId` to repoint it.", "properties": { "enabled": { "type": "boolean" }, "intervalMinutes": { "type": "integer", "minimum": 5, "maximum": 10080 }, "environmentId": { "type": "string", "minLength": 1, "description": "Which attached environment scheduled runs use. Only meaningful when enabling. Omitted on a single-environment suite means that environment; omitted on a multi-environment suite is a VALIDATION_ERROR." } } }, "EvalSuiteUpdateRequest": { "type": "object", "description": "Partial update of a suite's settings. Omitted fields are left as they are.", "properties": { "name": { "type": "string", "minLength": 1 }, "description": { "type": "string" }, "environment": { "type": "object", "description": "LEGACY server bag. Unrelated to `environmentIds`.", "properties": { "servers": { "type": "array", "items": { "type": "string", "minLength": 1 } } } }, "environmentIds": { "type": [ "array", "null" ], "items": { "type": "string", "minLength": 1 }, "minItems": 1, "description": "Tri-state: a non-empty array sets/replaces the attachments, `null` CLEARS them (reverting the suite to legacy config), and `[]` is REJECTED rather than silently read as a clear." }, "executionConfig": { "type": "object", "properties": { "model": { "type": "string", "minLength": 1 }, "systemPrompt": { "type": "string" }, "temperature": { "type": "number" } } }, "hosts": { "type": "array", "items": { "type": "object" } } } }, "ChatSession": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string" }, "title": { "type": [ "string", "null" ] }, "status": { "type": [ "string", "null" ] }, "projectId": { "type": [ "string", "null" ] }, "visibility": { "type": [ "string", "null" ], "description": "`private` or `project`." }, "lastActivityAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "createdAt": { "type": [ "number", "null" ] }, "isPinned": { "type": "boolean" }, "isUnread": { "type": "boolean" } } }, "ChatSessionPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ChatSession" } }, "nextCursor": { "type": "string", "description": "Pass as `before` to fetch the next page. Omitted on the last page." } } }, "ImportTokensResult": { "type": "object", "required": [ "imported" ], "properties": { "imported": { "type": "boolean", "const": true }, "expiresAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds when the stored access token expires, or `null` if unknown." } } }, "ExportSnapshot": { "type": "object", "required": [ "serverId", "exportedAt", "tools", "resources", "prompts" ], "properties": { "serverId": { "type": "string" }, "exportedAt": { "type": "string", "format": "date-time" }, "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } }, "resources": { "type": "array", "items": { "$ref": "#/components/schemas/Resource" } }, "prompts": { "type": "array", "items": { "$ref": "#/components/schemas/Prompt" } } } }, "ChatboxLink": { "type": "object", "description": "Share link for the chatbox. The URL embeds the access token; visible to any caller who can read the chatbox (the same audience that sees it in the hosted UI).", "required": [ "path", "url" ], "properties": { "path": { "type": "string", "description": "App-relative share path." }, "url": { "type": "string", "description": "Absolute share URL." } } }, "ChatboxServer": { "type": "object", "description": "A server attached to the chatbox. Chatboxes attach HTTP servers only, so `url` is the server's endpoint.", "required": [ "id", "name", "useOAuth" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "url": { "type": [ "string", "null" ], "description": "HTTP endpoint of the server." }, "useOAuth": { "type": "boolean" } } }, "Chatbox": { "type": "object", "description": "Summary of a published chatbox, as returned by the list endpoint.", "required": [ "id", "name", "serverCount", "serverNames" ], "properties": { "id": { "type": "string" }, "projectId": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "mode": { "type": [ "string", "null" ], "description": "Who can use the chatbox.", "enum": [ "project_members", "invited_only", "anyone_with_link", null ] }, "hostStyle": { "type": [ "string", "null" ], "description": "Chat surface style the chatbox renders (e.g. `claude`, `chatgpt`)." }, "hostId": { "type": [ "string", "null" ], "description": "The named host the chatbox publishes (hosts and chatboxes are 1:1)." }, "hostName": { "type": [ "string", "null" ] }, "serverCount": { "type": "integer" }, "serverNames": { "type": "array", "items": { "type": "string" } }, "link": { "oneOf": [ { "$ref": "#/components/schemas/ChatboxLink" }, { "type": "null" } ] }, "createdAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." } } }, "ChatboxDetail": { "type": "object", "description": "A chatbox's full read-only settings: the summary fields plus the host execution config and resolved servers.", "required": [ "id", "name", "serverCount", "serverNames", "requireToolApproval", "servers" ], "properties": { "id": { "type": "string" }, "projectId": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "mode": { "type": [ "string", "null" ], "description": "Who can use the chatbox.", "enum": [ "project_members", "invited_only", "anyone_with_link", null ] }, "hostStyle": { "type": [ "string", "null" ], "description": "Chat surface style the chatbox renders (e.g. `claude`, `chatgpt`)." }, "hostId": { "type": [ "string", "null" ], "description": "The named host the chatbox publishes (hosts and chatboxes are 1:1)." }, "hostName": { "type": [ "string", "null" ] }, "serverCount": { "type": "integer" }, "serverNames": { "type": "array", "items": { "type": "string" } }, "link": { "oneOf": [ { "$ref": "#/components/schemas/ChatboxLink" }, { "type": "null" } ] }, "createdAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." }, "updatedAt": { "type": [ "number", "null" ], "description": "Unix epoch milliseconds." }, "modelId": { "type": [ "string", "null" ], "description": "Model the chatbox chats with." }, "systemPrompt": { "type": [ "string", "null" ] }, "temperature": { "type": [ "number", "null" ] }, "requireToolApproval": { "type": "boolean" }, "servers": { "type": "array", "items": { "$ref": "#/components/schemas/ChatboxServer" } } } }, "ChatboxPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Chatbox" } } } }, "TunnelCreateRequest": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Server name to register the tunnel under. Reusing an existing server's name points that record at the tunnel: its `url` is overwritten and stdio records are converted to `transportType: \"http\"`." } } }, "TunnelGrant": { "type": "object", "description": "Everything the caller needs to host the tunnel connection. **Treat the whole object as a credential**: `url` embeds the plaintext `?k=` bearer secret and `connectToken` authenticates the relay WebSocket.", "required": [ "serverId", "name", "existed", "slug", "url", "connectToken", "relayWsUrl" ], "properties": { "serverId": { "type": "string", "description": "The registered project server's ID." }, "name": { "type": "string" }, "existed": { "type": "boolean", "description": "True when a server record with this name already existed (its config now points at the tunnel)." }, "previousUrl": { "type": "string", "description": "The record's previous URL, present when it existed with a different one (it has been overwritten)." }, "previousTransportType": { "type": "string", "description": "The record's previous transport, present when it existed (a `stdio` record has been converted to `http`)." }, "slug": { "type": "string", "description": "Stable public subdomain slug; survives re-creates for the same server." }, "url": { "type": "string", "description": "Public tunnel URL including the `?k=` bearer secret. Also persisted on the server record's `url`." }, "connectToken": { "type": "string", "description": "Bearer for the relay edge WebSocket handshake." }, "connectTokenExpiresAt": { "type": "number", "description": "Epoch-ms expiry of `connectToken`." }, "relayWsUrl": { "type": "string", "description": "Relay edge WebSocket endpoint to connect to." }, "secretVersion": { "type": "number", "description": "Monotonic secret version; each create bumps it." } } }, "TunnelClosed": { "type": "object", "required": [ "serverId", "status" ], "properties": { "serverId": { "type": "string" }, "status": { "type": "string", "enum": [ "closed" ] } } }, "Host": { "type": "object", "description": "A host in a project, as returned by the host list.", "required": [ "id", "name", "hostConfigId", "modelId", "serverCount", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "hostConfigId": { "type": "string", "description": "ID of the content-addressed host config this host points at." }, "modelId": { "type": "string", "description": "Resolved model id, e.g. `anthropic/claude-haiku-4.5`." }, "serverCount": { "type": "integer", "description": "Number of MCP servers attached to the host config." }, "createdAt": { "type": "number", "description": "Unix epoch milliseconds." }, "updatedAt": { "type": "number", "description": "Unix epoch milliseconds." } } }, "HostPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Host" } } } }, "HostDetail": { "type": "object", "description": "A host plus its resolved host config (model, capabilities, host context, MCP profile).", "required": [ "id", "name", "config" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "config": { "type": "object", "additionalProperties": true, "description": "Host config v2 DTO. Opaque object; shape mirrors the inspector's host editor." } } }, "HostCreateRequest": { "type": "object", "description": "Create a host from a built-in template OR a full host config. Exactly one of `template` or `config` is required.", "required": [ "name" ], "properties": { "name": { "type": "string", "description": "Display name for the new host." }, "template": { "type": "string", "description": "Built-in template id to seed the host config from.", "enum": [ "mcpjam", "claude", "claude-code", "chatgpt", "mistral", "cursor", "codex", "copilot", "vscode", "agentcore", "n8n", "perplexity" ] }, "theme": { "type": "string", "enum": [ "light", "dark" ], "description": "Theme stamped into the seeded config (template only)." }, "config": { "type": "object", "additionalProperties": true, "minProperties": 1, "required": [ "modelId" ], "properties": { "modelId": { "type": "string", "minLength": 1, "pattern": ".*\\S.*", "description": "Model id the host pins. Required and non-empty: an environment inherits its client's model when it sets no override, so a host created without one cannot back a headless environment and its launches are refused with `ENV_MODEL_REQUIRED`. A `PATCH` may not clear it either, though hosts created before this rule stay editable." } }, "description": "Full host config v2, used verbatim (alternative to `template`). Must pin `modelId`." } }, "oneOf": [ { "required": [ "template" ] }, { "required": [ "config" ] } ] }, "HostUpdateRequest": { "type": "object", "description": "Edit a host. Provide at least one of `name` or `config`; only the fields you pass change.", "properties": { "name": { "type": "string", "description": "New display name." }, "config": { "type": "object", "additionalProperties": true, "minProperties": 1, "description": "Replacement host config v2." } }, "anyOf": [ { "required": [ "name" ] }, { "required": [ "config" ] } ] }, "HostDeleted": { "type": "object", "required": [ "id", "deleted" ], "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "SandboxImageBuild": { "type": "object", "description": "One build of a sandbox image's image.", "required": [ "id", "status", "provider", "baseImageDigests", "createdAt" ], "properties": { "id": { "type": "string" }, "status": { "type": "string", "enum": [ "queued", "building", "ready", "failed" ] }, "provider": { "type": "string", "enum": [ "e2b", "stub" ] }, "e2bBuildId": { "type": "string" }, "baseImageDigests": { "type": "array", "items": { "type": "string" }, "description": "Resolved sha256 digest of every FROM base (the reproducibility pin)." }, "logPreview": { "type": "string", "description": "Capped, ANSI-stripped tail of the build log." }, "error": { "type": "string" }, "createdAt": { "type": "number" }, "startedAt": { "type": "number" }, "finishedAt": { "type": "number" } } }, "SandboxImage": { "type": "object", "description": "A project's custom Computer image: a digest-pinned blueprint plus its latest build.", "required": [ "id", "projectId", "name", "blueprint", "contentHash", "sharing", "isOwner", "currentBuild", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": "string" }, "contentHash": { "type": "string" }, "sharing": { "type": "string", "enum": [ "user", "project" ], "description": "`user` = a personal draft; `project` = shared with the whole project." }, "isOwner": { "type": "boolean" }, "currentBuild": { "oneOf": [ { "$ref": "#/components/schemas/SandboxImageBuild" }, { "type": "null" } ] }, "createdAt": { "type": "number" }, "updatedAt": { "type": "number" }, "blueprint": { "type": "string", "description": "The image's blueprint YAML (base / initialize / maintenance / knowledge). `base` must be an allowlisted official image pinned by sha256 digest." } } }, "SandboxImagePage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/SandboxImage" } }, "nextCursor": { "type": "string" } } }, "SandboxImageBuildPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/SandboxImageBuild" } }, "nextCursor": { "type": "string" } } }, "SandboxImageCreateRequest": { "type": "object", "additionalProperties": false, "description": "Create a sandbox image from a blueprint YAML document: an allowlisted, `@sha256`-digest-pinned `base` image plus `initialize` steps baked into the image, and optional `maintenance`/`knowledge` delivered to the agent at runtime.", "required": [ "name", "blueprint" ], "properties": { "name": { "type": "string", "minLength": 1, "description": "Display name for the new sandbox image; must be non-empty after trimming." }, "blueprint": { "type": "string", "minLength": 1, "description": "Blueprint YAML defining the image. Validated server-side; use the validate endpoint for fast feedback." } } }, "SandboxImageUpdateRequest": { "type": "object", "additionalProperties": false, "description": "Edit a sandbox image. Provide at least one of `name` or `blueprint`; only the fields you pass change.", "properties": { "name": { "type": "string", "minLength": 1, "description": "New display name; must be non-empty after trimming." }, "blueprint": { "type": "string", "minLength": 1, "description": "Replacement blueprint YAML. Edits to maintenance/knowledge take effect at the next chat turn without a rebuild; base/initialize edits require a new build." } }, "anyOf": [ { "required": [ "name" ] }, { "required": [ "blueprint" ] } ] }, "SandboxImageDeleted": { "type": "object", "required": [ "id", "deleted" ], "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "SandboxImageBuildStarted": { "type": "object", "description": "A build was accepted (202). It runs in the background \u2014 poll the builds list for status.", "required": [ "id", "buildId", "reused" ], "properties": { "id": { "type": "string", "description": "The sandbox image id." }, "buildId": { "type": "string" }, "reused": { "type": "boolean", "description": "True when an identical prior build was reused instead of rebuilding." } } }, "ComputerAttached": { "type": "object", "required": [ "imageId", "computerId", "status" ], "properties": { "imageId": { "type": "string" }, "computerId": { "type": "string" }, "status": { "type": "string", "description": "The computer's lifecycle status after attaching (it re-provisions)." } } }, "ComputerReset": { "type": "object", "required": [ "projectId", "reset" ], "properties": { "projectId": { "type": "string" }, "reset": { "type": "boolean" } } }, "EnvironmentSkillSelection": { "type": "object", "description": "An explicit pinned skill selection. Cannot be empty \u2014 clear the field instead (send `null` on update) to mean \"no pinned skills\".", "required": [ "mode", "skillIds" ], "properties": { "mode": { "type": "string", "enum": [ "explicit" ] }, "skillIds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "Project-shared skill IDs. Skills carrying supporting files or extra frontmatter, and plugin-component skills, cannot be pinned." } } }, "ProjectEnvironment": { "type": "object", "description": "A project environment: a named, live-editable execution bundle that eval suites and journeys run against.", "required": [ "id", "projectId", "name", "hostId", "revision", "archived", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": "string", "description": "Unique among the project's live (non-archived) environments." }, "description": { "type": "string" }, "hostId": { "type": "string", "description": "The host this environment runs against." }, "serverAttachmentId": { "type": "string", "description": "Present only when the environment pins a standalone server group; otherwise the host config's own servers apply." }, "modelId": { "type": "string", "description": "The environment's model override. ABSENT means the environment INHERITS the model pinned on its host — not that it has no model. Resolve the environment and read `effectiveModelId` to learn what will actually run." }, "skillSelection": { "$ref": "#/components/schemas/EnvironmentSkillSelection" }, "pluginVersionIds": { "type": "array", "items": { "type": "string" }, "description": "Pinned plugin VERSION IDs. Narrow by design: the plugin must be installed and enabled, the version must be `ready`, at most one version per plugin may be pinned, and none of its skills may carry supporting files. Not a general-purpose plugin list." }, "sandboxImageId": { "type": "string", "description": "Sandbox-image pin: a project-shared image (see the images endpoints) that eval runs in this environment boot a fresh sandbox from. Absent when unpinned.", "minLength": 1, "pattern": ".*\\S.*" }, "revision": { "type": "integer", "description": "Optimistic-concurrency counter. Pass this back as `expectedRevision` on the next write; if it no longer matches, the write is rejected with 409 instead of overwriting a concurrent edit." }, "archived": { "type": "boolean", "description": "Archived environments cannot be edited or launched until restored." }, "archivedAt": { "type": "number", "description": "Unix epoch milliseconds. Present only when archived." }, "createdAt": { "type": "number", "description": "Unix epoch milliseconds." }, "updatedAt": { "type": "number", "description": "Unix epoch milliseconds." } } }, "ProjectEnvironmentPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectEnvironment" } } } }, "Plugin": { "type": "object", "description": "An Agent Plugin (agent-plugins.org bundle) imported into a project. Versions materialize MCP servers and skills; environments pin version ids to run them.", "required": [ "id", "projectId", "name", "enabled", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": "string", "description": "Normalized plugin name \u2014 the namespace its skills load under." }, "displayName": { "type": "string" }, "description": { "type": "string" }, "enabled": { "type": "boolean", "description": "Disabled plugins keep their versions but resolve for no run." }, "activeVersionId": { "type": "string", "description": "The version environment pins default to. Absent before the first activation." }, "createdAt": { "type": "number" }, "updatedAt": { "type": "number" } } }, "PluginPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Plugin" } } } }, "PluginVersion": { "type": "object", "description": "One immutable imported plugin version with its component projections.", "required": [ "id", "pluginId", "bundleHash", "status", "componentCounts", "servers", "skills", "createdAt" ], "properties": { "id": { "type": "string" }, "pluginId": { "type": "string" }, "declaredVersion": { "type": "string", "description": "`manifest.version` \u2014 metadata only; `bundleHash` is the identity." }, "bundleHash": { "type": "string" }, "manifestHash": { "type": "string" }, "status": { "type": "string", "enum": [ "staging", "ready", "invalid" ], "description": "Only `ready` versions resolve at runtime." }, "componentCounts": { "type": "object", "description": "Per-component tallies. `apps` counts preserved `.app.json` metadata entries only (no runtime effect).", "required": [ "skills", "servers", "apps", "assets", "unsupported" ], "properties": { "skills": { "type": "number" }, "servers": { "type": "number" }, "apps": { "type": "number" }, "assets": { "type": "number" }, "unsupported": { "type": "number" } } }, "servers": { "type": "array", "description": "Declared MCP server components, with the project server row each one materialized as.", "items": { "type": "object", "required": [ "componentId", "componentKey", "declaredName", "placement", "authenticationPolicy", "materializedServerId" ], "properties": { "componentId": { "type": "string" }, "componentKey": { "type": "string" }, "declaredName": { "type": "string" }, "placement": { "type": "string", "enum": [ "remote", "local", "computer" ], "description": "Where the component can execute; `local`/`computer` components never run on MCPJam hosted servers." }, "authenticationPolicy": { "type": "string", "enum": [ "on_install", "on_use" ] }, "materializedServerId": { "type": "string" } } } }, "skills": { "type": "array", "description": "Declared skill components, with the project skill row each one materialized as.", "items": { "type": "object", "required": [ "componentId", "componentKey", "declaredName", "modelRef", "materializedSkillId" ], "properties": { "componentId": { "type": "string" }, "componentKey": { "type": "string" }, "declaredName": { "type": "string" }, "modelRef": { "type": "string", "description": "Namespaced model-facing reference: `/`." }, "materializedSkillId": { "type": "string" } } } }, "createdAt": { "type": "number" }, "readyAt": { "type": "number" } } }, "ProjectEnvironmentCreateRequest": { "type": "object", "description": "Unknown fields are rejected.", "required": [ "name", "hostId" ], "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1 }, "description": { "type": "string" }, "hostId": { "type": "string" }, "serverAttachmentId": { "type": "string" }, "modelId": { "type": "string", "minLength": 1, "pattern": ".*\\S.*", "description": "Model to run instead of the host's; omit to inherit the host's. Stored verbatim — no alias canonicalization — so pass exactly the id the provider request should carry." }, "skillSelection": { "$ref": "#/components/schemas/EnvironmentSkillSelection" }, "pluginVersionIds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "Pinned plugin VERSION IDs. Narrow by design: the plugin must be installed and enabled, the version must be `ready`, at most one version per plugin may be pinned, and none of its skills may carry supporting files. Not a general-purpose plugin list." }, "sandboxImageId": { "type": "string", "minLength": 1, "description": "Optional sandbox-image pin. Must be a project-shared image; personal drafts are rejected \u2014 promote them first.", "pattern": ".*\\S.*" } } }, "ProjectEnvironmentUpdateRequest": { "type": "object", "description": "Only the fields you send change. `serverAttachmentId`, `skillSelection`, and `pluginVersionIds` are three-state: omit to leave unchanged, send `null` to CLEAR, send a value to set. An empty array is rejected \u2014 it is not a way to clear. Send at least one field besides `expectedRevision`. Unknown fields are rejected.", "required": [ "expectedRevision" ], "additionalProperties": false, "properties": { "expectedRevision": { "type": "integer", "minimum": 0, "description": "The `revision` you last read. If the environment changed since, the write is rejected with 409 rather than clobbering the other edit." }, "name": { "type": "string", "minLength": 1 }, "description": { "type": "string", "description": "Send an empty string to clear the description." }, "hostId": { "type": "string" }, "serverAttachmentId": { "type": "string", "nullable": true, "description": "`null` clears the pin and falls back to the host config's servers." }, "modelId": { "type": "string", "minLength": 1, "pattern": ".*\\S.*", "nullable": true, "description": "New model override, or `null` to CLEAR it and fall back to the host's model. Omit to leave unchanged. An empty string is rejected — it is not a way to clear." }, "skillSelection": { "allOf": [ { "$ref": "#/components/schemas/EnvironmentSkillSelection" } ], "nullable": true, "description": "`null` clears the pinned skill selection." }, "pluginVersionIds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "nullable": true, "description": "Pinned plugin VERSION IDs. Narrow by design: the plugin must be installed and enabled, the version must be `ready`, at most one version per plugin may be pinned, and none of its skills may carry supporting files. Not a general-purpose plugin list. `null` clears all pins." }, "sandboxImageId": { "type": [ "string", "null" ], "description": "New sandbox-image pin, or null to clear it. Omit to leave unchanged.", "minLength": 1, "pattern": ".*\\S.*" } } }, "ProjectEnvironmentRevisionRequest": { "type": "object", "description": "The optimistic-concurrency precondition. Unknown fields are rejected.", "required": [ "expectedRevision" ], "additionalProperties": false, "properties": { "expectedRevision": { "type": "integer", "minimum": 0, "description": "The `revision` you last read for this environment." } } }, "ProjectEnvironmentResolved": { "type": "object", "description": "What an environment resolves to right now \u2014 the same resolution an eval run performs.", "required": [ "environment", "hostId", "hostName", "hostConfigId", "selectedServerIds", "effectiveServerIds", "pluginVersions", "servers" ], "properties": { "environment": { "type": "object", "required": [ "id", "name", "revision" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "revision": { "type": "integer", "description": "The revision this resolution reflects." } } }, "hostId": { "type": "string" }, "hostName": { "type": "string" }, "hostConfigId": { "type": "string", "description": "The host's config at resolve time \u2014 hosts rotate configs live, so this can change without the environment's `revision` changing." }, "modelId": { "type": "string", "description": "The environment's stored override, when it sets one." }, "effectiveModelId": { "type": "string", "description": "The model this environment WILL RUN — the override if it has one, else the host config's. Always present on a successful resolve: an environment with no model anywhere cannot be resolved for launch and fails with 409 `details.reason: \"environment_model_required\"`." }, "modelSource": { "type": "string", "enum": [ "environment", "host" ], "description": "Which of the two supplied `effectiveModelId`." }, "serverAttachmentId": { "type": "string" }, "sandboxImageId": { "type": "string", "description": "The environment's sandbox-image pin, when set.", "minLength": 1, "pattern": ".*\\S.*" }, "selectedServerIds": { "type": "array", "items": { "type": "string" }, "description": "The closed non-plugin server set." }, "effectiveServerIds": { "type": "array", "items": { "type": "string" }, "description": "`selectedServerIds` plus the servers contributed by pinned plugin versions \u2014 the set a run actually connects. Identical to `selectedServerIds` when no plugins are pinned." }, "pluginVersions": { "type": "array", "description": "The resolved plugin version pins, in pin order.", "items": { "type": "object", "required": [ "pluginId", "pluginVersionId", "name", "bundleHash" ], "properties": { "pluginId": { "type": "string" }, "pluginVersionId": { "type": "string" }, "name": { "type": "string" }, "bundleHash": { "type": "string" } } } }, "servers": { "type": "array", "description": "Connectable projection of `effectiveServerIds`, healed to the current live servers.", "items": { "type": "object", "required": [ "serverId", "name" ], "properties": { "serverId": { "type": "string" }, "name": { "type": "string" } } } } } }, "SandboxImageBlueprintValidateRequest": { "type": "object", "additionalProperties": false, "required": [ "blueprint" ], "properties": { "blueprint": { "type": "string", "description": "Blueprint YAML to lint." } } }, "SandboxImageBlueprintValidateResult": { "type": "object", "description": "Lint result. `ok: true` carries the resolved base digest; `ok: false` carries structured errors. Always returned with HTTP 200 \u2014 an invalid blueprint is a successful lint.", "required": [ "ok" ], "properties": { "ok": { "type": "boolean" }, "baseImageDigest": { "type": "string", "description": "Resolved sha256 digest of `base` (present when ok)." }, "errors": { "type": "array", "items": { "type": "object", "required": [ "path", "message" ], "properties": { "path": { "type": "string", "description": "Location of the violation, e.g. `initialize[0].run` (empty for document-level errors)." }, "message": { "type": "string" } } } } }, "oneOf": [ { "required": [ "baseImageDigest" ], "properties": { "ok": { "const": true } } }, { "required": [ "errors" ], "properties": { "ok": { "const": false } } } ] }, "AgentTurnRequest": { "type": "object", "required": [ "messages" ], "properties": { "messages": { "type": "array", "minItems": 1, "maxItems": 50, "description": "The conversation so far, oldest first. The caller owns state and resends the full history each turn. Each message content is capped at 8,000 characters AND 8,192 UTF-8 bytes; the whole history is additionally capped at 98,304 UTF-8 bytes (96 KB).", "items": { "type": "object", "required": [ "role", "content" ], "properties": { "role": { "type": "string", "enum": [ "user", "assistant" ] }, "content": { "type": "string", "minLength": 1, "maxLength": 8000, "description": "Plain text. Max 8,000 characters and 8,192 UTF-8 bytes." } } } }, "idempotencyKey": { "type": "string", "minLength": 1, "maxLength": 200, "description": "A STABLE identity for the triggering event \u2014 not a fresh uuid per attempt. Each write the turn performs derives its own key from this one, so a retried turn re-issues the same mutations onto the same rows instead of authoring duplicates. Printable ASCII only (the value becomes a header on every write the turn issues). Omitting it preserves the older non-idempotent behaviour rather than being rejected." }, "conversationId": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Where an approval control can be rendered for this turn (a channel, a thread, a DM). Its presence is what makes the GATED tools available at all: without somewhere to collect a click, the turn is not offered them, so the model never proposes an action nobody can approve." }, "slackChannelId": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Slack-named spelling of `conversationId`. Both are accepted indefinitely; `conversationId` wins when both are sent." } } }, "AgentTurnResponse": { "type": "object", "required": [ "reply", "toolCalls", "createdResources", "proposedActions", "usage" ], "properties": { "reply": { "type": "string", "description": "The assistant's final text for this turn." }, "toolCalls": { "type": "array", "items": { "type": "object", "required": [ "operation" ], "properties": { "operation": { "type": "string", "description": "Platform operation name invoked during the turn." } } } }, "createdResources": { "type": "array", "description": "Resources the turn created, with app deep links.", "items": { "type": "object", "required": [ "type", "id", "url" ], "properties": { "type": { "type": "string", "description": "Resource type. New types may be added; render an unknown type generically rather than dropping it." }, "id": { "type": "string" }, "name": { "type": "string" }, "url": { "type": "string", "format": "uri" } } } }, "proposedActions": { "type": "array", "description": "Actions the turn wants to take but may not take on its own \u2014 they SPEND, or reach a third-party system. Nothing here has happened: each entry is a persisted proposal awaiting a human click. The `actionId` is all an approval control needs to carry, because the server holds what the action does; the remaining fields are for RENDERING only and must never be echoed back as instructions.", "items": { "type": "object", "required": [ "actionId", "operation", "description", "buttonLabel", "kind" ], "properties": { "actionId": { "type": "string", "description": "Opaque id. The only value a click may carry." }, "operation": { "type": "string", "description": "Platform operation name. Display and telemetry only." }, "description": { "type": "string", "description": "Short summary of the TARGET, not a cost estimate. Model-influenced text \u2014 escape before rendering." }, "buttonLabel": { "type": "string", "description": "Verb for the approval control, e.g. \"Run it\"." }, "kind": { "type": "string", "enum": [ "start", "cancel", "generate", "schedule", "external" ], "description": "What approving it does, so a host can word its confirmation and its after-the-fact announcement truthfully. Treat an unrecognised value as neutral rather than guessing." }, "confirmSeverity": { "type": "string", "enum": [ "spend", "external", "none" ], "description": "How stern the confirmation copy should be: `spend` consumes quota or credits, `external` runs somewhere MCPJam does not control and cannot undo, `none` states explicitly that the default cost warning would be FALSE for this action (disabling a schedule stops spend). Absent means the host's default copy is honest enough." }, "target": { "type": "object", "required": [ "type", "selector" ], "properties": { "type": { "type": "string", "description": "Resource type the proposal is about, e.g. `eval_suite`." }, "selector": { "type": "string", "description": "The validated input's own selector for it \u2014 an id where the server minted the proposal, possibly a name where the model authored it. Match against both." } }, "description": "What the proposal is about, for correlating it with other turn output (e.g. suppressing a duplicate run affordance on the created suite it already offers to run). Display/dedup only \u2014 never an instruction. Absent means match-unknown; fall back to coarser behavior." } } } }, "usage": { "type": "object", "required": [ "inputTokens", "outputTokens" ], "properties": { "inputTokens": { "type": "integer" }, "outputTokens": { "type": "integer" } } } } }, "EvalRunEnvironment": { "type": [ "object", "null" ], "description": "The project environment a run is pinned to, at the revision resolved when it launched. `null` for a legacy run that used the suite's saved server selection.", "required": [ "id" ], "properties": { "id": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "revision": { "type": [ "integer", "null" ], "description": "The environment revision the run executed against." } } }, "ProjectCreateRequest": { "type": "object", "description": "Neither a client config nor a server map is required: the backend seeds both. Requires membership of the target organization, and counts against the plan's project limit.", "required": [ "name" ], "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1 }, "description": { "type": "string" }, "organizationId": { "type": "string", "description": "Defaults to the caller's default organization when omitted." }, "icon": { "type": "string" }, "visibility": { "type": "string", "enum": [ "public", "private" ] } } }, "ProjectUpdateRequest": { "type": "object", "description": "Metadata only, and at least one field is required. The project's server map is deliberately NOT updatable here: the underlying mutation treats it as a wholesale replace and deletes every server absent from the payload. Use the per-server routes instead. Requires project admin.", "additionalProperties": false, "minProperties": 1, "properties": { "name": { "type": "string", "minLength": 1 }, "description": { "type": "string" }, "icon": { "type": "string" }, "visibility": { "type": "string", "enum": [ "public", "private" ] } } }, "ProjectDeleted": { "type": "object", "required": [ "id", "deleted" ], "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "ProjectServerCreateRequest": { "type": "object", "description": "Creates a saved server in the project. Server names are unique per workspace \u2014 a clash responds `409`. Scope comes from the path: `projectId`, `serverId` and `workspaceId` are rejected in the body.", "required": [ "name", "enabled", "transportType" ], "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1 }, "enabled": { "type": "boolean" }, "transportType": { "type": "string", "enum": [ "stdio", "http" ] }, "command": { "type": "string", "description": "stdio transport only." }, "args": { "type": "array", "items": { "type": "string" } }, "env": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Stored encrypted; never returned by any read." }, "url": { "type": "string", "format": "uri", "description": "http transport only." }, "headers": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Stored encrypted; never returned by any read." }, "hasBearerToken": { "type": "boolean" }, "timeout": { "type": "number", "exclusiveMinimum": 0 }, "clientCapabilities": { "description": "Opaque MCP client capabilities bag." }, "useOAuth": { "type": "boolean" }, "oauthScopes": { "type": "array", "items": { "type": "string" } }, "clientId": { "type": "string" }, "oauthResourceUrl": { "type": "string" }, "oauthProtocolMode": { "type": "string" }, "oauthProtocolVersion": { "type": "string" }, "oauthRegistrationStrategy": { "type": "string" }, "xaaAuthzIssuer": { "type": "string" }, "xaaAllowPathScopedIssuer": { "type": "boolean" }, "oauthAllowPathScopedIssuer": { "type": "boolean" }, "useXaa": { "type": "boolean" }, "authServerMode": { "type": "string", "enum": [ "mcpjam", "own" ] }, "xaaSubject": { "type": "string" }, "xaaEmail": { "type": "string" }, "xaaIdentityAssertionFormat": { "type": "string" }, "xaaClientAuth": { "type": "string" }, "authMethod": { "type": "string" }, "registrationMode": { "type": "string" }, "clientSecret": { "type": "string", "description": "Stored encrypted; never returned. Reads expose only `hasClientSecret`." } } }, "ProjectServerUpdateRequest": { "type": "object", "description": "Sparse update \u2014 omitted fields are unchanged, and at least one field is required. Editing a server that is shared as an OAuth connection requires project admin.", "additionalProperties": false, "minProperties": 1, "properties": { "name": { "type": "string", "minLength": 1 }, "enabled": { "type": "boolean" }, "transportType": { "type": "string", "enum": [ "stdio", "http" ] }, "command": { "type": "string", "description": "stdio transport only." }, "args": { "type": "array", "items": { "type": "string" } }, "env": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Stored encrypted; never returned by any read." }, "url": { "type": "string", "format": "uri", "description": "http transport only." }, "headers": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Stored encrypted; never returned by any read." }, "hasBearerToken": { "type": "boolean" }, "timeout": { "type": "number", "exclusiveMinimum": 0 }, "clientCapabilities": { "description": "Opaque MCP client capabilities bag." }, "useOAuth": { "type": "boolean" }, "oauthScopes": { "type": "array", "items": { "type": "string" } }, "clientId": { "type": "string" }, "oauthResourceUrl": { "type": "string" }, "oauthProtocolMode": { "type": "string" }, "oauthProtocolVersion": { "type": "string" }, "oauthRegistrationStrategy": { "type": "string" }, "xaaAuthzIssuer": { "type": "string" }, "xaaAllowPathScopedIssuer": { "type": "boolean" }, "oauthAllowPathScopedIssuer": { "type": "boolean" }, "useXaa": { "type": "boolean" }, "authServerMode": { "type": "string", "enum": [ "mcpjam", "own" ] }, "xaaSubject": { "type": "string" }, "xaaEmail": { "type": "string" }, "xaaIdentityAssertionFormat": { "type": "string" }, "xaaClientAuth": { "type": "string" }, "authMethod": { "type": "string" }, "registrationMode": { "type": "string" }, "clientSecret": { "type": "string", "description": "Stored encrypted; never returned. Reads expose only `hasClientSecret`." }, "clearClientSecret": { "type": "boolean", "description": "Removes the stored client secret. Mutually exclusive with `clientSecret`." }, "clearXaaConfig": { "type": "boolean" } } }, "ProjectServerDeleted": { "type": "object", "required": [ "id", "deleted" ], "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "HostServersRequest": { "type": "object", "description": "Replaces the host's server attachments wholesale. Every id must name a live server in the same project.", "required": [ "serverIds" ], "additionalProperties": false, "properties": { "serverIds": { "type": "array", "items": { "type": "string" } }, "optionalServerIds": { "type": "array", "items": { "type": "string" }, "description": "Attached but not required to connect." } } }, "HostDuplicateRequest": { "type": "object", "description": "Optional name for the copy; defaults to a derived name.", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1 } } }, "ServerConnection": { "type": "object", "description": "The state of one \"connect this MCP server\" request. Every server-connection route returns this shape, so a caller polls exactly what it created.", "required": [ "connectionRequestId", "status", "expiresAt" ], "properties": { "connectionRequestId": { "type": "string", "description": "Public `scr_\u2026` identifier. Safe to print; it is never sufficient to act on a request." }, "status": { "type": "string", "enum": [ "discovering", "awaiting_project", "awaiting_authorization", "authorizing", "validating", "ready", "failed", "expired", "cancelled" ] }, "handoffUrl": { "type": "string", "description": "Where the user finishes in a browser, for both `awaiting_project` and `awaiting_authorization`. Returned ONLY by the create call \u2014 the raw token behind it is minted once and never stored. Treat it as a private, single-person capability: never post it to a shared channel." }, "expiresAt": { "type": "string", "format": "date-time" }, "projectId": { "type": "string" }, "serverId": { "type": "string" }, "server": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "url": { "type": "string", "description": "Redacted: query values are replaced, because a keyed-endpoint URL's query can be the credential." }, "enabled": { "type": "boolean" } } }, "error": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" }, "retryable": { "type": "boolean", "description": "Whether retrying THIS request could succeed." }, "candidates": { "type": "array", "description": "Present only on AMBIGUOUS_SERVER: the saved servers the URL matched, so the caller can re-send with a serverId.", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "url": { "type": "string" } } } } } } } }, "ServerConnectionCreateBody": { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "description": "The MCP server URL to connect." }, "projectId": { "type": "string", "description": "Omit to have the user choose in the browser; a request with no project resolves to `awaiting_project` rather than defaulting to one." }, "serverId": { "type": "string", "description": "Disambiguates when a project already has several saved servers on this URL." }, "name": { "type": "string", "description": "Used only if a server row is created. Ignored when an existing row is reused." }, "reauthorize": { "type": "boolean", "description": "Skip deduplication and force a fresh authorization." } } }, "ProjectEnvironmentCapabilities": { "type": "object", "required": [ "modelOverrides", "modelMatrix" ], "properties": { "modelOverrides": { "type": "boolean", "description": "`modelId` is accepted on environment create and update." }, "modelMatrix": { "type": "boolean", "description": "Environment cells may vary by model on one host (the compare grid)." } } }, "Persona": { "type": "object", "description": "A reusable synthetic character. The GOAL lives on the journey, not here \u2014 one persona can be pointed at many different tasks.", "required": [ "id", "projectId", "slug", "name", "role", "notes", "source", "avatar", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Durable id \u2014 what journeys reference and what every route here addresses." }, "projectId": { "type": "string" }, "slug": { "type": "string", "description": "Stable slug key, shared with exported session data. Useful for correlating transcripts; NOT an address for this API." }, "name": { "type": "string" }, "role": { "type": "string" }, "notes": { "type": [ "string", "null" ] }, "source": { "type": "string", "description": "How the persona came to exist.", "enum": [ "manual", "generated", "cluster" ] }, "seedKeywords": { "type": "array", "items": { "type": "string" }, "description": "Present on cluster-derived personas: the terms the cluster was built from." }, "avatar": { "type": "object", "description": "Look, as indices into the app's fixed shape/palette sets. `null` when unset \u2014 the app then derives one from the id.", "required": [ "shape", "palette" ], "properties": { "shape": { "type": [ "integer", "null" ] }, "palette": { "type": [ "integer", "null" ] } } }, "createdAt": { "type": "number", "description": "Epoch milliseconds." }, "updatedAt": { "type": "number", "description": "Epoch milliseconds." } } }, "PersonaPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Persona" } }, "nextCursor": { "type": "string", "description": "Present only when another page exists. Opaque \u2014 do not parse it." } } }, "PersonaCreateRequest": { "type": "object", "required": [ "name", "role" ], "additionalProperties": false, "properties": { "name": { "type": "string", "maxLength": 120, "description": "1\u2013120 characters." }, "role": { "type": "string", "maxLength": 120, "description": "What this person is, in the product's terms \u2014 \"finance ops lead\", \"first-time buyer\". 1\u2013120 characters." }, "notes": { "type": "string", "maxLength": 2000, "description": "Free-text context the model is grounded in. Up to 2000 characters." }, "avatarShape": { "type": "integer", "minimum": 0, "description": "Index into the app's fixed shape set. Out of range is a 400 naming the field, not an upstream error about a constant you have never seen." }, "avatarPalette": { "type": "integer", "minimum": 0, "description": "Index into the app's fixed palette set." } } }, "PersonaUpdateRequest": { "type": "object", "description": "At least one field is required \u2014 an empty patch is a 400 rather than a silent no-op.", "additionalProperties": false, "minProperties": 1, "properties": { "name": { "type": "string", "maxLength": 120, "description": "1\u2013120 characters." }, "role": { "type": "string", "maxLength": 120, "description": "What this person is, in the product's terms \u2014 \"finance ops lead\", \"first-time buyer\". 1\u2013120 characters." }, "notes": { "type": "string", "maxLength": 2000, "description": "Free-text context the model is grounded in. Up to 2000 characters." }, "avatarShape": { "type": "integer", "minimum": 0, "description": "Index into the app's fixed shape set. Out of range is a 400 naming the field, not an upstream error about a constant you have never seen." }, "avatarPalette": { "type": "integer", "minimum": 0, "description": "Index into the app's fixed palette set." } } }, "PersonaDeleted": { "type": "object", "required": [ "id", "projectId", "deleted" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "deleted": { "type": "boolean", "enum": [ true ] } } }, "Journey": { "type": "object", "description": "A persona's task: what they are trying to accomplish, and across which targets. Launching one is `POST /projects/{projectId}/journeys/{journeyId}/runs`.", "required": [ "id", "projectId", "name", "goal", "personaId", "swarmId", "environmentIds", "sessionsPerTarget", "maxTurns", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": "string" }, "goal": { "type": "string", "description": "What the persona is trying to accomplish. This drives the whole run." }, "personaId": { "type": "string" }, "swarmId": { "type": [ "string", "null" ], "description": "The swarm container this journey was authored under. Provenance only \u2014 nothing resolves through it at run time, and a journey created outside a swarm has none." }, "environmentIds": { "type": "array", "items": { "type": "string" }, "description": "Environments this journey fans out across. EMPTY on a host-pinned journey \u2014 that is a different execution model, not a journey with no targets." }, "serverAttachmentId": { "type": "string", "description": "Legacy host-pinned grounding. Absent on an environment-based journey." }, "sessionsPerTarget": { "type": [ "integer", "null" ], "description": "Sessions run against EACH target. Total sessions = targets \u00d7 this." }, "maxTurns": { "type": [ "integer", "null" ] }, "createdAt": { "type": "number", "description": "Epoch milliseconds." }, "updatedAt": { "type": "number", "description": "Epoch milliseconds." } } }, "JourneyPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Journey" } }, "nextCursor": { "type": "string", "description": "Present only when another page exists. Opaque \u2014 do not parse it." } } }, "JourneyCreateRequest": { "type": "object", "required": [ "goal", "personaId", "sessionsPerTarget", "maxTurns" ], "additionalProperties": false, "properties": { "name": { "type": "string", "maxLength": 200 }, "goal": { "type": "string", "maxLength": 4000, "description": "1\u20134000 characters." }, "personaId": { "type": "string", "description": "An existing persona in this project." }, "swarmId": { "type": "string", "description": "Authoring provenance only \u2014 which swarm container this was made in." }, "environmentIds": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "The environments to fan out across. NON-EMPTY when present: `[]` reads as \"these, naming none\", and would silently fall back to something else." }, "serverAttachmentId": { "type": "string", "description": "Legacy host-pinned grounding." }, "hostIds": { "type": "array", "items": { "type": "string" } }, "sessionsPerTarget": { "type": "integer", "minimum": 1, "maximum": 100 }, "maxTurns": { "type": "integer", "minimum": 1, "maximum": 200 } } }, "JourneyUpdateRequest": { "type": "object", "description": "PATCH with a TRI-STATE, and the third state is the point: a field absent leaves it alone, present sets it, and `null` CLEARS it. There is no other way to say \"stop fanning this journey out across environments and go back to its host targets\".\n\n`sessionsPerTarget` and `maxTurns` must be sent TOGETHER \u2014 they are one config object upstream, so a partial update would need a read-modify-write that could silently clobber a concurrent edit. Requiring both is a 400 you can fix rather than a lost update you never see.", "additionalProperties": false, "minProperties": 1, "properties": { "name": { "type": "string", "maxLength": 200 }, "goal": { "type": "string", "maxLength": 4000 }, "environmentIds": { "description": "A non-empty list to set, or `null` to clear and fall back to host targets.", "oneOf": [ { "type": "array", "items": { "type": "string" }, "minItems": 1 }, { "type": "null" } ] }, "serverAttachmentId": { "type": [ "string", "null" ], "description": "`null` clears the attachment." }, "hostIds": { "type": "array", "items": { "type": "string" } }, "sessionsPerTarget": { "type": "integer", "minimum": 1, "maximum": 100 }, "maxTurns": { "type": "integer", "minimum": 1, "maximum": 200 } } }, "JourneyArchived": { "type": "object", "description": "Journeys ARCHIVE rather than delete: existing runs keep pointing at the definition they executed.", "required": [ "id", "projectId", "archived" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "archived": { "type": "boolean", "enum": [ true ] } } }, "Swarm": { "type": "object", "description": "An authoring container for journeys. It holds DEFAULTS, not execution state \u2014 launching happens per journey.", "required": [ "id", "projectId", "name", "description", "environmentIds", "sessionsPerTarget", "maxTurns", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "environmentIds": { "type": "array", "items": { "type": "string" }, "description": "Default fan-out for journeys authored under this container." }, "sessionsPerTarget": { "type": [ "integer", "null" ] }, "maxTurns": { "type": [ "integer", "null" ] }, "createdAt": { "type": "number", "description": "Epoch milliseconds." }, "updatedAt": { "type": "number", "description": "Epoch milliseconds." } } }, "SwarmPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Swarm" } }, "nextCursor": { "type": "string", "description": "Present only when another page exists. Opaque \u2014 do not parse it." } } }, "SwarmCreateRequest": { "type": "object", "required": [ "name", "sessionsPerTarget", "maxTurns" ], "additionalProperties": false, "properties": { "name": { "type": "string", "maxLength": 200 }, "description": { "type": "string", "maxLength": 2000 }, "environmentIds": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "sessionsPerTarget": { "type": "integer", "minimum": 1, "maximum": 100 }, "maxTurns": { "type": "integer", "minimum": 1, "maximum": 200 } } }, "SwarmUpdateRequest": { "type": "object", "description": "Same tri-state as the journey patch (`null` clears), and the same rule that `sessionsPerTarget` and `maxTurns` travel together.", "additionalProperties": false, "minProperties": 1, "properties": { "name": { "type": "string", "maxLength": 200 }, "description": { "type": [ "string", "null" ], "maxLength": 2000 }, "environmentIds": { "oneOf": [ { "type": "array", "items": { "type": "string" }, "minItems": 1 }, { "type": "null" } ] }, "sessionsPerTarget": { "type": "integer", "minimum": 1, "maximum": 100 }, "maxTurns": { "type": "integer", "minimum": 1, "maximum": 200 } } }, "SwarmArchived": { "type": "object", "required": [ "id", "projectId", "archived" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "archived": { "type": "boolean", "enum": [ true ] } } }, "PersonaGenerateRequest": { "type": "object", "description": "Exactly ONE grounding source: `serverAttachmentId` or `environmentId`. Sending both is a 400 \u2014 a request that is already confused about what it wants should not have a winner picked for it.", "additionalProperties": false, "properties": { "serverAttachmentId": { "type": "string" }, "environmentId": { "type": "string" }, "journeyCount": { "type": "integer", "description": "Journeys to draft per persona." }, "personaCount": { "type": "integer", "minimum": 1, "maximum": 12, "description": "Ask for a SLATE of N personas. Omitting it is not the same as `1`: it selects the legacy single-persona response shape, so a default would switch the response under a caller that only knows the other one." }, "description": { "type": "string", "maxLength": 2000, "description": "Free-text audience brief, forwarded verbatim into the prompt." }, "existingPersonas": { "type": "array", "description": "Personas you already have, so the model drafts different ones. A prompt hint, not a data feed \u2014 only the first few dozen are used.", "items": { "type": "object", "required": [ "name", "role" ], "properties": { "name": { "type": "string" }, "role": { "type": "string" } } } } } }, "JourneyGenerateRequest": { "type": "object", "description": "Exactly ONE grounding source: `serverAttachmentId` or `environmentId`. Sending both is a 400 \u2014 a request that is already confused about what it wants should not have a winner picked for it.", "required": [ "persona" ], "additionalProperties": false, "properties": { "serverAttachmentId": { "type": "string" }, "environmentId": { "type": "string" }, "journeyCount": { "type": "integer" }, "persona": { "type": "object", "description": "The persona to draft journeys FOR, passed BY VALUE rather than by id. Not an oversight: the create flow drafts a persona and its journeys before either exists, so requiring a persisted persona would force you to save a draft you may discard.", "required": [ "name", "role" ], "properties": { "name": { "type": "string" }, "role": { "type": "string" }, "notes": { "type": "string" } } }, "description": { "type": "string", "maxLength": 2000 } } }, "GenerationDrafts": { "type": "object", "description": "DRAFTS. Nothing here is persisted \u2014 feed what you want to keep to `POST /projects/{projectId}/personas` or `POST /projects/{projectId}/journeys`.\n\nThe shape depends on the request: `POST /personas/generate` returns `{persona, journeys}` without `personaCount` and `{personas: [{persona, journeys}]}` with it; `POST /journeys/generate` returns `{journeys}`.", "additionalProperties": true, "properties": { "persona": { "$ref": "#/components/schemas/PersonaDraft" }, "personas": { "type": "array", "items": { "type": "object", "properties": { "persona": { "$ref": "#/components/schemas/PersonaDraft" }, "journeys": { "type": "array", "items": { "$ref": "#/components/schemas/JourneyDraft" } } } } }, "journeys": { "type": "array", "items": { "$ref": "#/components/schemas/JourneyDraft" } } } }, "PersonaDraft": { "type": "object", "description": "A generated persona draft. Nothing is persisted until you create it.", "required": [ "name", "role" ], "additionalProperties": true, "properties": { "name": { "type": "string" }, "role": { "type": "string" }, "notes": { "type": "string" } } }, "JourneyDraft": { "type": "object", "required": [ "goal" ], "additionalProperties": true, "properties": { "name": { "type": "string" }, "goal": { "type": "string" }, "suggestedChecks": { "type": "array", "description": "Deterministic checks the backend suggests. Only `toolCalledAtLeastOnce` is ever suggested \u2014 it is the one kind whose validity the grounding snapshot can prove, because the tool provably exists.", "items": { "type": "object", "required": [ "type", "toolName" ], "properties": { "type": { "type": "string", "enum": [ "toolCalledAtLeastOnce" ] }, "toolName": { "type": "string" } } } } } }, "JourneyRunTarget": { "type": "object", "required": [ "hostId" ], "properties": { "hostId": { "type": "string" }, "hostName": { "type": "string" }, "targetId": { "type": "string", "description": "Execution identity. TWO TARGETS CAN SHARE A `hostId` \u2014 two environments may resolve to the same host with different servers \u2014 so this, not `hostId`, is what identifies a target within a run." }, "modelId": { "type": "string" } } }, "JourneyRunAttempt": { "type": "object", "description": "One session's execution record: (target, session index).", "required": [ "chatSessionId", "hostId", "targetId", "sessionIndex", "status", "errorCode", "errorMessage" ], "properties": { "chatSessionId": { "type": [ "string", "null" ], "description": "`null` until the attempt claims a session." }, "hostId": { "type": "string" }, "targetId": { "type": [ "string", "null" ] }, "sessionIndex": { "type": "integer" }, "status": { "type": "string", "description": "`pending` \u2192 `running` \u2192 terminal. A `pending` attempt cannot jump straight to a terminal state.", "enum": [ "pending", "running", "succeeded", "failed", "rate_limited" ] }, "errorCode": { "type": [ "string", "null" ] }, "errorMessage": { "type": [ "string", "null" ], "description": "Capped and redacted before it reaches this response." } } }, "JourneyRunSummary": { "type": "object", "required": [ "total", "succeeded", "failed", "rateLimited" ], "properties": { "total": { "type": "integer", "description": "Targets \u00d7 `sessionsPerTarget`, fixed at launch." }, "succeeded": { "type": "integer" }, "failed": { "type": "integer" }, "rateLimited": { "type": "integer" } } }, "JourneyRun": { "type": "object", "required": [ "id", "projectId", "journeyId", "status", "canceled", "stale", "summary", "targets", "createdAt" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "journeyId": { "type": "string" }, "waveId": { "type": "string", "description": "The batch this run was launched with. Sibling runs of one co-launched wave share it; a solo relaunch is a wave of one. Absent on a run launched without one." }, "status": { "type": "string", "description": "Poll until it leaves `running`.", "enum": [ "running", "completed", "partial", "failed", "rate_limited" ] }, "canceled": { "type": "boolean", "description": "True when somebody STOPPED this run. **Check this before showing a run as a failure**: a cancelled run reports `status: \"failed\"`, because cancellation is recorded as a marker rather than a status of its own." }, "stale": { "type": "boolean", "description": "True when the runner went silent and the watchdog settled the run. Distinct from a run that failed while still reporting." }, "error": { "type": "string", "description": "Raw marker behind `canceled` / `stale`." }, "summary": { "$ref": "#/components/schemas/JourneyRunSummary" }, "targets": { "type": "array", "items": { "$ref": "#/components/schemas/JourneyRunTarget" } }, "persona": { "type": "object", "description": "The persona AS PINNED AT LAUNCH, from the run's immutable snapshot \u2014 not the persona's current values, which may have been edited since.", "properties": { "personaId": { "type": [ "string", "null" ] }, "name": { "type": [ "string", "null" ] }, "role": { "type": [ "string", "null" ] } } }, "attempts": { "type": "array", "description": "Per-session execution records. Present on the single-run read only \u2014 lists stay compact.", "items": { "$ref": "#/components/schemas/JourneyRunAttempt" } }, "targetSummaries": { "type": "array", "description": "The same counts as `summary`, broken down per target.", "items": { "type": "object", "required": [ "hostId", "total", "succeeded", "failed", "rateLimited" ], "properties": { "hostId": { "type": "string" }, "targetId": { "type": "string" }, "total": { "type": "integer" }, "succeeded": { "type": "integer" }, "failed": { "type": "integer" }, "rateLimited": { "type": "integer" } } } }, "createdAt": { "type": "number", "description": "Epoch milliseconds." }, "lastHeartbeatAt": { "type": "number", "description": "Epoch milliseconds of the runner's last check-in. A long-stale heartbeat on a `running` run is what the watchdog acts on." }, "insights": { "allOf": [ { "$ref": "#/components/schemas/InsightsEnvelope" } ], "description": "The common actionable-insights envelope. Present on the DETAIL response only \u2014 lists stay compact \u2014 and absent when the caller may not have it or the deployment cannot produce one. Treat absence exactly like `status: \"not_available\"`." } } }, "JourneyRunPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/JourneyRun" } }, "nextCursor": { "type": "string", "description": "Present only when another page exists." } } }, "JourneyRunSession": { "type": "object", "required": [ "id", "chatSessionId", "projectId", "status", "readiness", "goalScore", "messageCount", "startedAt", "lastActivityAt" ], "properties": { "id": { "type": "string", "description": "The session's document id \u2014 the same value `GET /chat-sessions` returns as `id`, so a session found here can be looked up there." }, "chatSessionId": { "type": "string", "description": "The RUNTIME key for the same session, used by the chat transport and the app's deep links. Distinct from `id` and NOT interchangeable with it." }, "projectId": { "type": "string" }, "hostId": { "type": "string" }, "runId": { "type": "string" }, "journeyId": { "type": "string" }, "personaId": { "type": "string" }, "personaLabel": { "type": "string" }, "status": { "type": [ "string", "null" ], "description": "ARCHIVAL state. A run session stays `active` forever unless archived, so this says nothing about how the run went \u2014 read `outcome` for the verdict.", "enum": [ "active", "archived", null ] }, "outcome": { "type": [ "string", "null" ], "description": "How this session's attempt ended. `null` when the attempt cannot be matched (historical runs); absent on servers that predate the field.", "enum": [ "succeeded", "failed", "rate_limited", "running", "pending", null ] }, "readiness": { "description": "Opaque readiness block." }, "goalScore": { "description": "Opaque goal-completion block." }, "messageCount": { "type": "integer" }, "preview": { "type": "string", "description": "First message only. The transcript is a separate read." }, "modelId": { "type": "string" }, "startedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "lastActivityAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." } } }, "JourneyRunSessionPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/JourneyRunSession" } }, "nextCursor": { "type": "string", "description": "Present only when another page exists." } } }, "JourneyRunLaunchRequest": { "type": "object", "description": "Optional. A launch with no options is a BODYLESS POST \u2014 omit the body entirely rather than sending `null`, which is rejected.", "additionalProperties": false, "properties": { "waveId": { "type": "string", "maxLength": 64, "description": "Opaque id linking the sibling runs of one co-launched batch. Mint it yourself and reuse it across the launches you want grouped." }, "environmentIds": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "Fan out across THESE environments instead of the journey's authored targets. NON-EMPTY when present: `[]` is a caller saying \"across these\" while naming none, and would have silently launched the authored targets instead. Silently running something other than what was asked for is bad anywhere; on an operation that spends it is worse. Omit the field to mean \"as authored\"." } } }, "JourneyRunLaunched": { "type": "object", "required": [ "id", "journeyId", "projectId", "status", "deduped" ], "properties": { "id": { "type": "string", "description": "The run id. Poll it, or stop it with `cancel`." }, "journeyId": { "type": "string" }, "projectId": { "type": "string" }, "status": { "type": "string", "description": "Always `running` \u2014 the run row exists and its fan-out has started. This is a 202: nothing here says the journey has finished, only that it is under way.", "enum": [ "running" ] }, "deduped": { "type": "boolean", "description": "True when an `Idempotency-Key` replayed onto a run that ALREADY existed, so nothing new was started. The response is otherwise identical, which is how a retry of a dropped request tells \"I launched it\" from \"it was already going\" without a second read." } } }, "JourneyRunCanceled": { "type": "object", "required": [ "id", "status", "canceled", "alreadyCanceled", "finalized" ], "properties": { "id": { "type": "string" }, "status": { "type": "string", "description": "The run's terminal status after the cancel settled it." }, "canceled": { "type": "boolean", "enum": [ true ] }, "alreadyCanceled": { "type": "boolean", "description": "True when the run was already cancelled and this call did nothing." }, "finalized": { "type": "integer", "description": "Attempts this call moved to terminal. Zero on an idempotent replay." } } }, "ScorecardCriterion": { "type": "object", "required": [ "id", "label", "kind", "passCount", "failCount", "pendingCount", "failedGradingCount" ], "properties": { "id": { "type": "string" }, "label": { "type": [ "string", "null" ] }, "kind": { "type": "string" }, "passCount": { "type": "integer" }, "failCount": { "type": "integer" }, "pendingCount": { "type": "integer", "description": "Claimed for grading and unfinished \u2014 includes crashed runners." }, "failedGradingCount": { "type": "integer", "description": "Sessions whose GRADING broke. Deliberately separate from `failCount`: folding them together makes a crashed judge look like a regression." } } }, "RunScorecard": { "type": "object", "description": "Deterministic rubric result for one run. NO MODEL IS INVOLVED and nothing is spent \u2014 reach for this before the model-generated wave insights, which usually is not the question you actually have.", "required": [ "runId", "criteria", "sessionsTotal", "sessionsGraded" ], "properties": { "runId": { "type": "string" }, "criteria": { "type": "array", "description": "EVERY criterion the run's rubric declared, in snapshot order \u2014 including ones nothing was graded against. An absent row would be indistinguishable from a criterion that was never configured.", "items": { "$ref": "#/components/schemas/ScorecardCriterion" } }, "sessionsTotal": { "type": "integer" }, "sessionsGraded": { "type": "integer", "description": "The DENOMINATOR for any rate you compute. Never divide by `sessionsTotal` \u2014 3 failures of 4 graded sessions out of 40 attempted is not 7.5%." } } }, "SwarmFinding": { "type": "object", "description": "A criterion that keeps failing, tracked ACROSS waves. One finding is one problem over time, not one row per occurrence.", "required": [ "id", "fingerprint", "dimension", "subject", "status", "occurrenceCount", "lastSeenWaveId", "firstSeenAt", "lastSeenAt", "resolvedAt", "dismissedAt", "updatedAt" ], "properties": { "id": { "type": "string" }, "fingerprint": { "type": "string", "description": "Stable identity across waves \u2014 this is what makes a streak a streak rather than a new finding each time." }, "dimension": { "type": "string" }, "subject": { "type": "object", "required": [ "kind", "id", "label" ], "properties": { "kind": { "type": "string" }, "id": { "type": "string", "description": "Identity. The fingerprint is built from this, never from the label." }, "label": { "type": "string", "description": "Display only; refreshed on every firing so renames show through." } } }, "status": { "type": "string", "description": "A LIFECYCLE: first seen, seen again, came back after being resolved, stopped firing. Note what is NOT here \u2014 `dismissed` is not a status. Dismissal is the orthogonal `dismissedAt` below, so a finding can be both recurring and dismissed.", "enum": [ "new", "recurring", "regressed", "resolved" ] }, "occurrenceCount": { "type": "integer" }, "lastSeenWaveId": { "type": "string" }, "firstSeenAt": { "type": "number", "description": "Epoch milliseconds." }, "lastSeenAt": { "type": "number", "description": "Epoch milliseconds." }, "resolvedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "dismissedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds. ORTHOGONAL to `status` \u2014 dismissing hides a finding from your view without claiming it stopped happening." }, "updatedAt": { "type": "number", "description": "Epoch milliseconds." } } }, "SwarmFindingPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/SwarmFinding" } }, "nextCursor": { "type": "string", "description": "Present only when another page exists." } } }, "FindingDismissed": { "type": "object", "required": [ "id", "projectId", "dismissed" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "dismissed": { "type": "boolean", "description": "The finding's dismissal state AFTER this call." } } }, "SwarmOverviewFinding": { "type": "object", "required": [ "criterionId", "label", "kind", "failCount", "pendingCount", "failedGradingCount", "sessionsGraded", "runStreak" ], "properties": { "criterionId": { "type": "string" }, "label": { "type": [ "string", "null" ] }, "kind": { "type": [ "string", "null" ] }, "failCount": { "type": "integer" }, "pendingCount": { "type": "integer" }, "failedGradingCount": { "type": "integer" }, "sessionsGraded": { "type": "integer", "description": "The DENOMINATOR for any rate you compute. Never divide by the session total." }, "runStreak": { "type": "integer", "description": "Consecutive runs of this journey where the criterion failed." } } }, "SwarmOverviewRun": { "type": "object", "required": [ "runId", "journeyId", "journeyName", "journeyArchived", "personaName", "status", "summary", "goalCompletion", "findings", "targets", "createdAt" ], "properties": { "runId": { "type": "string" }, "journeyId": { "type": "string" }, "journeyName": { "type": "string" }, "journeyArchived": { "type": "boolean", "description": "The run stays readable after its journey is archived; this is how you tell." }, "personaName": { "type": "string" }, "status": { "type": "string" }, "waveId": { "type": "string" }, "summary": { "$ref": "#/components/schemas/JourneyRunSummary" }, "goalCompletion": { "description": "`null` when nothing has been graded \u2014 never zeroes, which would read as \"everything failed\".", "oneOf": [ { "type": "object", "required": [ "gradedCount", "passedCount", "avgScore", "pendingCount", "failedCount" ], "properties": { "gradedCount": { "type": "integer" }, "passedCount": { "type": "integer" }, "avgScore": { "type": [ "number", "null" ] }, "pendingCount": { "type": [ "integer", "null" ] }, "failedCount": { "type": [ "integer", "null" ] } } }, { "type": "null" } ] }, "findings": { "type": "array", "items": { "$ref": "#/components/schemas/SwarmOverviewFinding" } }, "targets": { "type": "array", "items": { "type": "object", "required": [ "hostName", "modelId" ], "properties": { "hostName": { "type": "string" }, "modelId": { "type": "string" }, "environmentName": { "type": "string" } } } }, "createdAt": { "type": "number", "description": "Epoch milliseconds." } } }, "SwarmOverview": { "type": "object", "description": "Project-wide roll-up across recent runs. Deterministic; nothing is spent.", "required": [ "runs", "runsConsidered", "goalCompletion" ], "properties": { "runs": { "type": "array", "items": { "$ref": "#/components/schemas/SwarmOverviewRun" } }, "runsConsidered": { "type": "integer", "description": "How many runs the roll-up scanned. Bounded \u2014 this is not \"all runs ever\"." }, "goalCompletion": { "type": "object", "required": [ "gradedCount", "passedCount", "passRate", "runsWithGrades", "trend" ], "properties": { "gradedCount": { "type": "integer" }, "passedCount": { "type": "integer" }, "passRate": { "type": [ "number", "null" ], "description": "`null` when nothing is graded yet \u2014 never `0`, which would read as \"everything failed\"." }, "runsWithGrades": { "type": "integer" }, "trend": { "type": "array", "items": { "type": "object", "required": [ "dayStartMs", "gradedCount", "passedCount", "passRate" ], "properties": { "dayStartMs": { "type": "number", "description": "UTC day start, epoch milliseconds." }, "gradedCount": { "type": "integer" }, "passedCount": { "type": "integer" }, "passRate": { "type": "number" } } } } } } } }, "WaveInsights": { "type": "object", "description": "Model analysis over a whole wave. Produced asynchronously \u2014 POLL this rather than re-requesting, because a second request SPENDS again.", "required": [ "waveId", "status", "insights", "discovery", "errorCode", "errorMessage", "updatedAt" ], "properties": { "waveId": { "type": "string" }, "status": { "type": "string", "enum": [ "pending", "completed", "failed" ] }, "insights": { "type": [ "object", "null" ], "description": "The DIRECTED lane: an explanation of the findings the deterministic pass already surfaced. `null` until generation completes. An opaque object \u2014 read it, do not pattern-match its internals.", "additionalProperties": true }, "discovery": { "type": [ "object", "null" ], "description": "The DISCOVERY lane: what the model noticed unprompted. `null` while only the directed lane has finished, which is a normal intermediate state rather than a failure. Opaque, like `insights`.", "additionalProperties": true }, "errorCode": { "type": [ "string", "null" ] }, "errorMessage": { "type": [ "string", "null" ] }, "updatedAt": { "type": "number", "description": "Epoch milliseconds." } } }, "WaveInsightsRequested": { "type": "object", "required": [ "waveId", "projectId", "status" ], "properties": { "waveId": { "type": "string" }, "projectId": { "type": "string" }, "status": { "type": "string", "enum": [ "pending" ] } } }, "WaveInsightsCanceled": { "type": "object", "required": [ "waveId", "projectId", "canceled" ], "properties": { "waveId": { "type": "string" }, "projectId": { "type": "string" }, "canceled": { "type": "boolean", "enum": [ true ] } } }, "WaveInsightsRequest": { "type": "object", "additionalProperties": false, "properties": { "force": { "type": "boolean", "description": "Regenerate over a wave that already has insights. OFF by default because it SPENDS a second time against your organization's shared daily ledger \u2014 and the common cause of a repeated request is a caller that did not poll." } } }, "Scenario": { "type": "object", "description": "A published environment: what a visitor lands on when you share a link.", "required": [ "id", "environmentId", "name", "mode", "accessVersion", "link" ], "properties": { "id": { "type": "string" }, "environmentId": { "type": "string" }, "name": { "type": "string" }, "mode": { "type": "string", "description": "Who may open the share link:\n- `project_members` \u2014 signed-in members of the project only\n- `invited_only` \u2014 named members, invited individually\n- `anyone_with_link` \u2014 **anyone holding the URL**, signed in or not", "enum": [ "project_members", "invited_only", "anyone_with_link" ] }, "accessVersion": { "type": "integer", "description": "Bumped whenever access NARROWS \u2014 a mode change, or a member removal. Sessions minted under an older version stop working, which is what makes those changes take effect at once rather than at expiry. A link ROTATION is deliberately not one of them: it mints a new URL without bumping this, so anyone who already redeemed the old link keeps working. Rotating is half of closing a leak \u2014 see the rotate-link operation." }, "link": { "type": [ "string", "null" ], "description": "The share link. `null` when the scenario has no link token." }, "created": { "type": "boolean", "description": "False when the environment was ALREADY published and this returned the existing scenario. Publishing is idempotent, so this is how you tell \"I published it\" from \"it was already there\" without a preflight read." }, "overridesIgnored": { "type": "boolean", "description": "True when the create-time overrides (`name`, `description`, `mode`) were NOT applied because the environment was already published. Paired with `created: false` \u2014 change an existing scenario with `PATCH .../user-testing/scenarios/{scenarioId}`." } } }, "ScenarioPublishRequest": { "type": "object", "description": "Optional CREATE-TIME overrides, applied in the same call so the scenario is never briefly live in a wider mode than you asked for. Ignored on a republish \u2014 see `overridesIgnored`.", "additionalProperties": false, "properties": { "name": { "type": "string", "maxLength": 200 }, "description": { "type": "string", "maxLength": 2000 }, "mode": { "type": "string", "description": "Who may open the share link:\n- `project_members` \u2014 signed-in members of the project only\n- `invited_only` \u2014 named members, invited individually\n- `anyone_with_link` \u2014 **anyone holding the URL**, signed in or not", "enum": [ "project_members", "invited_only", "anyone_with_link" ] } } }, "ScenarioDeleted": { "type": "object", "required": [ "environmentId", "deleted" ], "properties": { "environmentId": { "type": "string" }, "deleted": { "type": "boolean", "description": "False when the environment had no scenario. NOT an error \u2014 unpublishing something already unpublished is a no-op." }, "id": { "type": "string" } } }, "UserTestingScenario": { "type": "object", "description": "Scenario metadata after an update. Deliberately WITHOUT `accessVersion`: a mode change bumps it upstream, but the envelope this route re-reads does not carry the new value \u2014 reporting a stale one would document a revocation signal the response cannot deliver. The publish response carries the real one.", "required": [ "id", "projectId", "name", "description", "mode" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] }, "mode": { "type": [ "string", "null" ], "enum": [ "project_members", "invited_only", "anyone_with_link", null ] } } }, "ScenarioUpdateRequest": { "type": "object", "description": "At least one field. **Send `mode` on its own**: identity and exposure are separate operations upstream, and applying them in sequence could leave the scenario live in a mode you did not ask for.", "additionalProperties": false, "minProperties": 1, "properties": { "name": { "type": "string", "maxLength": 200 }, "description": { "type": "string", "maxLength": 2000 }, "mode": { "type": "string", "description": "Who may open the share link:\n- `project_members` \u2014 signed-in members of the project only\n- `invited_only` \u2014 named members, invited individually\n- `anyone_with_link` \u2014 **anyone holding the URL**, signed in or not", "enum": [ "project_members", "invited_only", "anyone_with_link" ] } } }, "UserTestingSession": { "type": "object", "description": "One visitor's session with a published scenario. A SUMMARY, not a transcript.", "required": [ "id", "chatSessionId", "messageCount", "preview", "visitor", "feedback", "startedAt", "lastActivityAt" ], "properties": { "id": { "type": "string", "description": "The address for the transcript route." }, "chatSessionId": { "type": "string" }, "messageCount": { "type": "integer" }, "preview": { "type": "string", "description": "First message only. The transcript is a separate, explicit read." }, "modelId": { "type": "string" }, "toolCallCount": { "type": "integer" }, "authInterrupted": { "type": "boolean", "description": "The visitor abandoned mid-flow because a server demanded auth." }, "visitor": { "type": "object", "properties": { "displayName": { "type": "string" }, "segment": { "type": "string" }, "authType": { "type": "string", "enum": [ "signedIn", "guest" ] }, "recency": { "type": "string", "enum": [ "new", "returning" ] }, "deviceKind": { "type": "string" }, "language": { "type": "string" } } }, "feedback": { "type": "object", "required": [ "rating", "comment", "count" ], "properties": { "rating": { "type": [ "number", "null" ] }, "comment": { "type": [ "string", "null" ] }, "count": { "type": "integer" } } }, "theme": { "type": "object", "required": [ "id", "label", "keywords" ], "properties": { "id": { "type": "string" }, "label": { "type": [ "string", "null" ] }, "keywords": { "type": "array", "items": { "type": "string" } } } }, "startedAt": { "type": "number", "description": "Epoch milliseconds." }, "lastActivityAt": { "type": "number", "description": "Epoch milliseconds." } } }, "UserTestingSessionPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/UserTestingSession" } }, "nextCursor": { "type": "string", "description": "Present only when another page exists." } } }, "TranscriptMessage": { "type": "object", "description": "One projected transcript message. Tool payloads and blobs are dropped.", "required": [ "role", "text" ], "properties": { "role": { "type": "string" }, "text": { "type": "string" }, "toolName": { "type": "string" }, "createdAt": { "type": "number", "description": "Epoch milliseconds." } } }, "UserTestingSessionDetail": { "type": "object", "description": "A session's transcript, paged. The stored blob URL is NEVER returned: it is a direct handle with no further authorization, so handing it out would turn one authorized read into an unbounded, unrevocable one.", "required": [ "id", "scenarioId", "chatSessionId", "modelId", "startedAt", "lastActivityAt", "messageCount", "messages" ], "properties": { "id": { "type": "string" }, "scenarioId": { "type": "string" }, "chatSessionId": { "type": [ "string", "null" ] }, "modelId": { "type": [ "string", "null" ] }, "startedAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "lastActivityAt": { "type": [ "number", "null" ], "description": "Epoch milliseconds." }, "messageCount": { "type": [ "integer", "null" ], "description": "`null` \u2014 never `0` \u2014 when the transcript could not be read. Zero would be a claim the visitor said nothing, the opposite of what an unreadable blob means, and a caller checking only this field would act on it." }, "transcriptUnavailable": { "type": "boolean", "description": "True when the stored conversation could not be read. Distinct from an empty `messages`, which means the visitor genuinely said nothing." }, "messages": { "type": "array", "items": { "$ref": "#/components/schemas/TranscriptMessage" } }, "nextCursor": { "type": "string" } } }, "GuestExecution": { "type": "object", "description": "The spend dial for ANONYMOUS visitors. Replaced as a whole, never patched: these caps only mean something as a set, and a partial update that raised `dailyCreditCap` while leaving a stale `maxConcurrentComputers` behind would produce a combination nobody chose.", "required": [ "enabled", "computerEnabled", "sharedSkillsEnabled", "dailyCreditCap", "dailyComputerStartCap", "maxConcurrentComputers" ], "properties": { "enabled": { "type": "boolean", "description": "Master switch. Everything below is inert while this is false." }, "computerEnabled": { "type": "boolean" }, "sharedSkillsEnabled": { "type": "boolean" }, "dailyCreditCap": { "type": "number", "minimum": 0, "description": "Hard ceiling on what visitors can spend per day, in credits." }, "dailyComputerStartCap": { "type": "integer", "minimum": 0 }, "maxConcurrentComputers": { "type": "integer", "minimum": 0 }, "harnessEnabled": { "type": "boolean" }, "dailyHarnessSpendCapMicros": { "type": "integer", "minimum": 0, "description": "Integer USD micros." }, "dailyHarnessCallCap": { "type": "integer", "minimum": 0 }, "maxConcurrentHarnessRuns": { "type": "integer", "minimum": 0 } } }, "ScenarioLinkRotated": { "type": "object", "required": [ "id", "projectId", "rotated", "link" ], "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "rotated": { "type": "boolean", "enum": [ true ] }, "link": { "type": [ "string", "null" ], "description": "The NEW share link. The old one stops working immediately." } } }, "ScenarioMemberRemoved": { "type": "object", "required": [ "scenarioId", "projectId", "removed" ], "properties": { "scenarioId": { "type": "string" }, "projectId": { "type": "string" }, "removed": { "type": "string", "description": "The id or email that was removed, echoed back." } } }, "ScenarioMemberRequest": { "type": "object", "description": "UPSERT by email, so re-inviting somebody is not an error.", "required": [ "email" ], "additionalProperties": false, "properties": { "email": { "type": "string", "maxLength": 320 }, "sendInviteEmail": { "type": "boolean", "description": "OFF by default: adding somebody quietly is not the same as inviting them." } } }, "ScenarioRebindRequest": { "type": "object", "required": [ "environmentId" ], "additionalProperties": false, "properties": { "environmentId": { "type": "string", "description": "The environment to point this scenario at. Must belong to the SAME project." } } }, "ActionableFindingEvidence": { "type": "object", "required": [ "kind", "excerpt" ], "description": "One citation behind a finding. Evidence is what makes a finding checkable rather than an assertion.", "properties": { "sessionId": { "type": "string" }, "iterationId": { "type": "string" }, "kind": { "type": "string", "enum": [ "tool_error", "transcript", "feedback", "judge", "contrast" ] }, "excerpt": { "type": "string", "description": "Scrubbed and clipped at the producer. Never a full transcript." }, "toolName": { "type": "string" }, "errorCode": { "type": "string" } } }, "ActionableFinding": { "type": "object", "required": [ "id", "signalFingerprint", "title", "category", "attribution", "actionTarget", "actionability", "severity", "confidence", "observed", "recommendation", "acceptanceCriteria", "affected", "evidence" ], "description": "One actionable finding. The fields are ordered from cheapest to trust to most: `observed` is deterministic, `recommendation` is the model's, and `evidence` lets you check both.", "properties": { "id": { "type": "string", "description": "Stable remediation id (`rf_<16 hex>`). Survives dynamic error values, so the same problem keeps the same id across runs \u2014 dismiss it once and it stays dismissed." }, "signalFingerprint": { "type": "string", "description": "The registry signal this derives from. Several findings can share one." }, "title": { "type": "string" }, "category": { "type": "string", "enum": [ "unknown", "tool_contract", "tool_runtime", "capability_gap", "workflow", "agent_behavior", "test_design", "environment" ] }, "attribution": { "type": "string", "description": "WHOSE problem this is. `server_*` points at the MCP server; `agent_or_prompt` and `test_design` point back at the caller.", "enum": [ "unknown", "server_contract", "server_runtime", "server_capability", "agent_or_prompt", "test_design", "environment" ] }, "actionTarget": { "type": "string", "description": "What you would change to fix it.", "enum": [ "investigate", "mcp_server", "agent_configuration", "eval_case", "environment" ] }, "actionability": { "type": "string", "description": "`ready` means the finding names a specific target and change. `investigate` means it does not yet. `informational` means there is nothing to do.", "enum": [ "informational", "investigate", "ready" ] }, "severity": { "type": "string", "enum": [ "info", "low", "medium", "high" ] }, "confidence": { "type": "string", "enum": [ "low", "medium", "high" ] }, "observed": { "type": "string", "description": "DETERMINISTIC observation \u2014 counts and identities, never model prose. This is the part you can verify yourself." }, "rootCause": { "type": "string" }, "recommendation": { "type": "string" }, "acceptanceCriteria": { "type": "array", "description": "How you would know the fix worked.", "items": { "type": "string" } }, "affected": { "type": "object", "required": [ "count", "total", "unit" ], "description": "How much of the analyzed population hit this. Read it as a ratio \u2014 `1/40` and `38/40` are different problems.", "properties": { "count": { "type": "integer" }, "total": { "type": "integer" }, "unit": { "type": "string", "enum": [ "iterations", "sessions" ] } } }, "patternSlug": { "type": "string" }, "target": { "type": "object", "required": [ "serverId", "surface", "snapshotHash" ], "description": "Present only when a server (and, for tool surfaces, a tool) resolved against the pinned snapshot. Required for `mcp_server` / `ready`.", "properties": { "serverId": { "type": "string" }, "toolName": { "type": "string" }, "surface": { "type": "string", "enum": [ "description", "input_schema", "output_schema", "handler", "server_instructions", "capability" ] }, "fieldPath": { "type": "string" }, "snapshotHash": { "type": "string", "description": "The pinned snapshot the target resolved against, so a finding cannot silently re-point at a definition that changed after it was written." }, "currentDefinition": { "type": "object", "required": [ "truncated" ], "properties": { "description": { "type": "string" }, "inputSchemaJson": { "type": "string" }, "outputSchemaJson": { "type": "string" }, "truncated": { "type": "boolean" } } } } }, "evidence": { "type": "array", "items": { "$ref": "#/components/schemas/ActionableFindingEvidence" } } } }, "InsightScope": { "type": "object", "required": [ "kind", "id" ], "description": "What this envelope is about. The extra fields depend on `kind`.", "properties": { "kind": { "type": "string", "enum": [ "eval_run", "swarm_wave", "user_testing_window" ] }, "id": { "type": "string" }, "runId": { "type": "string", "description": "`swarm_wave` only." }, "scenarioId": { "type": "string", "description": "`user_testing_window` only." }, "windowStartAt": { "type": "integer", "description": "`user_testing_window` only." }, "windowEndAt": { "type": "integer", "description": "`user_testing_window` only." } } }, "InsightsEnvelope": { "type": "object", "required": [ "schemaVersion", "scope", "status", "reasonCode", "retryable", "error", "generatedAt", "updatedAt", "summary", "coverage", "findings", "truncation" ], "description": "The common insights envelope, shared by eval runs, swarm waves and user-testing windows.\n\nOne shape for three producers, so a caller writes the reading code once. An ABSENT envelope and `status: \"not_available\"` mean the same thing and both are normal: the field is an enrichment, and a caller who may not have it gets the resource without it rather than an error.", "properties": { "schemaVersion": { "type": "integer", "enum": [ 1 ] }, "scope": { "$ref": "#/components/schemas/InsightScope" }, "status": { "type": "string", "description": "`not_available` means this deployment cannot produce insights at all \u2014 treat an ABSENT envelope the same way. `not_requested` means nobody has asked. `pending` means one is running: poll, do not re-request.", "enum": [ "not_available", "not_requested", "pending", "completed", "failed" ] }, "reasonCode": { "type": [ "string", "null" ] }, "retryable": { "type": "boolean", "description": "Whether asking again could produce a different answer. False on a `failed` envelope means the input, not the attempt, was the problem." }, "error": { "oneOf": [ { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "string" }, "message": { "type": "string" } } }, { "type": "null" } ] }, "generatedAt": { "type": [ "integer", "null" ] }, "updatedAt": { "type": [ "integer", "null" ] }, "summary": { "type": [ "string", "null" ] }, "coverage": { "type": "object", "required": [ "unit", "analyzed", "total", "truncated", "lowConfidence" ], "description": "READ THIS BEFORE QUOTING ANY FINDING. `truncated` and `lowConfidence` are the difference between \"this happens\" and \"this happened in the part we looked at\".", "properties": { "unit": { "type": "string", "enum": [ "iterations", "sessions" ] }, "analyzed": { "type": "integer" }, "total": { "type": "integer" }, "gradedCount": { "type": "integer" }, "feedbackCount": { "type": "integer" }, "truncated": { "type": "boolean", "description": "The analysis saw `analyzed` of `total`, not all of it." }, "lowConfidence": { "type": "boolean", "description": "Too little was analyzed to generalize. Findings still stand as observations of what WAS seen." } } }, "findings": { "type": "array", "items": { "$ref": "#/components/schemas/ActionableFinding" } }, "runHealth": { "type": "object", "required": [ "targets" ], "description": "Swarm only. Launch outcomes never appear as findings \u2014 a run that could not start is an operational fact, not something the server under test did.", "properties": { "targets": { "type": "array", "items": { "type": "object", "required": [ "subjectKind", "subjectId", "subjectLabel", "attempted", "succeeded", "failed", "rateLimited" ], "properties": { "subjectKind": { "type": "string", "enum": [ "environment", "host" ] }, "subjectId": { "type": "string" }, "subjectLabel": { "type": "string" }, "attempted": { "type": "integer" }, "succeeded": { "type": "integer" }, "failed": { "type": "integer" }, "rateLimited": { "type": "integer" } } } } } }, "truncation": { "type": "object", "required": [ "truncated", "omittedFindings", "omittedEvidence", "contractTruncated" ], "description": "What this RESPONSE dropped to stay a sane size, as distinct from what the ANALYSIS did not look at (`coverage`).", "properties": { "truncated": { "type": "boolean" }, "omittedFindings": { "type": "integer" }, "omittedEvidence": { "type": "integer" }, "contractTruncated": { "type": "boolean" } } } } }, "UserTestingScenarioDetail": { "type": "object", "required": [ "id", "projectId", "name", "description", "mode", "environmentId" ], "description": "A published scenario, enriched with findings over its latest analyzed window. NO `accessVersion`: the settings envelope this is read from does not carry it \u2014 the publish and rebind responses do.", "properties": { "id": { "type": "string" }, "projectId": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] }, "mode": { "type": [ "string", "null" ] }, "environmentId": { "type": [ "string", "null" ] }, "insights": { "allOf": [ { "$ref": "#/components/schemas/InsightsEnvelope" } ], "description": "Present when the caller may have it. The envelope needs workspace MEMBERSHIP while the scenario itself is visible more widely, so a lower-privilege viewer gets the scenario without this field rather than a 403." } } }, "ScenarioMetrics": { "type": "object", "additionalProperties": true, "description": "Session metrics for the scenario: volumes, completion and feedback rates, cohort splits.\n\nDeliberately UNPINNED. The upstream projection grows with the product and the SDK types it as an open record for the same reason \u2014 pinning a field list here would turn every added metric into a spec violation. Read what you recognize and ignore the rest.", "properties": {} }, "ScenarioUsage": { "type": "object", "additionalProperties": true, "description": "Usage breakdown: models, tools and servers the scenario's sessions actually used.\n\n**Read `scan.truncated` before quoting any rate from this.** True means the rates were computed over the most recent N sessions rather than all of them, and dropping the flag turns a conditional statistic into an unconditional claim.\n\nUnpinned for the same reason as `ScenarioMetrics`.", "properties": {} }, "ScenarioSignals": { "type": "object", "additionalProperties": true, "description": "What the miner extracted from the scenario's current window \u2014 and how you learn the current window id, which the window-insights read takes. There is no separate \"list windows\" route because the only window anyone asks about is the live one.\n\nUnpinned; see `ScenarioMetrics`.", "properties": {} }, "ScenarioWindowInsights": { "type": "object", "required": [ "windowId" ], "additionalProperties": true, "description": "One window's generated insights, plus the `windowId` they belong to. The generated part is unpinned; see `ScenarioMetrics`.", "properties": { "windowId": { "type": "string" } } }, "ScenarioFinding": { "type": "object", "additionalProperties": true, "description": "One finding over a scenario window. Unpinned; see `ScenarioMetrics`. The stable fields are the document id (used by dismiss/undismiss) and the dismissal state.", "properties": {} }, "ScenarioFindingPage": { "type": "object", "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ScenarioFinding" } }, "nextCursor": { "type": "string" } } }, "ScenarioInsightsRequest": { "type": "object", "additionalProperties": false, "description": "Optional. A bodyless POST is the ordinary case.", "properties": { "force": { "type": "boolean", "description": "Regenerate even when a completed generation already exists. SPENDS A SECOND TIME \u2014 that is the whole point of the flag, so it is never inferred." } } }, "ScenarioInsightsRequested": { "type": "object", "required": [ "scenarioId", "projectId", "windowId", "status" ], "properties": { "scenarioId": { "type": "string" }, "projectId": { "type": "string" }, "windowId": { "type": "string", "description": "The window this generation covers. Poll `GET .../windows/{windowId}/insights` with it." }, "status": { "type": "string", "enum": [ "pending" ] } } }, "ScenarioInsightsCancelRequest": { "type": "object", "required": [ "windowId" ], "additionalProperties": false, "description": "Which generation to cancel. In the body rather than the path because the recovery case this exists for \u2014 a window stuck `pending` \u2014 is precisely when you have the id and nothing else works.", "properties": { "windowId": { "type": "string", "minLength": 1 } } }, "ScenarioInsightsCanceled": { "type": "object", "required": [ "scenarioId", "projectId", "windowId", "canceled" ], "properties": { "scenarioId": { "type": "string" }, "projectId": { "type": "string" }, "windowId": { "type": "string" }, "canceled": { "type": "boolean", "enum": [ true ] } } }, "ScenarioFindingDismissed": { "type": "object", "required": [ "id", "scenarioId", "projectId", "dismissed" ], "properties": { "id": { "type": "string" }, "scenarioId": { "type": "string" }, "projectId": { "type": "string" }, "dismissed": { "type": "boolean", "description": "True from `/dismiss`, false from `/undismiss`. Dismissal is keyed on the finding's stable remediation id, so the same problem stays dismissed across regenerations." } } }, "EvalRunInsightsRequest": { "type": "object", "additionalProperties": false, "description": "Optional. A bodyless POST is the ordinary case.", "properties": { "force": { "type": "boolean", "description": "Regenerate even when insights already exist. SPENDS AGAIN. Must be a real boolean \u2014 `\"false\"` is rejected rather than read as consent." } } }, "EvalRunInsightsRequested": { "type": "object", "required": [ "runId", "projectId", "status" ], "properties": { "runId": { "type": "string" }, "projectId": { "type": "string" }, "status": { "type": "string", "enum": [ "pending" ] } } }, "ProjectCapabilities": { "type": "object", "required": [ "projectId", "organizationId", "role", "projectRole", "surface", "features", "plan", "can" ], "description": "What the caller may do in this project, asked BEFORE they try it.\n\n**A planning aid, not a gate.** Every enforcement point is unchanged: the write path checks roles and the beta flag inside the platform regardless of what this returned a second earlier. A caller that reads a `true` here and races a flag flip gets the same clean `403` it would have got without asking. Nothing may consult this INSTEAD of its own check.\n\nIt exists because every agent surface MCPJam ships is static \u2014 one MCP tool catalog built with no organization in hand, a CLI command tree fixed at install \u2014 so an agent planning a task otherwise has to attempt the write and read the failure. By then it has usually already told a human what it was about to do.", "properties": { "projectId": { "type": "string" }, "organizationId": { "type": [ "string", "null" ] }, "role": { "type": "string", "description": "The caller's ORGANIZATION role: `guest`, `member`, `admin` or `owner`. This is what the platform's role checks rank; a project grant does not raise it." }, "projectRole": { "type": "string", "description": "`admin` or `editor`, when the caller holds a project grant." }, "surface": { "type": "string", "description": "Which channel we resolved this request to arrive on. Echoed so an agent can confirm it is labelled the way it expects \u2014 a CLI showing up as `rest` means its user agent is not reaching us." }, "features": { "type": "object", "required": [ "sandboxes" ], "properties": { "sandboxes": { "type": "object", "required": [ "enabled", "mode", "enforced" ], "properties": { "enabled": { "type": "boolean" }, "mode": { "type": "string", "description": "`off` | `dark` | `enforce`. Only `enforce` turns a disabled flag into a refusal; in `dark` the platform logs what it would have blocked and lets the write through.", "enum": [ "off", "dark", "enforce" ] }, "enforced": { "type": "boolean" }, "reason": { "type": "string" } } } } }, "plan": { "oneOf": [ { "type": "object", "required": [ "name", "limits", "features" ], "properties": { "name": { "type": "string" }, "limits": { "type": "object", "additionalProperties": true }, "features": { "type": "object", "additionalProperties": true } } }, { "type": "null" } ] }, "can": { "type": "object", "required": [ "readSwarms", "readUserTesting", "writeSwarms", "launchJourneyRun", "cancelJourneyRun", "publishUserTestingScenario", "unpublishUserTestingScenario", "changeUserTestingExposure", "manageUserTestingGuestExecution", "requestInsights" ], "description": "The booleans to branch on, derived server-side rather than left for each caller to re-derive from `role` plus flag state.", "properties": { "readSwarms": { "type": "boolean" }, "readUserTesting": { "type": "boolean" }, "writeSwarms": { "type": "boolean", "description": "Authoring personas, journeys and swarms." }, "launchJourneyRun": { "type": "boolean" }, "cancelJourneyRun": { "type": "boolean", "description": "Stays TRUE for an organization that has lost the beta. Losing the feature is exactly when stopping a run matters most." }, "publishUserTestingScenario": { "type": "boolean", "description": "Project admin, and behind the beta gate." }, "unpublishUserTestingScenario": { "type": "boolean", "description": "Project admin, NOT gated \u2014 same reasoning as cancelling a run." }, "changeUserTestingExposure": { "type": "boolean", "description": "Mode changes, member invites and removals, link rotation, renames \u2014 the controls an ordinary MEMBER can use, none of them gated. Guest execution is not covered here; it needs admin and has its own key." }, "manageUserTestingGuestExecution": { "type": "boolean", "description": "The guest-execution spend caps. Project admin, ungated." }, "requestInsights": { "type": "boolean" } } } } } }, "responses": { "InternalError": { "description": "Something failed on MCPJam's side.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "INTERNAL_ERROR", "message": "Unexpected internal error" } } } }, "ValidationError": { "description": "Malformed body or parameters.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "VALIDATION_ERROR", "message": "Invalid JSON body" } } } }, "Unauthorized": { "description": "Missing, invalid, revoked, or orphaned key (`UNAUTHORIZED`) \u2014 or the **target MCP server** needs an OAuth grant (`OAUTH_REQUIRED`), which is a property of the server, not your key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "badKey": { "summary": "Invalid or revoked key", "value": { "code": "UNAUTHORIZED", "message": "Invalid API key" } }, "oauthRequired": { "summary": "Target server needs an OAuth grant", "value": { "code": "OAUTH_REQUIRED", "message": "Server requires OAuth authorization" } } } } } }, "Forbidden": { "description": "Key is valid but not allowed to do this.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "FORBIDDEN", "message": "You do not have access to this project" } } } }, "NotFound": { "description": "Unknown project, server, or resource.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "NOT_FOUND", "message": "Server not found" } } } }, "FeatureNotSupported": { "description": "The target server doesn't support this MCP capability.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "FEATURE_NOT_SUPPORTED", "message": "Server does not support resources" } } } }, "RateLimited": { "description": "Per-key rate limit exceeded (60 requests/minute sustained, bursts up to 10). Honor `Retry-After` and back off with jitter.", "headers": { "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "RATE_LIMITED", "message": "API key rate limit exceeded. Slow down and retry." } } } }, "ServerUnreachable": { "description": "Could not connect to the target MCP server.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "SERVER_UNREACHABLE", "message": "Failed to connect to server" } } } }, "Timeout": { "description": "The target MCP server connected but didn't respond in time.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "TIMEOUT", "message": "Request to server timed out" } } } }, "Conflict": { "description": "The resource is not in a state that accepts this write \u2014 a stale `expectedRevision`, a duplicate name, or an environment that cannot currently be launched. The request was well-formed; re-read the resource and retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "code": "CONFLICT", "message": "Environment changed since you loaded it (expected revision 3, current 5). Reload and retry." } } } } } } }