Query governed data
Run a deterministic governed query end to end — resolve structured intent into an exact plan, execute it with typed filters, and read plan_hash and validated.
This guide walks you through the full governed-query flow against a registered source — resolve structured intent into an exact, executable plan, run it with typed filters, and read back the audit fields (plan_hash, schema_revision, validated) that make the result reproducible. By the end you will have a number you can defend and replay. Prerequisites: an API key (see Authentication) and one already-registered source or onboarded dataset — registration returns the dataset_id and the inferred schema (column names and roles) you will reference below.
Read the schema you will query against
You need the source's exact column names and their structural roles. Registration (POST /v1/sources/register) returns them in schema.columns; you can also re-read them later with POST /v1/sources/schema (tag Semantic Query), which returns each source's schema plus query_hints.
curl -s -X POST "$ALGENTA_BASE_URL/v1/sources/schema" \
-H "Authorization: Bearer $ALGENTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Verify: the response lists your source under sources[] with a schema.columns[] array. Note one column whose role is derived_measure (a formula-proven aggregate such as revenue) and one whose role is category or identifier to group by. You will use these exact name values as metric_column and group_column.
Resolve structured intent into an exact plan
POST /v1/resolve (tag Semantic Query) is a bounded, deterministic planner: it turns structured intent into an exact resolved_plan using only registered-source metadata, the join graph, the planner cache, and precomputed statistics. It does not scan datasets or execute anything. Scope it with dataset_id, or set allow_org_scope=true to plan across all visible datasets.
import os
import httpx
base = os.environ["ALGENTA_BASE_URL"]
headers = {"Authorization": f"Bearer {os.environ['ALGENTA_API_KEY']}"}
resolve = httpx.post(
f"{base}/v1/resolve",
headers=headers,
json={
"dataset_id": "bookings",
"metric": {"role": "derived_measure", "hint": "revenue"},
"group_by": ["customer"],
"aggregation": "sum",
"limit": 5,
},
).json()
print(resolve["clarification_required"], resolve["intent_signature"])Verify: the response includes a resolved_plan object (carrying source_name, metric_column, group_column, and schema_revision) and clarification_required is false. If it is true, the planner was not confident — read the ranked candidates and disambiguate; the planner never widens its search or falls back to a slower path.
Execute the plan with the exact spec
Send the column names back to POST /v1/query. The recommended production path is the exact spec: source_name plus metric_column, and optionally group_column. This bypasses both the source resolver and the column resolver — the engine only validates the named columns exist, then executes. (The hint spec — metric.role + group_by dimension words — is the exploratory fallback for before the schema is known; migrate off it once you know the column names, because it can be ambiguous.)
result = httpx.post(
f"{base}/v1/query",
headers=headers,
json={
"source_name": "Bookings",
"metric_column": "measure_total",
"group_column": "dimension_label",
"aggregation": "sum",
"limit": 5,
},
).json()
print(result["exact_spec"], result["row_count"])Verify: exact_spec is true and result holds your grouped rows. If exact_spec were false, the request fell through to the hint path — confirm both source_name and metric_column are set, since the exact path requires both.
Narrow the result with typed filters
Filters are deterministic record predicates over normalized rows, not SQL clauses. Add a filter object: a relative time_filter window (last_quarter, this_quarter, last_month, this_month, last_year, this_year) and a list of conditions. Each condition names a column (or a dimension_hint when the exact name is unknown), an op from the fixed set — eq, in, gt, gte, lt, lte, is_null, is_not_null — and a value (or values for in).
result = httpx.post(
f"{base}/v1/query",
headers=headers,
json={
"source_name": "Bookings",
"metric_column": "measure_total",
"group_column": "dimension_label",
"aggregation": "sum",
"limit": 5,
"filter": {
"time_filter": "last_quarter",
"conditions": [
{"column": "status", "op": "in", "values": ["paid", "shipped"]},
{"column": "amount", "op": "gte", "value": 100},
{"column": "cancelled_at", "op": "is_null"},
],
},
},
).json()
print(result["row_count"], result["result"])Verify: the request returns 200 and row_count reflects the narrowed set. The operator grammar is enforced — op: "in" requires values (not value), is_null/is_not_null accept neither, and every other op requires value; a malformed condition is rejected with a 422 before execution.
Read the audit fields
Every governed result carries the proof of what ran. Read validated, plan_hash, and schema_revision from the response — identical intent over the same schema revision produces an identical plan_hash every time, which is what makes the number replayable and auditable.
print("validated:", result["validated"])
print("plan_hash:", result["plan_hash"])
print("schema_revision:", result["schema_revision"])
print("decision_path:", result["decision_path"])Verify: validated is true, plan_hash is a stable hex string, and schema_revision is populated. Run the same request again — plan_hash must be identical. If it changed, the underlying schema changed (a new schema_revision); re-resolve before comparing numbers across runs.
Expected result: POST /v1/query returns 200 with exact_spec: true, validated: true, your grouped rows under result, a stable plan_hash, and a populated schema_revision. Re-running the identical request yields the same plan_hash — a deterministic, auditable answer over governed data.
Troubleshooting
Next
Governed data & queryAPI endpoint referenceLast updated
Was this helpful?