# Premium org API — OpenAPI 3.1
# Served at GET /openapi/v1.yaml · Interactive docs at GET /docs/api
# Human overview: docs/PREMIUM_API.md

openapi: 3.1.0
info:
  title: Rulebooks Premium Org API
  version: "1.0.0"
  summary: Read-only rulebook access + rules inquiry for Premium orgs
  description: |
    Machine-readable access for **Premium** organizations. Authenticate with one org API key
    (`rbk_…`) on every request.

    Keys are created, rotated, and revoked by an org owner on **Integrations**.
    The key only sees rulebooks owned by that org (cross-org IDs return `404`).

    **Key scope** (chosen on create/rotate):

    - `published` (recommended for bots) — tagged/released revisions only; draft Markdown soft-404s
    - `full` — includes untagged draft tips via `/revisions/{id}` and `/diff` (treat like a password)

    There is no `/me` endpoint — call `GET /api/v1/books` to verify a key.

    **Rate limits** (per org, in-memory per instance): reads 60/min; Ask 10/min.

    Human overview: [PREMIUM_API.md](https://github.com/netizen01/rulebooks/blob/main/docs/PREMIUM_API.md)
  contact:
    name: Rulebooks
    url: https://rulebooks.gg
  license:
    name: Proprietary

servers:
  - url: /
    description: Current host (use when browsing docs on this deployment)
  - url: https://rulebooks.gg
    description: Production

tags:
  - name: Books
    description: Org-scoped rulebook metadata
  - name: Revisions
    description: Version history (list is metadata-only; bodies via get/current)
  - name: Search
    description: Section search over the published index
  - name: Ask
    description: Natural-language rules inquiry (LLM; same path as Discord /rules)

security:
  - bearerAuth: []
  - apiKeyAuth: []

paths:
  /api/v1/books:
    get:
      tags: [Books]
      operationId: listBooks
      summary: List org rulebooks
      description: Metadata only for every rulebook in the keyed org.
      responses:
        "200":
          description: Book list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BookListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}:
    get:
      tags: [Books]
      operationId: getBook
      summary: Get rulebook metadata
      parameters:
        - $ref: "#/components/parameters/BookID"
      responses:
        "200":
          description: One book
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BookResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}/current:
    get:
      tags: [Revisions]
      operationId: getCurrentRevision
      summary: Current published revision
      description: |
        The `is_current` revision including full Markdown.
        Prefer this over listing revisions when you only need the live tip.
      parameters:
        - $ref: "#/components/parameters/BookID"
      responses:
        "200":
          description: Current revision
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RevisionResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          description: Book missing or no current (published) revision
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}/published:
    get:
      tags: [Books]
      operationId: getPublishedDocument
      summary: Published document snapshot
      description: |
        Same document shape as public `/r/{uuid}.json` (Markdown, HTML, TOC, contributors),
        wrapped as `{ ok, published }`. Requires API key (works for org-scoped books).
      parameters:
        - $ref: "#/components/parameters/BookID"
      responses:
        "200":
          description: Published snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishedResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}/search:
    get:
      tags: [Search]
      operationId: searchSections
      summary: Search published sections
      parameters:
        - $ref: "#/components/parameters/BookID"
        - name: q
          in: query
          required: true
          description: Search query
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Search hits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}/diff:
    get:
      tags: [Revisions]
      operationId: diffRevisions
      summary: Unified Markdown diff
      description: |
        Diff between two revisions of this book. A `published`-scoped key soft-404s if either
        revision is an untagged draft.
      parameters:
        - $ref: "#/components/parameters/BookID"
        - name: from
          in: query
          required: true
          description: From revision UUID
          schema:
            type: string
            format: uuid
        - name: to
          in: query
          required: true
          description: To revision UUID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Diff + revision metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DiffResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}/revisions:
    get:
      tags: [Revisions]
      operationId: listRevisions
      summary: List revisions (metadata only)
      description: |
        Newest first. **Does not include Markdown bodies** — use get revision or `/current`.

        With a **published**-scoped key, untagged draft tips are omitted from the list.
        Response includes `scope` (`full` | `published`).
      parameters:
        - $ref: "#/components/parameters/BookID"
      responses:
        "200":
          description: Revision list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RevisionListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}/revisions/{revID}:
    get:
      tags: [Revisions]
      operationId: getRevision
      summary: Get one revision (with Markdown)
      description: |
        Full Markdown for one revision. Prefer `/current` for the live tip.

        **Security:** a `full`-scoped key can read untagged drafts (high blast radius if leaked).
        A `published`-scoped key soft-404s drafts (`error: not_found`).
      parameters:
        - $ref: "#/components/parameters/BookID"
        - $ref: "#/components/parameters/RevisionID"
      responses:
        "200":
          description: Revision including Markdown
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RevisionResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/books/{bookID}/ask:
    post:
      tags: [Ask]
      operationId: askRules
      summary: Ask a rules question
      description: |
        Retrieval + cite (+ optional labeled guidance). Same pipeline as public Ask and Discord `/rules`.
        Requires LLM configured on the server. Rate limit 10/min per org.
      parameters:
        - $ref: "#/components/parameters/BookID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AskRequest"
            example:
              question: Can a player substitute after the whistle?
      responses:
        "200":
          description: Answer with citations (or empty citations + message)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AskResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/ProRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          description: Request body too large
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          description: LLM retrieval error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          description: LLM unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: rbk_
      description: Org API key as `Authorization: Bearer rbk_…`
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Org API key as `X-API-Key: rbk_…`

  parameters:
    BookID:
      name: bookID
      in: path
      required: true
      description: Rulebook UUID (must belong to the keyed org)
      schema:
        type: string
        format: uuid
    RevisionID:
      name: revID
      in: path
      required: true
      description: Revision UUID
      schema:
        type: string
        format: uuid

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: unauthorized
            message: Provide Authorization Bearer rbk_… or X-API-Key.
    ProRequired:
      description: Org is not on Premium
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: pro_required
            message: This API requires Premium.
    NotFound:
      description: Book/revision not found (or not in this org)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: not_found
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RateLimited:
      description: Too many requests for this org bucket
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
      required: [error]

    Book:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        activity:
          type: string
        theme:
          type: string
        shortcode:
          type: string
        slug:
          type: string
        published:
          type: boolean
        view_count:
          type: integer
          format: int64
        created_at:
          type: string
          format: date-time
        url:
          type: string
          format: uri
          description: Public reader URL (`{BASE}/r/{id}`)
      required: [id, title, theme, published, view_count, created_at, url]

    BookListResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        org_id:
          type: string
          format: uuid
        books:
          type: array
          items:
            $ref: "#/components/schemas/Book"
      required: [ok, org_id, books]

    BookResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        book:
          $ref: "#/components/schemas/Book"
      required: [ok, book]

    RevisionMeta:
      type: object
      description: Revision metadata without Markdown body
      properties:
        id:
          type: string
          format: uuid
        book_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        is_current:
          type: boolean
        tag:
          type: string
        changelog:
          type: string
        author:
          type: string
      required: [id, book_id, created_at, is_current]

    Revision:
      allOf:
        - $ref: "#/components/schemas/RevisionMeta"
        - type: object
          properties:
            markdown:
              type: string
          required: [markdown]

    RevisionListResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        book_id:
          type: string
          format: uuid
        scope:
          type: string
          enum: [full, published]
          description: Scope of the API key used for this request
        revisions:
          type: array
          items:
            $ref: "#/components/schemas/RevisionMeta"
      required: [ok, book_id, revisions, scope]

    RevisionResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        book_id:
          type: string
          format: uuid
          description: Present on `/current`; omitted on single-revision get
        revision:
          $ref: "#/components/schemas/Revision"
      required: [ok, revision]

    TOCItem:
      type: object
      properties:
        id:
          type: string
        text:
          type: string
        level:
          type: integer
      required: [id, text, level]

    Contributor:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
      required: [id, name]

    PublishedDocument:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        org:
          type: string
        activity:
          type: string
        theme:
          type: string
        tag:
          type: string
        revision_id:
          type: string
          format: uuid
        published_at:
          type: string
          format: date-time
        url:
          type: string
          format: uri
        markdown:
          type: string
        html:
          type: string
        toc:
          type: array
          items:
            $ref: "#/components/schemas/TOCItem"
        contributors:
          type: array
          items:
            $ref: "#/components/schemas/Contributor"
      required: [id, title, org, theme, revision_id, published_at, url, markdown, html, toc, contributors]

    PublishedResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        published:
          $ref: "#/components/schemas/PublishedDocument"
      required: [ok, published]

    SearchHit:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        snippet:
          type: string
        score:
          type: number
          format: double
      required: [id, title, snippet, score]

    SearchResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        book_id:
          type: string
          format: uuid
        q:
          type: string
        hits:
          type: array
          items:
            $ref: "#/components/schemas/SearchHit"
      required: [ok, book_id, q, hits]

    DiffResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        book_id:
          type: string
          format: uuid
        from:
          $ref: "#/components/schemas/RevisionMeta"
        to:
          $ref: "#/components/schemas/RevisionMeta"
        diff:
          type: string
          description: Unified diff of Markdown
      required: [ok, book_id, from, to, diff]

    AskRequest:
      type: object
      properties:
        question:
          type: string
          maxLength: 400
          description: Natural-language rules question
      required: [question]

    Citation:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        snippet:
          type: string
        url:
          type: string
          format: uri
      required: [id, title, snippet, url]

    AskResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: true
        guidance:
          type: string
        disclaimer:
          type: string
        citations:
          type: array
          items:
            $ref: "#/components/schemas/Citation"
        tag:
          type: string
          description: Current revision tag when known
        book_id:
          type: string
          format: uuid
        message:
          type: string
          description: Present when no clear governing section was found
      required: [ok, book_id, citations]
