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.

List all available mutations

Mutations work the same way — query mutationType instead of queryType:

{
  __schema {
    mutationType {
      fields {
        name
        description
      }
    }
  }
}

This returns every mutation you can call — for example addExecution, addEvidenceToExecution, and so on.

Info

A read-only machine-to-machine credential can introspect mutations (read their names) but cannot run them. To execute a mutation, authenticate with the Device Authorization Grant so your token carries write permissions.

Inspect a mutation's arguments

Add args to see what input each mutation expects:

{
  __schema {
    mutationType {
      fields {
        name
        args {
          name
          description
          type {
            name
            kind
            ofType {
              name
              kind
            }
          }
        }
      }
    }
  }
}

When an argument is an input object (for example ExecutionInput), inspect its fields with __type and inputFields:

{
  __type(name: "ExecutionInput") {
    inputFields {
      name
      description
      type {
        name
        kind
        ofType {
          name
        }
      }
    }
  }
}

For a worked example that chains several mutations together, see Adding evidence.

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)