openapi: 3.1.0
info:
  title: LongevityLens API
  version: "1.0.0"
  description: |
    Public read-only endpoints and a single demo-booking write endpoint for
    LongevityLens, a longevity clinic intelligence platform built for Southeast Asia.
  contact:
    name: LongevityLens sales
    email: demo@longevitylens.com
    url: https://longevitylens.ai/#book-demo
  license:
    name: Proprietary
servers:
  - url: https://api.longevitylens.ai/v1
    description: Production

paths:
  /pricing:
    get:
      operationId: get_pricing
      summary: Current pricing
      description: Returns the active price list. Cacheable for up to 24 hours.
      responses:
        "200":
          description: Pricing object
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Pricing" }
              example:
                plan_id: ll_core
                currency: SGD
                price_per_seat: 250
                billing_period: month
                setup_fee: 3000
                eligible_regions: [SG, MY, ID, TH, PH, VN, AU]
                features:
                  - Innoquest 1:1 matching
                  - Native Plato integration
                  - PDPA / GDPR compliance
                  - Branded patient portal
                  - Local SG support

  /integrations:
    get:
      operationId: get_integrations
      summary: Supported lab and CMS integrations
      responses:
        "200":
          description: Integrations object
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Integrations" }
              example:
                labs:
                  - { name: Innoquest Diagnostics, type: native_1to1, regions: [SG] }
                cms:
                  - { name: Plato Medical, type: native, regions: [SG, AU, APAC] }
                  - { name: SGiMED, type: supported, regions: [SG] }

  /compliance:
    get:
      operationId: get_compliance
      summary: Compliance frameworks covered
      responses:
        "200":
          description: Compliance object
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Compliance" }
              example:
                frameworks:
                  - { code: PDPA_SG,        name: Personal Data Protection Act (Singapore), status: covered }
                  - { code: GDPR,           name: General Data Protection Regulation,        status: covered }
                  - { code: MOH_AG_2023_09, name: MOH Advisory Guidelines (revised Sep 2023), status: covered }
                  - { code: HCSA,           name: Healthcare Services Act,                    status: covered }
                  - { code: CSA_CTM,        name: CSA Cyber Trust Mark,                       status: ready }

  /faq:
    get:
      operationId: get_faq
      summary: Frequently asked questions
      responses:
        "200":
          description: FAQ object
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FAQ" }

  /demo:
    post:
      operationId: book_demo
      summary: Book a 30-minute demo with the team
      description: |
        Creates a sales-pipeline record and sends an email to the LongevityLens team.
        Pass an `Idempotency-Key` header (UUID v4) to make the request safe to retry.
      parameters:
        - in: header
          name: Idempotency-Key
          schema: { type: string, format: uuid }
          required: false
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DemoRequest" }
            example:
              clinic_name: "Atelier Longevity"
              contact_name: "Dr Tan Wei Ming"
              email: "drtan@atelierlongevity.sg"
              phone: "+65 9123 4567"
              country: SG
              practitioners: 3
              current_lab: innoquest
              current_cms: plato
              notes: "Interested in May 2026 cohort."
      responses:
        "201":
          description: Demo request accepted
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DemoResponse" }
              example:
                booking_id: "bk_01HXYZ2K8N4F3J9R6P0V"
                status: received
                next_steps: "We'll be in touch within 1 business day to schedule a 30-minute call."
        "400":
          description: Validation error
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "429":
          description: Rate limited
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "503":
          description: Temporarily unavailable. Retry with the same `Idempotency-Key`.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }

  /blog/articles:
    post:
      operationId: publish_blog
      summary: Publish or update a blog article
      description: |
        Validates markdown frontmatter, commits to `content/blog/{slug}.md` via GitHub,
        and invalidates the blog cache. Posts go live within seconds — no full site rebuild.
        Requires `Authorization: Bearer <BLOG_PUBLISH_TOKEN>`.
      security:
        - BlogPublishToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/BlogPublishRequest" }
            example:
              markdown: |
                ---
                title: "Example Article"
                slug: example-article
                description: "Short description for SEO and index listing."
                target_keywords: ["longevity clinic Singapore"]
                pillar: "Compliance Is Local"
                status: published
                date: 2026-06-09
                author: LongevityLens Team
                estimated_read_time: 5 min
                ---

                ## First Section

                Article body in markdown.
      responses:
        "201":
          description: Article published
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BlogPublishResponse" }
        "400":
          description: Validation error
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "500":
          description: Publish failed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }

  /blog/revalidate:
    post:
      operationId: revalidate_blog
      summary: Invalidate blog page cache
      description: |
        Revalidates `/blog` and all known article paths. Useful after manual git commits.
        Requires `Authorization: Bearer <BLOG_PUBLISH_TOKEN>`.
      security:
        - BlogPublishToken: []
      responses:
        "200":
          description: Cache invalidated
          content:
            application/json:
              schema:
                type: object
                required: [ok, revalidated, paths]
                properties:
                  ok: { type: boolean }
                  revalidated: { type: boolean }
                  paths: { type: array, items: { type: string } }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }

