{
  "openapi": "3.0.3",
  "info": {
    "title": "BD Event API",
    "version": "1.0.0",
    "summary": "Community, submissions and subscription APIs for BD Event — the curated B2B event directory for Vietnam.",
    "description": "Public JSON API for BD Event (events.gomievibes.online). The primary machine interface for the event catalog is the web pages themselves, which also serve Markdown via `Accept: text/markdown` (see /docs/markdown.md). Write endpoints require a CSRF token returned by GET bootstrap (cookie-based session) and are rate-limited per visitor.",
    "contact": {
      "name": "BD Event",
      "url": "https://events.gomievibes.online/contact"
    },
    "license": {
      "name": "Proprietary — see privacy policy",
      "url": "https://events.gomievibes.online/privacy"
    }
  },
  "servers": [
    { "url": "https://events.gomievibes.online" }
  ],
  "tags": [
    { "name": "community", "description": "Event community: RSVP attendance, public comments, CSRF bootstrap." },
    { "name": "submissions", "description": "Suggest an event by URL; AI-assisted extraction with admin review." },
    { "name": "subscriptions", "description": "Email notifications for a single event (explicit consent)." },
    { "name": "telegram", "description": "Private webhook for the BD Event Telegram bot." }
  ],
  "paths": {
    "/api/community.php": {
      "get": {
        "tags": ["community"],
        "summary": "Bootstrap session or read community data",
        "description": "action=bootstrap returns a CSRF token for this visitor session (cookie-based). action=summary and action=comments are public read operations.",
        "parameters": [
          { "name": "action", "in": "query", "required": true, "schema": { "type": "string", "enum": ["bootstrap", "summary", "comments"] }, "description": "bootstrap: get CSRF token; summary: RSVP summary for an event; comments: public discussion comments." },
          { "name": "event_id", "in": "query", "required": false, "schema": { "type": "integer" }, "description": "Required when action=summary or action=comments." }
        ],
        "responses": {
          "200": {
            "description": "bootstrap returns {\"ok\":true,\"csrf\":\"...\"}; summary/comments return the requested data.",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "400": { "description": "Invalid action." },
          "404": { "description": "Event not found for summary/comments." }
        }
      },
      "post": {
        "tags": ["community"],
        "summary": "RSVP to an event or post a public comment",
        "description": "Requires csrf from GET bootstrap. Rate-limited per visitor (comment: 8/hour, rsvp: 15/hour).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["action", "csrf", "event_id"],
                "properties": {
                  "action": { "type": "string", "enum": ["rsvp", "comment"] },
                  "csrf": { "type": "string", "description": "CSRF token from GET bootstrap." },
                  "event_id": { "type": "integer" },
                  "status": { "type": "string", "enum": ["going", "interested", "none"], "description": "RSVP status. none removes the RSVP." },
                  "intent": { "type": "string", "enum": ["find_companion", "networking", "question", "review"], "description": "Comment intent. review requires a past event." },
                  "body": { "type": "string", "maxLength": 1000, "description": "Comment body (>=2 chars)." },
                  "display_name": { "type": "string" },
                  "role": { "type": "string" },
                  "zalo": { "type": "string", "description": "Stored privately; never exposed publicly." },
                  "email": { "type": "string", "format": "email", "description": "Stored privately; never exposed publicly." },
                  "honeypot": { "type": "string", "description": "Spam honeypot; any value is silently accepted and ignored." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "RSVP returns the updated summary; comment returns the comment list." },
          "400": { "description": "Invalid action." },
          "401": { "description": "Missing/invalid CSRF token (returns 403 in older builds)." },
          "403": { "description": "CSRF validation failed — reload the page for a fresh token." },
          "422": { "description": "Validation error (invalid status/intent, short comment, no profile, review on non-past event)." },
          "500": { "description": "Server error." }
        }
      }
    },
    "/api/submissions.php": {
      "get": {
        "tags": ["submissions"],
        "summary": "Poll an event submission status/preview",
        "description": "Returns the submission identified by id (public_id like BD-XXXX) for THIS visitor only. The preview omits private contact values.",
        "parameters": [
          { "name": "id", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Submission public_id returned by POST submit (e.g. BD-1A2B)." }
        ],
        "responses": {
          "200": { "description": "{\"ok\":true,\"submission\":{...}} with status, confidence, event_id, preview." },
          "404": { "description": "Submission not found for this visitor." }
        }
      },
      "post": {
        "tags": ["submissions"],
        "summary": "Submit an event URL for AI-assisted extraction and admin review",
        "description": "Validates the URL (SSRF-protected), extracts event facts, and routes to published / needs_review / duplicate / processing. Requires csrf. Rate-limited to 5/hour per visitor.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["action", "csrf", "url"],
                "properties": {
                  "action": { "type": "string", "enum": ["submit"] },
                  "csrf": { "type": "string" },
                  "url": { "type": "string", "format": "uri", "description": "Public event URL to extract." },
                  "honeypot": { "type": "string", "description": "Spam honeypot; any value is silently accepted and ignored." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "{\"ok\":true,\"submission_id\":\"BD-XXXX\",\"status\":\"processing|published|duplicate|needs_review\",\"event_id\":...}" },
          "400": { "description": "Invalid action." },
          "403": { "description": "CSRF validation failed." },
          "422": { "description": "Invalid URL or extraction failed." }
        }
      }
    },
    "/api/subscriptions.php": {
      "post": {
        "tags": ["subscriptions"],
        "summary": "Subscribe an email to notifications for one event",
        "description": "Explicit consent only. Email is stored hashed (sha256 of lowercased email) with an active status; unsubscribe supported. Requires csrf. Rate-limited to 5/hour per visitor.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["action", "csrf", "event_id", "email"],
                "properties": {
                  "action": { "type": "string", "enum": ["subscribe"] },
                  "csrf": { "type": "string" },
                  "event_id": { "type": "integer" },
                  "email": { "type": "string", "format": "email" },
                  "notify_registration": { "type": "integer", "enum": [0, 1], "description": "Notify about registration-related updates." },
                  "notify_updates": { "type": "integer", "enum": [0, 1], "description": "Notify about event updates." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "{\"ok\":true,\"message\":\"...\"}" },
          "400": { "description": "Invalid action or no notification type selected." },
          "403": { "description": "CSRF validation failed." },
          "422": { "description": "Invalid email or event." },
          "500": { "description": "Server error." }
        }
      }
    },
    "/api/telegram.php": {
      "post": {
        "tags": ["telegram"],
        "summary": "Telegram bot webhook (private)",
        "description": "Receives Telegram update payloads for the BD Event bot. Requires header X-Telegram-Bot-Api-Secret-Token matching the configured webhook secret; returns 401 otherwise. A message containing a link triggers event extraction, an image is stored as a flyer, plain text is saved as notes.",
        "security": [{ "telegramSecret": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "type": "object", "description": "Standard Telegram update object." }
            }
          }
        },
        "responses": {
          "200": { "description": "Webhook acknowledged." },
          "401": { "description": "Missing or invalid X-Telegram-Bot-Api-Secret-Token header." },
          "500": { "description": "Server error." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "telegramSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Telegram-Bot-Api-Secret-Token",
        "description": "Telegram webhook secret configured for the BD Event bot."
      }
    }
  }
}