API-referentie

Authenticatie

De Tidal Control API gebruikt Bearer-tokens voor authenticatie. Tokens worden uitgegeven door de Keycloak-realm van je tenant via de OAuth 2.0 Device Authorization Grant (RFC 8628). Deze flow is ontworpen voor scripts en CLI-tools die zelf geen browservenster kunnen openen.

Vereisten

Je hebt je tenantnaam nodig — de Keycloak-realm-identifier van je organisatie. Dit is de organisatie-identifier in je portal-URL:

https://portal.tidalcontrol.com/{jouw-tenant}/

Als je portal-URL bijvoorbeeld https://portal.tidalcontrol.com/demo/ is, is je tenantnaam demo.

Vervang in deze handleiding {jouw-tenant} door je eigen tenantnaam.

De authenticatiestroom

De Device Authorization Grant werkt in drie stappen:

  1. Je script vraagt een apparaatcode op bij Keycloak
  2. Jij (of de gebruiker van het script) logt in via een browser-URL
  3. Je script pollt op het toegangstoken zodra de inlog is voltooid

Stap 1 — Apparaatcode opvragen

curl -X POST \
  "https://auth.tidalcontrol.com/realms/{jouw-tenant}/protocol/openid-connect/auth/device" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=portal&scope=openid"

Antwoord:

{
  "device_code": "...",
  "user_code": "ABCD-1234",
  "verification_uri": "https://auth.tidalcontrol.com/realms/{jouw-tenant}/device",
  "verification_uri_complete": "https://auth.tidalcontrol.com/realms/{jouw-tenant}/device?user_code=ABCD-1234",
  "expires_in": 600,
  "interval": 5
}

Stap 2 — Inloggen via browser

Open verification_uri_complete in een browser en log in met je Tidal Control-gegevens. Je script wacht terwijl je dit doet.

Stap 3 — Pollen op het token

Poll het token-endpoint elke interval seconden totdat de inlog is voltooid:

curl -X POST \
  "https://auth.tidalcontrol.com/realms/{jouw-tenant}/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id=portal&device_code={device_code}"

Zodra de gebruiker is ingelogd, bevat het antwoord je toegangstoken:

{
  "access_token": "eyJhbGci...",
  "expires_in": 300,
  "refresh_token": "eyJhbGci...",
  "refresh_expires_in": 1800,
  "token_type": "Bearer"
}
Info

Toegangstokens verlopen na 5 minuten. Gebruik de refresh_token om een nieuw toegangstoken op te halen zonder opnieuw in te loggen (zie hieronder).

API-verzoeken versturen

Voeg het toegangstoken toe als Bearer-header aan elk GraphQL-verzoek:

curl -X POST https://portal.tidalcontrol.com/graphql \
  -H "Authorization: Bearer {toegangstoken}" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ tenant { name displayName } }"}'

Volledig Python-voorbeeld

Dit script doorloopt de volledige authenticatiestroom en hergebruikt het token:

import requests
import time
import json

TENANT = "{jouw-tenant}"
BASE_URL = f"https://auth.tidalcontrol.com/realms/{TENANT}/protocol/openid-connect"
CLIENT_ID = "portal"
GRAPHQL_URL = "https://portal.tidalcontrol.com/graphql"


def get_token():
    # Stap 1: Apparaatcode opvragen
    r = requests.post(
        f"{BASE_URL}/auth/device",
        data={"client_id": CLIENT_ID, "scope": "openid"},
    )
    r.raise_for_status()
    device = r.json()

    print(f"\nOpen deze URL in je browser om in te loggen:")
    print(f"  {device['verification_uri_complete']}\n")

    # Stap 2: Pollen op token
    interval = device.get("interval", 5)
    deadline = time.time() + device["expires_in"]

    while time.time() < deadline:
        time.sleep(interval)
        r = requests.post(
            f"{BASE_URL}/token",
            data={
                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                "client_id": CLIENT_ID,
                "device_code": device["device_code"],
            },
        )
        if r.status_code == 200:
            print("Authenticatie geslaagd.")
            return r.json()
        body = r.json()
        if body.get("error") == "authorization_pending":
            continue
        if body.get("error") == "slow_down":
            interval += 5
            continue
        r.raise_for_status()

    raise RuntimeError("Authenticatie verlopen.")


def refresh_token(refresh_tok):
    r = requests.post(
        f"{BASE_URL}/token",
        data={
            "grant_type": "refresh_token",
            "client_id": CLIENT_ID,
            "refresh_token": refresh_tok,
        },
    )
    r.raise_for_status()
    return r.json()


def graphql(query, variables, access_token):
    r = requests.post(
        GRAPHQL_URL,
        json={"query": query, "variables": variables},
        headers={"Authorization": f"Bearer {access_token}"},
    )
    r.raise_for_status()
    result = r.json()
    if "errors" in result:
        raise RuntimeError(result["errors"])
    return result["data"]


if __name__ == "__main__":
    token = get_token()
    data = graphql("{ tenant { name displayName } }", {}, token["access_token"])
    print(json.dumps(data, indent=2))

Token vernieuwen

Wanneer het toegangstoken verloopt, gebruik je het refresh-token in plaats van de volledige inlogstroom opnieuw te doorlopen:

token = refresh_token(token["refresh_token"])
access_token = token["access_token"]
Tip

Refresh-tokens verlopen ook (doorgaans na 30 minuten inactiviteit). Als een refresh mislukt, doorloop dan opnieuw de volledige get_token()-stroom.