{
  "openapi": "3.1.0",
  "info": {
    "title": "Maskera Gateway",
    "version": "0.1.0",
    "description": "Stateless Swedish PII masking and an OpenAI-compatible text proxy for customer-controlled infrastructure. Payload is processed in request memory and is never written to Gateway storage or observability."
  },
  "servers": [{ "url": "http://127.0.0.1:8788" }],
  "security": [{ "bearer": [] }],
  "paths": {
    "/v1/mask": {
      "post": {
        "summary": "Mask text using the principal's central policy",
        "operationId": "mask",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MaskRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Masked text; restore map is present only when policy and request allow it",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MaskResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/openai/chat/completions": {
      "post": {
        "summary": "Mask an OpenAI chat completion request before an allowlisted upstream",
        "operationId": "chatCompletions",
        "parameters": [
          {
            "name": "X-Maskera-Upstream",
            "in": "header",
            "description": "Configured upstream id. Defaults to the policy's default/first upstream.",
            "schema": { "type": "string" }
          },
          {
            "name": "X-Upstream-Authorization",
            "in": "header",
            "description": "Required only when the selected upstream uses caller authorization.",
            "schema": { "type": "string" }
          },
          {
            "name": "X-Maskera-Restore",
            "in": "header",
            "description": "true or false, subject to central restore policy. Streaming requires true.",
            "schema": { "type": "string", "enum": ["true", "false"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChatRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON or SSE response from the configured upstream, restored according to policy"
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" },
          "502": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/healthz": {
      "get": {
        "summary": "Liveness",
        "security": [],
        "responses": { "200": { "description": "Process is alive" } }
      }
    },
    "/readyz": {
      "get": {
        "summary": "Readiness for traffic",
        "security": [],
        "responses": {
          "200": { "description": "Model, configuration and license are ready" },
          "503": { "description": "Required model or license is not ready" }
        }
      }
    },
    "/metrics": {
      "get": {
        "summary": "Payload-free Prometheus metrics",
        "responses": {
          "200": { "description": "Prometheus text exposition" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Gateway API key or OIDC access token"
      }
    },
    "responses": {
      "Error": {
        "description": "Error response whose message never contains request payload",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    },
    "schemas": {
      "MaskRequest": {
        "type": "object",
        "required": ["text"],
        "properties": {
          "text": { "type": "string", "minLength": 1 },
          "ner": {
            "type": "boolean",
            "description": "false only when policy permits rules-only downgrade"
          },
          "return_map": {
            "type": "boolean",
            "description": "false suppresses a map; true cannot override policy denial"
          }
        }
      },
      "MaskResponse": {
        "type": "object",
        "required": ["text", "entities", "meta"],
        "properties": {
          "text": { "type": "string" },
          "map": { "type": "object", "additionalProperties": { "type": "string" } },
          "entities": { "type": "object", "additionalProperties": { "type": "integer" } },
          "meta": {
            "type": "object",
            "required": ["ner", "duration_ms", "chars", "policy"],
            "properties": {
              "ner": { "type": "boolean" },
              "duration_ms": { "type": "integer" },
              "chars": { "type": "integer" },
              "policy": { "type": "string" }
            }
          }
        }
      },
      "ChatRequest": {
        "type": "object",
        "required": ["messages"],
        "additionalProperties": false,
        "description": "Fail-closed subset of Chat Completions. Unknown fields are rejected with 422 rather than forwarded uninspected.",
        "properties": {
          "audio": { "type": "object" },
          "frequency_penalty": { "type": "number" },
          "logit_bias": { "type": "object" },
          "logprobs": { "type": "boolean" },
          "max_completion_tokens": { "type": "integer" },
          "max_tokens": { "type": "integer" },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/ChatMessage" }
          },
          "modalities": { "type": "array", "items": { "type": "string" } },
          "model": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._:/@-]{1,256}$",
            "description": "Protocol identifier; not text-masked and must not contain PII."
          },
          "n": { "type": "integer" },
          "parallel_tool_calls": { "type": "boolean" },
          "presence_penalty": { "type": "number" },
          "reasoning_effort": { "type": "string" },
          "response_format": {
            "type": "object",
            "description": "Non-structural string values are masked."
          },
          "seed": { "type": "integer" },
          "service_tier": { "type": "string" },
          "store": { "type": "boolean" },
          "stream": { "type": "boolean" },
          "stream_options": { "type": "object" },
          "temperature": { "type": "number" },
          "tool_choice": {},
          "tools": {
            "type": "array",
            "description": "Non-structural string values, including descriptions and schema examples, are masked."
          },
          "top_logprobs": { "type": "integer" },
          "top_p": { "type": "number" },
          "verbosity": { "type": "string" }
        }
      },
      "ChatMessage": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "content": {
            "description": "String and text parts are masked; non-text parts are policy controlled.",
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "object" } },
              { "type": "null" }
            ]
          },
          "function_call": {
            "type": "object",
            "description": "Legacy function arguments are masked."
          },
          "refusal": { "type": "string" },
          "role": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._:/@-]{1,256}$",
            "description": "Protocol identifier; not text-masked."
          },
          "tool_call_id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._:/@-]{1,256}$",
            "description": "Protocol identifier; not text-masked."
          },
          "tool_calls": {
            "type": "array",
            "description": "Function arguments are masked."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": { "message": { "type": "string" } }
          }
        }
      }
    }
  }
}
