{
  "openapi": "3.1.0",
  "info": {
    "title": "philippfalkenhagen.de — agent API",
    "version": "1.0.0",
    "description": "Public, read-only endpoints with the facts on this website about photojournalist Philipp Falkenhagen. No sign-in, no keys. Versioning: the search API lives under /v1/ (with /ask as the NLWeb standard alias). An incompatible change gets /v2/; /v1/ then stays available for at least six months, and retirement is announced via Deprecation and Sunset response headers. Errors are JSON with code, message and hint. Text and data mining and AI training on this content are not permitted (Section 44b(3) German Copyright Act).",
    "contact": {
      "name": "Philipp Falkenhagen",
      "email": "info@philippfalkenhagen.de",
      "url": "https://philippfalkenhagen.de"
    }
  },
  "servers": [
    {
      "url": "https://philippfalkenhagen.de"
    }
  ],
  "paths": {
    "/v1/ask": {
      "get": {
        "operationId": "askSite",
        "summary": "Ask a question about Philipp Falkenhagen",
        "description": "Natural-language search over this website (NLWeb). Returns matching sections with Schema.org objects. Set streaming=true for Server-Sent Events (start, result, complete).",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "The question, e.g. \"contact email\".",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 300
            },
            "example": "contact email"
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Prefer results in this language.",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          },
          {
            "name": "streaming",
            "in": "query",
            "required": false,
            "description": "Stream results as Server-Sent Events.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching sections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE events: start, result (one per hit), complete."
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "askSitePost",
        "summary": "Ask a question (NLWeb v0.55 request body)",
        "description": "Same as GET /ask, with the question in the JSON body. prefer.streaming=true returns Server-Sent Events.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matching sections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ask": {
      "get": {
        "operationId": "nlwebAsk",
        "summary": "Ask a question (NLWeb standard path, same as /v1/ask)",
        "description": "Natural-language search over this website (NLWeb). Returns matching sections with Schema.org objects. Set streaming=true for Server-Sent Events (start, result, complete).",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "The question, e.g. \"contact email\".",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 300
            },
            "example": "contact email"
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Prefer results in this language.",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          },
          {
            "name": "streaming",
            "in": "query",
            "required": false,
            "description": "Stream results as Server-Sent Events.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching sections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE events: start, result (one per hit), complete."
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "nlwebAskPost",
        "summary": "Ask a question (NLWeb standard path, POST)",
        "description": "Same as GET /ask, with the question in the JSON body. prefer.streaming=true returns Server-Sent Events.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matching sections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcpJsonRpc",
        "summary": "MCP server (Streamable HTTP, JSON-RPC 2.0)",
        "description": "Model Context Protocol endpoint. Tools: get_profile, get_page, search_site. All read-only.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted (no body)."
          },
          "400": {
            "description": "Malformed JSON-RPC.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AskRequest": {
        "type": "object",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string"
                  }
                }
              }
            ]
          },
          "context": {
            "type": "object",
            "properties": {
              "prev": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "prefer": {
            "type": "object",
            "properties": {
              "streaming": {
                "type": "boolean"
              },
              "mode": {
                "type": "string",
                "enum": [
                  "list"
                ]
              }
            }
          }
        }
      },
      "AskResponse": {
        "type": "object",
        "required": [
          "_meta",
          "query_id",
          "results"
        ],
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "response_type": {
                "type": "string",
                "example": "answer"
              },
              "version": {
                "type": "string",
                "example": "0.55"
              }
            }
          },
          "query_id": {
            "type": "string"
          },
          "query": {
            "type": "string"
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Result"
            }
          }
        }
      },
      "Result": {
        "type": "object",
        "required": [
          "url",
          "name",
          "site",
          "score"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Page (and section) on philippfalkenhagen.de."
          },
          "name": {
            "type": "string",
            "description": "Title of the matching section."
          },
          "site": {
            "type": "string",
            "example": "philippfalkenhagen.de"
          },
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Relevance, 0–1."
          },
          "description": {
            "type": "string",
            "description": "Matching text from the page."
          },
          "markdown": {
            "type": "string",
            "format": "uri",
            "description": "The page as Markdown."
          },
          "schema_object": {
            "type": "object",
            "description": "Schema.org JSON-LD for the result."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code, e.g. missing_query."
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              },
              "hint": {
                "type": "string",
                "description": "How to fix the request."
              }
            }
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object"
          }
        }
      }
    }
  }
}