Skip to content

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).

pip install requests
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.