components:
  securitySchemes:
    BlogPublishToken:
      type: http
      scheme: bearer
      description: Shared secret configured as BLOG_PUBLISH_TOKEN on the server.

  schemas:
    Pricing:
      type: object
      required: [plan_id, currency, price_per_seat, billing_period, setup_fee, eligible_regions, features]
      properties:
        plan_id:          { type: string, example: ll_core }
        currency:         { type: string, example: SGD }
        price_per_seat:   { type: integer, example: 250 }
        billing_period:   { type: string, enum: [month, year], example: month }
        setup_fee:        { type: integer, example: 3000 }
        eligible_regions: { type: array, items: { type: string } }
        features:         { type: array, items: { type: string } }

    Integrations:
      type: object
      properties:
        labs:
          type: array
          items:
            type: object
            properties:
              name:    { type: string }
              type:    { type: string, enum: [native_1to1, native, supported, planned] }
              regions: { type: array, items: { type: string } }
        cms:
          type: array
          items:
            type: object
            properties:
              name:    { type: string }
              type:    { type: string, enum: [native, supported, planned] }
              regions: { type: array, items: { type: string } }

    Compliance:
      type: object
      properties:
        frameworks:
          type: array
          items:
            type: object
            required: [code, name, status]
            properties:
              code:   { type: string }
              name:   { type: string }
              status: { type: string, enum: [covered, ready, planned] }

    FAQ:
      type: object
      properties:
        items:
          type: array
          items:
            type: object
            properties:
              q: { type: string }
              a: { type: string }

    DemoRequest:
      type: object
      required: [clinic_name, contact_name, email, country, practitioners]
      additionalProperties: false
      properties:
        clinic_name:   { type: string, minLength: 1, maxLength: 200 }
        contact_name:  { type: string, minLength: 1, maxLength: 200 }
        email:         { type: string, format: email }
        phone:         { type: string, maxLength: 40 }
        country:       { type: string, enum: [SG, MY, ID, TH, PH, VN, AU, OTHER] }
        practitioners: { type: integer, minimum: 1, maximum: 50 }
        current_lab:   { type: string, enum: [innoquest, other, none] }
        current_cms:   { type: string, enum: [plato, sgimed, other, none] }
        notes:         { type: string, maxLength: 2000 }

    DemoResponse:
      type: object
      required: [booking_id, status]
      properties:
        booking_id: { type: string, example: "bk_01HXYZ2K8N4F3J9R6P0V" }
        status:     { type: string, enum: [received, scheduled] }
        next_steps: { type: string }

    Error:
      type: object
      required: [error]
      properties:
        error:     { type: string }
        message:   { type: string }
        field:     { type: string, description: "Present on validation errors." }

    BlogPublishRequest:
      type: object
      description: |
        Provide a full markdown string with YAML frontmatter, or separate frontmatter + body.
        Frontmatter schema: https://longevitylens.ai/api/v1/blog-schema.json
      oneOf:
        - required: [markdown]
        - required: [frontmatter, body]
      properties:
        markdown:
          type: string
          description: Full markdown file including YAML frontmatter delimiters.
        frontmatter:
          type: object
          description: Parsed frontmatter fields (see blog-schema.json).
        body:
          type: string
          description: Markdown body without frontmatter.

    BlogPublishResponse:
      type: object
      required: [ok, slug, url, revalidated]
      properties:
        ok:           { type: boolean }
        slug:         { type: string }
        url:          { type: string, format: uri }
        commit_sha:   { type: string }
        revalidated:  { type: boolean }
