{
  "openapi": "3.1.0",
  "info": {
    "title": "AirCode Ø private AI gateway",
    "version": "1.0.0",
    "description": "Private text generation through the owner’s connected native accounts. The documentation website is not an API endpoint. Configure the issuing instance URL and its gateway bearer privately. Only the documented Chat Completions subset is supported; no owner APIs, tools, Responses or Anthropic Messages. Streaming is buffered SSE."
  },
  "servers": [
    {
      "url": "{instanceOrigin}/api/ai-gateway/v1",
      "variables": {
        "instanceOrigin": {
          "default": "https://your-instance.example",
          "description": "Replace with the owner-authorized instance origin, including port when needed; no trailing slash, credentials, query or fragment."
        }
      }
    }
  ],
  "security": [
    {
      "gatewayToken": []
    }
  ],
  "paths": {
    "/models": {
      "get": {
        "operationId": "listGatewayModels",
        "summary": "Discover supported models and reasoning efforts",
        "description": "Requires an enabled gateway and its bearer token. No query parameters. Use returned IDs unchanged and obtain the owner’s model choice.",
        "responses": {
          "200": {
            "description": "Live eligible native account models.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data"
                  ],
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Model"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or unsupported parameter. Do not retry unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired, revoked or wrong-instance gateway token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Gateway disabled, cross-origin browser request or instance access refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Local concurrency/rate limit or provider quota. Do not retry automatically.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Instance or provider unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/chat/completions": {
      "post": {
        "operationId": "createGatewayChatCompletion",
        "summary": "Generate one text answer",
        "description": "Supply history each time. One active request per token, two per instance, 30 admitted generation calls per token per minute. No automatic retry, queue or resume. Disconnect cancels work. Buffered SSE delivers the complete answer after validation; an in-stream error closes without [DONE]. No query parameters.",
        "x-max-http-body-bytes": 81920,
        "x-max-normalized-messages-bytes": 65536,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON completion, or buffered SSE if stream is true. In-stream errors use Error inside a data frame; HTTP 200 alone is not success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Completion"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Ignore SSE comments. Parse data frames: one content chunk, a stop chunk, optional usage with choices: [], then [DONE]. Frames can span network chunks. Error frames terminate unsuccessfully."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or unsupported parameter. Do not retry unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired, revoked or wrong-instance gateway token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Gateway disabled, cross-origin browser request or instance access refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Model unavailable; refresh the catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Cancelled request or changed model selection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Normalized conversation exceeds 64 KiB or HTTP body exceeds 80 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Local concurrency/rate limit or provider quota. Do not retry automatically.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Provider response could not be validated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Instance or provider unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "gatewayToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "acgw_…",
        "description": "Personal application token issued by this instance. Send only in Authorization. It does not authenticate owner APIs or the desktop/mobile client. Never embed a token in this document."
      }
    },
    "schemas": {
      "Model": {
        "type": "object",
        "required": [
          "id",
          "object",
          "created",
          "owned_by",
          "label",
          "engine",
          "model",
          "efforts"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Use this exact returned ID; do not re-encode it."
          },
          "object": {
            "const": "model"
          },
          "created": {
            "type": "integer"
          },
          "owned_by": {
            "type": "string"
          },
          "engine": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "engineLabel": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "connectionId": {
            "type": "string"
          },
          "connectionLabel": {
            "type": "string"
          },
          "efforts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Omit reasoning_effort for the default, otherwise choose from this list."
          }
        }
      },
      "Message": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "enum": [
              "system",
              "developer",
              "user",
              "assistant"
            ]
          },
          "content": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^[^\u0000]*$"
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "type",
                    "text"
                  ],
                  "properties": {
                    "type": {
                      "const": "text"
                    },
                    "text": {
                      "type": "string",
                      "pattern": "^[^\u0000]*$"
                    }
                  }
                }
              }
            ],
            "description": "Text blocks are joined with newlines. Null characters are refused."
          }
        }
      },
      "ChatRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "model",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Must include a user message with non-whitespace text after normalization. Normalized UTF-8 JSON must fit in 64 KiB."
          },
          "reasoning_effort": {
            "type": "string",
            "description": "A value advertised by this exact model; no fixed local model or effort list."
          },
          "stream": {
            "type": "boolean",
            "default": false
          },
          "stream_options": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "include_usage": {
                "type": "boolean"
              }
            }
          },
          "n": {
            "const": 1
          },
          "store": {
            "const": false
          }
        },
        "allOf": [
          {
            "if": {
              "required": [
                "stream_options"
              ]
            },
            "then": {
              "required": [
                "stream"
              ],
              "properties": {
                "stream": {
                  "const": true
                }
              }
            }
          }
        ]
      },
      "Usage": {
        "type": "object",
        "required": [
          "prompt_tokens",
          "completion_tokens",
          "total_tokens"
        ],
        "properties": {
          "prompt_tokens": {
            "type": "integer",
            "minimum": 0
          },
          "completion_tokens": {
            "type": "integer",
            "minimum": 0
          },
          "total_tokens": {
            "type": "integer",
            "minimum": 0
          },
          "prompt_tokens_details": {
            "type": "object",
            "properties": {
              "cached_tokens": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      },
      "Completion": {
        "type": "object",
        "required": [
          "id",
          "object",
          "created",
          "model",
          "choices",
          "aircode_usage"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "const": "chat.completion"
          },
          "created": {
            "type": "integer"
          },
          "model": {
            "type": "string"
          },
          "choices": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1,
            "items": {
              "type": "object",
              "required": [
                "index",
                "message",
                "finish_reason"
              ],
              "properties": {
                "index": {
                  "const": 0
                },
                "finish_reason": {
                  "const": "stop"
                },
                "logprobs": {
                  "type": "null"
                },
                "message": {
                  "type": "object",
                  "required": [
                    "role",
                    "content"
                  ],
                  "properties": {
                    "role": {
                      "const": "assistant"
                    },
                    "content": {
                      "type": "string",
                      "x-max-javascript-code-units": 49152
                    },
                    "refusal": {
                      "type": "null"
                    }
                  }
                }
              }
            }
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          },
          "aircode_usage": {
            "type": "object",
            "description": "Native accounting and API-equivalent estimates. Missing counters/cost remain unknown; costComplete indicates estimate completeness."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type",
              "param",
              "code"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "enum": [
                  "authentication_error",
                  "rate_limit_error",
                  "invalid_request_error",
                  "server_error"
                ]
              },
              "param": {
                "type": "null"
              },
              "code": {
                "type": "string"
              },
              "resets_at": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Present for provider quota failures only; null means no reset time was published."
              }
            }
          }
        }
      }
    }
  }
}
