LEAF REST API — Examples#
This notebook shows how to query the LEAF Portal REST API using Python.
Prerequisites#
pip install requests pandas
Authentication#
All endpoints require a token passed as a Bearer header:
Authorization: Bearer <token>
Tokens can be generated on the API Tokens page in the portal (/tokens).
Set your token as an environment variable before running this notebook:
export API_TOKEN=your_token_here
The base URL for all endpoints is https://leaf-portal.containers.wur.nl.
%pip install requests pandas
import os
import requests
import pandas as pd
BASE_URL = "https://leaf-portal.containers.wur.nl"
# Load API token from environment variable
# Set it in your terminal: export API_TOKEN=your_token_here
TOKEN = os.getenv("API_TOKEN", "")
if not TOKEN:
print("Warning: API_TOKEN is not set. Requests will return 401.")
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
GET /api/managements#
Lists all data managements accessible to your token. Each management represents a scoped slice of data (organisation → department → entity → time window) that your token has been granted access to.
Use the returned id values to query GET /api/data by management UUID (if supported), or use the organisation and department names as parameters.
response = requests.get(f"{BASE_URL}/api/managements", headers=HEADERS)
if response.status_code != 200:
raise RuntimeError(f"Error {response.status_code}: {response.text}")
df = pd.DataFrame(response.json())
df.head()
| id | organisation | department | entity | time_start | time_end | |
|---|---|---|---|---|---|---|
| 0 | c6200b9a-20f3-46ed-9c69-fd55b48f0701 | RWTH | AVT | None | None | None |
| 1 | 37881edb-6925-48c8-b2eb-4e1c980201ef | WUR | SSB | None | None | None |
| 2 | dc940e69-0a68-4db6-9ed3-d54fca8828e3 | UNLOCK | FDP | None | None | None |
| 3 | 02c31ce3-cb65-44a4-b80b-3627d0697fa3 | UNLOCK | MBP | None | None | None |
GET /api/data/recent#
Returns the most recent sensor readings across all departments accessible to your token.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
int |
20 |
Number of rows to return (max 1000) |
response = requests.get(f"{BASE_URL}/api/data/recent", headers=HEADERS, params={"limit": 20})
if response.status_code != 200:
raise RuntimeError(f"Error {response.status_code}: {response.text}")
df = pd.DataFrame(response.json())
df["time"] = pd.to_datetime(df["time"])
df.head()
| time | entity | metric | value | tags | department_id | organisation_id | |
|---|---|---|---|---|---|---|---|
| 0 | 2026-03-18 08:50:30+00:00 | ssb.bioind4 | cpu.usage_idle | 9.831245e+01 | {'cpu': 'cpu-total', 'topic': 'ssb/server/ssb.... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 1 | 2026-03-18 08:50:30+00:00 | ssb.bioind4 | disk.free | 2.922352e+10 | {'mode': 'rw', 'path': '/docker', 'label': 'Vs... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 2 | 2026-03-18 08:50:30+00:00 | ssb.bioind4 | disk.used | 1.702638e+11 | {'mode': 'rw', 'path': '/docker', 'label': 'Vs... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 3 | 2026-03-18 08:50:30+00:00 | ssb.bioind4 | disk.used_percent | 8.535069e+01 | {'mode': 'rw', 'path': '/docker', 'label': 'Vs... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 4 | 2026-03-18 08:50:30+00:00 | ssb.bioind4 | mem.used | 1.598564e+10 | {'topic': 'ssb/server/ssb.bioind4/mem'} | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
GET /api/data#
Returns sensor data for a specific organisation and department, with optional filters.
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
str |
yes |
Organisation name (e.g. |
|
str |
yes |
Department name (e.g. |
|
str |
no |
Filter to a single entity |
|
str |
no |
Filter to a single metric |
|
str |
no |
Start time, ISO 8601 (inclusive) |
|
str |
no |
End time, ISO 8601 (exclusive) |
|
int |
no |
Max rows to return (default 1000, max 10000) |
response = requests.get(
f"{BASE_URL}/api/data",
headers=HEADERS,
params={
"organisation": "WUR",
"department": "SSB",
"entity": "ssb.bioind4",
"metric": "cpu.usage_idle",
"from": "2026-03-17T00:00:00Z",
"to": "2026-03-17T05:00:00Z",
"limit": 1000,
},
)
if response.status_code != 200:
raise RuntimeError(f"Error {response.status_code}: {response.text}")
df = pd.DataFrame(response.json())
df["time"] = pd.to_datetime(df["time"])
df.head()
| time | entity | metric | value | tags | department_id | organisation_id | |
|---|---|---|---|---|---|---|---|
| 0 | 2026-03-17 04:59:50+00:00 | ssb.bioind4 | cpu.usage_idle | 96.811435 | {'cpu': 'cpu-total', 'topic': 'ssb/server/ssb.... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 1 | 2026-03-17 04:59:40+00:00 | ssb.bioind4 | cpu.usage_idle | 97.173711 | {'cpu': 'cpu-total', 'topic': 'ssb/server/ssb.... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 2 | 2026-03-17 04:59:30+00:00 | ssb.bioind4 | cpu.usage_idle | 96.985288 | {'cpu': 'cpu-total', 'topic': 'ssb/server/ssb.... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 3 | 2026-03-17 04:59:20+00:00 | ssb.bioind4 | cpu.usage_idle | 96.939482 | {'cpu': 'cpu-total', 'topic': 'ssb/server/ssb.... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |
| 4 | 2026-03-17 04:59:10+00:00 | ssb.bioind4 | cpu.usage_idle | 97.074690 | {'cpu': 'cpu-total', 'topic': 'ssb/server/ssb.... | 74c70fdf-12d7-4982-bb2c-2e0d46fa31f0 | c9ebd03f-c9c5-412a-99c9-5b05b3fe24aa |