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.
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)
- Previous
- Reporting use cases
- Next
- Adding evidence