openapi: 3.0.3
info:
  title: Lustra LDS-1 B2B Gateway API
  version: 1.0.0
  description: |
    Read-only REST contract for LDS-1 legislative data.

    Use Authorize with your X-API-Key to run live requests.
servers:
  - url: https://data.lustra.dev
    description: Production API
security:
  - ApiKeyAuth: []
tags:
  - name: Health
  - name: Legislations
  - name: Members
  - name: Votes
paths:
  /health:
    get:
      tags: [Health]
      summary: Service health and available parliaments
      security: []
      responses:
        '200':
          description: Service health
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GeneralHealth' }
  /v1/{parliament}/health:
    get:
      tags: [Health]
      summary: Parliament data health
      security: []
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
      responses:
        '200':
          description: Latest parliament data-integrity health document
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ParliamentHealth' }
        '404': { $ref: '#/components/responses/NotFound' }
  /v1/{parliament}/{term}/legislations:
    get:
      tags: [Legislations]
      summary: List legislations
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/LangParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
        - $ref: '#/components/parameters/ViewParam'
        - name: status
          in: query
          schema: { type: string }
        - name: subStatus
          in: query
          schema:
            type: string
            enum: [accepted, rejected, process, upcoming_session, expired]
        - name: documentType
          in: query
          schema: { type: string }
        - name: documentSubType
          in: query
          schema:
            type: string
            enum: [bill, resolution, other]
        - name: category
          in: query
          schema:
            type: string
            enum: [Health, Education, Family, Taxes, Labor, Security, Environment, Courts and Law, Transport, Benefits, Real Estate, Media and Culture, Local Government, Migration, Agriculture, Public Investments, Informatization, Economy, Other]
        - name: sort
          in: query
          schema:
            type: string
            default: -lastUpdated
            enum: [lastUpdated, -lastUpdated, processStartDate, -processStartDate, votingDate, -votingDate, id, -id]
      responses:
        '200':
          description: Legislations page
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Legislation' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
  /v1/{parliament}/{term}/legislations/{id}:
    get:
      tags: [Legislations]
      summary: Get legislation
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/ResourceIdParam'
        - $ref: '#/components/parameters/LangParam'
      responses:
        '200':
          description: Single legislation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Legislation' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
  /v1/{parliament}/{term}/legislations/{id}/votes:
    get:
      tags: [Legislations, Votes]
      summary: List votes for a legislation
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/ResourceIdParam'
        - $ref: '#/components/parameters/LangParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
        - $ref: '#/components/parameters/ViewParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/FromParam'
        - $ref: '#/components/parameters/ToParam'
        - $ref: '#/components/parameters/VoteSortParam'
      responses:
        '200':
          description: Votes connected to legislation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Vote' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
  /v1/{parliament}/{term}/members:
    get:
      tags: [Members]
      summary: List members
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/LangParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
        - $ref: '#/components/parameters/ViewParam'
        - name: active
          in: query
          schema: { type: boolean }
        - name: club
          in: query
          schema: { type: string }
        - name: district
          in: query
          schema: { type: string }
        - name: sort
          in: query
          schema: { type: string }
      responses:
        '200':
          description: Members page
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Member' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
  /v1/{parliament}/{term}/members/{id}:
    get:
      tags: [Members]
      summary: Get member
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/ResourceIdParam'
        - $ref: '#/components/parameters/LangParam'
      responses:
        '200':
          description: Single member
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Member' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
  /v1/{parliament}/{term}/members/{id}/votes:
    get:
      tags: [Members, Votes]
      summary: List votes for a member
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/ResourceIdParam'
        - $ref: '#/components/parameters/LangParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
        - $ref: '#/components/parameters/ViewParam'
        - $ref: '#/components/parameters/FromParam'
        - $ref: '#/components/parameters/ToParam'
        - $ref: '#/components/parameters/VoteSortParam'
      responses:
        '200':
          description: Votes for member
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Vote' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
  /v1/{parliament}/{term}/votes:
    get:
      tags: [Votes]
      summary: List votes
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/LangParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
        - $ref: '#/components/parameters/ViewParam'
        - $ref: '#/components/parameters/IncludeParam'
        - name: legislationId
          in: query
          schema: { type: string }
        - name: memberId
          in: query
          schema: { type: string }
        - name: chamber
          in: query
          schema: { type: string }
        - name: voteType
          in: query
          schema:
            type: string
            enum: [passage, amendment, rejection, procedure]
        - $ref: '#/components/parameters/FromParam'
        - $ref: '#/components/parameters/ToParam'
        - $ref: '#/components/parameters/VoteSortParam'
      responses:
        '200':
          description: Votes page
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Vote' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
  /v1/{parliament}/{term}/votes/{id}:
    get:
      tags: [Votes]
      summary: Get vote
      parameters:
        - $ref: '#/components/parameters/ParliamentParam'
        - $ref: '#/components/parameters/TermParam'
        - $ref: '#/components/parameters/ResourceIdParam'
        - $ref: '#/components/parameters/LangParam'
      responses:
        '200':
          description: Single vote
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Vote' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/InternalServerError' }
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  parameters:
    ParliamentParam:
      name: parliament
      in: path
      required: true
      description: Supported jurisdictions are exposed as the LDS-1 API coverage expands.
      schema:
        type: string
        enum: [uk, us, pl]
        example: uk
    TermParam:
      name: term
      in: path
      required: true
      schema:
        oneOf:
          - type: string
            enum: [current]
          - type: integer
        example: current
    ResourceIdParam:
      name: id
      in: path
      required: true
      schema: { type: string }
    LangParam:
      name: lang
      in: query
      schema:
        type: string
        default: eng
        enum: [eng, pl, de, fr, es, it, pt, nl]
    LimitParam:
      name: limit
      in: query
      schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
    CursorParam:
      name: cursor
      in: query
      schema: { type: string }
    ViewParam:
      name: view
      in: query
      schema:
        type: string
        default: summary
        enum: [summary, detail]
      description: detail requires Developer / Business plan or higher.
    IncludeParam:
      name: include
      in: query
      schema:
        type: string
        enum: [participants]
      description: participants include requires Developer / Business plan or higher.
    FromParam:
      name: from
      in: query
      schema: { type: string, format: date-time }
    ToParam:
      name: to
      in: query
      schema: { type: string, format: date-time }
    VoteSortParam:
      name: sort
      in: query
      schema:
        type: string
        default: -votingDate
        enum: [votingDate, -votingDate, lastUpdated, -lastUpdated, id, -id]
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code: { type: string }
                  message: { type: string }
              meta:
                type: object
                properties:
                  requestId: { type: string }
    Forbidden:
      description: Forbidden by key policy or tier restrictions
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code: { type: string }
                  message: { type: string }
              meta:
                type: object
                properties:
                  requestId: { type: string }
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code: { type: string }
                  message: { type: string }
              meta:
                type: object
                properties:
                  requestId: { type: string }
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code: { type: string }
                  message: { type: string }
              meta:
                type: object
                properties:
                  requestId: { type: string }
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code: { type: string }
                  message: { type: string }
              meta:
                type: object
                properties:
                  requestId: { type: string }
  schemas:
    GeneralHealth:
      type: object
      properties:
        status: { type: string, example: ok }
        service: { type: string, example: b2b-gateway }
        version: { type: string }
        availableParliaments:
          type: object
          additionalProperties:
            type: object
            properties:
              terms:
                type: array
                items: { type: integer }
    ParliamentHealth:
      type: object
      additionalProperties: true
      description: Latest data-integrity health document from metadata/{parliament}/health/latest.
    Meta:
      type: object
      properties:
        parliament: { type: string, example: uk }
        term: { oneOf: [{ type: integer }, { type: string }], example: 59 }
        lang: { type: string, example: eng }
        view: { type: string, enum: [summary, detail] }
        count: { type: integer }
        nextCursor: { type: string, nullable: true }
    Legislation:
      type: object
      properties:
        id: { type: string, example: 59_SI_ZYYJRX6J }
        parliament: { type: string, example: uk }
        term: { type: integer, example: 59 }
        documentType: { type: string, example: Statutory Instrument }
        documentSubType: { type: string }
        status: { type: string }
        subStatus:
          type: string
          enum: [accepted, rejected, process, upcoming_session, expired]
        titleOfficial: { type: string }
        category:
          type: array
          items: { type: string }
        processStartDate: { type: string, format: date-time }
        lastUpdated: { type: string, format: date-time }
        summaryAI:
          type: object
          properties:
            lang: { type: string }
            title: { type: string }
            description: { type: string }
            keyPoints:
              type: array
              items: { type: string }
        noDocument:
          type: boolean
          description: True when no public/source document is available from the official source.
        sourceDocumentCacheUrl:
          type: string
          nullable: true
          description: Lustra cached copy of the canonical source document.
        textSourceUrl:
          type: string
          nullable: true
          description: Lustra HTML/text rendition prepared for AI and machine reading.
    Member:
      type: object
      properties:
        id: { type: string, example: 59_172 }
        parliament: { type: string, example: uk }
        term: { type: integer, example: 59 }
        fullName: { type: string }
        firstName: { type: string }
        lastName: { type: string }
        active: { type: boolean }
        club: { type: string }
        district: { type: string }
        imageUrl: { type: string }
        memberType: { type: string }
    Vote:
      type: object
      properties:
        id: { type: string, example: 59_commons_2350 }
        parliament: { type: string, example: uk }
        term: { type: integer, example: 59 }
        legislationId: { type: string, example: 59_SI_6IHSUYR8 }
        chamber: { type: string, example: Commons }
        voteType:
          type: string
          enum: [passage, amendment, rejection, procedure]
        voteQuestion: { type: string }
        votesFor: { type: integer }
        votesAgainst: { type: integer }
        votesAbstain: { type: integer }
        votesOther: { type: integer }
        notParticipating: { type: integer }
        votingNumber: { type: integer }
        votingDate: { type: string, format: date-time }
        votingUrl: { type: string }
        documentType: { type: string }
        titleOfficial: { type: string }
        memberVote:
          type: object
          properties:
            memberId: { type: string }
            vote: { type: string }
            club: { type: string }
        participants:
          type: array
          items:
            type: object
            additionalProperties: true
