{
  "openapi": "3.1.0",
  "info": {
    "title": "make1day API",
    "version": "1.0.0",
    "summary": "Configurable Next.js SaaS starter generator, licensing and payments",
    "description": "make1day sells a configurable Next.js 15 SaaS starter. The Builder posts a selection to /api/generate and receives a pruned, buildable, agent-native ZIP containing AGENTS.md, design.md, scoped rules, Cursor rules, CLAUDE.md and up to 12 skills. An MCP endpoint lets coding agents assemble a valid configuration programmatically. Source downloads are gated by an offline license key or an active subscription / email claim — the source ZIP is the product, so anonymous visitors never receive it. A source-free preview package is available without a license. Full human/agent-readable overview: /llms-full.txt. Go-live notes: docs/waffo-launch-checklist.md."
  },
  "servers": [
    {
      "url": "https://make1day-saas-template.vercel.app",
      "description": "make1day site"
    }
  ],
  "tags": [
    {
      "name": "Generator",
      "description": "Build and download a customized project"
    },
    {
      "name": "License",
      "description": "Offline HMAC license keys"
    },
    {
      "name": "Payments",
      "description": "Pluggable checkout + webhooks (Waffo Pancake active by default; Stripe / Creem / PayPal / Lemon Squeezy also available)"
    },
    {
      "name": "RAG",
      "description": "Hybrid BM25 + embeddings retrieval"
    },
    {
      "name": "AI",
      "description": "Multi-provider chat"
    },
    {
      "name": "Analytics",
      "description": "First-party, consent-gated statistics"
    },
    {
      "name": "Mail",
      "description": "Transactional email configuration (make1day site)"
    },
    {
      "name": "Support",
      "description": "Support tickets / contact form"
    },
    {
      "name": "Auth recovery",
      "description": "Password reset + email verification"
    },
    {
      "name": "Privacy",
      "description": "Consent, retention, data export and erasure"
    },
    {
      "name": "Observability",
      "description": "Health probe and client/server error collection"
    },
    {
      "name": "Content / GEO",
      "description": "Machine-readable docs and Markdown variants"
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol tools for agent-driven scaffolding"
    }
  ],
  "paths": {
    "/api/generate": {
      "post": {
        "tags": [
          "Generator"
        ],
        "summary": "Generate and download a customized Next.js project ZIP",
        "description": "Requires an entitlement. Order: offline license key (body or sk_license cookie) -> active subscription / email auto-claim -> 402. The gate protects this site only; generated projects ship with LICENSE_GATE=off.",
        "security": [
          {
            "licenseCookie": []
          },
          {
            "supabaseSession": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/BuilderConfig"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "licenseKey": {
                        "type": "string",
                        "maxLength": 4096,
                        "description": "Optional SK1.<payload>.<mac> offline license key"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated project archive",
            "headers": {
              "Content-Disposition": {
                "description": "attachment; filename=\"<slug>-shipkit.zip\"",
                "schema": {
                  "type": "string"
                }
              },
              "X-ShipKit-Entitlement": {
                "description": "Which rule allowed the download (never the key itself)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid BuilderConfig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "No entitlement",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequired"
                }
              }
            }
          },
          "500": {
            "description": "Packaging failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/generate/preview": {
      "post": {
        "tags": [
          "Generator"
        ],
        "summary": "Download a source-free preview of the configured build",
        "description": "Anonymous visitors may download a preview package that contains the FILE INVENTORY of the configured build plus the design tokens — never application source. The quota is counted per signed visitor token and per trusted network.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BuilderConfig"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preview package archive",
            "headers": {
              "Content-Disposition": {
                "description": "attachment; filename=\"<slug>-shipkit-preview.zip\"",
                "schema": {
                  "type": "string"
                }
              },
              "X-ShipKit-Entitlement": {
                "description": "Which rule allowed the preview (never the token itself)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid BuilderConfig",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Preview limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequired"
                }
              }
            }
          },
          "500": {
            "description": "Preview packaging failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/license": {
      "post": {
        "tags": [
          "License"
        ],
        "summary": "Redeem a license key into an httpOnly cookie",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "licenseKey"
                ],
                "properties": {
                  "licenseKey": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key redeemed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LicenseRedeemResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid license key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "License"
        ],
        "summary": "Current gate + redeemed key status",
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LicenseStatus"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "License"
        ],
        "summary": "Clear the sk_license cookie",
        "responses": {
          "200": {
            "description": "Cleared",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/checkout": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Create a Waffo Pancake checkout session",
        "description": "Requires a Supabase-authenticated user. Buyout is a one-time product, Care is a monthly subscription.",
        "security": [
          {
            "supabaseSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "buyout",
                      "care"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Gateway error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Gateway returned no checkout URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/webhook": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Single webhook endpoint for every payment provider",
        "description": "Pancake: X-Waffo-Signature: t=<ms>,v1=<base64>; RSA-SHA256 over the raw body prefixed by the timestamp and a dot, verified with the platform Webhook Public Key (test/prod). Events are deduplicated on eventType+eventId; successful paid events issue an offline license key. Verification failures return 400; persistence failures return 500 so the gateway retries.",
        "security": [
          {
            "waffoSignature": []
          }
        ],
        "parameters": [
          {
            "name": "X-Waffo-Signature",
            "in": "header",
            "required": false,
            "description": "Pancake signature header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Waffo-Event",
            "in": "header",
            "required": false,
            "description": "Pancake event type (also present in the payload)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified and processed (Pancake: {received:true}; Acquiring: {message:success})",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Signature verification failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Persistence failed — retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/payments/self-check": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Read-only integration readiness (never returns secrets)",
        "responses": {
          "200": {
            "description": "Configuration state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SelfCheck"
                }
              }
            }
          }
        }
      }
    },
    "/api/mail/self-check": {
      "get": {
        "tags": [
          "Mail"
        ],
        "summary": "Mail provider configuration state (no secret values)",
        "description": "Reports MAIL_PROVIDER, implemented/ready/degraded flags, the NAMES of missing env vars and booleans for MAIL_FROM / MAIL_REPLY_TO / operator inbox. Never returns an API key, webhook URL or token. smtp is reported as NOT implemented; none is reported as degraded.",
        "responses": {
          "200": {
            "description": "Mail configuration state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MailSelfCheck"
                }
              }
            }
          }
        }
      }
    },
    "/api/support/tickets": {
      "post": {
        "tags": [
          "Support"
        ],
        "summary": "Create a support ticket and send two notification emails",
        "description": "Public. Zod-validated (email, subject 3-200, message 10-5000), honeypot field `company`, same-origin/CSRF protection at the Edge and a persistent dual-backend rate limit (5 / minute, SQLite transaction or Supabase RPC). A signed-in Supabase user is associated when present. The row is written FIRST, then two best-effort five-language emails (requester acknowledgement + operator notice) are sent — a mail outage never loses the ticket. Response includes mailDelivered flags.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "subject",
                  "message"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "subject": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "message": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 5000
                  },
                  "company": {
                    "type": "string",
                    "description": "Honeypot — leave empty"
                  },
                  "lang": {
                    "type": "string",
                    "enum": [
                      "en",
                      "zh",
                      "ja",
                      "fr",
                      "de"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ticket created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketCreatedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Storage error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Support"
        ],
        "summary": "List support tickets",
        "description": "Operator (local operator session OR Supabase admin/staff role) sees ALL tickets; a signed-in user sees only rows whose user_id matches; anonymous requests get 401.",
        "security": [
          {
            "supabaseSession": []
          },
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Ticket list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tickets": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Ticket"
                      }
                    },
                    "operator": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Storage error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/support/tickets/{id}": {
      "patch": {
        "tags": [
          "Support"
        ],
        "summary": "Change a ticket status (operator only)",
        "description": "Only open / answered / closed. The operator guard is the same one used by the /admin console; a non-operator receives 403 regardless of whether the id exists.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "operatorSession": []
          },
          {
            "supabaseSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "answered",
                      "closed"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated ticket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "ticket": {
                      "$ref": "#/components/schemas/Ticket"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid status or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Operator only",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ticket not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Storage error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/password-reset": {
      "post": {
        "tags": [
          "Auth recovery"
        ],
        "summary": "Request or confirm a password reset",
        "description": "action=request ALWAYS returns the same {ok:true} body (anti-enumeration) and never reveals whether the address exists. action=confirm consumes a one-time, expiring token (Supabase: updateUser on the recovery session). Supabase path uses resetPasswordForEmail / updateUser; local SQLite path stores tokens only as SHA-256 hashes and scrypt password hashes. Rate-limited per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "action",
                      "email"
                    ],
                    "properties": {
                      "action": {
                        "type": "string",
                        "const": "request"
                      },
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "lang": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "action",
                      "password"
                    ],
                    "properties": {
                      "action": {
                        "type": "string",
                        "const": "confirm"
                      },
                      "token": {
                        "type": "string"
                      },
                      "password": {
                        "type": "string",
                        "minLength": 8,
                        "maxLength": 200
                      },
                      "lang": {
                        "type": "string"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generic success / password updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or invalid/expired token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/verify-email": {
      "post": {
        "tags": [
          "Auth recovery"
        ],
        "summary": "Send or confirm an email verification",
        "description": "action=send ALWAYS returns the same generic success (anti-enumeration). action=confirm consumes a one-time token (local) or calls Supabase verifyOtp with token_hash (Supabase resend / verifyOtp). Rate-limited per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "send",
                      "confirm"
                    ]
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "token": {
                    "type": "string"
                  },
                  "tokenHash": {
                    "type": "string"
                  },
                  "lang": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generic success / verified",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or invalid/expired token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Auth recovery"
        ],
        "summary": "Confirm a verification token from an email link, then redirect",
        "description": "Accepts ?token= (local raw token) or ?token_hash= (Supabase). Consumes the one-time token and redirects to /verify-email?status=ok|invalid — the token never appears in the final URL.",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "token_hash",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to /verify-email with a status flag"
          }
        }
      }
    },
    "/api/rag/ingest": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "Chunk, embed and index a document",
        "description": "Requires a signed-in user or an operator. Anonymous visitors get 401 — the index is shared state and must not be writable by anyone who lands on the page.",
        "security": [
          {
            "supabaseSession": []
          },
          {
            "operatorSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "text"
                ],
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 200000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Indexed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RagIngestResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing title or text",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "RAG"
        ],
        "summary": "List indexed documents",
        "description": "Requires a signed-in user or an operator. Anonymous visitors get 401.",
        "security": [
          {
            "supabaseSession": []
          },
          {
            "operatorSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/rag/query": {
      "post": {
        "tags": [
          "RAG"
        ],
        "summary": "Hybrid retrieval (BM25 + embeddings, RRF) with cited answer",
        "description": "Requires a signed-in user WITH an active subscription (401 without a session, 402 without a subscription) — every call runs a real LLM. The anonymous trial quota does not apply.",
        "security": [
          {
            "supabaseSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "question"
                ],
                "properties": {
                  "question": {
                    "type": "string",
                    "maxLength": 1000
                  },
                  "provider": {
                    "type": "string",
                    "maxLength": 60
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Answer with citations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RagQueryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing question",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Retrieval failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ai": {
      "post": {
        "tags": [
          "AI"
        ],
        "summary": "Multi-provider streaming chat (plan-gated outside demo mode)",
        "description": "Real token-by-token streaming for OpenAI-compatible, Anthropic and Gemini providers. A demo session is unlimited; signed-in subscribers are charged one message per day from AI_MESSAGES_PER_DAY (default 50, UTC-day bucket). Provider failures are logged server-side and surfaced as a generic [AI provider error] text, never as a fake reply.",
        "security": [
          {
            "supabaseSession": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "properties": {
                  "message": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "provider": {
                    "type": "string",
                    "maxLength": 40
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Incremental plain-text stream (text/plain; charset=utf-8), one chunk per provider token. Carries X-AI-Quota-Limit / X-AI-Quota-Used / X-AI-Quota-Remaining for billed users."
          },
          "400": {
            "description": "Invalid message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Daily AI quota exhausted (ai_quota_exceeded); resetAt is the NEXT 00:00 UTC and Retry-After is the real seconds left",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/track": {
      "post": {
        "tags": [
          "Analytics"
        ],
        "summary": "Aggregate pageview (only after cookie consent)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "path": {
                    "type": "string",
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/stats": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "First-party aggregate stats",
        "description": "Requires an operator session (sign in at /admin) when configured.",
        "security": [
          {
            "adminBasic": []
          },
          {}
        ],
        "responses": {
          "200": {
            "description": "Aggregates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/blog/{slug}/md": {
      "get": {
        "tags": [
          "Content / GEO"
        ],
        "summary": "Plain-text Markdown variant of a blog article",
        "description": "A GEO surface for AI crawlers and answer engines. Returns the title, company byline (make1day), published/updated dates, canonical path, category, tags, full body and the article's structured FAQ as text/markdown. ?lang=en|zh|ja|fr|de selects a translation with English fallback. Declared on the HTML article as an alternate of type text/markdown and listed in /sitemap.xml.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Canonical article slug or a legacy alias"
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "zh",
                "ja",
                "fr",
                "de"
              ],
              "default": "en"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown document",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Unknown slug"
          }
        }
      }
    },
    "/api/markdown": {
      "get": {
        "tags": [
          "Content / GEO"
        ],
        "summary": "Internal target of the ?format=md page variant",
        "description": "Append ?format=md to any public page. middleware.ts rewrites it here with ?path=<page>&lang=<lang>. /faq returns the full localised Q&A, /blog returns the article index, /blog/<slug> returns the complete article, and other allowlisted pages return a factual Markdown digest. Content-Language and a canonical Link header describe the real body language. Direct calls are allowed; the same-origin rule applies.",
        "parameters": [
          {
            "name": "path",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Original page pathname"
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "zh",
                "ja",
                "fr",
                "de"
              ],
              "default": "en"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown document",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Page has no Markdown variant"
          }
        }
      }
    },
    "/.well-known/agent-card.json": {
      "get": {
        "tags": [
          "Content / GEO"
        ],
        "summary": "Agent capability discovery descriptor",
        "description": "Declares the machine-readable endpoints, security scheme and explicit limitations (no A2A task lifecycle, no streaming, no push notifications).",
        "responses": {
          "200": {
            "description": "Agent card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/license/claim": {
      "post": {
        "tags": [
          "License"
        ],
        "summary": "Re-send a license key to the checkout email",
        "description": "Always returns { ok: true } to prevent account enumeration. Two persistent rate-limit buckets (IP + hashed address). Requires a configured mail provider; failures are queued in mail_outbox.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "lang": {
                    "type": "string",
                    "enum": [
                      "en",
                      "zh",
                      "ja",
                      "fr",
                      "de"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generic success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/privacy/export": {
      "post": {
        "tags": [
          "Privacy"
        ],
        "summary": "Export the signed-in user's data as JSON",
        "description": "Requires a session. Returns profile, licences, orders, tickets, consent/delivery events, queued mail and attributed traffic events.",
        "responses": {
          "200": {
            "description": "my-data.json",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/privacy/erase": {
      "post": {
        "tags": [
          "Privacy"
        ],
        "summary": "Erase the signed-in user's personal data",
        "description": "Requires a session and an exact confirmEmail match. Order/licence rows are kept for accounting with an irreversible email tombstone.",
        "responses": {
          "200": {
            "description": "Erasure result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/privacy/consent": {
      "post": {
        "tags": [
          "Privacy"
        ],
        "summary": "Record an accept / withdraw / policy_view consent event (hashed subject)",
        "responses": {
          "200": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/cron/retention": {
      "post": {
        "tags": [
          "Privacy"
        ],
        "summary": "Run the 90-day data retention job",
        "description": "Protected by CRON_SECRET (disabled in production when unset). Deletes time-bounded records; never deletes pending mail, orders or licences.",
        "responses": {
          "200": {
            "description": "Per-table deletion counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "503": {
            "description": "CRON_SECRET not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/cron/mail-outbox": {
      "get": {
        "tags": [
          "Mail"
        ],
        "summary": "Outbox stats (pending / dead / delivered)",
        "responses": {
          "200": {
            "description": "Stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Mail"
        ],
        "summary": "Retry pending transactional mail",
        "description": "Protected by CRON_SECRET (disabled in production when unset).",
        "responses": {
          "200": {
            "description": "Flush result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "503": {
            "description": "CRON_SECRET not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/errors": {
      "post": {
        "tags": [
          "Observability"
        ],
        "summary": "Client error collector for the error boundaries",
        "description": "Rate limited. Writes error_events and forwards to ERROR_REPORT_URL when configured. Never accepts arbitrary PII.",
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "Observability"
        ],
        "summary": "Readiness probe (SQLite SELECT 1 + dependency summary)",
        "description": "Returns 503 when the database is unavailable. Lists missing operator variables without exposing secret values.",
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "503": {
            "description": "Degraded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/local": {
      "post": {
        "tags": [
          "Auth recovery"
        ],
        "summary": "Local password sign-in / sign-out (no Supabase configured)",
        "description": "Rate limited. Sessions are stored as SHA-256 token hashes in local_sessions and set in an httpOnly cookie.",
        "responses": {
          "200": {
            "description": "Sign-in result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Bad credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "tags": [
          "Content / GEO"
        ],
        "summary": "Short machine-readable product overview with the full blog + FAQ index",
        "responses": {
          "200": {
            "description": "llms.txt",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "tags": [
          "Content / GEO"
        ],
        "summary": "Long-form machine-readable reference (config enum, pruning, API, limitations)",
        "responses": {
          "200": {
            "description": "llms-full.txt",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "summary": "MCP JSON-RPC 2.0 endpoint (tools + resources)",
        "description": "Stateless Model Context Protocol over HTTP. Tools: get_product_info, get_builder_config, scaffold_project, get_agent_delivery. Resources: llms.txt, llms-full.txt, openapi.json via resources/list + resources/read. No sampling, prompts, logging, completions, subscriptions or SSE. scaffold_project returns a validated BuilderConfig plus the exact POST body for /api/generate; includeZip=true returns the ZIP as base64 and is subject to the SAME license gate. Notifications (requests without id) receive HTTP 204. Reference: docs/mcp.md.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method"
                ],
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "enum": [
                      "2.0"
                    ]
                  },
                  "id": {
                    "type": [
                      "string",
                      "number",
                      "null"
                    ]
                  },
                  "method": {
                    "type": "string",
                    "enum": [
                      "initialize",
                      "ping",
                      "tools/list",
                      "tools/call",
                      "resources/list",
                      "resources/read"
                    ]
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC 2.0 result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jsonrpc": {
                      "type": "string",
                      "enum": [
                        "2.0"
                      ]
                    },
                    "id": {
                      "type": [
                        "string",
                        "number",
                        "null"
                      ]
                    },
                    "result": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "integer"
                        },
                        "message": {
                          "type": "string"
                        },
                        "data": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "Notification accepted (no response body)"
          },
          "400": {
            "description": "Invalid JSON-RPC request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal tool error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "MCP"
        ],
        "summary": "Service and tool discovery (human/agent friendly)",
        "responses": {
          "200": {
            "description": "Server info + tool list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "protocolVersion": {
                      "type": "string"
                    },
                    "tools": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "licenseCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sk_license",
        "description": "httpOnly cookie set by POST /api/license"
      },
      "supabaseSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sb-access-token",
        "description": "Supabase auth cookie; the exact name is project-specific"
      },
      "waffoSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Waffo-Signature"
      },
      "operatorSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sk_admin_session",
        "description": "Operator (console) session cookie; alternative to a Supabase session"
      },
      "adminBasic": {
        "type": "http",
        "scheme": "basic"
      }
    },
    "schemas": {
      "BuilderConfig": {
        "type": "object",
        "required": [
          "appName",
          "auth",
          "payment",
          "ai",
          "features",
          "deploy",
          "style",
          "skills"
        ],
        "properties": {
          "appName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          },
          "auth": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "string",
              "enum": [
                "email",
                "google",
                "github",
                "apple",
                "microsoft"
              ]
            }
          },
          "payment": {
            "type": "string",
            "enum": [
              "none",
              "stripe",
              "creem",
              "paypal",
              "waffo",
              "lemonsqueezy"
            ]
          },
          "ai": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "string",
              "enum": [
                "openai",
                "anthropic",
                "gemini"
              ]
            }
          },
          "features": {
            "type": "array",
            "maxItems": 6,
            "description": "account = user console (/account), admin = operator console (/admin). Both are pruned from the ZIP when unchecked.",
            "items": {
              "type": "string",
              "enum": [
                "landing",
                "dashboard",
                "rag",
                "multitenancy",
                "account",
                "admin"
              ]
            }
          },
          "deploy": {
            "type": "string",
            "enum": [
              "vercel",
              "selfhost",
              "cloudflare"
            ]
          },
          "style": {
            "type": "string",
            "enum": [
              "doodle",
              "midnight",
              "paper",
              "aurora",
              "emerald",
              "sunset",
              "ocean",
              "mono",
              "slate",
              "candy",
              "sand"
            ]
          },
          "skills": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "string",
              "enum": [
                "frontend",
                "ui",
                "backend",
                "testing",
                "seo",
                "auth",
                "payments",
                "database",
                "rag",
                "i18n",
                "deploy",
                "agents"
              ]
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "PaymentRequired": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "license_required"
          },
          "code": {
            "type": "string",
            "const": "PAYMENT_REQUIRED"
          },
          "pricingUrl": {
            "type": "string",
            "example": "/pricing"
          },
          "licenseUrl": {
            "type": "string",
            "example": "/license"
          }
        }
      },
      "LicenseStatus": {
        "type": "object",
        "properties": {
          "gateEnabled": {
            "type": "boolean"
          },
          "redeemed": {
            "type": "boolean"
          },
          "plan": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "LicenseRedeemResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "plan": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CheckoutResponse": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SelfCheck": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string"
          },
          "waffo": {
            "type": "object",
            "additionalProperties": true
          },
          "license": {
            "type": "object",
            "additionalProperties": true
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ready": {
            "type": "boolean"
          }
        }
      },
      "MailSelfCheck": {
        "type": "object",
        "description": "Configuration state only — no secret values.",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "resend",
              "webhook",
              "smtp",
              "none"
            ]
          },
          "implemented": {
            "type": "boolean",
            "description": "false for smtp (no nodemailer dependency)"
          },
          "ready": {
            "type": "boolean"
          },
          "degraded": {
            "type": "boolean"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "requiredEnvVars": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "missingEnvVars": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "flags": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Ticket": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "answered",
              "closed"
            ]
          },
          "userId": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TicketCreatedResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "ticketId": {
            "type": [
              "string",
              "null"
            ]
          },
          "mailDelivered": {
            "type": "object",
            "properties": {
              "user": {
                "type": "boolean"
              },
              "operator": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "RagIngestResponse": {
        "type": "object",
        "properties": {
          "document": {
            "type": "object",
            "additionalProperties": true
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "RagQueryResponse": {
        "type": "object",
        "properties": {
          "answer": {
            "type": "string",
            "description": "May contain inline [n] citations"
          },
          "citations": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "mode": {
            "type": "string"
          },
          "retrieval": {
            "type": "object",
            "description": "Which branches ran: lexical / semantic / pgvector / store / strategy",
            "additionalProperties": true
          }
        }
      }
    }
  }
}