Skolkoll API — Code Examples

Ready-to-use examples for integrating Swedish school data into your projects. Full API reference.

Base URL: https://skolkoll.se/api/v1
Auth: Optional X-API-Key header for higher rate limits (100 req/min vs 30).

Python

List schools with filtering

import requests

BASE = "https://skolkoll.se/api/v1"
API_KEY = "sk_your_key_here"  # Optional

headers = {"X-API-Key": API_KEY} if API_KEY else {}

# List grundskolor in Stockholm
resp = requests.get(f"{BASE}/schools", headers=headers, params={
    "municipality": "0180",
    "school_type": "grundskola",
    "per_page": 100,
    "sort": "merit",
})
data = resp.json()

for school in data["data"]:
    print(f"{school['namn']:40s} Merit: {school.get('meritvarde_ak9', '–')}")

Get school detail with history

import requests

resp = requests.get("https://skolkoll.se/api/v1/schools/DIN_SKOLKOD")
school = resp.json()["data"]

print(f"Skola: {school['namn']}")
print(f"Kommun: {school['kommun']}")
print(f"Elever: {school['statistik']['elever']}")

# Print merit value trend
if "meritvarde_ak9" in (school.get("historik") or {}):
    for year, value in school["historik"]["meritvarde_ak9"]:
        print(f"  {year}: {value}")

Paginate all schools

import requests
import time

BASE = "https://skolkoll.se/api/v1"
all_schools = []
page = 1

while True:
    resp = requests.get(f"{BASE}/schools", params={"page": page, "per_page": 200})
    data = resp.json()
    all_schools.extend(data["data"])

    total_pages = data["meta"]["pagination"]["total_pages"]
    if page >= total_pages:
        break
    page += 1
    time.sleep(2)  # Respect rate limits (use API key for higher limits)

print(f"Fetched {len(all_schools)} schools")

Export to pandas DataFrame

import requests
import pandas as pd

resp = requests.get("https://skolkoll.se/api/v1/schools", params={
    "per_page": 200,
    "fields": "namn,kommun,elever,meritvarde_ak9,behoriga_larare_pct",
})
df = pd.DataFrame(resp.json()["data"])
print(df.describe())

JavaScript / Node.js

Fetch schools (browser or Node 18+)

const BASE = "https://skolkoll.se/api/v1";

const params = new URLSearchParams({
  municipality: "0180",
  school_type: "grundskola",
  per_page: "50",
});

const resp = await fetch(`${BASE}/schools?${params}`, {
  headers: { "X-API-Key": "sk_your_key_here" }, // Optional
});

const { data, meta } = await resp.json();
console.log(`Found ${meta.pagination.total_records} schools`);

data.forEach((s) => {
  console.log(`${s.namn} — Merit: ${s.meritvarde_ak9 ?? "–"}`);
});

Municipality comparison

const codes = ["0180", "1480", "1280"]; // Stockholm, Gothenburg, Malmo

const results = await Promise.all(
  codes.map((code) =>
    fetch(`https://skolkoll.se/api/v1/municipalities/${code}`)
      .then((r) => r.json())
      .then((d) => d.data)
  )
);

console.table(
  results.map((m) => ({
    Kommun: m.namn,
    Skolor: m.skolor?.antal,
    Merit: m.resultat?.meritvarde_snitt,
    Kostnad: m.resultat?.kostnad_per_elev,
  }))
);

R

Fetch and analyze school data

library(httr2)
library(jsonlite)
library(dplyr)

# Fetch grundskolor in Stockholm
resp <- request("https://skolkoll.se/api/v1/schools") |>
  req_url_query(municipality = "0180", school_type = "grundskola", per_page = 200) |>
  req_perform()

data <- resp |> resp_body_json()
schools <- bind_rows(data$data)

# Summary statistics
schools |>
  summarise(
    n = n(),
    merit_mean = mean(meritvarde_ak9, na.rm = TRUE),
    merit_median = median(meritvarde_ak9, na.rm = TRUE),
    pct_qualified = mean(behoriga_larare_pct, na.rm = TRUE)
  ) |>
  print()

