Python samples¶
Runnable requests-based snippets for the most common pulls. Replace the placeholders with your own values; requests builds the Basic auth header for you when you pass auth=(email, apiKey).
Shared setup
import requests
EMAIL = "user@acme.com" # your Kendis login email
API_KEY = "your-api-key-here" # Profile → API Keys
AUTH = (EMAIL, API_KEY)
# Versioned method (Risk, Strategic Themes & OKRs)
SUBDOMAIN = "yourcompany" # https://yourcompany.kendis.io
V1_BASE = f"https://{SUBDOMAIN}.kendis.io/api/v1"
# Legacy method (PI Board Items, PI Objectives)
COMPANY_PREFIX = "yourcompany"
LEGACY_BASE = f"https://rest.kendis.io/api/{COMPANY_PREFIX}"
PI Board Items¶
List collections¶
resp = requests.get(f"{LEGACY_BASE}/collections", auth=AUTH)
resp.raise_for_status()
print(resp.json())
List boards¶
resp = requests.get(
f"{LEGACY_BASE}/boards",
params={"collectionId": "<collection-id-1>,<collection-id-2>"}, # optional
auth=AUTH,
)
print(resp.json())
Fetch board items (default fields)¶
import json
payload = {"boardId": "<board-id>"}
resp = requests.post(
f"{LEGACY_BASE}/items",
data=json.dumps(payload),
headers={"Content-Type": "application/json"},
auth=AUTH,
)
print(resp.json())
Fetch paginated items with selected fields¶
payload = {
"boardId": "<board-id>",
"fields": ["createdOn", "cardType", "itemType", "iterationPath"],
"pageStart": 0,
"pageSize": 20,
}
resp = requests.post(
f"{LEGACY_BASE}/items",
data=json.dumps(payload),
headers={"Content-Type": "application/json"},
auth=AUTH,
)
print(resp.json())
Sprint / team / status metadata¶
for meta in ("sprints", "teams", "statuses"):
resp = requests.post(
f"{LEGACY_BASE}/{meta}",
data=json.dumps({"boardId": "<board-id>"}),
headers={"Content-Type": "application/json"},
auth=AUTH,
)
print(meta, "→", resp.json())
Risk¶
List risks for a register, all fields¶
resp = requests.get(
f"{V1_BASE}/RR-PROG/risk",
params={"fields": "all", "startAt": 0, "pageLimit": 50},
auth=AUTH,
)
print(resp.json())
Strategic Themes & OKRs¶
Page through every Strategic Theme¶
def fetch_all(url, params=None):
"""Walk a paginated v1 endpoint. Note the capital 'Data' envelope."""
params = dict(params or {})
params.setdefault("pageLimit", 50)
start, rows = 0, []
while True:
params["startAt"] = start
page = requests.get(url, params=params, auth=AUTH).json()
batch = page.get("Data", [])
rows.extend(batch)
start += params["pageLimit"]
if start >= page.get("total", 0) or not batch:
return rows
themes = fetch_all(f"{V1_BASE}/strategic-themes", {"fields": "all"})
print(f"Fetched {len(themes)} strategic themes")
Drill from theme → groups → objectives → key results¶
theme_key = "CV-20"
groups = fetch_all(f"{V1_BASE}/strategic-themes/{theme_key}/objective-groups", {"fields": "all"})
for g in groups:
objectives = fetch_all(f"{V1_BASE}/objective-groups/{g['id']}/objectives", {"fields": "all"})
for o in objectives:
krs = fetch_all(f"{V1_BASE}/objectives/{o['key']}/key-results", {"fields": "all"})
print(f"{g['title']} → {o['key']} {o['title']} ({len(krs)} KRs)")
Error handling
In production, branch on resp.status_code (or call resp.raise_for_status()) and back off on 504.
See Errors.