openapi: 3.1.0
info:
  title: XESO API
  summary: Personal knowledge API for XESO
  description: |
    The XESO API exposes reading and writing your own Brain, running
    searches, generating citations, managing billing, and handling webhooks.

    **Versioning.** See `docs/dev/API_VERSIONING.md`. The `/v1/` prefix is
    stable within the semantics defined in that document; breaking changes
    land at `/v2/` with ≥ 90 days overlap.

    **Auth.** Bearer tokens (Personal Access Tokens, PATs) via Clerk. See
    `docs/dev/PATS.md` (Wave 2.5 §DX-6).

    **Rate limits.** Per-tier; documented in `MONETIZATION.md`. HTTP 429
    with `Retry-After` header.

    **This is a skeleton.** Full endpoint coverage ships with Wave 2.5
    §DX-1 via a committed copy of the repo's route handlers.
  version: "0.1.0-skeleton"
  contact:
    name: XESO Developer Platform
    email: developers@xeso.ai
    url: https://xeso.ai/docs
  license:
    name: Proprietary
    url: https://xeso.ai/legal/terms
  termsOfService: https://xeso.ai/legal/terms

servers:
  - url: https://xeso.ai/api/v1
    description: Production
  - url: https://staging.xeso.ai/api/v1
    description: Staging

security:
  - bearerAuth: []

tags:
  - name: notes
    description: Create, read, update, delete notes.
  - name: search
    description: Full-text + vector search over your Brain.
  - name: chat
    description: Citation-first chat over your Brain.
  - name: segments
    description: Knowledge Gardens (segment / collection) management.
  - name: models
    description: Enumerate available models (OpenAI-compatible).
  - name: billing
    description: Subscription and usage metering.
  - name: integrations
    description: Connect external sources (RSS, MCP, webhooks).
  - name: health
    description: Service health endpoints.
  - name: webhooks
    description: Inbound and outbound webhook management.

