{
  "openapi": "3.1.0",
  "info": {
    "title": "Flowcenter API",
    "version": "1.0.0",
    "description": "Flowcenter API for education-center operations and public agent discovery. Versioning: v1 is the stable /api contract; clients may send X-API-Version: 1. Breaking changes require a new major version. Deprecated operations are announced at least six months before removal and return Deprecation and Sunset headers during the migration window.",
    "contact": { "name": "Flowcenter Support", "email": "support@flowcenter.vn", "url": "https://flowcenter.vn/contact" },
    "license": { "name": "Flowcenter API Terms", "url": "https://flowcenter.vn/terms" }
  },
  "servers": [
    { "url": "https://flowcenter.vn", "description": "Production" },
    { "url": "http://localhost:3000", "description": "Local development" }
  ],
  "security": [],
  "tags": [
    { "name": "System", "description": "Health and public discovery." },
    { "name": "Leads", "description": "Lead capture." },
    { "name": "Newsletter", "description": "Newsletter management." },
    { "name": "Content", "description": "Authenticated content operations." },
    { "name": "MCP", "description": "Public read-only Model Context Protocol discovery." }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "healthCheck", "summary": "Health check", "tags": ["System"],
        "description": "Return OK when the public website server is available. This endpoint does not require credentials.",
        "responses": {
          "200": { "description": "Service is healthy.", "content": { "text/plain": { "schema": { "type": "string", "const": "OK" } } } },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/ai-context.json": {
      "get": {
        "operationId": "getAiContext", "summary": "Get AI context", "tags": ["System"],
        "description": "Return Flowcenter product, architecture, onboarding, and agent integration context.",
        "responses": {
          "200": { "description": "Structured Flowcenter context.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AiContext" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/agents.json": {
      "get": {
        "operationId": "getAgentInstructions", "summary": "Get agent instructions", "tags": ["System"],
        "description": "Return the canonical Flowcenter when-to-use guidance and developer discovery links.",
        "responses": {
          "200": { "description": "Agent instructions and discovery URLs.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentInstructions" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/.well-known/agents.json": {
      "get": {
        "operationId": "getWellKnownAgentInstructions", "summary": "Get well-known agent instructions", "tags": ["System"],
        "description": "Return the same canonical agent instructions from the well-known discovery path.",
        "responses": {
          "200": { "description": "Agent instructions and discovery URLs.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentInstructions" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec", "summary": "Get the OpenAPI contract", "tags": ["System"],
        "description": "Return this versioned OpenAPI 3.1 contract for code generation and tool planning.",
        "responses": {
          "200": { "description": "OpenAPI 3.1 document.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OpenApiDocument" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt", "summary": "Get LLM-readable site guidance", "tags": ["System"],
        "description": "Return the concise Flowcenter product, API, MCP, onboarding, and safe-agent-use guide.",
        "responses": {
          "200": { "description": "Plain-text agent guidance.", "content": { "text/plain": { "schema": { "type": "string" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap", "summary": "Get XML sitemap", "tags": ["System"],
        "description": "Return the XML sitemap containing canonical public pages and language alternates.",
        "responses": {
          "200": { "description": "XML sitemap.", "content": { "application/xml": { "schema": { "type": "string" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsTxt", "summary": "Get crawler directives", "tags": ["System"],
        "description": "Return crawler access directives and the canonical sitemap URL.",
        "responses": {
          "200": { "description": "Robots directives.", "content": { "text/plain": { "schema": { "type": "string" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/mcp": {
      "get": {
        "operationId": "getMcpTransportStatus", "summary": "Inspect MCP transport", "tags": ["MCP"],
        "description": "The public server uses POST JSON-RPC over Streamable HTTP. GET is rejected because this deployment does not open an SSE stream.",
        "responses": { "405": { "$ref": "#/components/responses/McpMethodNotAllowed" } }
      },
      "post": {
        "operationId": "handleMcpJsonRpc", "summary": "Handle an MCP JSON-RPC request", "tags": ["MCP"],
        "description": "Initialize a session, list tools/resources, read public resources, or call a read-only discovery tool. Send Content-Type: application/json.",
        "parameters": [
          { "$ref": "#/components/parameters/McpSessionId" },
          { "$ref": "#/components/parameters/McpProtocolVersion" }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpRequest" } } } },
        "responses": {
          "200": { "description": "MCP JSON-RPC response.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpResponse" } } } },
          "202": { "description": "MCP notification accepted. The response body is empty." },
          "400": { "$ref": "#/components/responses/McpBadRequest" },
          "403": { "$ref": "#/components/responses/McpForbidden" },
          "404": { "$ref": "#/components/responses/McpNotFound" },
          "406": { "$ref": "#/components/responses/McpNotAcceptable" },
          "415": { "$ref": "#/components/responses/McpUnsupportedMediaType" }
        }
      },
      "delete": {
        "operationId": "closeMcpSession", "summary": "Close an MCP session", "tags": ["MCP"],
        "description": "Close a previously initialized Streamable HTTP MCP session.",
        "parameters": [{ "$ref": "#/components/parameters/McpSessionId" }],
        "responses": {
          "204": { "description": "MCP session closed." },
          "403": { "$ref": "#/components/responses/McpForbidden" },
          "404": { "$ref": "#/components/responses/McpNotFound" },
          "429": { "$ref": "#/components/responses/McpRateLimited" }
        }
      }
    },
    "/api/leads/discord": {
      "post": {
        "operationId": "submitLead", "summary": "Submit a lead", "tags": ["Leads"],
        "description": "Submit a lead form payload for delivery to the configured Flowcenter lead destination. This public endpoint is rate limited and does not expose account data.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadRequest" } } } },
        "responses": {
          "200": { "description": "Lead accepted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/BadGateway" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/newsletter/subscribe": {
      "post": {
        "operationId": "subscribeNewsletter", "summary": "Subscribe to Flowcenter Insights", "tags": ["Newsletter"],
        "description": "Subscribe an email address to Flowcenter Insights. Repeating an existing subscription is idempotent.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewsletterSubscribeRequest" } } } },
        "responses": {
          "200": { "description": "Subscription accepted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewsletterSubscribeResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/newsletter/unsubscribe": {
      "get": {
        "operationId": "unsubscribeNewsletter", "summary": "Unsubscribe from Flowcenter Insights", "tags": ["Newsletter"],
        "description": "Show the unsubscribe confirmation page and, when a valid token is provided, remove the address from Flowcenter Insights.",
        "parameters": [
          { "name": "token", "in": "query", "required": false, "schema": { "type": "string" } },
          { "name": "email", "in": "query", "required": false, "schema": { "type": "string", "format": "email" } }
        ],
        "responses": {
          "200": { "description": "Unsubscribe confirmation.", "content": { "text/html": { "schema": { "type": "string" } } } },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/generate-image": {
      "post": {
        "operationId": "generateImage", "summary": "Generate and store an image", "tags": ["Content"],
        "description": "Generate an image through the configured Replicate integration and store the resulting asset in Sanity. Requires a server-side Replicate token.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }, { "$ref": "#/components/parameters/ReplicateToken" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenerateImageRequest" } } } },
        "responses": {
          "200": { "description": "Image generated and stored.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenerateImageResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "502": { "$ref": "#/components/responses/BadGateway" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" },
          "504": { "$ref": "#/components/responses/GatewayTimeout" }
        }
      }
    },
    "/api/blog-covers/render": {
      "post": {
        "operationId": "renderBlogCover", "summary": "Render and attach a custom blog cover", "tags": ["Content"],
        "description": "Sanitize agent-authored HTML/CSS, render a 1200×630 blog cover, upload it to Sanity, and attach it to a draft post. Requires a Flowcenter bearer token.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BlogCoverRequest" } } } },
        "responses": {
          "200": { "description": "Cover attached.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BlogCoverResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api": {
      "post": {
        "operationId": "apiRouteNotFound", "summary": "API route fallback", "tags": ["System"],
        "description": "Return a stable machine-readable error when an API route is not present in this deployment.",
        "responses": { "404": { "$ref": "#/components/responses/NotFound" } }
      }
    }
  },
  "components": {
    "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "token" } },
    "parameters": {
      "ApiVersion": {
        "name": "X-API-Version", "in": "header", "required": false,
        "description": "Pin the stable Flowcenter API contract. Current value is 1.",
        "schema": { "type": "string", "enum": ["1"], "default": "1" }
      },
      "McpSessionId": { "name": "Mcp-Session-Id", "in": "header", "required": false, "description": "Session ID returned by MCP initialize.", "schema": { "type": "string", "format": "uuid" } },
      "McpProtocolVersion": { "name": "MCP-Protocol-Version", "in": "header", "required": false, "description": "Must match the version negotiated during initialize on session requests.", "schema": { "type": "string", "enum": ["2025-06-18"] } },
      "ReplicateToken": { "name": "x-replicate-api-token", "in": "header", "required": false, "description": "Optional Replicate token when the server is not configured with one.", "schema": { "type": "string", "writeOnly": true } }
    },
    "headers": {
      "RateLimit-Limit": { "description": "Maximum requests in the current window.", "schema": { "type": "integer", "minimum": 1 } },
      "RateLimit-Remaining": { "description": "Requests remaining in the current window.", "schema": { "type": "integer", "minimum": 0 } },
      "RateLimit-Reset": { "description": "Unix timestamp when the window resets.", "schema": { "type": "integer", "minimum": 0 } },
      "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer", "minimum": 1 } },
      "Mcp-Session-Id": { "description": "MCP session identifier returned by initialize.", "schema": { "type": "string", "format": "uuid" } }
    },
    "schemas": {
      "ErrorDetails": {
        "type": "object", "required": ["code", "message", "hint"],
        "properties": {
          "code": { "type": "string", "description": "Stable machine-readable error code." },
          "message": { "type": "string" },
          "hint": { "type": "string", "description": "Actionable resolution guidance." }
        },
        "additionalProperties": false
      },
      "ErrorResponse": {
        "type": "object", "required": ["ok", "error"],
        "properties": { "ok": { "type": "boolean", "const": false }, "error": { "$ref": "#/components/schemas/ErrorDetails" } },
        "additionalProperties": false
      },
      "OkResponse": { "type": "object", "required": ["ok"], "properties": { "ok": { "type": "boolean", "const": true } }, "additionalProperties": false },
      "NewsletterSubscribeResponse": {
        "type": "object", "required": ["ok", "status"],
        "properties": { "ok": { "type": "boolean", "const": true }, "status": { "type": "string", "enum": ["subscribed", "already_subscribed"] } },
        "additionalProperties": false
      },
      "LeadRequest": {
        "type": "object", "required": ["formName", "values"],
        "properties": {
          "formName": { "type": "string", "minLength": 1 },
          "locationLabel": { "type": "string" },
          "values": { "type": "object", "additionalProperties": { "type": ["string", "number", "boolean", "null"] } },
          "attribution": { "type": "object", "additionalProperties": true },
          "formStartedAt": { "type": "string", "format": "date-time" }
        },
        "additionalProperties": false
      },
      "NewsletterSubscribeRequest": {
        "type": "object", "required": ["email"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "language": { "type": "string", "enum": ["en", "vi", "zh"], "default": "en" },
          "source": { "type": "string" },
          "postSlug": { "type": "string" },
          "attribution": { "type": "object", "additionalProperties": true },
          "formStartedAt": { "type": "string", "format": "date-time" },
          "website": { "type": "string", "description": "Honeypot; leave empty." }
        },
        "additionalProperties": false
      },
      "GenerateImageRequest": {
        "type": "object", "required": ["prompt"],
        "properties": {
          "prompt": { "type": "string", "minLength": 1 },
          "model": { "type": "string", "default": "stability-ai/sdxl" },
          "width": { "type": "integer", "minimum": 256, "maximum": 2048, "default": 1024 },
          "height": { "type": "integer", "minimum": 256, "maximum": 2048, "default": 1024 },
          "apiKey": { "type": "string", "writeOnly": true }
        },
        "additionalProperties": false
      },
      "GenerateImageResponse": {
        "type": "object", "required": ["ok", "assetId", "url"],
        "properties": { "ok": { "type": "boolean", "const": true }, "assetId": { "type": "string" }, "url": { "type": "string", "format": "uri" }, "document": { "type": "object" } },
        "additionalProperties": false
      },
      "BlogCoverRequest": {
        "type": "object", "required": ["documentId", "html", "locale"],
        "properties": {
          "documentId": { "type": "string" }, "html": { "type": "string", "maxLength": 20000 }, "css": { "type": "string", "maxLength": 20000 },
          "title": { "type": "string" }, "tag": { "type": "string" }, "locale": { "type": "string", "enum": ["en", "vi", "zh"] }
        },
        "additionalProperties": false
      },
      "BlogCoverResponse": {
        "type": "object", "required": ["ok", "documentId", "assetId", "assetUrl", "locale"],
        "properties": {
          "ok": { "type": "boolean", "const": true }, "documentId": { "type": "string" }, "assetId": { "type": "string" }, "assetUrl": { "type": "string", "format": "uri" }, "locale": { "type": "string", "enum": ["en", "vi", "zh"] }, "blogCover": { "type": "object" }
        },
        "additionalProperties": false
      },
      "AgentInstructions": {
        "type": "object", "required": ["name", "description", "homepage", "whenToUse", "developerPortal", "discovery"],
        "properties": {
          "name": { "type": "string", "const": "Flowcenter" }, "description": { "type": "string" }, "version": { "type": "string" },
          "homepage": { "type": "string", "format": "uri" }, "whenToUse": { "type": "array", "minItems": 1, "items": { "type": "string" } },
          "developerPortal": { "type": "string", "format": "uri" }, "discovery": { "type": "object", "additionalProperties": { "type": "string", "format": "uri" } }
        },
        "additionalProperties": true
      },
      "AiContext": {
        "type": "object",
        "required": ["name", "version", "description", "developerPortal", "architecture", "endpoints"],
        "properties": {
          "name": { "type": "string", "const": "Flowcenter" },
          "version": { "type": "string" },
          "description": { "type": "string" },
          "developerPortal": { "type": "string", "format": "uri" },
          "whenToUse": { "type": "array", "items": { "type": "string" } },
          "architecture": { "type": "object", "additionalProperties": true },
          "endpoints": { "type": "object", "additionalProperties": { "type": "string" } },
          "capabilities": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": true
      },
      "OpenApiDocument": {
        "type": "object",
        "required": ["openapi", "info", "servers", "paths"],
        "properties": {
          "openapi": { "type": "string", "const": "3.1.0" },
          "info": {
            "type": "object",
            "required": ["title", "version", "description"],
            "properties": {
              "title": { "type": "string", "const": "Flowcenter API" },
              "version": { "type": "string" },
              "description": { "type": "string" }
            },
            "additionalProperties": true
          },
          "servers": { "type": "array", "items": { "type": "object", "required": ["url"], "properties": { "url": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "additionalProperties": true } },
          "paths": { "type": "object", "additionalProperties": true },
          "components": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": true
      },
      "McpRequest": {
        "type": "object", "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" }, "id": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }] },
          "method": { "type": "string" }, "params": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": false
      },
      "McpError": {
        "type": "object",
        "required": ["code", "message"],
        "properties": { "code": { "type": "integer" }, "message": { "type": "string" }, "data": {} },
        "additionalProperties": true
      },
      "McpResponse": {
        "type": "object", "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" }, "id": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }] },
          "result": { "type": "object", "description": "Method-specific MCP result.", "additionalProperties": true }, "error": { "$ref": "#/components/schemas/McpError" }
        },
        "oneOf": [{ "required": ["result"], "not": { "required": ["error"] } }, { "required": ["error"], "not": { "required": ["result"] } }],
        "additionalProperties": false
      },
      "McpErrorResponse": {
        "type": "object", "required": ["jsonrpc", "error"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }] },
          "error": { "$ref": "#/components/schemas/McpError" }
        },
        "additionalProperties": false
      },
      "EmptyResponse": { "type": "null", "description": "The HTTP response body is empty for notifications." }
    },
    "responses": {
      "BadRequest": { "description": "Invalid request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "Unauthorized": { "description": "Authentication failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "Forbidden": { "description": "Origin or permission rejected.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "NotFound": { "description": "Resource or route not found.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "RateLimited": { "description": "Rate limit exceeded.", "headers": { "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" }, "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" }, "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }, "Retry-After": { "$ref": "#/components/headers/Retry-After" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "ServiceUnavailable": { "description": "Service or dependency unavailable.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "BadGateway": { "description": "Upstream service failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "GatewayTimeout": { "description": "Upstream service timed out.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "PayloadTooLarge": { "description": "Request payload is too large.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "InternalError": { "description": "Unexpected server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "UnsupportedMediaType": { "description": "Content-Type must be application/json.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "NotAcceptable": { "description": "Accept must include application/json and text/event-stream.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
      "McpBadRequest": { "description": "Invalid MCP JSON-RPC request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpErrorResponse" } } } },
      "McpForbidden": { "description": "MCP Origin rejected.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpErrorResponse" } } } },
      "McpNotFound": { "description": "MCP session or resource not found.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpErrorResponse" } } } },
      "McpNotAcceptable": { "description": "Accept must include application/json and text/event-stream.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpErrorResponse" } } } },
      "McpUnsupportedMediaType": { "description": "Content-Type must be application/json.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpErrorResponse" } } } },
      "McpRateLimited": { "description": "MCP rate limit exceeded.", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpErrorResponse" } } } },
      "McpMethodNotAllowed": { "description": "Use POST JSON-RPC for Streamable HTTP.", "headers": { "Allow": { "schema": { "type": "string", "const": "POST" } } }, "content": { "application/json": { "schema": { "type": "object", "required": ["error", "message", "hint"], "properties": { "error": { "type": "string" }, "message": { "type": "string" }, "hint": { "type": "string" } } } } } }
    }
  }
}
