{"openapi":"3.1.0","info":{"title":"Omni Octo API (OpenAI-compatible)","version":"1.0.0","description":"The `/v1` facade lets existing OpenAI SDKs and OpenAI-compatible tools (LiteLLM, LangChain, the `openai` Python/Node client, …) talk to Octo agents with zero code changes. Two compatibility rules apply to every operation below: (1) `model` selects from a curated allowlist. A listed id (see `GET /v1/models`) runs that model; an unknown id returns `400 model_not_found`; omitting `model` runs the default (`claude-sonnet-4-6`). The chosen model is echoed back in the response. (2) The request schema is intentionally tolerant (zod `.passthrough()`): unknown or OpenAI-SDK-only fields (e.g. `temperature`, `tools`, `metadata`) are accepted and silently ignored rather than rejected with a 400, so existing OpenAI client code works unmodified. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …)."},"servers":[{"url":"https://api.omniocto.com","description":"API server"}],"security":[{"ApiKey":[]}],"paths":{"/v1/responses":{"post":{"operationId":"createResponse","summary":"Create a response","description":"OpenAI Responses API compatibility endpoint. Runs one Octo agent turn and returns (or streams) the result. `model` selects from a curated allowlist. A listed id (see `GET /v1/models`) runs that model; an unknown id returns `400 model_not_found`; omitting `model` runs the default (`claude-sonnet-4-6`). The chosen model is echoed back in the response. The request schema is intentionally tolerant (zod `.passthrough()`): unknown or OpenAI-SDK-only fields (e.g. `temperature`, `tools`, `metadata`) are accepted and silently ignored rather than rejected with a 400, so existing OpenAI client code works unmodified. `previous_response_id` continues the conversation the referenced response ran in (stateful chaining); omit it to start a new conversation. `background: true` returns a `queued` response immediately and completes the turn out-of-band (poll `GET /v1/responses/{id}`, or use `store: true` webhooks). `stream: true` returns a `text/event-stream` of `response.created` → `response.output_text.delta`* → `response.completed` (or an `error` event) frames. `store: false` returns the response inline but persists no retrievable record — it cannot be fetched later, chained via `previous_response_id`, or idempotently replayed. An `Idempotency-Key` header on a non-streaming, non-background, stored request replays the original result on retry instead of re-running the turn.","tags":["Responses"],"security":[{"ApiKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional client-generated key. Retrying the same key + request body returns the original stored response instead of re-running the turn (only applies when `store` is not `false` and the request is neither `background` nor `stream`)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["input"],"properties":{"model":{"type":"string","enum":["claude-sonnet-4-6","claude-haiku-4-5","claude-opus-4-7","gpt-5.4","gpt-5.4-mini","deepseek-v4-flash","deepseek-v4-pro"],"default":"claude-sonnet-4-6","description":"`model` selects from a curated allowlist. A listed id (see `GET /v1/models`) runs that model; an unknown id returns `400 model_not_found`; omitting `model` runs the default (`claude-sonnet-4-6`). The chosen model is echoed back in the response."},"input":{"description":"The turn's input: either a plain string, or an array of Responses input items (`{ role, content }`). Leading `system`/`developer` items are lifted into additional instructions; the last `user` item is the turn text.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","description":"The role of the input item (e.g. `system`, `developer`, `user`, `assistant`)."},"content":{"description":"The item's content: a string, `null` (e.g. an assistant item with only tool calls), or an array of content parts.","oneOf":[{"type":"string"},{"type":"null"},{"type":"array","items":{"type":"object","additionalProperties":true}}]}},"additionalProperties":true}}]},"instructions":{"type":"string","description":"Additional instructions appended to Octo's own system prompt (never replaces it)."},"previous_response_id":{"type":"string","description":"The `id` of a prior `/v1/responses` result. Continues that response's conversation. 404s if unknown."},"background":{"type":"boolean","description":"Run the turn asynchronously; returns immediately with `status: \"queued\"`. Poll `GET /v1/responses/{id}` for completion, or configure a workspace webhook."},"stream":{"type":"boolean","description":"Stream the turn as Server-Sent Events instead of a single JSON body."},"store":{"type":"boolean","default":true,"description":"Whether to persist a retrievable record of this response. `false` skips persistence entirely (no GET, no chaining, no idempotency replay)."}},"additionalProperties":true,"description":"The request schema is intentionally tolerant (zod `.passthrough()`): unknown or OpenAI-SDK-only fields (e.g. `temperature`, `tools`, `metadata`) are accepted and silently ignored rather than rejected with a 400, so existing OpenAI client code works unmodified."}}}},"responses":{"200":{"description":"The completed (or queued, for `background: true`) response object. When `stream: true`, this is instead a `text/event-stream` body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Response"}},"text/event-stream":{"schema":{"type":"string","description":"SSE frames: `response.created`, `response.output_text.delta` (repeated), then `response.completed` (or `error`)."}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/responses/{id}":{"get":{"operationId":"getResponse","summary":"Retrieve a response","description":"Fetch a previously created response by id. Responses created with `store: false` are never retrievable here (404). A `background` response not yet finished returns a schema-conformant shell with `status: \"queued\"` (or `in_progress`) and empty `output`.","tags":["Responses"],"security":[{"ApiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The response id (`resp_…`) returned by `POST /v1/responses`."}],"responses":{"200":{"description":"The response object (possibly still queued/in_progress).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Response"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/chat/completions":{"post":{"operationId":"createChatCompletion","summary":"Create a chat completion","description":"OpenAI Chat Completions API compatibility endpoint. Runs one Octo agent turn from `messages[]` and returns a `chat.completion` object. `model` selects from a curated allowlist. A listed id (see `GET /v1/models`) runs that model; an unknown id returns `400 model_not_found`; omitting `model` runs the default (`claude-sonnet-4-6`). The chosen model is echoed back in the response. The request schema is intentionally tolerant (zod `.passthrough()`): unknown or OpenAI-SDK-only fields (e.g. `temperature`, `tools`, `metadata`) are accepted and silently ignored rather than rejected with a 400, so existing OpenAI client code works unmodified. Statefulness: pass the Octo extension field `conversation` (an id previously returned as `x_octo_conversation`) to continue server-side history — only the last user message in `messages[]` is consumed in that case. Without `conversation`, the call is stateless: the full `messages[]` array seeds a NEW, ephemeral, safe-tools-only turn (system/developer messages become additional instructions; nothing is persisted or chainable). `stream: true` returns a `text/event-stream` of `chat.completion.chunk` frames terminated by `data: [DONE]`; set `stream_options.include_usage: true` to get a final usage-bearing chunk.","tags":["Chat"],"security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["messages"],"properties":{"model":{"type":"string","enum":["claude-sonnet-4-6","claude-haiku-4-5","claude-opus-4-7","gpt-5.4","gpt-5.4-mini","deepseek-v4-flash","deepseek-v4-pro"],"default":"claude-sonnet-4-6","description":"`model` selects from a curated allowlist. A listed id (see `GET /v1/models`) runs that model; an unknown id returns `400 model_not_found`; omitting `model` runs the default (`claude-sonnet-4-6`). The chosen model is echoed back in the response."},"messages":{"type":"array","description":"OpenAI chat messages (`{ role, content }`). `system`/`developer` messages become additional instructions; the last `user` message is the turn's input.","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","description":"The role of the message author (`system`, `developer`, `user`, `assistant`, `tool`)."},"content":{"description":"The message content: a string, `null` (e.g. an assistant message with only tool calls), or an array of content parts.","oneOf":[{"type":"string"},{"type":"null"},{"type":"array","items":{"type":"object","additionalProperties":true}}]}},"additionalProperties":true}},"stream":{"type":"boolean","description":"Stream as SSE `chat.completion.chunk` frames."},"stream_options":{"type":"object","properties":{"include_usage":{"type":"boolean","description":"Include a final usage-bearing chunk before `[DONE]` when streaming."}},"additionalProperties":true},"conversation":{"type":"string","description":"Octo extension (non-standard): a conversation id/short-id from a prior `x_octo_conversation`. Continues that server-side conversation statefully; unknown values 404."}},"additionalProperties":true,"description":"The request schema is intentionally tolerant (zod `.passthrough()`): unknown or OpenAI-SDK-only fields (e.g. `temperature`, `tools`, `metadata`) are accepted and silently ignored rather than rejected with a 400, so existing OpenAI client code works unmodified."}}}},"responses":{"200":{"description":"The chat completion object, or (when `stream: true`) an SSE stream of `chat.completion.chunk` frames.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletion"}},"text/event-stream":{"schema":{"type":"string","description":"SSE `chat.completion.chunk` frames terminated by `data: [DONE]`."}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/models":{"get":{"operationId":"listModels","summary":"List models","description":"Lists the curated allowlist of models selectable via the `model` field on `/v1/responses` and `/v1/chat/completions`. Send any listed id to run that model; an unknown id returns `400 model_not_found`.","tags":["Models"],"security":[{"ApiKey":[]}],"responses":{"200":{"description":"The list of runnable models.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelList"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/models/{id}":{"get":{"operationId":"getModel","summary":"Retrieve a model","description":"Fetch a single model by id. 404s (`model_not_found`) if the id isn't one Octo runs.","tags":["Models"],"security":[{"ApiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"A model id as returned by `GET /v1/models`."}],"responses":{"200":{"description":"The model object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Model"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"post":{"operationId":"createWebhookEndpoint","summary":"Register a webhook endpoint","description":"Subscribe a public https URL to a set of workspace event types. The response includes a one-time `secret` used to verify delivery signatures (Standard Webhooks / svix scheme) — it is never returned again. Deliveries are signed and retried with exponential backoff.","tags":["Webhooks"],"security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}},"responses":{"201":{"description":"The created endpoint, including the one-time `secret`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listWebhookEndpoints","summary":"List webhook endpoints","description":"List the workspace's webhook endpoints. `secret` is never included.","tags":["Webhooks"],"security":[{"ApiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200},"description":"Max endpoints to return (1–200, default 100). Out of range → 400."}],"responses":{"200":{"description":"The list of endpoints.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The endpoint short id (`whe-…`) or UUID."}],"get":{"operationId":"getWebhookEndpoint","summary":"Retrieve a webhook endpoint","tags":["Webhooks"],"security":[{"ApiKey":[]}],"responses":{"200":{"description":"The endpoint (without `secret`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateWebhookEndpoint","summary":"Update a webhook endpoint","description":"Change the `url`, `event_types`, `description`, or toggle `enabled`. Re-enabling clears the failure state.","tags":["Webhooks"],"security":[{"ApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookRequest"}}}},"responses":{"200":{"description":"The updated endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","tags":["Webhooks"],"security":[{"ApiKey":[]}],"responses":{"204":{"description":"Deleted."},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/events":{"get":{"operationId":"streamEvents","summary":"Stream workspace events (SSE)","description":"A long-lived `text/event-stream` of workspace events. First **replays** durable events after the resume cursor (`?after=<seq>` or the `Last-Event-ID` header), then **tails** live events. Each SSE frame's `id:` is a monotonic cursor and `event:` is the event `type`; the `data:` payload is the event envelope. A `:ping` comment is sent every ~15s.","tags":["Events"],"security":[{"ApiKey":[]}],"parameters":[{"name":"after","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Resume cursor — stream events with a sequence greater than this."},{"name":"types","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated event types to filter to (e.g. `turn.completed,run.completed`). An unknown type returns 400."},{"name":"Last-Event-ID","in":"header","required":false,"schema":{"type":"string"},"description":"Standard SSE resume header — an alternative to `?after`."}],"responses":{"200":{"description":"An SSE stream. Each frame's `data` is a WorkspaceEvent envelope (see schema).","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/WorkspaceEvent"}}}},"400":{"description":"Invalid request body. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Workspace unavailable (e.g. spend cap / suspension). Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (per-key or per-workspace). Retry after the `retry-after` header. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error. Errors use the OpenAI-shaped envelope `{ error: { message, type, param, code } }` so the official OpenAI SDKs map (status, body) to their typed exceptions (AuthenticationError, BadRequestError, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","description":"API key issued from the workspace Settings → API Keys page. Send as `Authorization: Bearer sk-octo_...`. Every request without a valid key returns the OpenAI-shaped 401 error envelope."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["message","type"],"properties":{"message":{"type":"string"},"type":{"type":"string"},"param":{"type":["string","null"]},"code":{"type":["string","null"]}},"additionalProperties":true}},"additionalProperties":false},"ChatCompletion":{"type":"object","required":["id","object","created","model","choices"],"properties":{"id":{"type":"string"},"object":{"const":"chat.completion"},"created":{"type":"integer"},"model":{"type":"string"},"choices":{"type":"array","items":{"type":"object","required":["index","message","finish_reason"],"properties":{"index":{"type":"integer"},"message":{"type":"object","required":["role","content"],"properties":{"role":{"const":"assistant"},"content":{"type":["string","null"]}},"additionalProperties":true},"logprobs":{"type":["object","null"]},"finish_reason":{"type":["string","null"],"enum":["stop","length","tool_calls","content_filter","function_call",null]}},"additionalProperties":true}},"usage":{"type":"object","required":["prompt_tokens","completion_tokens","total_tokens"],"properties":{"prompt_tokens":{"type":"integer"},"completion_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}},"additionalProperties":true},"x_octo_conversation":{"type":"string","description":"Octo extension: present only for stateful calls (request carried `conversation`). Pass it back as `conversation` on the next call to continue server-side history."}},"additionalProperties":true},"Response":{"type":"object","required":["id","object","created_at","status","model","output"],"properties":{"id":{"type":"string"},"object":{"const":"response"},"created_at":{"type":"integer"},"status":{"type":"string","enum":["completed","incomplete","in_progress","failed","queued"]},"model":{"type":"string"},"output":{"type":"array","items":{"type":"object","required":["type"],"properties":{"type":{"type":"string"}},"additionalProperties":true}},"output_text":{"type":"string"},"usage":{"type":"object","required":["input_tokens","output_tokens","total_tokens"],"properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}},"additionalProperties":true},"metadata":{"type":"object","additionalProperties":true}},"additionalProperties":true},"Model":{"type":"object","required":["id","object","created","owned_by"],"properties":{"id":{"type":"string"},"object":{"const":"model"},"created":{"type":"integer"},"owned_by":{"type":"string"}},"additionalProperties":true},"ModelList":{"type":"object","required":["object","data"],"properties":{"object":{"const":"list"},"data":{"type":"array","items":{"type":"object","required":["id","object","created","owned_by"],"properties":{"id":{"type":"string"},"object":{"const":"model"},"created":{"type":"integer"},"owned_by":{"type":"string"}},"additionalProperties":true}}},"additionalProperties":true},"WorkspaceEvent":{"type":"object","description":"A workspace event envelope. Identical over an outbound webhook and the SSE feed. `data` is a snake_case, per-type payload.","properties":{"id":{"type":"string","description":"Opaque event id, e.g. \"evt_9f8c…\"."},"type":{"type":"string","enum":["conversation.created","turn.completed","confirmation.requested","confirmation.resolved","spend.recorded","run.completed"]},"workspace_id":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":true}},"required":["id","type","workspace_id","created_at","data"],"additionalProperties":false},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","description":"Short id, e.g. \"whe-0123456789\"."},"object":{"const":"webhook_endpoint"},"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string"},"description":"Subscribed types, or `[\"*\"]` for all."},"description":{"type":["string","null"]},"enabled":{"type":"boolean"},"created_at":{"type":"integer","description":"Unix seconds."},"updated_at":{"type":"integer","description":"Unix seconds."},"secret":{"type":"string","description":"Signing secret (\"whsec_…\"). Returned ONLY in the POST create response — never on reads."}},"required":["id","object","url","event_types","enabled","created_at","updated_at"],"additionalProperties":true},"WebhookEndpointList":{"type":"object","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Short id, e.g. \"whe-0123456789\"."},"object":{"const":"webhook_endpoint"},"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string"},"description":"Subscribed types, or `[\"*\"]` for all."},"description":{"type":["string","null"]},"enabled":{"type":"boolean"},"created_at":{"type":"integer","description":"Unix seconds."},"updated_at":{"type":"integer","description":"Unix seconds."},"secret":{"type":"string","description":"Signing secret (\"whsec_…\"). Returned ONLY in the POST create response — never on reads."}},"required":["id","object","url","event_types","enabled","created_at","updated_at"],"additionalProperties":true}}},"additionalProperties":true},"CreateWebhookRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Public https endpoint. Rejected (400) if it resolves to a private/loopback/link-local/metadata address."},"event_types":{"type":"array","items":{"type":"string","enum":["conversation.created","turn.completed","confirmation.requested","confirmation.resolved","spend.recorded","run.completed","*"]},"description":"Optional; defaults to `[\"*\"]` (all events)."},"description":{"type":"string","maxLength":500}},"required":["url"],"additionalProperties":false},"UpdateWebhookRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string","enum":["conversation.created","turn.completed","confirmation.requested","confirmation.resolved","spend.recorded","run.completed","*"]}},"enabled":{"type":"boolean"},"description":{"type":"string","maxLength":500}},"additionalProperties":false}}}}