{
  "openapi": "3.1.0",
  "info": {
    "title": "thMenu Public API",
    "description": "Read-only public endpoints for thMenu — the multi-tenant restaurant QR menu and ordering SaaS. This spec covers what crawlers, AI agents, and third-party integrators can call against thmenu.com without auth. For authenticated/tenant APIs see admin.thmenu.com/openapi.json and menu.thmenu.com/openapi.json (separate specs, separate origins).",
    "version": "1.0.0",
    "contact": {
      "email": "thmenu@synaltix.io",
      "name": "thMenu Support",
      "url": "https://thmenu.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://thmenu.com/en/legal/terms"
    },
    "termsOfService": "https://thmenu.com/en/legal/terms"
  },
  "externalDocs": {
    "description": "AI retrieval guide (llms.txt)",
    "url": "https://thmenu.com/llms.txt"
  },
  "servers": [
    {
      "url": "https://thmenu.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Blog",
      "description": "Blog content — 1 000 SEO+LLM-optimized posts across 20 locales."
    },
    {
      "name": "Pricing",
      "description": "Subscription tier definitions and prices."
    },
    {
      "name": "FAQ",
      "description": "Frequently-asked questions, ready for AI citation."
    },
    {
      "name": "Static",
      "description": "Static manifests (sitemap, robots, llms.txt)."
    }
  ],
  "paths": {
    "/sitemap.xml": {
      "get": {
        "tags": [
          "Static"
        ],
        "summary": "Sitemap index",
        "description": "Sitemap index pointing to 5 sub-sitemaps (~20 000 URLs total).",
        "responses": {
          "200": {
            "description": "XML sitemap index",
            "content": {
              "application/xml": {}
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "tags": [
          "Static"
        ],
        "summary": "Crawler permissions",
        "description": "Includes explicit AI-bot whitelist (GPTBot, ClaudeBot, PerplexityBot, etc).",
        "responses": {
          "200": {
            "description": "Plain-text robots.txt",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "tags": [
          "Static"
        ],
        "summary": "AI retrieval guide",
        "description": "Markdown manifest aimed at LLM ingestion. Lists canonical URLs, tier definitions, contact info.",
        "responses": {
          "200": {
            "description": "Markdown llms.txt",
            "content": {
              "text/markdown": {}
            }
          }
        }
      }
    },
    "/{locale}/blog/feed.xml": {
      "get": {
        "tags": [
          "Blog"
        ],
        "summary": "RSS feed for the blog in a given locale",
        "description": "Latest 50 posts in RSS 2.0, sorted newest-first.",
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "tr",
                "en",
                "de",
                "fr",
                "es",
                "it",
                "pt",
                "ru",
                "ar",
                "zh",
                "ja",
                "ko",
                "nl",
                "pl",
                "cs",
                "da",
                "fi",
                "hu",
                "no",
                "sv"
              ]
            },
            "description": "Language code"
          }
        ],
        "responses": {
          "200": {
            "description": "RSS 2.0 feed",
            "content": {
              "application/rss+xml": {}
            }
          }
        }
      }
    },
    "/api/text/blog/{locale}/{slug}": {
      "get": {
        "tags": [
          "Blog"
        ],
        "summary": "Clean-text view of a single blog post",
        "description": "Returns the post stripped of nav, footer, scripts. Optimized for AI retrieval — title + metadata header followed by plain text or markdown body.",
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Language code"
          },
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Post slug (without .html)"
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "text",
                "markdown"
              ]
            },
            "description": "Output format. Default text."
          }
        ],
        "responses": {
          "200": {
            "description": "Plain-text or markdown post body",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Post not found for this (locale, slug) tuple"
          }
        }
      }
    },
    "/{locale}/blog": {
      "get": {
        "tags": [
          "Blog"
        ],
        "summary": "Blog index (HTML)",
        "description": "HTML page listing all posts in a given locale. For machine-readable, paginated access prefer the feed.xml or the per-post .md endpoints.",
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Language code"
          }
        ],
        "responses": {
          "200": {
            "description": "HTML index page"
          }
        }
      }
    },
    "/{locale}/blog/{slug}": {
      "get": {
        "tags": [
          "Blog"
        ],
        "summary": "Single blog post (HTML)",
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "HTML post page with BlogPosting JSON-LD"
          },
          "404": {
            "description": "Post not found"
          }
        }
      }
    },
    "/{locale}/pricing": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "summary": "Subscription tier page (HTML)",
        "description": "Renders the 4-tier comparison: Starter ($0), Pro ($29), Platinum ($59), Diamond ($99). Each tier has both monthly and yearly prices.",
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "HTML pricing page"
          }
        }
      }
    },
    "/{locale}/faq": {
      "get": {
        "tags": [
          "FAQ"
        ],
        "summary": "FAQ page (HTML, with FAQPage JSON-LD)",
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "HTML with embedded FAQPage schema"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BlogPost": {
        "type": "object",
        "required": [
          "slug",
          "locale",
          "title",
          "excerpt",
          "category",
          "date",
          "readTime",
          "author"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "example": "why-digital-menus-increase-revenue"
          },
          "locale": {
            "type": "string",
            "example": "en"
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "tips",
              "news",
              "industry",
              "guides"
            ]
          },
          "emoji": {
            "type": "string",
            "example": "📈"
          },
          "date": {
            "type": "string",
            "format": "date",
            "example": "2026-03-18"
          },
          "readTime": {
            "type": "integer",
            "example": 6
          },
          "author": {
            "type": "string",
            "example": "thMenu Team"
          },
          "topicId": {
            "type": "integer",
            "nullable": true,
            "description": "Stable canonical id shared across locale variants."
          }
        }
      },
      "PricingTier": {
        "type": "object",
        "properties": {
          "tier": {
            "type": "string",
            "enum": [
              "starter",
              "pro",
              "platinum",
              "diamond"
            ]
          },
          "label": {
            "type": "string"
          },
          "priceMonthly": {
            "type": "integer",
            "description": "USD"
          },
          "priceYearly": {
            "type": "integer",
            "description": "USD"
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}