{
  "openapi": "3.0.3",
  "info": {
    "title": "OpenPi API",
    "version": "1.0.0",
    "description": "HTTP API so other apps can call OpenPi. Set PI_GPT_API_KEY in the router environment. Send that value as `Authorization: Bearer <key>` or as the `X-API-Key` header. Do not put the key in the query string. If PI_GPT_API_KEY is unset, chat and health return 503. A missing or wrong key returns 401. POST /api/chat uses the same inference cap as the page. When every slot is in use the request waits in a queue of 8 for up to 15 minutes. The page shows Waiting for a free slot. A full queue or a wait that runs out returns 503 with a short message and does not name the board. A map hit does not take a slot. `GET /openapi.json` and `GET /docs` do not require the key. If `mode` is omitted, the model is Flash. Flash is the fleet checkpoint `qwen3:0.6b` on pi4 (the default tag is `qwen3:0.6b` unless MESH_MODEL is set). `low`, `medium`, and `high` keep that same checkpoint. Flash and Pro both send Ollama `think` false, with top_p 0.8 and top_k 20. Flash uses temperature 0.3 and presence_penalty 0. Pro uses temperature 0.5, presence_penalty 0 at Low, and 0.5 at Medium and High. Low allows 96 answer tokens, Medium allows 256, and High allows 512. The system prompt asks for a shorter, moderate, or fuller answer. On the LAN page, High stays on Flash unless Pro was chosen. A leaked `<think>` block is removed from the answer. Omitted mode uses Medium. The LAN page (`/`, `/health`, `/v1/chat/completions`) does not use this key."
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "tags": [
    {
      "name": "chat",
      "description": "One completion."
    },
    {
      "name": "health",
      "description": "Router and peer snapshot."
    },
    {
      "name": "meta",
      "description": "This document and the Swagger UI."
    }
  ],
  "paths": {
    "/api/chat": {
      "post": {
        "operationId": "postChat",
        "tags": [
          "chat"
        ],
        "summary": "Chat with OpenPi",
        "description": "One user turn, or a short session in `messages`. Omitting `mode` selects the Flash model. Every reply is generated on pi4. Stored sentences are not served.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              },
              "example": {
                "messages": [
                  {
                    "role": "user",
                    "content": "status"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assistant message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatResponse"
                }
              }
            }
          },
          "400": {
            "description": "The body or mode was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "413": {
            "description": "The JSON body is larger than 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "502": {
            "description": "pi4 did not answer, or the pin was refused.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "PI_GPT_API_KEY is not set, or the inference queue is full. A full cap waits in a queue of 8, and the page shows Waiting for a free slot. A full queue or a wait that runs out returns 503 with a short message, the same as POST /v1/chat/completions. A map hit does not take a slot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "health"
        ],
        "summary": "Health of the router and peers",
        "description": "Same snapshot as `GET /health`, plus the public model name. Requires the API key.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Router is answering.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "PI_GPT_API_KEY is not set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenAPI",
        "tags": [
          "meta"
        ],
        "summary": "OpenAPI document",
        "security": [],
        "responses": {
          "200": {
            "description": "This document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/docs": {
      "get": {
        "operationId": "getDocs",
        "tags": [
          "meta"
        ],
        "summary": "Swagger UI",
        "description": "Swagger UI for this document. No API key.",
        "security": [],
        "responses": {
          "200": {
            "description": "HTML page.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The value of the PI_GPT_API_KEY environment variable. Header: Authorization: Bearer <key>."
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The same PI_GPT_API_KEY value, in the X-API-Key header."
      }
    },
    "schemas": {
      "ChatMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant"
            ]
          },
          "content": {
            "type": "string"
          }
        }
      },
      "ChatRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            }
          },
          "mode": {
            "type": "string",
            "enum": [
              "flash",
              "low",
              "medium",
              "high"
            ],
            "default": "flash",
            "description": "Omit this field to use the Flash model (`qwen3:0.6b`) at the medium decode budget. low, medium, and high keep the Flash checkpoint."
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "When true, the reply is server-sent events."
          },
          "web_search": {
            "type": "boolean",
            "default": false,
            "description": "When true, search before answering. Omit or false to search only if the message asks to search or asks for the news (latest news, news about, news on, latest on, in the news, what's the latest). A bare news, latest, current, or today does not. Any other JSON type is rejected."
          },
          "think": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ],
            "description": "Optional thinking level. When set, it replaces the level implied by mode. It does not change the Flash checkpoint. Reasoning comes back on reasoning_content, not in content."
          },
          "model": {
            "type": "string",
            "description": "Ignored. Omitted mode selects Flash, and every listed mode stays on the Flash checkpoint."
          }
        }
      },
      "ChatResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string"
          },
          "model": {
            "type": "string",
            "description": "Public model name. Flash is `flash`.",
            "example": "flash"
          },
          "mode": {
            "type": "string",
            "example": "flash"
          },
          "checkpoint": {
            "type": "string",
            "description": "Ollama tag used for a miss.",
            "example": "qwen3:0.6b"
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "pi_model": {
            "type": "string"
          },
          "pi_peer": {
            "type": "string"
          },
          "pi_chip": {
            "type": "string"
          },
          "pi_think": {
            "type": "string"
          }
        }
      },
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "model": {
            "type": "string",
            "description": "Fleet checkpoint tag."
          },
          "pro_model": {
            "type": "string",
            "description": "Pro tag. Both tags stay resident.",
            "example": "qwen3:1.7b"
          },
          "public_model": {
            "type": "string",
            "example": "flash"
          },
          "default_mode": {
            "type": "string",
            "example": "flash"
          },
          "checkpoint": {
            "type": "string"
          },
          "peers_up": {
            "type": "integer"
          },
          "peers": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "slots": {
            "type": "integer"
          },
          "in_flight": {
            "type": "integer"
          },
          "cache_ttl": {
            "type": "number"
          },
          "uptime_s": {
            "type": "integer"
          },
          "services": {
            "type": "object"
          }
        }
      }
    }
  }
}