openapi: 3.1.0
info:
  title: GhostCite Developer API
  summary: Deterministic legal citation and quotation verification.
  description: |
    **GhostCite** is a deterministic legal citation verification platform. Submit
    citations and quotations (or a document containing them) and receive a
    deterministic verification of each, with structured evidence and a
    provider-neutral reference to the supporting authority.

    This document is the formal specification of the **frozen** v1.1 public JSON
    contract. It is a faithful transcription of that contract — it adds, renames,
    and removes nothing.

    ## What the API does
    - Detects hallucinated citations and quotations before they are relied upon.
    - Explains each verdict with deterministic, legal-attribute evidence.
    - Identifies the supporting authority and guarantees it is inspectable.
    - Accepts a batch of items or a document, and returns results in document order.

    ## Guarantees (product contract)
    - **Deterministic:** the same input under the same authority state yields the
      same verification facts.
    - **Authority identified on success**, and every resolved authority is
      inspectable (see `opinion.documents`).
    - **Provider-neutral:** source identity is expressed only in field *values*
      (e.g. `provider`), never in field *names*.
    - **Additive-only across versions:** see the *Versioning* section at the end
      of this description.

    ## Versioning
    - **v1** — the core contract: `results[]` of `{ id, type, citation, verdict,
      opinion?, quote? }`, plus `summary` and `usage`. `verdict.status` is a
      **closed** set (`verified | warning | critical`) fixed for the life of v1.x.
    - **v1.1 additions (this document):** `Result.evidence` (structured
      comparisons).
    - **Reserved (specified but not emitted in v1.1):** `Result.position`
      (document offsets), `opinion.reference` (authority anchor),
      `Result.citationId`, `Quote.passage`, and `Quote.location`. Clients must
      treat their absence in v1.1 as "not yet available".
    - **Additive-only policy:** breaking changes (removing/renaming a field,
      changing a type or meaning, removing a value from a closed enum) require a
      new major version. Adding optional fields, newly populating an optional
      field, and adding values to open enums (`outcome`, `system`, `format`,
      `provider`, `attribute`) are additive and may occur within v1.x.
  version: 1.1.0
  contact:
    name: GhostCite API Support
    url: https://app.rule26ai.com
    email: support@rule26.ai
  license:
    name: Proprietary — GhostCite Developer API Terms
    url: https://app.rule26ai.com/terms

servers:
  - url: https://app.rule26ai.com
    description: Production
  - url: https://staging.app.rule26ai.com
    description: Staging / development

security:
  - ApiKeyAuth: []

tags:
  - name: Verification
    description: Verify citations and quotations against their supporting authorities.

