{
  "openapi": "3.1.0",
  "info": {
    "title": "Webcentive API",
    "version": "1.0.0",
    "description": "Read-only JSON API for Webcentive Today News and category catalogs. Versioned under /api/v1/. No authentication. Rate limits apply per IP. Deprecation uses Deprecation and Sunset headers (RFC 9745 / RFC 8594) with at least 90 days notice before removal of a major version. See https://www.webcentive.com/docs/",
    "contact": {
      "name": "Webcentive",
      "email": "webcentive@yahoo.com",
      "url": "https://www.webcentive.com/contact/"
    }
  },
  "servers": [
    {
      "url": "https://www.webcentive.com",
      "description": "Production (API Version 1)"
    }
  ],
  "tags": [
    {
      "name": "news",
      "description": "Today News JSON feeds"
    },
    {
      "name": "directory",
      "description": "Directory / todaynews category metadata"
    },
    {
      "name": "mcp",
      "description": "MCP status (tool calls use Streamable HTTP at /api/mcp)"
    }
  ],
  "paths": {
    "/api/v1/news": {
      "get": {
        "operationId": "getTodayNewsV1",
        "tags": [
          "news"
        ],
        "summary": "Today News feed for one category",
        "description": "Returns a merged JSON news feed for a public todaynews category slug. Preferred versioned path. Sends API-Version: 1 and RateLimit headers.",
        "parameters": [
          {
            "name": "cat",
            "in": "query",
            "required": true,
            "description": "Category slug: 1-30 characters of a-z, 0-9 and hyphen.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]{1,30}$",
              "minLength": 1,
              "maxLength": 30,
              "examples": [
                "firewalls",
                "bis",
                "solar"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merged feed",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TodayNewsFeed"
                }
              }
            }
          },
          "400": {
            "description": "Bad cat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed (only GET and HEAD are accepted)",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Upstream unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/categories": {
      "get": {
        "operationId": "listTodayNewsCategoriesV1",
        "tags": [
          "directory"
        ],
        "summary": "List Today News category slugs",
        "description": "Lists public todaynews category slugs and titles from published conf files only. Cached. Sends API-Version and RateLimit headers.",
        "responses": {
          "200": {
            "description": "Category list",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryList"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed (only GET and HEAD are accepted)",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": []
      }
    },
    "/cgi-bin/todaynews/todaynews.cgi": {
      "get": {
        "operationId": "getTodayNewsLegacy",
        "tags": [
          "news"
        ],
        "summary": "Today News feed (legacy path)",
        "description": "Legacy unversioned path. Prefer GET /api/v1/news?cat=. Still supported; same JSON success shape.",
        "deprecated": true,
        "parameters": [
          {
            "name": "cat",
            "in": "query",
            "required": true,
            "description": "Category slug: 1-30 characters of a-z, 0-9 and hyphen.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]{1,30}$",
              "minLength": 1,
              "maxLength": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merged feed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TodayNewsFeed"
                }
              }
            }
          },
          "404": {
            "description": "Unknown category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed (only GET and HEAD are accepted)",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "GET only"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/cgi-bin/api/categories.cgi": {
      "get": {
        "operationId": "listTodayNewsCategoriesLegacy",
        "tags": [
          "directory"
        ],
        "summary": "List categories (legacy path)",
        "description": "Legacy path. Prefer GET /api/v1/categories.",
        "deprecated": true,
        "responses": {
          "200": {
            "description": "Category list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryList"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed (only GET and HEAD are accepted)",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": []
      }
    },
    "/cgi-bin/mcp/status.cgi": {
      "get": {
        "operationId": "getMcpStatus",
        "tags": [
          "mcp"
        ],
        "summary": "Legacy MCP status",
        "description": "Status for the original MCP endpoint at /cgi-bin/mcp/mcp.cgi. Preferred Streamable HTTP endpoint is POST /api/mcp (also mapped from /.well-known/mcp).",
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpStatus"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed (only GET and HEAD are accepted)",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MethodStatus"
                }
              }
            }
          }
        },
        "parameters": []
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "code",
              "message",
              "hint"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code"
              },
              "message": {
                "type": "string",
                "description": "Human-readable summary"
              },
              "hint": {
                "type": "string",
                "description": "How to fix the request"
              }
            }
          }
        }
      },
      "TodayNewsItem": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "title",
          "link"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Headline"
          },
          "link": {
            "type": "string",
            "format": "uri",
            "description": "Article URL"
          },
          "date": {
            "type": "string",
            "description": "Publication date when known"
          },
          "source": {
            "type": "string",
            "description": "Feed or publisher name"
          },
          "summary": {
            "type": "string",
            "description": "Short snippet"
          },
          "category": {
            "type": "string",
            "description": "Category slug"
          },
          "also": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Related category slugs"
          }
        }
      },
      "TodayNewsFeed": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "count",
          "items"
        ],
        "properties": {
          "updated": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO timestamp of cache refresh"
          },
          "stale": {
            "type": "boolean",
            "description": "True when serving a stale cache"
          },
          "cat": {
            "type": "string",
            "description": "Requested category slug"
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of items"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TodayNewsItem"
            }
          }
        }
      },
      "Category": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "cat",
          "title",
          "news"
        ],
        "properties": {
          "cat": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,30}$",
            "description": "Slug"
          },
          "title": {
            "type": "string",
            "description": "Human title"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Optional category page URL"
          },
          "news": {
            "type": "string",
            "format": "uri",
            "description": "Versioned news URL for this cat"
          }
        }
      },
      "CategoryList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "count",
          "categories"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Category"
            }
          }
        }
      },
      "McpStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status",
          "server",
          "endpoint"
        ],
        "properties": {
          "status": {
            "type": "string"
          },
          "server": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "endpoint": {
            "type": "string",
            "format": "uri"
          },
          "tools": {
            "type": "integer",
            "minimum": 0
          },
          "index_updated": {
            "type": "string"
          }
        }
      },
      "MethodStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "Short hint, e.g. 'use GET'"
          }
        }
      }
    },
    "headers": {
      "Allow": {
        "description": "Methods this endpoint accepts",
        "schema": {
          "type": "string",
          "examples": [
            "GET, HEAD, OPTIONS"
          ]
        }
      },
      "API-Version": {
        "description": "API major version",
        "schema": {
          "type": "string",
          "enum": [
            "1"
          ]
        }
      },
      "RateLimit": {
        "description": "IETF draft RateLimit header",
        "schema": {
          "type": "string",
          "examples": [
            "limit=60, remaining=59, reset=60"
          ]
        }
      },
      "RateLimit-Policy": {
        "description": "IETF draft RateLimit-Policy header",
        "schema": {
          "type": "string",
          "examples": [
            "60;w=60"
          ]
        }
      }
    }
  },
  "security": []
}
