openapi: "3.0.3"
info:
  title: "Skolkoll API"
  version: "1.0.0-beta"
  description: |
    Publikt read-only API för Sveriges skoldata.
    Data uppdateras dagligen från Skolverket, SCB och Kolada.

    ## Rate limiting
    - Anonym: 30 req/min
    - Med API-nyckel (X-API-Key header): 100 req/min

    ## Kom igång
    - [Kodexempel (Python, JS, R, PowerBI, Excel)](/api/v1/examples/)
    - [Registrera API-nyckel](/for-journalister/)
  license:
    name: "CC BY 4.0"
    url: "https://creativecommons.org/licenses/by/4.0/"
  contact:
    name: "Skolkoll"
    url: "https://skolkoll.se"

servers:
  - url: "https://skolkoll.se/api/v1"
    description: "Production"
  - url: "https://skolnavigator-staging.web.app/api/v1"
    description: "Staging"

paths:
  /schools:
    get:
      summary: "Lista skolor"
      description: "Returnerar paginerad lista med aktiva skolor. Stöder filtrering, sökning och sortering."
      operationId: listSchools
      tags: [Skolor]
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1, minimum: 1 }
          description: Sidnummer
        - name: per_page
          in: query
          schema: { type: integer, default: 50, minimum: 1, maximum: 200 }
          description: Poster per sida
        - name: municipality
          in: query
          schema: { type: string, example: "0180" }
          description: Filtrera på kommunKod
        - name: school_type
          in: query
          schema: { type: string, enum: [grundskola, gymnasieskola, forskoleklass, fritidshem, specialskola, sameskola, grundsarskola, gymnasiesarskola, gr, gy] }
          description: Filtrera på skolform
        - name: provider
          in: query
          schema: { type: string }
          description: Filtrera på huvudmanOrgNr
        - name: organizer_type
          in: query
          schema: { type: string, enum: [kommun, enskild] }
          description: Filtrera på organisationstyp
        - name: search
          in: query
          schema: { type: string }
          description: Fritext-sökning (skolnamn, kommun, huvudman)
        - name: fields
          in: query
          schema: { type: string, example: "namn,kommun,elever" }
          description: Kommaseparerad lista av fält att inkludera
        - name: sort
          in: query
          schema: { type: string, enum: [name, municipality, pupils, merit], default: name }
          description: Sortering
      responses:
        "200":
          description: Paginerad lista med skolor
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/MetaEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/School"
        "429":
          $ref: "#/components/responses/RateLimited"

  /schools/{skolenhetskod}:
    get:
      summary: "Hämta skoldetalj"
      description: "Returnerar fullständig data för en enskild skola med statistik, historik och events."
      operationId: getSchool
      tags: [Skolor]
      parameters:
        - name: skolenhetskod
          in: path
          required: true
          schema: { type: string, pattern: "^\\d{8}$", example: "12345678" }
          description: 8-siffrig skolenhetskod
      responses:
        "200":
          description: Fullständig skoldata
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/MetaEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/SchoolDetail"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /schools/{skolenhetskod}/kpis:
    get:
      summary: "Hämta nyckeltal för skola"
      description: "Returnerar statistik, historik, kvalitetsflaggor och hälsopoäng för en enskild skola."
      operationId: getSchoolKpis
      tags: [Skolor]
      parameters:
        - name: skolenhetskod
          in: path
          required: true
          schema: { type: string, pattern: "^\\d{8}$", example: "12345678" }
      responses:
        "200":
          description: Nyckeltal med historik
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /municipalities/{kod}/kpis:
    get:
      summary: "Hämta nyckeltal för kommun"
      description: "Returnerar resultat, Kolada-KPI:er och historik för en kommun."
      operationId: getMunicipalityKpis
      tags: [Kommuner]
      parameters:
        - name: kod
          in: path
          required: true
          schema: { type: string, pattern: "^\\d{4}$", example: "0180" }
      responses:
        "200":
          description: Kommun-KPI:er med historik
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /providers/{org}/schools:
    get:
      summary: "Lista skolor för huvudman"
      description: "Returnerar paginerad lista med skolor som tillhör en specifik huvudman."
      operationId: getProviderSchools
      tags: [Referensdata]
      parameters:
        - name: org
          in: path
          required: true
          schema: { type: string, example: "2120000142" }
          description: Huvudmannens organisationsnummer
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: per_page
          in: query
          schema: { type: integer, default: 50, maximum: 200 }
      responses:
        "200":
          description: Paginerad lista med skolor
        "404":
          $ref: "#/components/responses/NotFound"

  /municipalities:
    get:
      summary: "Lista kommuner"
      description: "Returnerar alla ~290 svenska kommuner med grundläggande statistik."
      operationId: listMunicipalities
      tags: [Kommuner]
      parameters:
        - name: county
          in: query
          schema: { type: string, example: "Stockholms län" }
          description: Filtrera på län
        - name: search
          in: query
          schema: { type: string }
          description: Fritext-sökning på kommunnamn
        - name: sort
          in: query
          schema: { type: string, enum: [name, population, schools], default: name }
      responses:
        "200":
          description: Lista med kommuner
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/MetaEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/Municipality"

  /municipalities/{kod}:
    get:
      summary: "Hämta kommundetalj"
      description: "Fullständig kommundata med demografi, skolstatistik och Kolada-KPI:er."
      operationId: getMunicipality
      tags: [Kommuner]
      parameters:
        - name: kod
          in: path
          required: true
          schema: { type: string, pattern: "^\\d{4}$", example: "0180" }
          description: 4-siffrig kommunKod
      responses:
        "200":
          description: Kommundetalj
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/MetaEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/MunicipalityDetail"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /providers:
    get:
      summary: "Lista huvudmän"
      description: "Aggregerad lista över skolhuvudmän med skolantal och koncerntillhörighet."
      operationId: listProviders
      tags: [Referensdata]
      parameters:
        - name: search
          in: query
          schema: { type: string }
        - name: type
          in: query
          schema: { type: string, enum: [kommun, enskild] }
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: per_page
          in: query
          schema: { type: integer, default: 50, maximum: 200 }
      responses:
        "200":
          description: Lista med huvudmän

  /groups:
    get:
      summary: "Lista koncerner"
      description: "Alla skolkoncerner med dotterbolag."
      operationId: listGroups
      tags: [Referensdata]
      responses:
        "200":
          description: Lista med koncerner

  /metrics/definitions:
    get:
      summary: "Metriker"
      description: "Maskinläsbart definitionsregister för alla statistiska mått."
      operationId: getMetricDefinitions
      tags: [Referensdata]
      responses:
        "200":
          description: Metrikdefinitioner

  /datasets/releases:
    get:
      summary: "Dataversioner"
      description: "Lista arkiverade dataversioner."
      operationId: listDatasetReleases
      tags: [Referensdata]
      responses:
        "200":
          description: Versionslista

