API Reference

Schema introspection

GraphQL has built-in introspection: you can query the schema itself to discover what queries, types, and fields are available. This is useful when building scripts or exploring what data you can access.

All introspection queries use the same endpoint and authentication as regular queries.

List all available queries

{
  __schema {
    queryType {
      fields {
        name
        description
      }
    }
  }
}

This returns the full list of root query fields — for example controls_paged, activities_paged, risks_paged, and so on.

Inspect a specific type

Use __type to see the fields available on any type:

{
  __type(name: "Activity") {
    fields {
      name
      description
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

Replace "Activity" with any type name — for example "Control", "Risk", "Issue", or "Assessment".

List enum values

To see the valid values for an enum type:

{
  __type(name: "ActivityDueStatus") {
    enumValues {
      name
    }
  }
}

Common enums worth inspecting: ActivityDueStatus, Effectiveness, ControlCategory, Priority, IssueType.

Complete Python example

This script authenticates and prints a summary of the schema: all query fields and the fields on a given type.

import requests
import json
import time

TENANT = "{your-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():
    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 this URL in your browser to log in:")
    print(f"  {device['verification_uri_complete']}\n")

    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("Authentication successful.")
            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("Authentication timed out.")


def graphql(query, access_token):
    r = requests.post(
        GRAPHQL_URL,
        json={"query": query, "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"]


def list_queries(access_token):
    data = graphql(
        "{ __schema { queryType { fields { name description } } } }",
        access_token,
    )
    fields = data["__schema"]["queryType"]["fields"]
    print(f"\n{len(fields)} query fields available:\n")
    for f in sorted(fields, key=lambda x: x["name"]):
        desc = f.get("description") or ""
        print(f"  {f['name']:<40} {desc}")


def describe_type(type_name, access_token):
    data = graphql(
        f'{{ __type(name: "{type_name}") {{ fields {{ name description '
        f'type {{ name kind ofType {{ name }} }} }} }} }}',
        access_token,
    )
    t = data.get("__type")
    if not t:
        print(f"Type '{type_name}' not found.")
        return
    print(f"\nFields on {type_name}:\n")
    for f in t["fields"]:
        type_info = f["type"]
        type_name_str = type_info.get("name") or (type_info.get("ofType") or {}).get("name", "")
        print(f"  {f['name']:<30} {type_name_str}")


if __name__ == "__main__":
    token = get_token()
    access_token = token["access_token"]

    list_queries(access_token)
    describe_type("Activity", access_token)
    describe_type("Control", access_token)