Lab
Lab is the analytical engine over your passport data. It runs PassQL — read queries and bulk transforms — against a Postgres read model, so nothing here touches the passport store directly and every call is metered Postgres-only · 0 RCU. Run and validate queries, translate PassQL to and from an AST, preview and execute transforms, then track every change and roll the latest one back.
Lab must be enabled for your team in the app; until it is, every Lab endpoint returns 409 lab_not_enabled. Read operations require the lab:read ability. Previewing or executing a transform, and rolling one back, additionally require lab:write and a key owned by a team admin or owner; transforms also need your team to be on the deployment's transform allowlist, otherwise 403 transforms_not_enabled.
Most write bodies take an ast — the compiled PassQL query. You rarely hand-write one: build it in the visual Blocks builder, or write PassQL text and parse it into an AST. The bundle types ast as a free-form object; the shapes shown below are illustrative of what parse emits.
The query result model
The result of running a query. Row queries return a columns/rows grid; grouped queries return one row per group. Every result carries a watermark — the "data as of" freshness marker for the read model.
- Name
object- Type
- string
- Description
Always
lab.query_result.
- Name
run_id- Type
- integer
- Description
Integer id of the run this query was recorded as (e.g.
4207). Queries are runs too.
- Name
mode- Type
- string
- Description
The result shape —
rowsfor a projection,groupsfor agroup by/aggresult.
- Name
columns- Type
- array
- Description
Ordered column descriptors for the result set (dimensions and property references).
- Name
rows- Type
- array
- Description
The result rows. Values come back in their raw PassQL shape — numbers as numbers, tags as lists.
- Name
next_cursor- Type
- string
- Description
Keyset cursor for the next page of rows, or
nullon the last page. See Pagination. Nullable.
- Name
duration_ms- Type
- integer
- Description
Server-side execution time in milliseconds.
- Name
cached- Type
- boolean
- Description
Whether the result was served from the auto-invalidating query cache.
- Name
watermark- Type
- integer or string
- Description
"Data as of" marker for read-model freshness. Nullable.
The run model
Every query, transform, rollback, backfill, and export is recorded as a run. Transforms and rollbacks are asynchronous: execute hands you a run and you poll retrieve a run until status is terminal (complete, failed, or partial).
- Name
object- Type
- string
- Description
Always
lab.run.
- Name
id- Type
- integer
- Description
The run's id — a plain integer (e.g.
4210), not a prefixed string.
- Name
type- Type
- string
- Description
query,transform,rollback,backfill, orexport.
- Name
status- Type
- string
- Description
pending,running,complete,failed, orpartial. Apartialrun applied some passports and skipped or conflicted on others.
- Name
total_count- Type
- integer
- Description
Passports the run targets. Nullable.
- Name
processed_count- Type
- integer
- Description
Passports processed so far. Nullable.
- Name
failed_count- Type
- integer
- Description
Passports whose assignment failed and was skipped. Nullable.
- Name
conflict_count- Type
- integer
- Description
Passports flagged as conflicts (changed out from under the run). Nullable.
- Name
result_count- Type
- integer
- Description
Rows returned, for query runs. Nullable.
- Name
duration_ms- Type
- integer
- Description
Execution time in milliseconds. Nullable.
- Name
dsl_text- Type
- string
- Description
The PassQL source stored with the run, when supplied. Nullable.
- Name
rolls_back_run_id- Type
- integer
- Description
For a rollback run, the transform run it reverses. Nullable.
- Name
rolled_back_by_run_id- Type
- integer
- Description
For a transform run, the rollback run that reverted it. Nullable.
- Name
rollback_eligible- Type
- boolean
- Description
Whether this run is the team's latest completed, non-rolled-back transform and can currently be rolled back.
- Name
cache_hit- Type
- boolean
- Description
Whether a query run was served from the cache.
- Name
created_at- Type
- string
- Description
ISO 8601 UTC timestamp of creation. Nullable.
- Name
updated_at- Type
- string
- Description
ISO 8601 UTC timestamp of the last update. Nullable.
Run a query
Executes a PassQL read query over the Lab read model and returns a columns/rows result with a freshness watermark. Send the compiled ast; optionally include the dsl_text it came from, which is stored on the resulting run for display. Requires lab:read. A query that fails to compile returns 422 with the error envelope whose error.errors lists the offending stages.
Required attributes
- Name
ast- Type
- object
- Description
The compiled PassQL query AST — a
fromclause plus stages. Obtain one fromPOST /v1/lab/dsl/parse, or let the Blocks builder emit it.
Optional attributes
- Name
dsl_text- Type
- string
- Description
The PassQL source the AST came from. Stored on the run so the query reads back in the UI; ignored for execution.
Request
curl https://api.synexcloud.com/v1/lab/query \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ast": {
"from": { "ontologies": ["ontology_00a1b2c3d4e5f607"], "depth": ["C"] },
"where": [{ "property": "Weight", "op": ">", "value": 10 }]
},
"dsl_text": "from ontology:\"Battery X\" depth:C | where p\"Weight\" > 10"
}'
Response
{
"object": "lab.query_result",
"run_id": 4207,
"mode": "rows",
"columns": ["friendly_id", "tppmp", "Weight"],
"rows": [
["EU-00417", "tppmp_9f2k7d", 12.5],
["EU-00419", "tppmp_3a8c1e", 41.0]
],
"next_cursor": "cursor_c2Vjb25kcGFnZQ",
"duration_ms": 84,
"cached": false,
"watermark": "2026-07-16T11:59:40Z"
}
Validate a query
Compile-checks an ast without executing it — the fast way to confirm a query is well formed before you run or preview it. Requires lab:read. A valid AST returns the mode it would produce; an invalid one returns 422 with valid: false and the compile feedback.
Required attributes
- Name
ast- Type
- object
- Description
The PassQL AST to compile-check.
Request
curl https://api.synexcloud.com/v1/lab/query/validate \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ast": {
"from": { "ontologies": ["ontology_00a1b2c3d4e5f607"], "depth": ["C"] },
"where": [{ "property": "Weight", "op": ">", "value": 10 }]
}
}'
Response
{
"valid": true,
"mode": "rows"
}
// On failure the endpoint returns 422 with { "valid": false, ...details }
Parse PassQL to an AST
Parses PassQL source text into the ast the other endpoints consume, resolving ontology names in the from clause to ontology ids along the way. This is the usual way to obtain an AST outside the Blocks builder: write PassQL, parse it, then hand the AST to run, validate, or preview. Requires lab:read. Invalid PassQL returns 422 with positioned errors — each carries a message, a byte offset, a length, and what was expected.
Required attributes
- Name
dsl_text- Type
- string
- Description
The PassQL source to parse.
Request
curl https://api.synexcloud.com/v1/lab/dsl/parse \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "dsl_text": "from ontology:\"Battery X\" depth:C | where p\"Weight\" > 10" }'
Response
{
"valid": true,
"kind": "query",
"ast": {
"from": { "ontologies": ["ontology_00a1b2c3d4e5f607"], "depth": ["C"] },
"where": [{ "property": "Weight", "op": ">", "value": 10 }]
}
}
// On a parse error, 422 returns:
// { "valid": false, "errors": [{ "message": "expected a property", "offset": 42, "length": 6, "expected": ["p\"...\""] }] }
Print an AST as PassQL
The inverse of parse: renders an ast back into canonical PassQL text. Useful for showing a Blocks-built query as source, or for round-tripping a machine-built AST into something readable. Requires lab:read.
Required attributes
- Name
ast- Type
- object
- Description
The AST to render as PassQL.
Request
curl https://api.synexcloud.com/v1/lab/dsl/print \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ast": {
"from": { "ontologies": ["ontology_00a1b2c3d4e5f607"], "depth": ["C"] },
"where": [{ "property": "Weight", "op": ">", "value": 10 }]
}
}'
Response
{
"dsl_text": "from ontology:\"Products\" depth:C | where p\"Weight\" > 10"
}
Preview a transform
Dry-runs a transform — a query whose AST carries a set stage — and returns its projected impact (how many passports it would touch, plus a before/after sample) together with a preview_id. Nothing is written. You must pass that preview_id back to execute within 15 minutes; after that it expires.
Requires lab:write and a key owned by a team admin or owner, and your team must be on the deployment's transform allowlist — otherwise 403 transforms_not_enabled. See PassQL's | set stage for how to build a transform AST; setting publish_status pins it, and clearing it lets the passport inherit again.
Required attributes
- Name
ast- Type
- object
- Description
A transform AST — a
from/whereselection plus asetstage. Build it in the Blocks builder or parse PassQL that contains aset.
Request
curl https://api.synexcloud.com/v1/lab/transforms/preview \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ast": {
"from": { "ontologies": ["ontology_00a1b2c3d4e5f607"] },
"where": [{ "property": "Origin", "op": "is_empty" }],
"set": [{ "property": "Origin", "value": "EU" }]
}
}'
Response
{
"object": "lab.transform_preview",
"preview_id": "labprev_5f6071829abc",
"affected_count": 128,
"expires_at": "2026-07-16T12:15:00Z",
"sample": [
{
"passport": "pass_00a1b2c3d4e5f607",
"property_id": "property_7c3e1a",
"old_value": null,
"new_value": "EU"
}
]
}
Execute a transform
Executes a transform you have just previewed. Pass the same ast along with the preview_id you were handed; if the preview has expired or no longer matches the AST, the call fails with 409 preview_mismatch — so you always confirm a fresh impact before anything is written. Values are recomputed from live passport data at apply time, and the run proceeds asynchronously: it returns a run_id you poll via retrieve a run.
Requires lab:write and a team admin or owner, the same gating as preview. Accepts an optional Idempotency-Key header.
Required attributes
- Name
ast- Type
- object
- Description
The transform AST — must match the one you previewed.
- Name
preview_id- Type
- string
- Description
The token from the matching preview, still within its 15-minute window.
Optional attributes
- Name
dsl_text- Type
- string
- Description
The PassQL source, stored on the run for display.
Request
curl https://api.synexcloud.com/v1/lab/transforms \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1b2c3d4-transform-01" \
-d '{
"ast": {
"from": { "ontologies": ["ontology_00a1b2c3d4e5f607"] },
"where": [{ "property": "Origin", "op": "is_empty" }],
"set": [{ "property": "Origin", "value": "EU" }]
},
"preview_id": "labprev_5f6071829abc",
"dsl_text": "from ontology:\"Products\" | where p\"Origin\" is_empty | set p\"Origin\" = \"EU\""
}'
Response
{
"object": "lab.run",
"run_id": 4210,
"status": "pending"
}
List runs
Lists your team's 50 most recent Lab runs — queries, transforms, rollbacks, and exports — newest first. This is a fixed-size window rather than a paginated cursor list. Returns a list envelope of run objects. Requires lab:read.
Request
curl https://api.synexcloud.com/v1/lab/runs \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "list",
"data": [
{
"object": "lab.run",
"id": 4210,
"type": "transform",
"status": "complete",
"total_count": 128,
"processed_count": 128,
"failed_count": 0,
"conflict_count": 0,
"result_count": null,
"duration_ms": 5120,
"dsl_text": "from ontology:\"Products\" | where p\"Origin\" is_empty | set p\"Origin\" = \"EU\"",
"rolls_back_run_id": null,
"rolled_back_by_run_id": null,
"rollback_eligible": true,
"cache_hit": false,
"created_at": "2026-07-16T12:02:00Z",
"updated_at": "2026-07-16T12:02:05Z"
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/lab/runs"
}
Retrieve a run
Retrieves one run by its integer run id. Poll this after executing a transform or rollback until status reaches a terminal value — complete, failed, or partial (the counts break down what applied, failed, or conflicted). Requires lab:read.
Request
curl https://api.synexcloud.com/v1/lab/runs/4210 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "lab.run",
"id": 4210,
"type": "transform",
"status": "complete",
"total_count": 128,
"processed_count": 128,
"failed_count": 0,
"conflict_count": 0,
"result_count": null,
"duration_ms": 5120,
"dsl_text": "from ontology:\"Products\" | where p\"Origin\" is_empty | set p\"Origin\" = \"EU\"",
"rolls_back_run_id": null,
"rolled_back_by_run_id": null,
"rollback_eligible": true,
"cache_hit": false,
"created_at": "2026-07-16T12:02:00Z",
"updated_at": "2026-07-16T12:02:05Z"
}
List a run's changes
Lists the individual property changes a transform or rollback recorded — up to 200 records, each pinning the passport, the property_id, the old_value/new_value, its status, and an error when the assignment was skipped. Filter with the optional status query parameter to isolate, for instance, only the conflicts. Requires lab:read.
Optional attributes
- Name
status- Type
- string
- Description
Filter to one change status —
pending,applied,failed, orconflict.
Request
curl -G https://api.synexcloud.com/v1/lab/runs/4210/changes \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d status=applied
Response
{
"object": "list",
"data": [
{
"passport": "pass_00a1b2c3d4e5f607",
"property_id": "property_7c3e1a",
"old_value": null,
"new_value": "EU",
"status": "applied",
"error": null
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/lab/runs/4210/changes?status=applied"
}
Roll back a transform
Queues a rollback of a completed transform, restoring every applied change to its recorded old_value. Only the team's latest completed, non-rolled-back transform is eligible — anything else returns 409 not_rollback_eligible, so check rollback_eligible on the run first. Like executing a transform, this runs asynchronously and requires lab:write plus a team admin or owner. Poll the returned rollback run to completion.
Request
curl -X POST https://api.synexcloud.com/v1/lab/runs/4210/rollback \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "lab.run",
"run_id": 4213,
"type": "rollback",
"status": "pending",
"rolls_back_run_id": 4210
}
Retrieve Lab usage
Returns your team's Lab footprint — how much read-model storage it occupies and an accounting of runs by type. Requires lab:read.
Request
curl https://api.synexcloud.com/v1/lab/usage \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "lab.usage",
"storage_bytes": 48210944,
"passport_rows": 128044,
"runs": {
"queries": 512,
"transforms": 24,
"rollbacks": 3
},
"watermark": "2026-07-16T11:59:40Z"
}