openapi: 3.1.0
info:
  title: Wat Doet De Boer API
  description: |
    Public API for watdoetdeboer.nl — Dutch government transparency data.
    All responses follow the JSON:API 1.1 specification (https://jsonapi.org/).
  version: 0.1.0
  license:
    name: CC0-1.0
    identifier: CC0-1.0
  contact:
    name: Wat Doet De Boer
    url: https://watdoetdeboer.nl

servers:
  - url: https://api.watdoetdeboer.nl
    description: Production
  - url: http://127.0.0.1:8000
    description: Local

tags:
  - name: ministries
    description: Dutch ministries (hoofdstukken van de Rijksbegroting)
  - name: ministry-subunits
    description: Kerndepartementen, agentschappen, ZBOs
  - name: budget-snapshots
    description: Ministry-level budget totals per year/phase
  - name: budget-articles
    description: Budget drill-down per begrotingsartikel
  - name: headcount-snapshots
    description: FTE per ministry/subunit/year

paths:
  /api/v1/ministries:
    get:
      tags: [ministries]
      summary: List ministries
      parameters:
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/PageNumber'
        - $ref: '#/components/parameters/Sort'
        - $ref: '#/components/parameters/Include'
        - name: filter[slug]
          in: query
          schema: { type: string }
        - name: filter[hoofdstukNummer]
          in: query
          schema: { type: string }
        - name: filter[category]
          in: query
          schema: { type: string, enum: [ministerie, hoog_college, fonds, schuld] }
      responses:
        '200':
          description: OK
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/MinistryCollection'

  /api/v1/ministries/{id}:
    get:
      tags: [ministries]
      summary: Get a ministry
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Include'
      responses:
        '200':
          description: OK
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/MinistryDocument'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/ministries/{id}/budget-snapshots:
    get:
      tags: [ministries, budget-snapshots]
      summary: Budget snapshots for a ministry
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: OK
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/BudgetSnapshotCollection'

  /api/v1/ministries/{id}/budget-articles:
    get:
      tags: [ministries, budget-articles]
      summary: Budget articles drill-down for a ministry
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: filter[year]
          in: query
          schema: { type: integer }
        - name: filter[level]
          in: query
          schema: { type: string, enum: [artikel, artikelonderdeel, instrument, detail] }
      responses:
        '200':
          description: OK
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/BudgetArticleCollection'

  /api/v1/ministries/{id}/headcount-snapshots:
    get:
      tags: [ministries, headcount-snapshots]
      summary: Headcount snapshots for a ministry
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: OK
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/HeadcountSnapshotCollection'

components:
  parameters:
    PageSize:
      name: page[size]
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 15 }
    PageNumber:
      name: page[number]
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
    Sort:
      name: sort
      in: query
      schema: { type: string }
    Include:
      name: include
      in: query
      schema: { type: string }
      description: Comma-separated list of related resources to include.

  responses:
    NotFound:
      description: Resource not found
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'

  schemas:
    JsonApiVersion:
      type: object
      properties:
        version: { type: string, example: "1.0" }

    Links:
      type: object
      additionalProperties:
        oneOf:
          - { type: string, format: uri }
          - type: object

    Ministry:
      type: object
      required: [type, id, attributes]
      properties:
        type: { type: string, const: ministries }
        id: { type: string }
        attributes:
          type: object
          properties:
            slug: { type: string }
            name: { type: string }
            shortName: { type: string, nullable: true }
            hoofdstukNummer: { type: string, nullable: true }
            category: { type: string, enum: [ministerie, hoog_college, fonds, schuld] }
            minister: { type: string, nullable: true }
            website: { type: string, format: uri, nullable: true }
            color: { type: string, nullable: true }
            logoPath: { type: string, nullable: true }
            description: { type: string, nullable: true }
            createdAt: { type: string, format: date-time }
            updatedAt: { type: string, format: date-time }
        relationships:
          type: object
          properties:
            subunits: { $ref: '#/components/schemas/RelationshipLinks' }
            budgetSnapshots: { $ref: '#/components/schemas/RelationshipLinks' }
            budgetArticles: { $ref: '#/components/schemas/RelationshipLinks' }
            headcountSnapshots: { $ref: '#/components/schemas/RelationshipLinks' }
        links:
          type: object
          properties:
            self: { type: string, format: uri }

    BudgetSnapshot:
      type: object
      properties:
        type: { type: string, const: budget-snapshots }
        id: { type: string }
        attributes:
          type: object
          properties:
            year: { type: integer }
            phase: { type: string, enum: [OWB, O1, SBS, O2, JV] }
            kind: { type: string, enum: [verplichtingen, uitgaven, ontvangsten] }
            totalEur: { type: integer }
            sourceUrl: { type: string, format: uri, nullable: true }
            sourcePublishedAt: { type: string, format: date, nullable: true }

    BudgetArticle:
      type: object
      properties:
        type: { type: string, const: budget-articles }
        id: { type: string }
        attributes:
          type: object
          properties:
            year: { type: integer }
            phase: { type: string, enum: [OWB, O1, SBS, O2, JV] }
            kind: { type: string, enum: [verplichtingen, uitgaven, ontvangsten] }
            level: { type: string, enum: [artikel, artikelonderdeel, instrument, detail] }
            ibosCode: { type: string, nullable: true }
            articleNumber: { type: string, nullable: true }
            name: { type: string }
            description: { type: string, nullable: true }
            amountEur: { type: integer }

    HeadcountSnapshot:
      type: object
      properties:
        type: { type: string, const: headcount-snapshots }
        id: { type: string }
        attributes:
          type: object
          properties:
            year: { type: integer }
            peildatum: { type: string, format: date }
            fte: { type: number }
            personnelCostEur: { type: integer, nullable: true }
            externalHireCostEur: { type: integer, nullable: true }
            source: { type: string, enum: [jbr_pdf, agentschappen_csv, zbo_csv, manual] }
            sourceUrl: { type: string, format: uri, nullable: true }
            verifiedAt: { type: string, format: date-time, nullable: true }

    RelationshipLinks:
      type: object
      properties:
        links:
          type: object
          properties:
            self: { type: string, format: uri }
            related: { type: string, format: uri }

    MinistryDocument:
      type: object
      properties:
        jsonapi: { $ref: '#/components/schemas/JsonApiVersion' }
        data: { $ref: '#/components/schemas/Ministry' }
        included:
          type: array
          items: { type: object }

    MinistryCollection:
      type: object
      properties:
        jsonapi: { $ref: '#/components/schemas/JsonApiVersion' }
        data:
          type: array
          items: { $ref: '#/components/schemas/Ministry' }
        links: { $ref: '#/components/schemas/Links' }
        meta:
          type: object
          properties:
            page:
              type: object

    BudgetSnapshotCollection:
      type: object
      properties:
        jsonapi: { $ref: '#/components/schemas/JsonApiVersion' }
        data:
          type: array
          items: { $ref: '#/components/schemas/BudgetSnapshot' }

    BudgetArticleCollection:
      type: object
      properties:
        jsonapi: { $ref: '#/components/schemas/JsonApiVersion' }
        data:
          type: array
          items: { $ref: '#/components/schemas/BudgetArticle' }

    HeadcountSnapshotCollection:
      type: object
      properties:
        jsonapi: { $ref: '#/components/schemas/JsonApiVersion' }
        data:
          type: array
          items: { $ref: '#/components/schemas/HeadcountSnapshot' }

    ErrorDocument:
      type: object
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              status: { type: string }
              title: { type: string }
              detail: { type: string }
              source: { type: object }