components:
  schemas:
    MetaEnvelope:
      type: object
      properties:
        meta:
          type: object
          properties:
            status:
              type: string
              enum: [ok, error]
            timestamp:
              type: string
              format: date-time
            source:
              type: string
            pagination:
              $ref: "#/components/schemas/Pagination"

    Pagination:
      type: object
      properties:
        page: { type: integer, example: 1 }
        per_page: { type: integer, example: 50 }
        total_records: { type: integer, example: 16234 }
        total_pages: { type: integer, example: 325 }

    School:
      type: object
      properties:
        skolenhetskod: { type: string, example: "12345678" }
        namn: { type: string, example: "Testskolan" }
        kommun: { type: string, example: "Stockholm" }
        kommunKod: { type: string, example: "0180" }
        lan: { type: string, example: "Stockholms län" }
        lat: { type: number, example: 59.329, nullable: true }
        lng: { type: number, example: 18.068, nullable: true }
        huvudman: { type: string, example: "Stockholms kommun" }
        huvudmanOrgNr: { type: string, example: "2120000142" }
        organisationstyp: { type: string, enum: [kommun, enskild] }
        skolformer: { type: array, items: { type: string }, example: ["GR"] }
        status: { type: string, example: "AKTIV" }
        elever: { type: integer, nullable: true, example: 450 }
        larare_per_elev: { type: number, nullable: true, example: 12.3 }
        behoriga_larare_pct: { type: number, nullable: true, example: 85.2 }
        meritvarde_ak9: { type: number, nullable: true, example: 225.4 }

    SchoolDetail:
      type: object
      properties:
        skolenhetskod: { type: string }
        namn: { type: string }
        kommun: { type: string }
        kommunKod: { type: string }
        lan: { type: string }
        lat: { type: number, nullable: true }
        lng: { type: number, nullable: true }
        huvudman:
          type: object
          properties:
            namn: { type: string }
            orgNr: { type: string }
            typ: { type: string, enum: [kommun, enskild] }
        koncern:
          type: object
          nullable: true
          properties:
            namn: { type: string }
            orgNr: { type: string }
        skolformer: { type: array, items: { type: string } }
        status: { type: string }
        adress:
          type: object
          nullable: true
          properties:
            besoksadress: { type: string }
            postnummer: { type: string }
            postort: { type: string }
        statistik:
          type: object
          nullable: true
          properties:
            elever: { type: integer, nullable: true }
            larare_per_elev: { type: number, nullable: true }
            behoriga_larare_pct: { type: number, nullable: true }
            meritvarde_ak9: { type: number, nullable: true }
            godkand_alla_amnen_ak6: { type: number, nullable: true }
            godkand_alla_amnen_ak9: { type: number, nullable: true }
            np_svenska_ak9: { type: number, nullable: true }
            np_matte_ak9: { type: number, nullable: true }
            np_engelska_ak9: { type: number, nullable: true }
            examen_inom_3_ar: { type: number, nullable: true }
            behorig_hogskola: { type: number, nullable: true }
        kvalitet:
          type: object
          additionalProperties: true
        quality_flags:
          type: array
          items:
            type: object
        historik:
          type: object
          additionalProperties:
            type: array
        events:
          type: array
          items:
            type: object
        lankar:
          type: object
          properties:
            skolkoll: { type: string, format: uri }

    Municipality:
      type: object
      properties:
        kommunKod: { type: string, example: "0180" }
        namn: { type: string, example: "Stockholm" }
        lan: { type: string, example: "Stockholms län" }
        population: { type: integer, nullable: true, example: 984748 }
        antal_skolor: { type: integer, example: 342 }
        antal_grundskolor: { type: integer, example: 248 }
        antal_gymnasieskolor: { type: integer, example: 94 }
        meritvarde_snitt: { type: number, nullable: true, example: 225.0 }

    MunicipalityDetail:
      type: object
      properties:
        kommunKod: { type: string }
        namn: { type: string }
        lan: { type: string }
        demografi:
          type: object
          properties:
            population: { type: integer, nullable: true }
            utlandsk_bakgrund_pct: { type: number, nullable: true }
            medianinkomst: { type: number, nullable: true }
            hogutbildade_pct: { type: number, nullable: true }
        skolor:
          type: object
          properties:
            antal: { type: integer }
            grundskolor: { type: integer }
            gymnasieskolor: { type: integer }
            friskoleandel_pct: { type: number }
        resultat:
          type: object
          properties:
            meritvarde_snitt: { type: number, nullable: true }
            behoriga_larare_snitt: { type: number, nullable: true }
            kostnad_per_elev: { type: number, nullable: true }
        kolada_kpi:
          type: object
          additionalProperties:
            type: object
            properties:
              value: { type: number }
              year: { type: integer }
        lankar:
          type: object
          properties:
            skolkoll: { type: string, format: uri }

    ErrorResponse:
      type: object
      properties:
        meta:
          type: object
          properties:
            status: { type: string, enum: [error] }
            timestamp: { type: string, format: date-time }
        error:
          type: object
          properties:
            message: { type: string }
            details: { type: object }

  responses:
    BadRequest:
      description: Ogiltigt format
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Resursen hittades inte
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimited:
      description: Rate limit överskriden
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: "Valfri API-nyckel för högre rate limits (100 req/min istället för 30)"

security:
  - {}
  - apiKey: []
