Ready-to-use examples for integrating Swedish school data into your projects. Full API reference.
https://skolkoll.se/api/v1X-API-Key header for higher rate limits (100 req/min vs 30).
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', '–')}")
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}")
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")
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())
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 ?? "–"}`);
});
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,
}))
);
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()
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)
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
=WEBSERVICE("https://skolkoll.se/api/v1/schools/DIN_SKOLKOD")
https://skolkoll.se/api/v1/schools?municipality=0180&per_page=200
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"
=IMPORTDATA("https://skolkoll.se/api/v1/schools?municipality=0180&per_page=10&fields=namn,elever,meritvarde_ak9")
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
]);
});
}
fetchSchools. You can set a time-based trigger for automatic updates.
# 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")