{
  "openapi": "3.1.0",
  "info": {
    "title": "Backoffice Log API",
    "version": "1.0.0",
    "description": "Read-only log queries using a personal API key. Start with GET /observability/schema. [Agent guide](/llms.txt)."
  },
  "servers": [
    {
      "url": "/v1"
    }
  ],
  "paths": {
    "/observability/schema": {
      "get": {
        "operationId": "describeLogSchema",
        "description": "Live DESCRIBE output for otel.otel_logs, using the same authentication and query budget.",
        "security": [
          {
            "LogApiKey": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/LogQueryResult"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          },
          "502": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          },
          "504": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/observability/query": {
      "post": {
        "operationId": "queryLogsSql",
        "description": "One SELECT or WITH statement, typed ClickHouse parameters, fixed JSON output. FORMAT, INTO, SETTINGS and multiple statements are rejected. Requires a personal log API key; cookies do not authorize queries.",
        "security": [
          {
            "LogApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LogSqlQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/LogQueryResult"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "413": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          },
          "502": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          },
          "504": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/observability/logs/search": {
      "post": {
        "operationId": "searchLogs",
        "description": "Search the half-open interval from <= Timestamp < to, at most seven days. Returns newest logs first; narrow the interval to retrieve more.",
        "security": [
          {
            "LogApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LogSearch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/LogQueryResult"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          },
          "502": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          },
          "504": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/observability/logs/aggregate": {
      "post": {
        "operationId": "aggregateLogs",
        "description": "Count logs by service, severity and/or UTC time bucket. Uses the same filters and seven-day maximum interval as search.",
        "security": [
          {
            "LogApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LogAggregate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/LogQueryResult"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          },
          "502": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          },
          "504": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "LogApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Personal opaque log API key, restricted to observability.logs.query and this environment."
      }
    },
    "responses": {
      "LogQueryResult": {
        "description": "Complete bounded query result; budget overflow never silently returns partial aggregation. Int64/UInt64 values are strings, timestamps retain source precision. All responses are private, no-store.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LogQueryResult"
            }
          }
        }
      },
      "Problem": {
        "description": "Stable problem response.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          }
        }
      }
    },
    "schemas": {
      "LogQueryResult": {
        "type": "object",
        "required": [
          "queryId",
          "columns",
          "rows",
          "statistics"
        ],
        "properties": {
          "queryId": {
            "type": "string",
            "format": "uuid"
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "type"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                }
              }
            }
          },
          "rows": {
            "type": "array",
            "maxItems": 10000,
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "statistics": {
            "type": "object",
            "required": [
              "elapsedMs",
              "readRows",
              "readBytes"
            ],
            "properties": {
              "elapsedMs": {
                "type": "number"
              },
              "readRows": {
                "type": "string",
                "pattern": "^[0-9]+$"
              },
              "readBytes": {
                "type": "string",
                "pattern": "^[0-9]+$"
              }
            }
          }
        }
      },
      "ApiProblem": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "title",
          "status",
          "code",
          "message",
          "correlationId"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "correlationId": {
            "type": "string",
            "format": "uuid"
          },
          "prerequisite": {
            "type": "string"
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemField"
            }
          }
        }
      },
      "ProblemField": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "field",
          "code",
          "message"
        ],
        "properties": {
          "field": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "LogSqlQuery": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "sql"
        ],
        "properties": {
          "sql": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32768
          },
          "parameters": {
            "type": "object",
            "maxProperties": 100,
            "propertyNames": {
              "pattern": "^[A-Za-z][A-Za-z0-9_]{0,63}$"
            },
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "string",
                  "maxLength": 4096
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                }
              ]
            },
            "description": "Named values for ClickHouse placeholders such as '{service:String}'."
          }
        }
      },
      "LogSearch": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "service": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "severity": {
            "type": "string",
            "enum": [
              "TRACE",
              "DEBUG",
              "INFO",
              "WARN",
              "ERROR",
              "FATAL"
            ]
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1024
          },
          "traceId": {
            "type": "string",
            "pattern": "^[a-fA-F0-9]{32}$"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "default": 100
          }
        }
      },
      "LogAggregate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "service": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "severity": {
            "type": "string",
            "enum": [
              "TRACE",
              "DEBUG",
              "INFO",
              "WARN",
              "ERROR",
              "FATAL"
            ]
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1024
          },
          "traceId": {
            "type": "string",
            "pattern": "^[a-fA-F0-9]{32}$"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "default": 100
          },
          "groupBy": {
            "type": "array",
            "minItems": 1,
            "maxItems": 3,
            "default": [
              "service"
            ],
            "items": {
              "type": "string",
              "enum": [
                "service",
                "severity",
                "time"
              ]
            }
          },
          "bucketSeconds": {
            "type": "integer",
            "enum": [
              60,
              300,
              3600,
              86400
            ],
            "default": 300
          }
        }
      }
    }
  }
}
