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 — rows for a projection, groups for a group by / agg result.

  • 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 null on 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, or export.

  • Name
    status
    Type
    string
    Description

    pending, running, complete, failed, or partial. A partial run 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.


POST/v1/lab/query

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 from clause plus stages. Obtain one from POST /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

POST
/v1/lab/query
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"
}

POST/v1/lab/query/validate

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

POST
/v1/lab/query/validate
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 }

POST/v1/lab/dsl/parse

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

POST
/v1/lab/dsl/parse
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\"...\""] }] }

POST/v1/lab/dsl/print

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

POST
/v1/lab/dsl/print
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"
}

POST/v1/lab/transforms/preview

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/where selection plus a set stage. Build it in the Blocks builder or parse PassQL that contains a set.

Request

POST
/v1/lab/transforms/preview
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"
    }
  ]
}

POST/v1/lab/transforms

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

POST
/v1/lab/transforms
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"
}

GET/v1/lab/runs

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

GET
/v1/lab/runs
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"
}

GET/v1/lab/runs/:run

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

GET
/v1/lab/runs/4210
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"
}

GET/v1/lab/runs/:run/changes

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, or conflict.

Request

GET
/v1/lab/runs/4210/changes
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"
}

POST/v1/lab/runs/:run/rollback

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

POST
/v1/lab/runs/4210/rollback
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
}

GET/v1/lab/usage

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

GET
/v1/lab/usage
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"
}

Was this page helpful?