openapi: 3.1.0
info:
  title: Atlas of Judgment — Machine Reader API
  version: "1.0.0"
  description: >
    Static, zero-cost read-only API over the Atlas of Judgment's audited data islands
    and per-plate depositions. Served from Cloudflare Pages as plain JSON files —
    every endpoint below is a static resource, not a dynamic route.
  license:
    name: CC BY 4.0
    url: https://creativecommons.org/licenses/by/4.0/
servers:
  - url: https://atlas-of-judgment.pages.dev

paths:
  /api/v1/index.json:
    get:
      summary: Catalogue of endpoints, plates, and data islands
      operationId: getIndex
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IndexDocument"

  /api/v1/plates/{plateId}.json:
    get:
      summary: One plate's deposition — headline claims, sources, caveats
      operationId: getPlateDeposition
      parameters:
        - name: plateId
          in: path
          required: true
          schema:
            type: string
            enum:
              - plate-i
              - plate-ii
              - plate-iii
              - plate-iv
              - plate-v
              - plate-vi
              - plate-vii
              - plate-viii
              - plate-ix
              - plate-x
              - plate-xi
              - plate-xii
              - plate-xiii
              - plate-xiv
              - plate-xv
              - plate-xvi
              - plate-xvii
              - plate-xviii
              - plate-xix
              - plate-xx
              - plate-xxi
              - plate-xxii
              - plate-xxiii
              - plate-xxiv
              - plate-xxv
              - plate-xxvi
              - plate-xxvii
              - plate-xxviii
              - plate-xxix
              - plate-xxx
              - appendix-i
              - appendix-ii
              - appendix-iii
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Deposition"
        "404":
          description: Unknown plate id

  /api/v1/data/{island}.json:
    get:
      summary: A raw data island, as served to the human page's <script> tags
      operationId: getDataIsland
      parameters:
        - name: island
          in: path
          required: true
          schema:
            type: string
            description: Island filename stem (see /api/v1/index.json for the full, current list)
            example: viz-data
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                description: Shape is island-specific; see /api/v1/index.json for the field-name summary per island.
        "404":
          description: Unknown island

  /api/v1/corrections.json:
    get:
      summary: The correction ledger — every published claim later found wrong, corrected in place and recorded
      operationId: getCorrections
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CorrectionsDocument" }

  /api/v1/contact.json:
    get:
      summary: How to write back — the channels, the shape of a well-formed report, and what happens to one
      description: >
        This API is read-only: there is no POST endpoint on this host and no key is issued here.
        This document describes the channels that do accept writes (the project's public issue
        tracker, callable with the caller's own credentials) so that an agent can send a
        correction or a question without a human relaying it.
      operationId: getContact
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { type: object }

components:
  schemas:
    IndexDocument:
      type: object
      required: [name, version, generated, corpus, endpoints, plates, islands]
      properties:
        name: { type: string, example: "Atlas of Judgment — Machine Reader API" }
        version: { type: string, example: "1.0.0" }
        generated: { type: string, format: date, example: "2026-08-27" }
        corpus:
          type: object
          properties:
            units_2026: { type: integer, example: 410586 }
            reviews_2026: { type: integer, example: 74380 }
            units_2018_2026_direct: { type: integer, example: 1009592 }
            forums_2018_2026: { type: integer, example: 50861 }
            units_total: { type: integer, example: 1420178 }
        endpoints:
          type: object
          description: Path templates for every resource this document catalogues.
          additionalProperties: { type: string }
        plates:
          type: array
          items: { $ref: "#/components/schemas/IndexPlateEntry" }
        islands:
          type: array
          items: { $ref: "#/components/schemas/IndexIslandEntry" }

    IndexPlateEntry:
      type: object
      required: [id, number, title, act, islands, deposition, deposition_status]
      properties:
        id: { type: string, example: plate-i }
        number: { type: string, example: "I" }
        title: { type: string, example: "The Anatomy" }
        act: { type: string, example: "I · The Instrument" }
        islands:
          type: array
          description: Island filenames this plate reads (resolve via /api/v1/data/{file}).
          items: { type: string, example: viz-data.json }
        deposition:
          type: [string, "null"]
          description: Path of this plate's deposition, null while pending.
          example: "/api/v1/plates/plate-i.json"
        deposition_status:
          type: string
          enum: [available, pending]

    IndexIslandEntry:
      type: object
      required: [file, bytes, sha256, track, plates, status]
      properties:
        file:
          type: string
          example: viz-data.json
          description: Filename; fetch at /api/v1/data/{file}.
        bytes: { type: integer, example: 1404026 }
        sha256:
          type: string
          description: SHA-256 of the served file, for reproducibility checks.
        track:
          type: string
          enum: ["2026 full-depth", "2018-2026 direct"]
        plates:
          type: array
          description: Plate ids that read this island (empty for orphaned files).
          items: { type: string, example: plate-i }
        status:
          type: string
          enum: [active, orphaned]

    CorrectionsDocument:
      type: object
      required: [what, corrections]
      properties:
        what:
          type: string
          description: What this ledger is; source of truth is method sec. 10.
        corrections:
          type: array
          items: { $ref: "#/components/schemas/CorrectionEntry" }

    CorrectionEntry:
      type: object
      required: [id, date, plate, figure, claim_as_shipped, what_the_check_showed, corrected_reading, lived_on_page, recorded_in, links]
      properties:
        id: { type: string, example: watermark-object-overlap-size-null }
        date:
          type: [string, "null"]
          description: Correction date (ISO); null where method sec. 10 records no date.
        date_note: { type: string, description: Present only when date is null. }
        plate: { type: string, example: plate-xxix }
        figure: { type: string, example: fig-29c }
        claim_as_shipped: { type: string }
        what_the_check_showed: { type: string }
        corrected_reading: { type: string }
        lived_on_page:
          type: string
          description: How long the uncorrected reading was live, as honestly as the record allows.
        recorded_in: { type: string, example: "method §10" }
        links:
          type: object
          properties:
            deposition: { type: string, example: /api/v1/plates/plate-xxix.json }
            island_keys:
              type: array
              items: { type: string, example: "llmtrace-data.json → mix.jaccard_size_null" }

    Deposition:
      type: object
      required: [id, number, title, act, question, figures]
      properties:
        id: { type: string }
        number: { type: string }
        title: { type: string }
        act:
          type: object
          properties:
            number: { type: string }
            title: { type: string }
        corpus_scope: { type: string }
        question: { type: string }
        page_url: { type: string, format: uri }
        figures:
          type: array
          items: { $ref: "#/components/schemas/Figure" }
        caveats:
          type: array
          items: { $ref: "#/components/schemas/Caveat" }
        links:
          type: object
          properties:
            recompute_scripts:
              type: array
              items: { type: string }
            data:
              type: array
              items: { type: string }
            related_plates:
              type: array
              items: { type: string }

    Figure:
      type: object
      required: [id, title, deck, claims]
      properties:
        id: { type: string }
        title: { type: string }
        deck: { type: string }
        dom_host: { type: string }
        claims:
          type: array
          items: { $ref: "#/components/schemas/Claim" }

    Claim:
      type: object
      required: [id, statement, value, source_island, derivation]
      properties:
        id: { type: string }
        statement: { type: string }
        value:
          type: object
          description: Structured quantity — never a bare number, so ratios always carry their denominator.
          required: [quantity, unit]
          properties:
            quantity:
              oneOf:
                - type: number
                - type: array
                  items: { type: number }
            unit: { type: string, example: share_of_units }
            n: { type: integer }
            object: { type: string }
            standard: { type: string }
        source_island: { type: string, example: viz-data.json }
        source_path: { type: string, description: "The array/object the derivation runs over — not a single JSON leaf." }
        derivation: { type: string, description: "Plain-language arithmetic, not code — matches the site's existing .prov-pop DERIVATION field." }
        recompute: { type: string, description: "Path to the script that regenerates this island from raw review text." }
        dom_ref:
          type: string
          nullable: true
          description: "The hover-bind data-* key wiring this claim to a chart cell on the human page, if any."
        verified:
          type: string
          nullable: true
          description: "Human audit tag, e.g. 'D10 number audit, 2026-08-24'. Null means not yet audited — a real gap, not an omission."
        caveat_refs:
          type: array
          items: { type: string }

    Caveat:
      type: object
      required: [id, text, scope]
      properties:
        id: { type: string }
        text: { type: string }
        scope: { type: string }
        verified_by: { type: string }