paths:
  /health:
    get:
      tags: [health]
      summary: Liveness + dependency health
      security: []
      responses:
        "200":
          description: Service health summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /health/ai:
    get:
      tags: [health]
      summary: LLM provider health
      security: []
      responses:
        "200":
          description: AI provider health summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AIHealthResponse"

  /notes:
    get:
      tags: [notes]
      summary: List notes
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: segmentId
          in: query
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: A page of notes
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NoteList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags: [notes]
      summary: Create a note
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NoteCreate"
      responses:
        "201":
          description: Note created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Note"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /notes/{id}:
    parameters:
      - $ref: "#/components/parameters/NoteId"
    get:
      tags: [notes]
      summary: Get a note
      responses:
        "200":
          description: A note
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Note"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags: [notes]
      summary: Update a note
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NoteUpdate"
      responses:
        "200":
          description: Note updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Note"
    delete:
      tags: [notes]
      summary: Delete a note (soft-delete; hard-delete after retention window)
      responses:
        "204":
          description: Note deleted

  /search:
    get:
      tags: [search]
      summary: Search across your Brain
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 1024
        - name: mode
          in: query
          schema:
            type: string
            enum: [hybrid, fts, vector]
            default: hybrid
        - name: segmentId
          in: query
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: Search results with citations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchResponse"

  /chat:
    post:
      tags: [chat]
      summary: Citation-first chat
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatRequest"
      responses:
        "200":
          description: Streamed or aggregated chat response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatResponse"
            text/event-stream:
              schema:
                type: string
                description: Server-sent-events stream; each event is a partial chat message

  /segments:
    get:
      tags: [segments]
      summary: List segments (Knowledge Gardens)
      responses:
        "200":
          description: Segment list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SegmentList"

  /models:
    get:
      tags: [models]
      summary: List available models (OpenAI-compatible)
      description: |
        Returns the models `POST /api/v1/chat/completions` accepts, in
        OpenAI's `{ object: "list", data: [...] }` shape so drop-in OpenAI
        clients work unmodified. Today the catalog holds one entry,
        `xeso-1` — XESO's grounded completion surface. `xeso-1` is
        currently *served by* a managed foundation model (disclosed in the
        `served_by` field); the eval-gated sovereign model is deferred.
        A `read`-scoped PAT is sufficient.
      responses:
        "200":
          description: OpenAI-compatible model list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /webhooks:
    get:
      tags: [webhooks]
      summary: List configured outbound webhooks
      responses:
        "200":
          description: Webhook list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookList"
    post:
      tags: [webhooks]
      summary: Register an outbound webhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookCreate"
      responses:
        "201":
          description: Webhook created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: "xeso_pat"
      description: |
        Personal Access Token. Scoped per user. Obtain via
        `/settings/tokens`. Never commit tokens; rotate quarterly.

  parameters:
    Cursor:
      name: cursor
      in: query
      schema:
        type: string
      description: Opaque pagination cursor from a previous response.
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    NoteId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid

  responses:
    BadRequest:
      description: Validation failed
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    Unauthorized:
      description: Missing or invalid auth
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    NotFound:
      description: Resource not found
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"

  schemas:
    Problem:
      type: object
      description: RFC 9457 problem details
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
      required: [title, status]

    Note:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        content:
          type: string
        segmentId:
          type: string
          format: uuid
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required: [id, title, content, createdAt, updatedAt]

    NoteCreate:
      type: object
      properties:
        title:
          type: string
          maxLength: 500
        content:
          type: string
          maxLength: 200000
        segmentId:
          type: string
          format: uuid
          nullable: true
      required: [title, content]

    NoteUpdate:
      type: object
      properties:
        title:
          type: string
          maxLength: 500
        content:
          type: string
          maxLength: 200000
        segmentId:
          type: string
          format: uuid
          nullable: true

    NoteList:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Note"
        nextCursor:
          type: string
          nullable: true

    SearchResponse:
      type: object
      properties:
        query:
          type: string
        results:
          type: array
          items:
            type: object
            properties:
              noteId:
                type: string
                format: uuid
              title:
                type: string
              excerpt:
                type: string
              score:
                type: number
                format: float
              citation:
                type: object
                properties:
                  noteId:
                    type: string
                    format: uuid
                  offset:
                    type: integer

    ChatRequest:
      type: object
      properties:
        threadId:
          type: string
          format: uuid
          nullable: true
        message:
          type: string
          minLength: 1
          maxLength: 10000
        segmentId:
          type: string
          format: uuid
          nullable: true
        stream:
          type: boolean
          default: true
      required: [message]

    ChatResponse:
      type: object
      properties:
        threadId:
          type: string
          format: uuid
        messageId:
          type: string
          format: uuid
        content:
          type: string
        citations:
          type: array
          items:
            type: object
            properties:
              noteId:
                type: string
                format: uuid
              offset:
                type: integer
              quote:
                type: string

    SegmentList:
      type: object
      properties:
        items:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              noteCount:
                type: integer
              createdAt:
                type: string
                format: date-time

    Model:
      type: object
      description: |
        A model entry. OpenAI-compatible core fields (`id`, `object`,
        `created`, `owned_by`) plus XESO extensions. `served_by` is the
        managed model currently answering — a public transparency label,
        not a secret; OpenAI clients ignore the extra fields.
      properties:
        id:
          type: string
          description: Public model id — pass as `model` to /chat/completions.
          example: xeso-1
        object:
          type: string
          enum: [model]
        created:
          type: integer
          description: Unix seconds; stable creation marker.
        owned_by:
          type: string
          example: xeso
        status:
          type: string
          enum: [served-by, native]
          description: Delivery posture. `served-by` = grounded surface fronting a managed model.
        served_by:
          type: string
          description: The managed model currently answering. Public label, never a secret.
        capabilities:
          type: array
          items:
            type: string
          example: [chat, grounded-retrieval, citations, streaming, abstention]
        segments:
          type: array
          items:
            type: string
          example: [xeso-1-research, xeso-1-legal, xeso-1-finance]
      required: [id, object, created, owned_by]

    ModelList:
      type: object
      description: OpenAI-compatible list envelope.
      properties:
        object:
          type: string
          enum: [list]
        data:
          type: array
          items:
            $ref: "#/components/schemas/Model"
      required: [object, data]

    Webhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum:
              - note.created
              - note.updated
              - note.deleted
              - segment.created
              - chat.completed
              - billing.invoice.paid
        secret:
          type: string
          description: HMAC-SHA256 secret shared with your endpoint (returned only on create)
        createdAt:
          type: string
          format: date-time

    WebhookCreate:
      type: object
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
      required: [url, events]

    WebhookList:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Webhook"

    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok, degraded, down]
        db:
          type: string
          enum: [ok, degraded, down]
        auth:
          type: string
          enum: [ok, degraded, down]
        llm:
          type: string
          enum: [ok, degraded, down]

    AIHealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok, degraded, down]
        provider:
          type: string
        model:
          type: string
        latencyMs:
          type: integer