Compare municipalities

library(httr2)
library(purrr)

codes <- c("0180", "1480", "1280", "0380")

municipalities <- map_dfr(codes, function(code) {
  resp <- request(paste0("https://skolkoll.se/api/v1/municipalities/", code)) |>
    req_perform() |>
    resp_body_json()
  tibble(
    namn = resp$data$namn,
    skolor = resp$data$skolor$antal,
    merit = resp$data$resultat$meritvarde_snitt,
    kostnad = resp$data$resultat$kostnad_per_elev
  )
})

print(municipalities)

Power BI (Power Query M)

Import schools as table

let
    BaseUrl = "https://skolkoll.se/api/v1/schools",
    ApiKey = "sk_your_key_here",  // Optional: API key for higher rate limits
    FetchPage = (page as number) =>
        let
            Url = BaseUrl & "?page=" & Text.From(page) & "&per_page=200",
            Source = Json.Document(Web.Contents(Url, [Headers=[#"X-API-Key"=ApiKey]])),
            Data = Source[data],
            TotalPages = Source[meta][pagination][total_pages]
        in
            [Data = Data, TotalPages = TotalPages],

    FirstPage = FetchPage(1),
    TotalPages = FirstPage[TotalPages],
    Pages = List.Transform({1..TotalPages}, each FetchPage(_)[Data]),
    AllData = List.Combine(Pages),
    Table = Table.FromRecords(AllData)
in
    Table
In Power BI, go to Get Data > Blank Query, then paste the code above in the Advanced Editor. The query automatically paginates through all schools.

Excel

WEBSERVICE formula (single value)

=WEBSERVICE("https://skolkoll.se/api/v1/schools/DIN_SKOLKOD")
Excel's WEBSERVICE returns raw JSON text. For structured data, use Power Query (Data > Get Data > From Web) with the URL:
https://skolkoll.se/api/v1/schools?municipality=0180&per_page=200

Power Query (Get Data > From Web)

1. Data tab > Get Data > From Web
2. Enter URL: https://skolkoll.se/api/v1/schools?per_page=200
3. Click "Into Table" on the JSON record
4. Expand the "data" column
5. Select columns: namn, kommun, elever, meritvarde_ak9
6. Click "Close & Load"

Google Sheets

IMPORTDATA (simple list)

=IMPORTDATA("https://skolkoll.se/api/v1/schools?municipality=0180&per_page=10&fields=namn,elever,meritvarde_ak9")
IMPORTDATA works for simple JSON but has limitations. For full API access, use Apps Script (see below).

Apps Script (full API access)

function fetchSchools() {
  const url = "https://skolkoll.se/api/v1/schools?municipality=0180&per_page=50";
  const response = UrlFetchApp.fetch(url);
  const json = JSON.parse(response.getContentText());

  const sheet = SpreadsheetApp.getActiveSheet();
  sheet.clear();

  // Header row
  sheet.appendRow(["Namn", "Kommun", "Elever", "Meritvärde", "Behöriga lärare %"]);

  // Data rows
  json.data.forEach(function(school) {
    sheet.appendRow([
      school.namn,
      school.kommun,
      school.elever,
      school.meritvarde_ak9,
      school.behoriga_larare_pct
    ]);
  });
}
In Google Sheets: Extensions > Apps Script, paste the code, then run fetchSchools. You can set a time-based trigger for automatic updates.

Rate Limiting Best Practices

# Python example with exponential backoff
import requests
import time

def api_get(url, params=None, max_retries=3):
    for attempt in range(max_retries):
        resp = requests.get(url, params=params)
        if resp.status_code == 429:
            wait = min(60, 2 ** attempt * 5)
            print(f"Rate limited. Waiting {wait}s...")
            time.sleep(wait)
            continue
        resp.raise_for_status()
        return resp.json()
    raise Exception("Max retries exceeded")