SpeckleClient and execute_query, fetch saved Data Validation checks, read pass/fail summaries, and optionally flatten rule results into a pandas DataFrame. The Python examples are split into notebook cells so you can run them in Jupyter or save as a script. In GraphQL, checks are queried with type: "model_validation".
This guide is GraphQL-only and read-only. For what checks and results mean in the product UI, see Data Validation overview. For authentication and the /graphql endpoint, see GraphQL API.
Prerequisites
- A personal access token with
streams:readscope (Building with PATs) - Read access to a project that has at least one saved validation check
- Python 3.10+ with
specklepy,pandas, andpython-dotenv - JupyterLab or VS Code with a Python kernel (optional — the same cells run as a
.pyscript)
Python examples authenticate with
SpeckleClient.authenticate_with_token. SpecklePy
attaches the Bearer token to GraphQL requests — you do not need to set headers
manually. This flow uses standard project read access and streams:read; you do not
need server admin permissions or write scopes to consume saved results.On self-hosted Speckle Enterprise Server, Intelligence and Data Validation may require
the Intelligence feature flag. See Enterprise deployment —
Intelligence for setup; this page
does not reproduce Helm configuration.
Find your IDs
Use one consistent token set across this guide. Copy the segment after each path prefix from your browser URL, or readid fields from the discovery query below. Substitute your real IDs wherever you see PROJECT_ID, INSIGHT_ID, MODEL_ID, or VERSION_ID.
| ID | Token | Full URL pattern |
|---|---|---|
projectId | PROJECT_ID | https://app.speckle.systems/projects/{PROJECT_ID}/data-validation |
Check ID (insightId) | INSIGHT_ID | https://app.speckle.systems/projects/{PROJECT_ID}/data-validation/{INSIGHT_ID}/ |
modelId | MODEL_ID | https://app.speckle.systems/projects/{PROJECT_ID}/models/{MODEL_ID} |
versionId | VERSION_ID | https://app.speckle.systems/projects/{PROJECT_ID}/models/{MODEL_ID}@{VERSION_ID} or latestResults[].versionId |
/graphql
- Query
- Variables
projectInsights array; each item’s id is the check ID (insightId in GraphQL) for later steps.
Run the query in Apollo Studio to explore the schema, or from Python using the Step 4 cells.
Overview
The read-only flow:List validation checks
Call
projectInsights with type: "model_validation". Read each check’s latest KPI
from aggregateResults(limit: 1) summary — omit result here.Fetch check history
Open one check by
insightId. Use aggregateResults for score history and
latestResults for the newest result per tracked model.Load model results
Call
modelResults(modelId, limit) when you need version history for one model.
Request the result field only in this step — it is the heaviest payload.summary for dashboards and score history. Request result only when you need per-rule rows.
Step 1: List validation checks
List saved checks and the latest aggregate pass/fail counts for each. POST/graphql
- Query
- Variables
- Response
name, modelIds, and aggregateResults[0].summary containing numeric pass and fail.
Step 2: Fetch check history
Load aggregate history and per-model latest summaries for one check. Do not requestresult yet.
If you already have INSIGHT_ID from the check URL or your environment, skip Step 1.
POST /graphql
- Query
- Variables
- Response
summary, timestamp) plus latestResults with one entry per tracked model.
If you also know MODEL_ID, pick the latestResults row where modelId matches — that gives pass/fail for the newest stored result on that model without fetching result. Derive score and status from summary and metadata.displayConfig; see Compute KPI score and status.
Step 3: Load model results
Request stored results for one model. Results are ordered newest first. If you already havePROJECT_ID, INSIGHT_ID, and MODEL_ID (common for CI gates or post-publish scripts), skip Steps 1–2.
modelResults and versionResults require the model to appear in the check’s modelIds (visible in Step 1); otherwise responses are empty.
Version history
Use when you need multiple snapshots for one model, or the fullresult payload for rule breakdown.
POST /graphql
- Query
- Variables
modelResults ordered newest-first, each with versionId, summary, and a result object with columns and rows.
One version snapshot
Use when you knowVERSION_ID as well — from the model version URL (.../models/{MODEL_ID}@{VERSION_ID}) or a publish webhook — and want that snapshot only.
POST /graphql
- Query
- Variables
result from the selection set for summary-only CI gates. Add it when you need per-rule rows.
You should see… a (usually single-item) versionResults array for that version, or an empty array if validation has not run yet — see Wait for new results.
Step 4: Build a KPI DataFrame
Run the Python below as notebook cells or concatenate into a script. It authenticates with SpecklePy, runs the Step 1 query through the SDK GraphQL handler, and builds a KPI table. Helpers for score and status are in Extend the examples — start with Compute KPI score and status. Queries with GraphQL variables use the same authenticated client asexecute_query,
with variable_values passed to the underlying GraphQL client.
Create a .env file next to your notebook or script:
print(kpi_df.to_string(index=False)).
You should see… one row per check with columns name, score_pct, status, pass, fail, and evaluated_at.
For related GraphQL and Python patterns, see SpecklePy model data analytics.
Notebook
Download the notebook. Save the file locally, add your.env in the same folder, and run top to bottom.
Extend the examples
Optional depth after the main flow: how scores map to the UI, rule-level DataFrames, and polling when results are still processing.Compute KPI score and status
The web app score is not returned pre-computed. Derive it fromsummary and metadata.displayConfig:
- Read
summary.passandsummary.failfrom the latestaggregateResults[0]. - Set
total = pass + fail. Iftotal == 0, status is pending / no data (na). - Set
pass_rate = pass / total(a decimal between 0 and 1). - Set
score_pct = round(pass_rate * 100)— the large percentage on check cards. - Load thresholds from
metadata.displayConfigon each check. Defaults when absent:passThreshold: 0.9, optionalwarningThreshold. - Apply status rules:
pass_rate >= passThreshold→ pass- else if
warningThresholdis set andpass_rate >= warningThreshold→ warning - else → fail
- Per-rule status uses the same logic on each rule’s pass/fail ratio.
pass=870, fail=130 → pass_rate=0.87 → score_pct=87. With project passThreshold=0.9, status is warning. The same counts yield pass for a rule with rulePassThreshold of 0.85.
Thresholds are stored in metadata.displayConfig on each check (returned on projectInsights / insight queries). They are set in the Data Validation UI. Consumers read the stored config; updating checks via API is out of scope for this guide.
| Key | Scope | Example |
|---|---|---|
passThreshold | Project-wide default (0–1) | 0.9 |
warningThreshold | Project-wide optional band | 0.7 |
rulePassThreshold | Per-rule override map | { "Room name required": 0.95 } |
ruleWarningThreshold | Per-rule warn override | { "Room name required": 0.8 } |
ruleSeverity | Per-rule advisory vs error | { "Optional note": "info" } |
For PASS, WARN, and FAIL meaning in the UI, see Viewing results — result
states. For threshold
tuning in the product, see Checks — thresholds and
status.
Build a rule breakdown DataFrame
Theresult field is a tabular JSON object with columns and rows. Validation rows use dimensions rule, status, and gate, plus measure count.
aggregate_results from the API is newest-first; reverse for chronological charts.
Wait for new results
Results are computed asynchronously after a new model version is published. Poll until data appears, then stop.| Situation | Suggested interval | Stop when | Max wait guidance |
|---|---|---|---|
| New check or new version pushed | 15 seconds | aggregateResults has at least one row | ~10 minutes; then investigate |
| Waiting for aggregate rollup | 30 seconds | aggregate non-empty | ~15 minutes for large checks |
| Steady-state monitoring | No polling | — | Query on a schedule instead |
Troubleshooting
Common errors
| Error / symptom | Likely cause | What to do |
|---|---|---|
"Insights module is not enabled on this server" | Intelligence feature disabled on self-hosted | Enable per Enterprise deployment |
401 / auth errors | Missing or invalid token | Call authenticate_with_token with a valid PAT |
403 / forbidden | No project read or scoped token wrong project | Verify streams:read and project access |
Empty modelResults / versionResults | Model not in check modelIds or no run yet | Confirm MODEL_ID in check’s modelIds; poll Wait for new results |
Empty aggregateResults | Check still processing or no models attached | Poll Wait for new results; confirm modelIds |
Empty projectInsights | Wrong projectId or no checks saved | Run Find your IDs discovery query; confirm checks in UI |
GraphQL errors array | Malformed query or wrong variable types | Match variables to $projectId: String! and other declared types |
| Slow / timeout responses | Requesting result for many models at once | Drop result; reduce limit; fetch one model at a time |
HTTP 429 Too Many Requests | Rate limit (common when polling too fast) | Poll ≥15s; slow down or use scheduled queries instead |
| Score differs from UI | Threshold overrides or per-rule severity | Read metadata.displayConfig; use resolve_thresholds(..., rule_name=...) |
projectId.
Verify your notebook or script
After running Step 4:- The DataFrame has one row per check with
score_pct,status, andevaluated_at. - Re-run with your project’s
projectId; row count matches the number of checks in the UI. - If the table is empty, see Common errors above.
What developers need to know
How do I find my project ID?
How do I find my project ID?
Copy the segment after
/projects/ in the web app URL — that value is your PROJECT_ID in https://app.speckle.systems/projects/{PROJECT_ID}/. You can also list projects via SpecklePy or GraphQL. Check IDs come from the discovery query (projectInsights[].id).What is the difference between aggregateResults, latestResults, and modelResults?
What is the difference between aggregateResults, latestResults, and modelResults?
aggregateResults returns project-wide rollup history for a check (use limit: 1 for
the latest KPI). latestResults returns the newest stored result per tracked model
(excludes the aggregate row). modelResults(modelId, limit) returns version history
for one model, newest first. If you know MODEL_ID already, use Step 1 to resolve
INSIGHT_ID, Step 2 for latest summary per model, or Step 3 for history or a single
version snapshot.Why is aggregateResults empty?
Why is aggregateResults empty?
The check may still be processing after a version push, or no models are attached.
Poll every 15–30 seconds for up to 10–15 minutes. Confirm the check lists
modelIds
and those models have published versions.How do I run the GraphQL queries from Python?
How do I run the GraphQL queries from Python?
Copy the Step 4 cells into your own Jupyter project,
or download the
notebook
from GitHub. Authenticate a
SpeckleClient with authenticate_with_token, wrap each
authenticate_with_token, wrap each operation in gql(), then call
execute_query
or pass variable_values to the same authenticated GraphQL client when the operation
declares variables.Where is the full GraphQL schema?
Where is the full GraphQL schema?
See GraphQL API and the Apollo Studio
reference
for the full schema, including
projectInsights, insight, and related fields.What is not covered in this guide?
What is not covered in this guide?
Creating or updating checks (
insightMutations.create, update, delete). Ad-hoc validation without saving (executeQuery, executeVersionQuery). Webhooks or subscriptions when a result is ready (poll instead). REST endpoints for validation results. Authoring EAV query or rule DSL from scratch. Linking validation failures to Speckle issues (syncValidationIssues).