{
  "openapi": "3.1.0",
  "info": {
    "title": "Isomux Website API",
    "version": "1.0.0",
    "description": "Public API served by isomux.com. Self-hosted office APIs are documented by each office and in the Isomux source route table."
  },
  "servers": [{ "url": "https://isomux.com" }],
  "paths": {
    "/api/chat": {
      "post": {
        "operationId": "chatAboutIsomux",
        "summary": "Stream an answer about Isomux",
        "description": "Answer a question about Isomux, with optional documentation context supplied by the caller.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChatRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server-sent event stream. Each data frame contains a text delta; the final frame is [DONE].",
            "headers": {
              "RateLimit": { "$ref": "#/components/headers/RateLimit" },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "text/event-stream": { "schema": { "type": "string" } }
            }
          },
          "400": { "$ref": "#/components/responses/JsonError" },
          "405": { "$ref": "#/components/responses/JsonError" },
          "413": { "$ref": "#/components/responses/JsonError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/JsonError" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ChatMessage": {
        "type": "object",
        "required": ["role", "content"],
        "properties": {
          "role": { "type": "string", "enum": ["user", "assistant"] },
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 16000,
            "description": "Must contain a character other than whitespace. Lengths count UTF-16 code units, so a character such as an emoji counts as two."
          }
        },
        "additionalProperties": false
      },
      "ChatRequest": {
        "type": "object",
        "required": ["messages"],
        "properties": {
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 41,
            "description": "The conversation, oldest first. Roles alternate, starting and ending with user. The contents hold at most 48,000 characters in total. A request body over 1,000,000 bytes, or over these limits, gets 413.",
            "items": { "$ref": "#/components/schemas/ChatMessage" }
          },
          "pageContext": {
            "type": "string",
            "description": "Optional documentation context. The server uses at most the first 20,000 characters."
          },
          "page": {
            "type": "string",
            "enum": ["main", "hosted"],
            "description": "Page where the visitor opened the chat widget."
          },
          "conversationId": {
            "type": "string",
            "description": "Optional id that groups the requests of one conversation in the server log. The server ignores a value that does not match ^[a-z0-9]{8,32}$."
          }
        },
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "resolution"],
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" },
              "resolution": { "type": "string" }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      }
    },
    "headers": {
      "RateLimit-Policy": {
        "description": "The request quotas per client IP, as in the IETF draft draft-ietf-httpapi-ratelimit-headers: \"minute\";q=5;w=60, \"hour\";q=20;w=3600. q is the number of requests and w the window in seconds; both windows slide.",
        "schema": { "type": "string" }
      },
      "RateLimit": {
        "description": "Where the client stands in each policy of RateLimit-Policy (IETF draft draft-ietf-httpapi-ratelimit-headers): r is the remaining requests and t the seconds until the oldest counted request leaves that window. Requests other than POST are not counted.",
        "schema": { "type": "string" }
      },
      "Retry-After": {
        "description": "Seconds until the next request is allowed.",
        "schema": { "type": "integer", "minimum": 0 }
      }
    },
    "responses": {
      "JsonError": {
        "description": "Structured error with a stable code and recovery hint.",
        "headers": {
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "RateLimited": {
        "description": "A request quota is spent. Wait the Retry-After seconds before the next request.",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/Retry-After" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    }
  }
}
