openapi: 3.1.0 info: title: Unified Harness Protocol version: '2026-09-12' summary: An open standard for running complete agent harnesses as shared infrastructure. description: | UHP defines how a product drives a complete agent harness — Codex, Claude Code, Hermes, or any other — over HTTP: selecting and configuring a harness, sending work, following progress, continuing a session, cancelling execution, handling errors and files, and receiving results. This document is normative for **structure**. The prose specification in `protocol/versions/` is normative for **behaviour**; where they disagree about behaviour, the prose wins, because behaviour is not expressible in OpenAPI. Conformance is defined by the suite in `protocol/conformance/`, not by this document. This version is additive to 2026-08-11. It adds plugins (packages of tools and skills in the Agent Plugins format, installed into a harness, with stdio MCP servers inside them), the `plugins` capability, and the error codes those need. Every request and object valid under 2026-08-11 is valid here, and no object 2026-08-11 defined changes shape, so a server serves both versions from one code path. license: name: Apache-2.0 identifier: Apache-2.0 contact: name: UHP maintainers url: https://github.com/HarnessRouter/harnessrouter servers: - url: https://your-uhp-server description: Any conformant UHP server. The protocol requires no hosted service. - url: http://127.0.0.1:3000/api/harness description: HarnessRouter Community Edition, default local deployment security: - bearerAuth: [] tags: - name: Discovery description: Protocol version and capability discovery (conformance class Core) - name: Harnesses description: Discovering, selecting and managing configured harnesses - name: Tasks description: Running work and reading results - name: Sessions description: Continuing, inspecting and cancelling sessions - name: Files description: File input and artifact retrieval (conformance class Extended) - name: Plugins description: Packages of tools and skills installed into a harness (capability `plugins`) paths: /v1/uhp: get: tags: [Discovery] operationId: getDiscovery summary: Protocol discovery document description: | Served without authentication: a client must be able to learn whether it is talking to a UHP server, and which versions it speaks, before presenting credentials. The document contains nothing principal-specific. security: [] responses: '200': description: The discovery document headers: UHP-Version: { $ref: '#/components/headers/UHPVersion' } content: application/json: schema: { $ref: '#/components/schemas/Discovery' } /v1/harnesses: get: tags: [Harnesses] operationId: listHarnesses summary: List configured harnesses responses: '200': description: Harnesses within the caller's scope. May be empty. content: application/json: schema: type: object required: [harnesses] properties: harnesses: type: array items: { $ref: '#/components/schemas/Harness' } '401': { $ref: '#/components/responses/Unauthorized' } post: tags: [Harnesses] operationId: createHarness summary: Create a configured harness description: Conformance class Full. Check the `harness_management` capability first. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/HarnessCreate' } responses: '200': description: The created harness content: application/json: schema: { $ref: '#/components/schemas/Harness' } '401': { $ref: '#/components/responses/Unauthorized' } '422': { $ref: '#/components/responses/UnprocessableEntity' } /v1/harnesses/{harness_id}: parameters: - $ref: '#/components/parameters/HarnessId' get: tags: [Harnesses] operationId: getHarness summary: Get one harness responses: '200': description: The harness content: application/json: schema: { $ref: '#/components/schemas/Harness' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [Harnesses] operationId: updateHarness summary: Update a harness configuration description: | Conformance class Full. `id`, `base` and `createdAt` are immutable — changing the base of an existing harness would silently change the behaviour of every session attached to it. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/HarnessCreate' } responses: '200': description: The updated harness content: application/json: schema: { $ref: '#/components/schemas/Harness' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [Harnesses] operationId: deleteHarness summary: Delete a harness description: Must not delete the sessions or responses that used it. responses: '200': description: Deleted content: application/json: schema: type: object properties: id: { type: string } deleted: { type: boolean } '404': { $ref: '#/components/responses/NotFound' } /v1/harnesses/{harness_id}/skills/{skill_name}/files: parameters: - $ref: '#/components/parameters/HarnessId' - $ref: '#/components/parameters/SkillName' get: tags: [Harnesses] operationId: getSkillFiles summary: The complete file list of one skill description: | Conformance class Full. Whether or not the server stores the bundle out of line, this returns every file of the skill folder, byte-for-byte. responses: '200': description: The skill's files content: application/json: schema: { $ref: '#/components/schemas/FileList' } '404': { $ref: '#/components/responses/NotFound' } /v1/harnesses/{harness_id}/plugins/{plugin_name}/files: parameters: - $ref: '#/components/parameters/HarnessId' - $ref: '#/components/parameters/PluginName' get: tags: [Plugins] operationId: getPluginFiles summary: The complete package of one installed plugin description: | Conformance class Full, capability `plugins`. Every file of the package, byte-for-byte, whether or not the server stores it out of line. `404` with `plugin_not_found` when the harness has no plugin of that name. responses: '200': description: The plugin's files content: application/json: schema: { $ref: '#/components/schemas/FileList' } '404': { $ref: '#/components/responses/NotFound' } /v1/harnesses/{harness_id}/plugin: parameters: - $ref: '#/components/parameters/HarnessId' get: tags: [Plugins] operationId: exportHarnessPlugin summary: The harness's own tools and skills as an Agent Plugins package description: | Conformance class Full, capability `plugins`. Builds a package from the harness's direct `mcpServers` and `skills`: a `plugin.json` whose name derives from the harness name, an `mcp.json` of the enabled servers with `auth` and `headers` omitted (each omission recorded in `skipped`), and each enabled skill folder. Installed plugins, the system prompt, the model, the base and `disabledTools` are not included. The result installs into another harness by passing it to `plugins` unchanged. responses: '200': description: The package content: application/json: schema: { $ref: '#/components/schemas/Plugin' } '404': { $ref: '#/components/responses/NotFound' } /v1/models: get: tags: [Harnesses] operationId: listModels summary: The model catalogue, by backend responses: '200': description: Models grouped by backend, each with computed availability content: application/json: schema: { $ref: '#/components/schemas/ModelCatalog' } '401': { $ref: '#/components/responses/Unauthorized' } /v1/harnesses/{harness_id}/models: parameters: - $ref: '#/components/parameters/HarnessId' get: tags: [Harnesses] operationId: listHarnessModels summary: Models this harness can run responses: '200': description: The harness's allowed models, default and authorized fallback content: application/json: schema: { $ref: '#/components/schemas/HarnessModels' } '404': { $ref: '#/components/responses/NotFound' } /v1/responses: post: tags: [Tasks] operationId: createResponse summary: Run a task description: | The core of the protocol. With `stream: false` the server returns one Response object when the task reaches a terminal state. With `stream: true` it returns `text/event-stream` carrying the events defined in the Streaming chapter. Retries MUST carry `Idempotency-Key`: without one, a retry after a timeout runs the task a second time while the first may still be running. parameters: - name: Idempotency-Key in: header required: false schema: { type: string, maxLength: 255 } description: Repeating a key returns the first request's result and does not re-execute. - name: UHP-Version in: header required: false schema: { type: string, examples: ['2026-08-11'] } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CreateResponseRequest' } responses: '200': description: | The finished Response (non-streaming), or the event stream (streaming). headers: UHP-Version: { $ref: '#/components/headers/UHPVersion' } content: application/json: schema: { $ref: '#/components/schemas/Response' } text/event-stream: schema: type: string description: SSE; each `data:` line is one Event object. '401': { $ref: '#/components/responses/Unauthorized' } '404': { $ref: '#/components/responses/NotFound' } '409': { $ref: '#/components/responses/Conflict' } '422': { $ref: '#/components/responses/UnprocessableEntity' } '429': { $ref: '#/components/responses/RateLimited' } /v1/responses/{response_id}: parameters: - $ref: '#/components/parameters/ResponseId' get: tags: [Tasks] operationId: getResponse summary: Read a task back responses: '200': description: The Response content: application/json: schema: { $ref: '#/components/schemas/Response' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [Tasks] operationId: deleteResponse summary: Delete a stored response description: Must not cancel a running task — deletion and cancellation are different intentions. responses: '200': description: Deleted content: application/json: schema: type: object properties: id: { type: string } deleted: { type: boolean } '404': { $ref: '#/components/responses/NotFound' } /v1/responses/{response_id}/input_items: parameters: - $ref: '#/components/parameters/ResponseId' get: tags: [Tasks] operationId: getResponseInputItems summary: The input the task was created with responses: '200': description: Input items content: application/json: schema: type: object properties: object: { type: string, const: list } data: { type: array, items: { type: object, additionalProperties: true } } '404': { $ref: '#/components/responses/NotFound' } /v1/responses/{response_id}/cancel: parameters: - $ref: '#/components/parameters/ResponseId' post: tags: [Sessions] operationId: cancelResponse summary: Cancel a running task description: | Idempotent. Cancelling an already-terminal task succeeds and changes nothing — a client retrying a cancel after a dropped connection must not be punished for having succeeded. responses: '200': description: The Response, now cancelling or terminal content: application/json: schema: { $ref: '#/components/schemas/Response' } '404': { $ref: '#/components/responses/NotFound' } /v1/sessions: get: tags: [Sessions] operationId: listSessions summary: List sessions description: Conformance class Extended. parameters: - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } } - { name: cursor, in: query, schema: { type: string } } - { name: harness, in: query, schema: { type: string }, description: Filter by harness id } responses: '200': description: A page of sessions content: application/json: schema: { $ref: '#/components/schemas/SessionList' } '401': { $ref: '#/components/responses/Unauthorized' } /v1/sessions/{session_id}: parameters: - $ref: '#/components/parameters/SessionId' get: tags: [Sessions] operationId: getSession summary: Get one session responses: '200': description: The session content: application/json: schema: { $ref: '#/components/schemas/Session' } '404': { $ref: '#/components/responses/NotFound' } /v1/sessions/{session_id}/turns: parameters: - $ref: '#/components/parameters/SessionId' get: tags: [Sessions] operationId: getSessionTurns summary: The ordered task history of a session responses: '200': description: Turns, oldest first content: application/json: schema: type: object properties: turns: { type: array, items: { $ref: '#/components/schemas/TurnItem' } } '404': { $ref: '#/components/responses/NotFound' } /v1/sessions/{session_id}/share: parameters: - $ref: '#/components/parameters/SessionId' post: tags: [Sessions] operationId: shareSession summary: Publish a read-only view of the session (Sessions §5, class Full) description: | A body is OPTIONAL; no body means publish. A server MAY accept `{"enabled": bool}` as a toggle. Sharing itself is a MAY; a server that does not implement it answers 404/405/501. requestBody: required: false content: application/json: schema: type: object properties: enabled: { type: boolean, default: true } additionalProperties: true responses: '200': description: The share content: application/json: schema: { $ref: '#/components/schemas/SessionShare' } '404': { $ref: '#/components/responses/NotFound' } get: tags: [Sessions] operationId: getSessionShare summary: Read the session's share back responses: '200': description: The share, agreeing with what POST published content: application/json: schema: { $ref: '#/components/schemas/SessionShare' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [Sessions] operationId: revokeSessionShare summary: Revoke — every link minted for the session stops resolving responses: '200': description: Revoked content: application/json: schema: { $ref: '#/components/schemas/SessionShare' } '404': { $ref: '#/components/responses/NotFound' } /v1/sessions/{session_id}/cancel: parameters: - $ref: '#/components/parameters/SessionId' post: tags: [Sessions] operationId: cancelSession summary: Cancel whatever is running in a session responses: '200': description: Accepted content: application/json: schema: type: object properties: id: { type: string } status: { type: string } '404': { $ref: '#/components/responses/NotFound' } /v1/files: post: tags: [Files] operationId: uploadFile summary: Upload a file for use as task input description: Conformance class Extended. requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: { type: string, format: binary } purpose: { type: string, default: user_data } responses: '200': description: The stored file content: application/json: schema: { $ref: '#/components/schemas/File' } '413': { $ref: '#/components/responses/PayloadTooLarge' } /v1/sessions/{session_id}/files: parameters: - $ref: '#/components/parameters/SessionId' get: tags: [Files] operationId: listSessionFiles summary: Every artifact of a session description: Must include artifacts from earlier tasks, not only the most recent. responses: '200': description: Artifacts content: application/json: schema: type: object required: [files] properties: files: { type: array, items: { $ref: '#/components/schemas/File' } } '404': { $ref: '#/components/responses/NotFound' } /v1/sessions/{session_id}/files/archive: parameters: - $ref: '#/components/parameters/SessionId' get: tags: [Files] operationId: downloadSessionArchive summary: Every artifact as one archive responses: '200': description: An archive of the session's artifacts content: application/zip: schema: { type: string, format: binary } '404': { $ref: '#/components/responses/NotFound' } /v1/containers/{container_id}/files/{file_id}/content: parameters: - $ref: '#/components/parameters/ContainerId' - $ref: '#/components/parameters/FileId' get: tags: [Files] operationId: downloadFile summary: Download an artifact description: | Returns raw bytes with the file's own media type — never JSON-wrapped. Servers must send `X-Content-Type-Options: nosniff`: artifacts are attacker-influenceable content, and serving them without it turns an artifact into stored XSS against the client's origin. responses: '200': description: The bytes headers: Content-Disposition: schema: { type: string } X-Content-Type-Options: required: true schema: { type: string, const: nosniff } content: '*/*': schema: { type: string, format: binary } '404': { $ref: '#/components/responses/NotFound' } /v1/containers/{container_id}/files/{file_id}/pdf: parameters: - $ref: '#/components/parameters/ContainerId' - $ref: '#/components/parameters/FileId' get: tags: [Files] operationId: previewFile summary: A rendered PDF preview of a document artifact responses: '200': description: The rendered PDF content: application/pdf: schema: { type: string, format: binary } '404': { $ref: '#/components/responses/NotFound' } '501': description: This server does not implement preview conversion content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } '502': description: Conversion of this file failed content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } components: securitySchemes: bearerAuth: type: http scheme: bearer description: How tokens are issued is out of scope for this protocol. headers: UHPVersion: description: The protocol version actually used to serve this response. required: true schema: { type: string, examples: ['2026-08-11'] } parameters: HarnessId: name: harness_id in: path required: true schema: { type: string, pattern: '^chrn_' } SkillName: name: skill_name in: path required: true schema: { type: string } PluginName: name: plugin_name in: path required: true schema: { type: string } ResponseId: name: response_id in: path required: true schema: { type: string, pattern: '^resp_' } SessionId: name: session_id in: path required: true schema: { type: string } ContainerId: name: container_id in: path required: true schema: { type: string } FileId: name: file_id in: path required: true schema: { type: string } responses: Unauthorized: description: Missing, malformed or unknown credential content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } NotFound: description: | No such object in the caller's scope. Servers return 404 rather than 403 for objects outside the caller's scope, so that an id's existence is not disclosed. content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } Conflict: description: '`session_busy` or `harness_mismatch`' content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } UnprocessableEntity: description: '`model_unavailable` or `unsupported_base`' content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } PayloadTooLarge: description: '`file_too_large`' content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } RateLimited: description: '`rate_limited` or `quota_exhausted`' headers: Retry-After: schema: { type: string } content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } schemas: Discovery: type: object required: [object, protocol, versions, default_version, conformance_class, capabilities] properties: object: { type: string, const: uhp.discovery } protocol: { type: string, const: uhp } versions: type: array minItems: 1 items: { type: string } default_version: { type: string } conformance_class: { type: string, enum: [core, extended, full] } capabilities: { $ref: '#/components/schemas/Capabilities' } plugin_schemas: type: array items: { type: string, format: uri } description: | The Agent Plugins manifest schema identifiers this server installs. Present and non-empty when `capabilities.plugins` is true. examples: [['https://agent-plugins.org/schemas/1.0.0/plugin.schema.json']] implementation: type: object properties: name: { type: string } version: { type: string } additionalProperties: true additionalProperties: true Capabilities: type: object description: | Named booleans. A server reports `false` for a capability it does not implement rather than omitting it, so a client can distinguish "not supported" from "server predates this field". A client treats an absent key as `false`. properties: streaming: { type: boolean } sessions: { type: boolean } cancellation: { type: boolean } files_input: { type: boolean } files_output: { type: boolean } session_listing: { type: boolean } harness_management: { type: boolean } session_sharing: { type: boolean } idempotency: { type: boolean } plugins: type: boolean description: | The server installs Agent Plugins packages into harnesses, serves their files, and exports a harness as a package. Optional at every class. additionalProperties: { type: boolean } Harness: type: object required: [id, name, base] properties: id: { type: string, pattern: '^chrn_' } object: { type: string, const: harness } name: { type: string } base: type: string description: | Opaque. Not enumerated by this specification — a client must treat it as a string, or the protocol would need revising every time a harness is released. examples: [codex, claude-code, hermes] baseLabel: { type: string } defaultModel: { type: string } systemPrompt: { type: string } mcpServers: { type: array, items: { $ref: '#/components/schemas/McpServer' } } skills: { type: array, items: { $ref: '#/components/schemas/Skill' } } plugins: type: array items: { $ref: '#/components/schemas/Plugin' } description: | Installed plugins. Their servers and skills join the harness's own for every turn; `mcpServers` and `skills` above report only what was written to them directly. disabledTools: { type: array, items: { type: string } } maxStep: { type: [integer, 'null'] } timeoutSeconds: { type: [integer, 'null'] } createdAt: { type: integer, description: Unix milliseconds } additionalProperties: true McpServer: type: object required: [name, url] description: | A remote MCP server attached to a harness. Only enabled entries are connected for a turn; a disabled entry must not be contacted at all. An unreachable server must not fail the task. Unchanged from 2026-08-11: a process (stdio) server is declared inside a plugin, see PluginMcpServer. properties: name: { type: string, description: Sanitised to a CLI-safe identifier by the server. } url: { type: string, format: uri } transport: { type: string, enum: [http, sse], default: http } enabled: { type: boolean, default: true } headers: { type: object, additionalProperties: { type: string } } auth: type: string description: | Bearer token, or a server-side reference the server resolves. A server must never return a resolved credential to a client. additionalProperties: true PluginMcpServer: type: object required: [name] description: | An MCP server a plugin's mcp.json declares, derived by the server. The harness MCP server object plus the `stdio` transport: `url` is required for `http` and `sse`, `command` for `stdio`. Placeholders are reported unexpanded; `enabled` is always true, the plugin's own `enabled` governs. properties: name: { type: string } transport: type: string enum: [http, sse, stdio] default: http description: | `http` is Streamable HTTP (`streamable-http` in mcp.json); `sse` the older HTTP+SSE transport; `stdio` a process the server launches inside the agent's sandbox. url: { type: string, format: uri, description: Endpoint (`http` and `sse`). } headers: type: object additionalProperties: { type: string } description: Fixed request headers (`http` and `sse`). Never subject to placeholder expansion. command: type: string description: | One executable token (`stdio`): a bare name resolved on the sandbox's search path, or a plugin-relative path beginning with `./`. Never a shell string, never expanded. args: type: array items: { type: string } description: Arguments (`stdio`). `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` expand at run time. env: type: object additionalProperties: { type: string } description: | Environment overlay (`stdio`). Must not name PLUGIN_ROOT or PLUGIN_DATA; the server sets those. Values expand at run time. cwd: type: string description: | Working directory (`stdio`). Defaults to the plugin root; must stay within the plugin root or PLUGIN_DATA. enabled: { type: boolean, default: true } allOf: - if: properties: { transport: { const: stdio } } required: [transport] then: { required: [command] } else: { required: [url] } additionalProperties: true Skill: type: object required: [name] description: | A skill is a FOLDER, not a file. A server must materialise the whole folder where the agent can read it — materialising only SKILL.md breaks every skill carrying references, scripts or data. Round-tripping a harness through GET and PUT must not lose skill contents. properties: name: { type: string } enabled: type: boolean default: true description: false suppresses the skill, including one inherited from the base. files: type: array items: { $ref: '#/components/schemas/SkillFile' } description: The bundle. Must contain a SKILL.md. content: type: string description: Shorthand for a single-file bundle whose only member is SKILL.md. blob: type: string description: | Server-assigned handle for a bundle stored out of line. A client receives it, passes it back unchanged, and reads the files from the skill files endpoint. additionalProperties: true SkillFile: type: object required: [path] description: One file of a skill folder or of a plugin package. properties: path: type: string description: | Relative to the skill's own folder, or to the plugin root; nested directories are supported. A server must reject a path that escapes the folder. examples: ['SKILL.md', 'references/codes.md', 'assets/logo.png'] content: { type: string, description: Text content. } content_b64: { type: string, description: Base64 for binary content; preserved byte-for-byte. } additionalProperties: true FileList: type: object required: [files] properties: files: type: array items: { $ref: '#/components/schemas/SkillFile' } Plugin: type: object required: [name] description: | An Agent Plugins package installed into a harness: `plugin.json` at the root, MCP servers in `mcp.json`, skills under `skills/`. A client writes `files` (or passes back `blob`) and optionally `name` and `enabled`; the server derives `manifest`, `mcpServers`, `skills` and `skipped` from the package on every write and ignores those fields on input. A refused package leaves the harness unchanged. properties: name: type: string pattern: '^(?!.*(--|\.\.))[a-z0-9]([a-z0-9.-]*[a-z0-9])?$' maxLength: 64 description: The manifest's `name`. Optional on write; if sent it must equal the manifest's. enabled: type: boolean default: true description: false keeps the plugin installed and inert; nothing in it is materialised or contacted. files: type: array items: { $ref: '#/components/schemas/SkillFile' } description: The package. Must contain `plugin.json` at its root. blob: type: string description: | Server-assigned handle for a package stored out of line. A client passes it back unchanged and reads the files from the plugin files endpoint. manifest: { $ref: '#/components/schemas/PluginManifest' } mcpServers: type: array items: { $ref: '#/components/schemas/PluginMcpServer' } description: | Derived from `mcp.json`. Placeholders are reported unexpanded; `enabled` is always true, the plugin's own `enabled` governs. skills: type: array items: { $ref: '#/components/schemas/PluginSkill' } description: Derived from `skills/`, one entry per immediate child directory with a SKILL.md. skipped: type: array items: { $ref: '#/components/schemas/PluginSkipped' } description: | What the server found and could not load. Present on every plugin object the server returns, empty when nothing was skipped. additionalProperties: true PluginManifest: type: object required: ['$schema', name] description: | `plugin.json`, parsed. Mirrors the Agent Plugins 1.0.0 manifest schema (https://agent-plugins.org/schemas/1.0.0/plugin.schema.json, Apache-2.0) field for field, and permits additional properties because a UHP client ignores fields it does not know. The schema a manifest's own `$schema` names is authoritative; a server validates against that. properties: '$schema': { type: string, format: uri } name: type: string minLength: 1 maxLength: 64 pattern: '^(?!.*(--|\.\.))[a-z0-9]([a-z0-9.-]*[a-z0-9])?$' version: { type: string } description: { type: string } author: type: object properties: name: { type: string } email: { type: string } url: { type: string } additionalProperties: true homepage: { type: string } repository: { type: string } license: { type: string } keywords: { type: array, items: { type: string } } extensions: type: object description: Client-specific data keyed by reverse-domain namespace, per Agent Plugins §8. additionalProperties: { type: object } additionalProperties: true PluginSkill: type: object required: [name] properties: name: { type: string, description: The SKILL.md frontmatter name, equal to the directory name. } description: { type: string } additionalProperties: true PluginSkipped: type: object required: [path, reason] properties: path: type: string description: Where in the package, e.g. `skills/broken`, `mcp.json#/mcpServers/redline`, `plugin.json#/vendorField`. examples: ['skills/broken', 'mcp.json#/mcpServers/redline'] reason: { type: string, description: One sentence. } additionalProperties: true HarnessCreate: type: object required: [base] properties: name: { type: string } base: { type: string } default_model: { type: string } system_prompt: { type: string } mcp_servers: { type: array, items: { $ref: '#/components/schemas/McpServer' } } skills: { type: array, items: { $ref: '#/components/schemas/Skill' } } plugins: type: array items: { $ref: '#/components/schemas/Plugin' } description: Requires the `plugins` capability. Each item needs `files` or `blob`. disabled_tools: { type: array, items: { type: string } } max_step: { type: [integer, 'null'] } timeout_seconds: { type: [integer, 'null'] } additionalProperties: true ModelCatalog: type: object required: [backends] properties: backends: type: object additionalProperties: type: object required: [default, models] properties: default: { type: string } models: { type: array, items: { $ref: '#/components/schemas/Model' } } HarnessModels: type: object required: [models] properties: harness_id: { type: string } backend: { type: string } default: { type: string } fallback: { type: string } models: { type: array, items: { $ref: '#/components/schemas/Model' } } Model: type: object required: [id, available] properties: id: { type: string } label: { type: string } backend: { type: string } available: type: boolean description: | Computed, not asserted: true means the server can serve this model for this harness right now. Listing a model as available and then failing the task is the worst outcome for a client, because a user has already chosen it. default: { type: boolean } additionalProperties: true CreateResponseRequest: type: object required: [input] properties: input: oneOf: - type: string - type: array items: { type: object, additionalProperties: true } description: A bare string is shorthand for one user message. model: { type: string, description: Canonical model id. Omitted means the harness default. } metadata: type: object description: Client metadata. `harness_id` selects the configured harness. properties: harness_id: { type: string } additionalProperties: true stream: { type: boolean, default: false } previous_response_id: { type: [string, 'null'] } instructions: { type: string } store: { type: boolean, default: true } max_output_tokens: { type: [integer, 'null'] } max_step: { type: [integer, 'null'], description: Agent step (tool-call round) budget } timeout_seconds: { type: [integer, 'null'], description: Wall-clock budget } tools: type: array items: { type: object, additionalProperties: true } description: | Reserved and ignored. Accepted for wire compatibility, never acted on, and reported in `metadata.ignored_fields` on the response. A UHP harness invokes and executes tools itself and reports them in `output`; there is no input path for a tool result, so the client-executed tool loop this field implies cannot be completed by a conformant server. Configure tools on the harness instead — see Harnesses §4.1. Tasks §1.4. include: type: array items: { type: string } description: | Reserved and ignored. Accepted for wire compatibility, never acted on, and reported in `metadata.ignored_fields` on the response. No values are enumerated, so any string a server recognised would be one it named itself. Tasks §1.4. background: { type: boolean, default: false } additionalProperties: true Response: type: object required: [id, object, created_at, status, output, model] properties: id: { type: string, pattern: '^resp_' } object: { type: string, const: response } created_at: { type: integer, description: Unix seconds } status: { $ref: '#/components/schemas/ResponseStatus' } error: oneOf: - type: 'null' - $ref: '#/components/schemas/Error' description: Non-null only when status is `failed`. incomplete_details: { type: [object, 'null'], additionalProperties: true } previous_response_id: { type: [string, 'null'] } model: { type: string, description: The model that actually ran. } output: type: array items: { $ref: '#/components/schemas/OutputItem' } store: { type: boolean } usage: oneOf: - type: 'null' - $ref: '#/components/schemas/Usage' description: | null when the server cannot account for usage. A fabricated zero would be worse than an honest absence, because a client cannot tell it from a free task. metadata: type: object properties: session_id: { type: string } requested_model: type: string description: Present when the server ran a different model than was requested. model_fallback: { type: boolean } model_fallback_reason: { type: string } ignored_fields: type: array items: { type: string } description: | Request fields the server did not act on, by name, in any order. Required when the request carried `tools` or `include`, which are reserved and ignored. A silently ignored field is indistinguishable from an honoured one. Tasks §1.1 and §1.4. additionalProperties: true additionalProperties: true ResponseStatus: type: string enum: [in_progress, completed, failed, incomplete, cancelled] description: | `incomplete` means a budget stopped the work and is usually worth continuing. `failed` means it could not be done. `cancelled` means the client asked for a stop, and must never be reported as `failed`. OutputItem: type: object required: [type] description: | A client must tolerate item types it does not recognise. A client that renders only `message` items and ignores the rest is a valid client. properties: id: { type: string } type: type: string examples: [message, reasoning, function_call, function_call_output] status: { type: string } role: { type: string } content: type: array items: { $ref: '#/components/schemas/ContentPart' } summary: type: array items: { type: object, additionalProperties: true } call_id: { type: string } name: { type: string } arguments: { type: string, description: JSON, as a string } output: { type: string } additionalProperties: true ContentPart: type: object required: [type] properties: type: { type: string, examples: [output_text] } text: { type: string } annotations: type: array items: { $ref: '#/components/schemas/Annotation' } additionalProperties: true Annotation: type: object required: [type] properties: type: { type: string, const: container_file_citation } container_id: { type: string } file_id: { type: string } filename: { type: string } download_url: { type: string, format: uri } start_index: { type: integer } end_index: { type: integer } additionalProperties: true Usage: type: object properties: input_tokens: { type: integer, minimum: 0 } output_tokens: { type: integer, minimum: 0 } total_tokens: { type: integer, minimum: 0 } cache_read_tokens: { type: integer, minimum: 0 } cache_write_tokens: { type: integer, minimum: 0 } additionalProperties: true SessionShare: type: object description: | A published read-only view of a session (Sessions §5). `url` may be relative, resolving against the base URL the caller is already using — a server behind a proxy cannot know its public origin, and a base-relative path survives every fronting. The share's id MUST NOT function as a credential for the rest of the API. required: [id, url] properties: id: { type: string } url: { type: string } object: { type: string, description: 'SHOULD be "session.share"' } enabled: { type: boolean } additionalProperties: true TurnItem: type: object description: | One turn of a session's history (Sessions §3). Previously an object with no stated shape, which kept every conformance check at "the endpoint answered 200" and made transcript rebuilding an exercise in one implementation's habits. required: [id, status] properties: id: { type: string, description: "The response id, usable with GET /v1/responses/{id}" } status: { type: string } user: { type: string } assistant: { type: string } tools: { type: array, items: { type: object, additionalProperties: true } } files: { type: array, items: { type: object, additionalProperties: true } } additionalProperties: true Session: type: object required: [id] properties: id: { type: string } object: { type: string, const: session } harness_id: { type: string } title: { type: string } status: { type: string } created_at: { type: integer } updated_at: { type: integer } additionalProperties: true SessionList: type: object required: [sessions] properties: sessions: { type: array, items: { $ref: '#/components/schemas/Session' } } next_cursor: type: [string, 'null'] description: | null on the last page. A client must not be required to detect the end by receiving fewer items than requested — that heuristic is wrong whenever a page is exactly full. additionalProperties: true File: type: object required: [id, filename] properties: id: { type: string } object: { type: string, const: file } container_id: { type: string } filename: { type: string } bytes: { type: integer, minimum: 0 } created_at: { type: integer } additionalProperties: true ErrorEnvelope: type: object required: [error] properties: error: { $ref: '#/components/schemas/Error' } detail: type: string deprecated: true description: | A human-readable alias of `error.message`, retained for clients written against implementations that predate this envelope. Carries no information not in `error`. additionalProperties: true Error: type: object required: [type, code, message] properties: type: type: string enum: - invalid_request_error - authentication_error - permission_error - rate_limit_error - harness_error - server_error code: type: string description: | Specific and machine-readable. Servers may define additional codes for conditions this specification does not cover, and must namespace them with a vendor prefix so a future version cannot collide with them. examples: - unsupported_protocol_version - invalid_input - harness_not_found - response_not_found - session_not_found - plugin_not_found - file_not_found - session_expired - harness_mismatch - session_busy - plugin_conflict - file_too_large - model_unavailable - unsupported_base - plugin_invalid - unsupported_plugin_schema - unsupported_transport - missing_credential - invalid_credential - insufficient_scope - rate_limited - quota_exhausted - harness_error - harness_unavailable - provider_error - timeout - cancelled - preview_unavailable - preview_failed message: type: string description: | One sentence, safe to show a user. Must not contain credentials, internal hostnames, file paths, or stack traces. param: { type: [string, 'null'], description: Dotted path to the offending field. } detail: { type: [object, 'null'], additionalProperties: true } additionalProperties: true Event: type: object required: [type, sequence_number] description: | One streamed event. `sequence_number` starts at 0 and increases by exactly 1 per event, so a client can detect a dropped event rather than silently rendering a gap. properties: type: type: string examples: - response.created - response.in_progress - response.output_item.added - response.output_item.done - response.content_part.added - response.content_part.done - response.output_text.delta - response.output_text.done - response.output_text.annotation.added - response.reasoning_summary_part.added - response.reasoning_summary_text.delta - response.reasoning_summary_part.done - response.function_call_arguments.delta - response.function_call_arguments.done - response.completed - response.incomplete - response.failed - error sequence_number: { type: integer, minimum: 0 } response: { $ref: '#/components/schemas/Response' } item: { $ref: '#/components/schemas/OutputItem' } part: { $ref: '#/components/schemas/ContentPart' } annotation: { $ref: '#/components/schemas/Annotation' } delta: { type: string } text: { type: string } arguments: { type: string } item_id: { type: string } output_index: { type: integer, minimum: 0 } content_index: { type: integer, minimum: 0 } summary_index: { type: integer, minimum: 0 } annotation_index: { type: integer, minimum: 0 } code: { type: string } message: { type: string } param: { type: [string, 'null'] } additionalProperties: true