paths:
  /api/v1/verify:
    post:
      tags: [Verification]
      operationId: verifyCitations
      summary: Verify citations and quotations
      description: |
        Verifies a batch of citations and/or quotations (or the items extracted
        from a submitted document) and returns a deterministic verdict for each,
        with structured evidence and the supporting authority.

        The response `results[]` are returned in submitted-document order.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyRequest'
            examples:
              mixedBatch:
                summary: A verified case, a year mismatch, and a quotation
                value:
                  items:
                    - id: r_1
                      type: case
                      rawText: "Brown v. Board of Education, 347 U.S. 483 (1954)"
                    - id: r_2
                      type: case
                      rawText: "Smith v. Jones, 200 F.3d 1 (2001)"
                    - id: r_3
                      type: quote
                      quoteText: "separate educational facilities are inherently unequal"
                      parentCaseId: r_1
      responses:
        '200':
          description: Verification completed. Each item has a deterministic verdict.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Response'
              examples:
                successfulVerification:
                  summary: Successful verification (verified case)
                  value:
                    results:
                      - id: r_1
                        type: case
                        citation:
                          raw: "Brown v. Board of Education, 347 U.S. 483 (1954)"
                          normalized: "347 U.S. 483"
                          system: us-reporter
                          components: { volume: "347", reporter: "U.S.", page: "483", year: 1954 }
                        verdict:
                          status: verified
                          outcome: verified
                          message: "Citation verified."
                          asOf: "2026-07-14T00:00:00Z"
                        opinion:
                          caseName: "Brown v. Board of Education"
                          court: { name: "Supreme Court of the United States", jurisdiction: us }
                          year: 1954
                          citations: ["347 U.S. 483", "74 S. Ct. 686"]
                          documents:
                            - { provider: courtlistener, url: "https://www.courtlistener.com/opinion/…", format: html, official: false }
                    summary:
                      verified: 1
                      warning: 0
                      critical: 0
                      total: 1
                      unique_citations: 1
                      billed_citations: 1
                      is_fully_verified: true
                      is_fully_accounted: true
                      overallStatus: verified
                    usage: { used: 1, remaining: 99, limit: 100 }
                warningResult:
                  summary: Warning result (attribute mismatch)
                  value:
                    results:
                      - id: r_2
                        type: case
                        citation: { raw: "Smith v. Jones, 200 F.3d 1 (2001)", normalized: "200 F.3d 1" }
                        verdict:
                          status: warning
                          outcome: mismatch
                          message: "Decision year does not match the resolved authority."
                        evidence:
                          comparisons:
                            - { attribute: year, cited: "2001", found: "1999", match: false }
                        opinion:
                          caseName: "Smith v. Jones"
                          court: { name: "9th Circuit", jurisdiction: us-ca9 }
                          year: 1999
                          documents:
                            - { provider: ghostcite, url: "https://…", format: html, official: true }
                    summary:
                      verified: 0
                      warning: 1
                      critical: 0
                      total: 1
                      unique_citations: 1
                      billed_citations: 1
                      is_fully_verified: false
                      is_fully_accounted: true
                      overallStatus: warning
                    usage: { used: 1, remaining: 98, limit: 100 }
                criticalResult:
                  summary: Critical result (authority not found)
                  value:
                    results:
                      - id: r_9
                        type: case
                        citation: { raw: "Fictional v. Nonexistent, 999 U.S. 1 (2099)" }
                        verdict:
                          status: critical
                          outcome: not_found
                          message: "No supporting authority could be resolved for this citation."
                    summary:
                      verified: 0
                      warning: 0
                      critical: 1
                      total: 1
                      unique_citations: 1
                      billed_citations: 1
                      is_fully_verified: false
                      is_fully_accounted: true
                      overallStatus: critical
                    usage: { used: 1, remaining: 97, limit: 100 }
                quoteVerification:
                  summary: Quote verification (reserved, not-yet-emitted fields shown for illustration)
                  value:
                    results:
                      - id: r_3
                        type: quote
                        citationId: r_1
                        citation: { raw: "347 U.S. 483" }
                        verdict: { status: verified, outcome: verified, message: "Quotation located in the authority." }
                        position: { start: 300, end: 372 }
                        quote:
                          matched: true
                          snippet: "separate educational facilities are inherently unequal"
                          passage: "separate educational facilities are inherently unequal"
                          location: { page: 495 }
                    summary:
                      verified: 1
                      warning: 0
                      critical: 0
                      total: 1
                      unique_citations: 0
                      billed_citations: 0
                      is_fully_verified: true
                      is_fully_accounted: true
                      overallStatus: verified
                    usage: { used: 1, remaining: 96, limit: 100 }
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: |
        Authenticate with your GhostCite API key as a bearer token:

        `Authorization: Bearer <YOUR_API_KEY>`

  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unauthorized
              message: "Missing or invalid API key."
    Forbidden:
      description: The API key is valid but not permitted to perform this request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: forbidden
              message: "This API key is not permitted to access this resource."
    ValidationError:
      description: The request body failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
          example:
            error:
              code: validation_error
              message: "Request validation failed."
              details:
                - field: items
                  message: "items must be a non-empty array."
    RateLimitExceeded:
      description: Rate limit or quota exceeded.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema: { type: integer, examples: [30] }
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limit_exceeded
              message: "Rate limit exceeded. Retry after the indicated interval."
    InternalServerError:
      description: An unexpected error occurred.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: internal_error
              message: "An unexpected error occurred."

  schemas:

    # ----------------------------- Request -----------------------------
    VerifyRequest:
      type: object
      description: A batch of citations and/or quotations to verify.
      required: [items]
      properties:
        items:
          type: array
          minItems: 1
          description: The items to verify, in document order.
          items:
            $ref: '#/components/schemas/VerifyItem'
      additionalProperties: false

    VerifyItem:
      type: object
      description: A single citation or quotation to verify.
      required: [id, type]
      properties:
        id:
          type: string
          description: Client-supplied identifier, echoed back on the matching result.
          examples: [r_1]
        type:
          type: string
          description: The kind of item to verify.
          enum: [case, quote, statute, rule, constitution, link]
        rawText:
          type: string
          description: The citation text (for case/statute/link items).
          examples: ["Brown v. Board of Education, 347 U.S. 483 (1954)"]
        quoteText:
          type: string
          description: The quotation text (for quote items).
        parentCaseId:
          type: string
          description: For a quotation, the id of the citation item it belongs to.
          examples: [r_1]
        context:
          type: string
          description: Optional surrounding text to aid resolution.
        url:
          type: string
          description: For link items, the URL to verify.
      additionalProperties: false

    # ----------------------------- Envelope -----------------------------
    Response:
      type: object
      description: The verification envelope for a single request.
      required: [results, summary, usage]
      properties:
        results:
          type: array
          description: One entry per verified item, in submitted-document order.
          items:
            $ref: '#/components/schemas/Result'
        summary:
          $ref: '#/components/schemas/Summary'
        usage:
          $ref: '#/components/schemas/Usage'
      additionalProperties: true

    Result:
      type: object
      description: The verification of one citation or quotation.
      required: [id, type, citation, verdict]
      properties:
        id:
          type: string
          description: Stable identity of this item within the response.
          examples: [r_1]
        type:
          type: string
          description: The kind of item verified (frozen v1 value set).
          enum: [case, quote, statute, rule, constitution, link]
        citationId:
          type: string
          description: |
            **Reserved (v1.2, quotations only).** The `id` of the citation Result
            this quotation is attributed to. Not emitted in v1.1.
          examples: [r_1]
        citation:
          $ref: '#/components/schemas/Citation'
        verdict:
          $ref: '#/components/schemas/Verdict'
        position:
          $ref: '#/components/schemas/DocumentPosition'
        evidence:
          $ref: '#/components/schemas/Evidence'
        opinion:
          $ref: '#/components/schemas/Opinion'
        quote:
          $ref: '#/components/schemas/Quote'
      additionalProperties: true

    # ----------------------------- Citation -----------------------------
    Citation:
      type: object
      description: What was verified.
      required: [raw]
      properties:
        raw:
          type: string
          description: The citation exactly as submitted.
          examples: ["Brown v. Board of Education, 347 U.S. 483 (1954)"]
        normalized:
          type: string
          description: Canonical form of the citation.
          examples: ["347 U.S. 483"]
        system:
          type: string
          description: Citation system, as an open-enum value.
          examples: [us-reporter, neutral, ecli]
        components:
          $ref: '#/components/schemas/CitationComponents'
      additionalProperties: true

    CitationComponents:
      type: object
      description: Structured parts of the citation, each present when known.
      properties:
        volume: { type: string, examples: ["347"] }
        reporter: { type: string, examples: ["U.S."] }
        page: { type: string, examples: ["483"] }
        pinpoint: { type: string, examples: ["495"] }
        year: { type: integer, examples: [1954] }
      additionalProperties: true

    # ----------------------------- Verdict -----------------------------
    Verdict:
      type: object
      description: The verification judgment (`verdict`).
      required: [status]
      properties:
        status:
          type: string
          description: |
            Top-level verdict. **Closed set**, fixed for the life of v1.x.
          enum: [verified, warning, critical]
        outcome:
          type: string
          description: |
            Stable review category (open enum — new values may be added
            additively). Example values shown are illustrative.
          examples: [verified, unverified, not_found, mismatch, unsupported]
        message:
          type: string
          description: Human-readable explanation (display prose; wording may change).
          examples: ["Citation verified."]
        method:
          type: string
          description: Coarse provenance of the verdict (open enum).
          examples: [authority-lookup, curated-registry]
        asOf:
          type: string
          format: date-time
          description: When the underlying record was current.
          examples: ["2026-07-14T00:00:00Z"]
        language:
          type: string
          description: BCP-47 language tag of `message`, when the message is localized.
          examples: [en]
      additionalProperties: true

    # ----------------------------- Document position (reserved) -----------------------------
    DocumentPosition:
      type: object
      description: |
        **Reserved (future); not emitted in v1.1.** Location of the item in the
        submitted document. Offsets are into the exact submitted text, counted in
        Unicode code points, zero-based, as a half-open interval `[start, end)`.
        Format-independent (no pages/coordinates). Treat its absence in v1.1 as
        "not yet available".
      required: [start, end]
      properties:
        start:
          type: integer
          minimum: 0
          description: Offset of the first character (inclusive).
          examples: [120]
        end:
          type: integer
          minimum: 0
          description: Offset one past the last character (exclusive).
          examples: [168]
        text:
          type: string
          description: The exact matched span, for re-anchoring.
      additionalProperties: false

    # ----------------------------- Evidence (v1.1) -----------------------------
    Evidence:
      type: object
      description: |
        **v1.1.** Structured, deterministic explanation of the verdict, expressed
        only in legal attributes — no scores or engine state.
      required: [comparisons]
      properties:
        comparisons:
          type: array
          description: The cited-vs-found comparisons the verdict rests on.
          items:
            $ref: '#/components/schemas/Comparison'
      additionalProperties: false

    Comparison:
      type: object
      description: A single cited-vs-found comparison for one legal attribute.
      required: [attribute, cited, found, match]
      properties:
        attribute:
          type: string
          description: >
            The legal attribute compared. Open vocabulary (tolerate unknown future values). A
            comparison is emitted only for attributes the engine deterministically compared;
            `volume` is a recognized attribute but is never produced, because no deterministic
            volume comparison exists (see ADR-0004). Absence of an attribute means "not compared",
            not "mismatch".
          enum: [caseName, year, court, reporter, volume, page, pinpoint]
        cited:
          type: [string, "null"]
          description: Value as cited (`null` if none was cited).
          examples: ["1954"]
        found:
          type: [string, "null"]
          description: Value in the resolved authority (`null` if unknown).
          examples: ["1954"]
        match:
          type: boolean
          description: Whether `cited` and `found` agree.
      additionalProperties: false

    # ----------------------------- Opinion -----------------------------
    Opinion:
      type: object
      description: |
        The resolved supporting authority (`opinion`). Provider-neutral. When
        present, the authority is inspectable via `documents`. `court` is always
        present; the other fields appear when known.
      required: [court]
      properties:
        reference:
          type: string
          description: |
            **Reserved (future); not emitted in v1.1.** Stable, provider-neutral
            identifier for the authority. Opaque; not a URL. Treat its absence in
            v1.1 as "not yet available"; use `documents` to inspect the authority.
          examples: [authority_9f2c1]
        caseName:
          type: string
          description: Authority identity.
          examples: ["Brown v. Board of Education"]
        court:
          $ref: '#/components/schemas/Court'
        year:
          type: integer
          description: Decision year.
          examples: [1954]
        citations:
          type: array
          description: Parallel citations of the authority.
          items: { type: string }
          examples:
            - ["347 U.S. 483", "74 S. Ct. 686"]
        documents:
          type: array
          description: Inspectable renderings of the authority.
          items:
            $ref: '#/components/schemas/Document'
      additionalProperties: true

    Court:
      type: object
      description: The deciding court.
      properties:
        name:
          type: string
          examples: ["Supreme Court of the United States"]
        jurisdiction:
          type: string
          description: Jurisdiction, as an open-enum value.
          examples: [us, us-ca9]
      additionalProperties: true

    Document:
      type: object
      description: A single inspectable rendering of the authority.
      required: [provider, url]
      properties:
        provider:
          type: string
          description: Source identity, as a value.
          examples: [courtlistener, ghostcite]
        url:
          type: string
          description: Where to read this document. URL format is out of scope of the contract.
          examples: ["https://www.courtlistener.com/opinion/…"]
        format:
          type: string
          description: Document format.
          enum: [html, pdf]
        official:
          type: boolean
          description: Whether this is an official version.
        language:
          type: string
          description: BCP-47 language tag.
          examples: [en]
        identifiers:
          type: array
          description: Provider-scoped identifiers.
          items:
            $ref: '#/components/schemas/Identifier'
      additionalProperties: true

    Identifier:
      type: object
      description: A provider-scoped identifier for the authority document.
      required: [provider, kind, id]
      properties:
        provider:
          type: string
          examples: [courtlistener]
        kind:
          type: string
          examples: [opinion, cluster]
        id:
          type: string
          examples: ["108713"]
      additionalProperties: false

    # ----------------------------- Quote -----------------------------
    Quote:
      type: object
      description: Present when the result is a quotation.
      required: [matched]
      properties:
        matched:
          type: boolean
          description: Whether the quoted text was located in the resolved authority.
        snippet:
          type: string
          description: The submitted quotation text.
        passage:
          type: string
          description: |
            **Reserved (v1.2).** The matched text as it appears in the authority.
            Not emitted in v1.1.
        location:
          $ref: '#/components/schemas/QuoteLocation'
      additionalProperties: true

    QuoteLocation:
      type: object
      description: |
        **Reserved (v1.2).** Where the passage was found. Not emitted in v1.1.
      properties:
        page: { type: integer, examples: [495] }
        paragraph: { type: integer, examples: [12] }
      additionalProperties: false

    # ----------------------------- Summary -----------------------------
    Summary:
      type: object
      description: Document-level rollup, consistent with the results in the response.
      required:
        - verified
        - warning
        - critical
        - total
        - unique_citations
        - billed_citations
        - is_fully_verified
        - is_fully_accounted
        - overallStatus
      properties:
        verified: { type: integer, description: Count of verified results., examples: [1] }
        warning: { type: integer, description: Count of warning results., examples: [1] }
        critical: { type: integer, description: Count of critical results., examples: [0] }
        total: { type: integer, description: Number of results., examples: [2] }
        unique_citations: { type: integer, description: Distinct citations., examples: [2] }
        billed_citations: { type: integer, description: Billed citation count., examples: [2] }
        is_fully_verified: { type: boolean, description: All results verified., examples: [false] }
        is_fully_accounted: { type: boolean, description: Accounting reconciled., examples: [true] }
        overallStatus:
          type: string
          description: Worst-case overall status across results.
          enum: [verified, warning, critical]
      additionalProperties: true

    # ----------------------------- Usage -----------------------------
    Usage:
      type: object
      description: Quota consumption for the caller.
      required: [used, remaining, limit]
      properties:
        used: { type: integer, description: Quota consumed., examples: [1] }
        remaining: { type: integer, description: Quota remaining., examples: [99] }
        limit: { type: integer, description: Quota ceiling., examples: [100] }
      additionalProperties: true

    # ----------------------------- Errors -----------------------------
    Error:
      type: object
      description: Standard error envelope.
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              description: Machine-readable error code.
              examples: [unauthorized, forbidden, validation_error, rate_limit_exceeded, internal_error]
            message:
              type: string
              description: Human-readable error message.
          additionalProperties: true
      additionalProperties: false

    ValidationError:
      type: object
      description: Error envelope for request validation failures.
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum: [validation_error]
            message:
              type: string
            details:
              type: array
              description: Per-field validation problems.
              items:
                type: object
                properties:
                  field: { type: string, examples: [items] }
                  message: { type: string, examples: ["items must be a non-empty array."] }
                additionalProperties: true
          additionalProperties: true
      additionalProperties: false
