{
  "openapi": "3.1.0",
  "info": {
    "title": "ThrillSEO Public API",
    "version": "1.0.0",
    "summary": "Read-only programmatic access to thrillseo.com content.",
    "description": "ThrillSEO exposes its published content through the standard WordPress REST API under `/wp-json/`. Every endpoint documented here is public and requires no authentication. Responses are JSON; errors are JSON too, with a stable `{code, message, data.status}` shape (see the `Error` schema).\n\nIn addition to the REST API, every public HTML page performs content negotiation: send `Accept: text/markdown` (or append `?format=md`) and the same URL returns a Markdown representation of the page.\n\nDiscovery files:\n- `/llms.txt` — curated Markdown map of the site for LLMs\n- `/sitemaps.xml` — XML sitemap index\n- `/.well-known/agent-card.json` — A2A agent card\n- `/.well-known/mcp.json` — MCP server card\n- `/.well-known/ai-catalog.json` — Agentic Resource Discovery manifest\n- `/openapi.json` — this document",
    "contact": {
      "name": "ThrillSEO",
      "url": "https://thrillseo.com/contact/",
      "email": "hello@thrillseo.com"
    },
    "license": {
      "name": "Content © ThrillSEO / BRO OÜ — API access permitted for indexing and agent use",
      "url": "https://thrillseo.com/"
    }
  },
  "servers": [
    { "url": "https://thrillseo.com", "description": "Production" }
  ],
  "externalDocs": {
    "description": "LLM-oriented site map",
    "url": "https://thrillseo.com/llms.txt"
  },
  "tags": [
    { "name": "Discovery", "description": "API root and machine-readable discovery resources." },
    { "name": "Content", "description": "Published posts, pages and taxonomies via the WordPress REST API." },
    { "name": "Search", "description": "Full-text site search." },
    { "name": "Markdown", "description": "Markdown content negotiation on public pages." }
  ],
  "paths": {
    "/wp-json/": {
      "get": {
        "operationId": "getApiIndex",
        "tags": ["Discovery"],
        "summary": "REST API index",
        "description": "Returns the API root document: site name, description, available namespaces, the full route table and authentication methods.",
        "responses": {
          "200": {
            "description": "API index document.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RestIndex" }
              }
            }
          }
        }
      }
    },
    "/wp-json/wp/v2/posts": {
      "get": {
        "operationId": "listPosts",
        "tags": ["Content"],
        "summary": "List published posts",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          { "$ref": "#/components/parameters/Search" },
          {
            "name": "slug",
            "in": "query",
            "description": "Limit the result set to posts with one or more specific slugs.",
            "schema": { "type": "array", "items": { "type": "string" } }
          },
          {
            "name": "categories",
            "in": "query",
            "description": "Limit the result set to posts assigned to specific category IDs.",
            "schema": { "type": "array", "items": { "type": "integer" } }
          },
          {
            "name": "orderby",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "date",
              "enum": ["date", "id", "include", "title", "slug", "modified", "relevance"]
            }
          },
          {
            "name": "order",
            "in": "query",
            "schema": { "type": "string", "default": "desc", "enum": ["asc", "desc"] }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of posts. Pagination totals are returned in the `X-WP-Total` and `X-WP-TotalPages` response headers.",
            "headers": {
              "X-WP-Total": { "$ref": "#/components/headers/XWPTotal" },
              "X-WP-TotalPages": { "$ref": "#/components/headers/XWPTotalPages" },
              "Link": { "$ref": "#/components/headers/Link" }
            },
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Post" } }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/wp-json/wp/v2/posts/{id}": {
      "get": {
        "operationId": "getPost",
        "tags": ["Content"],
        "summary": "Get a single post",
        "parameters": [ { "$ref": "#/components/parameters/ResourceId" } ],
        "responses": {
          "200": {
            "description": "The requested post.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Post" } }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/wp-json/wp/v2/pages": {
      "get": {
        "operationId": "listPages",
        "tags": ["Content"],
        "summary": "List published pages",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          { "$ref": "#/components/parameters/Search" },
          {
            "name": "slug",
            "in": "query",
            "description": "Limit the result set to pages with one or more specific slugs.",
            "schema": { "type": "array", "items": { "type": "string" } }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of pages.",
            "headers": {
              "X-WP-Total": { "$ref": "#/components/headers/XWPTotal" },
              "X-WP-TotalPages": { "$ref": "#/components/headers/XWPTotalPages" },
              "Link": { "$ref": "#/components/headers/Link" }
            },
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Page" } }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/wp-json/wp/v2/pages/{id}": {
      "get": {
        "operationId": "getPage",
        "tags": ["Content"],
        "summary": "Get a single page",
        "parameters": [ { "$ref": "#/components/parameters/ResourceId" } ],
        "responses": {
          "200": {
            "description": "The requested page.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Page" } }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/wp-json/wp/v2/categories": {
      "get": {
        "operationId": "listCategories",
        "tags": ["Content"],
        "summary": "List categories",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          { "$ref": "#/components/parameters/Search" }
        ],
        "responses": {
          "200": {
            "description": "A page of categories.",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Term" } }
              }
            }
          }
        }
      }
    },
    "/wp-json/wp/v2/tags": {
      "get": {
        "operationId": "listTags",
        "tags": ["Content"],
        "summary": "List tags",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          { "$ref": "#/components/parameters/Search" }
        ],
        "responses": {
          "200": {
            "description": "A page of tags.",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Term" } }
              }
            }
          }
        }
      }
    },
    "/wp-json/wp/v2/search": {
      "get": {
        "operationId": "search",
        "tags": ["Search"],
        "summary": "Search the site",
        "description": "Full-text search across posts and pages. Returns lightweight result objects; follow the `url` of each to fetch the full resource (optionally as Markdown).",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          {
            "name": "search",
            "in": "query",
            "required": true,
            "description": "The search term.",
            "schema": { "type": "string", "minLength": 1 }
          },
          {
            "name": "type",
            "in": "query",
            "schema": { "type": "string", "default": "post", "enum": ["post", "term", "post-format"] }
          },
          {
            "name": "subtype",
            "in": "query",
            "description": "Restrict to a specific post type or taxonomy (default `any`).",
            "schema": { "type": "string", "default": "any" }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results.",
            "headers": {
              "X-WP-Total": { "$ref": "#/components/headers/XWPTotal" },
              "X-WP-TotalPages": { "$ref": "#/components/headers/XWPTotalPages" }
            },
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/SearchResult" } }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/{page_path}": {
      "get": {
        "operationId": "getPageAsMarkdown",
        "tags": ["Markdown"],
        "summary": "Fetch any public page as HTML or Markdown",
        "description": "Every public URL on thrillseo.com (home page, blog index, posts, pages, case studies) performs content negotiation. Request the URL normally for HTML, or send `Accept: text/markdown` — equivalently append `?format=md` — to receive a Markdown rendering of the same content. Responses carry `Vary: Accept` so intermediary caches keep the two representations separate. Unknown paths return `404` with a short Markdown recovery document when Markdown is requested.",
        "parameters": [
          {
            "name": "page_path",
            "in": "path",
            "required": true,
            "description": "Any site-relative path, e.g. `services/`, `case-studies/`, or `` for the home page.",
            "schema": { "type": "string" },
            "example": "services/"
          },
          {
            "name": "Accept",
            "in": "header",
            "description": "Set to `text/markdown` to receive Markdown instead of HTML.",
            "schema": { "type": "string", "enum": ["text/html", "text/markdown"] }
          },
          {
            "name": "format",
            "in": "query",
            "description": "Alternative to the `Accept` header. `md` or `markdown` returns Markdown.",
            "schema": { "type": "string", "enum": ["md", "markdown"] }
          }
        ],
        "responses": {
          "200": {
            "description": "The page content.",
            "headers": { "Vary": { "$ref": "#/components/headers/Vary" } },
            "content": {
              "text/html": { "schema": { "type": "string" } },
              "text/markdown": { "schema": { "type": "string" } }
            }
          },
          "404": {
            "description": "No such page. When Markdown is requested the body is a short Markdown document linking to the sitemap, llms.txt and the main site sections.",
            "headers": { "Vary": { "$ref": "#/components/headers/Vary" } },
            "content": {
              "text/markdown": { "schema": { "type": "string" } },
              "text/html": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": ["Discovery"],
        "summary": "LLM-oriented site map",
        "responses": {
          "200": {
            "description": "Curated Markdown overview of the site.",
            "content": { "text/plain": { "schema": { "type": "string" } } }
          }
        }
      }
    },
    "/sitemaps.xml": {
      "get": {
        "operationId": "getSitemapIndex",
        "tags": ["Discovery"],
        "summary": "XML sitemap index",
        "responses": {
          "200": {
            "description": "Sitemap index pointing at per-type sitemaps.",
            "content": { "application/xml": { "schema": { "type": "string" } } }
          }
        }
      }
    },
    "/.well-known/agent-card.json": {
      "get": {
        "operationId": "getAgentCard",
        "tags": ["Discovery"],
        "summary": "A2A agent card",
        "responses": {
          "200": {
            "description": "Agent-to-Agent (A2A) agent card describing the search and fetch-as-Markdown skills this site offers.",
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "getMcpCard",
        "tags": ["Discovery"],
        "summary": "MCP server card",
        "responses": {
          "200": {
            "description": "Model Context Protocol server card listing the site's exposed resources.",
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          }
        }
      }
    },
    "/.well-known/ai-catalog.json": {
      "get": {
        "operationId": "getAiCatalog",
        "tags": ["Discovery"],
        "summary": "Agentic Resource Discovery manifest",
        "responses": {
          "200": {
            "description": "ARD capability manifest linking the agent card, MCP card, agent skills, llms.txt, the REST API and this OpenAPI document.",
            "content": {
              "application/ai-catalog+json": { "schema": { "type": "object", "additionalProperties": true } }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Page": {
        "name": "page",
        "in": "query",
        "description": "Current page of the collection.",
        "schema": { "type": "integer", "minimum": 1, "default": 1 }
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "description": "Maximum number of items to return.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 }
      },
      "Search": {
        "name": "search",
        "in": "query",
        "description": "Limit results to those matching a string.",
        "schema": { "type": "string" }
      },
      "ResourceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Unique identifier for the resource.",
        "schema": { "type": "integer", "minimum": 1 }
      }
    },
    "headers": {
      "XWPTotal": {
        "description": "Total number of items in the unpaginated collection.",
        "schema": { "type": "integer" }
      },
      "XWPTotalPages": {
        "description": "Total number of pages available.",
        "schema": { "type": "integer" }
      },
      "Link": {
        "description": "RFC 8288 pagination links (`rel=\"next\"`, `rel=\"prev\"`).",
        "schema": { "type": "string" }
      },
      "Vary": {
        "description": "Always includes `Accept` on content-negotiated pages so caches keep HTML and Markdown variants separate.",
        "schema": { "type": "string", "example": "Accept, Accept-Encoding" }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "One or more parameters were invalid.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "code": "rest_invalid_param",
              "message": "Invalid parameter(s): per_page",
              "data": {
                "status": 400,
                "params": { "per_page": "per_page must be between 1 (inclusive) and 100 (inclusive)" }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource or route does not exist.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "examples": {
              "unknownRoute": {
                "value": {
                  "code": "rest_no_route",
                  "message": "No route was found matching the URL and request method.",
                  "data": { "status": 404 }
                }
              },
              "unknownId": {
                "value": {
                  "code": "rest_post_invalid_id",
                  "message": "Invalid post ID.",
                  "data": { "status": 404 }
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Standard WordPress REST API error envelope. Present on every 4xx/5xx JSON response.",
        "required": ["code", "message", "data"],
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable error code, e.g. `rest_no_route`, `rest_invalid_param`, `rest_post_invalid_id`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation, safe to surface to end users."
          },
          "data": {
            "type": "object",
            "required": ["status"],
            "properties": {
              "status": { "type": "integer", "description": "HTTP status code, mirrored in the response status line." },
              "params": {
                "type": "object",
                "additionalProperties": { "type": "string" },
                "description": "Per-parameter resolution hints (validation errors only)."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "Per-parameter structured detail (validation errors only)."
              }
            }
          }
        }
      },
      "RestIndex": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "description": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "home": { "type": "string", "format": "uri" },
          "namespaces": { "type": "array", "items": { "type": "string" } },
          "authentication": { "type": "object", "additionalProperties": true },
          "routes": { "type": "object", "additionalProperties": true }
        }
      },
      "RenderedText": {
        "type": "object",
        "properties": {
          "rendered": { "type": "string" },
          "protected": { "type": "boolean" }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "date": { "type": "string", "format": "date-time" },
          "date_gmt": { "type": "string", "format": "date-time" },
          "modified": { "type": "string", "format": "date-time" },
          "slug": { "type": "string" },
          "status": { "type": "string", "enum": ["publish"] },
          "type": { "type": "string", "example": "post" },
          "link": { "type": "string", "format": "uri" },
          "title": { "$ref": "#/components/schemas/RenderedText" },
          "content": { "$ref": "#/components/schemas/RenderedText" },
          "excerpt": { "$ref": "#/components/schemas/RenderedText" },
          "author": { "type": "integer" },
          "featured_media": { "type": "integer" },
          "categories": { "type": "array", "items": { "type": "integer" } },
          "tags": { "type": "array", "items": { "type": "integer" } }
        }
      },
      "Page": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "date": { "type": "string", "format": "date-time" },
          "modified": { "type": "string", "format": "date-time" },
          "slug": { "type": "string" },
          "status": { "type": "string", "enum": ["publish"] },
          "type": { "type": "string", "example": "page" },
          "link": { "type": "string", "format": "uri" },
          "title": { "$ref": "#/components/schemas/RenderedText" },
          "content": { "$ref": "#/components/schemas/RenderedText" },
          "excerpt": { "$ref": "#/components/schemas/RenderedText" },
          "parent": { "type": "integer" },
          "menu_order": { "type": "integer" }
        }
      },
      "Term": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "count": { "type": "integer" },
          "description": { "type": "string" },
          "link": { "type": "string", "format": "uri" },
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "taxonomy": { "type": "string", "example": "category" }
        }
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "title": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "type": { "type": "string", "example": "post" },
          "subtype": { "type": "string", "example": "post" }
        }
      }
    }
  }
}
