> ## Documentation Index
> Fetch the complete documentation index at: https://docs.narrative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from /nql/run to /v1/nql/execute

> Port callers of the deprecated POST /nql/run endpoint to POST /v1/nql/execute

`POST /nql/run` is deprecated. Its replacement, [`POST /v1/nql/execute`](/guides/nql/executing-nql-via-api), accepts the same statements and returns the same errors, but what you get back is a different object — a workflow and a run instead of a job. This guide covers why the change was made and exactly what to update in a caller.

<Info>
  Deprecated does not mean removed. `/nql/run` continues to work; the support window will be announced separately.
</Info>

## Why the change

NQL execution now runs on the same workflow engine as the rest of the platform — one execution model instead of two.

Most real requests are not one statement. Materializing an interactive query also needs a sample computed before anyone sees output. Registering a supplier dataset means creating mappings, then refreshing. Under the job model, the caller owned that orchestration: fire a job, poll it, decide whether to fire the next one, and work out what to do when step three of five fails. Every consumer reimplemented the same retry and partial-failure logic, slightly differently.

A [workflow](/guides/workflows/workflow-orchestration) makes the whole request one durable unit the platform owns. `/v1/nql/execute` is the single-statement case of that model, and the same task composes into larger multi-step workflows without the caller changing transport. Every execution is now a first-class run with an id, a status, start and close times, a cancel endpoint, and a schedule if it needs one — instead of a fire-and-forget job whose only control was request-cancellation.

The trade-off is real: for a genuinely single-step request, `/v1/nql/execute` is *more* work than `/nql/run`. The created dataset is no longer in the response, and reaching the job id takes an extra call. The sections below show the patterns that close both gaps.

## What changes

|                                                      | `POST /nql/run`                                                     | `POST /v1/nql/execute`                      |
| ---------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------- |
| Success status                                       | `201`                                                               | `200`                                       |
| Returns                                              | a **job** (`id` is the job id)                                      | a **workflow** plus `run_id`                |
| Created dataset in body                              | Yes, for `CREATE MATERIALIZED VIEW`                                 | No — resolve it after the run               |
| Supported statements                                 | `CREATE MATERIALIZED VIEW`, `INSERT`, `UPDATE`, `DELETE`, `EXPLAIN` | Same                                        |
| `MERGE`, bare `SELECT`                               | Rejected, `400`                                                     | Rejected, `400` (same body)                 |
| `create_as_view`, `compute_pool_id`, `data_plane_id` | Honored                                                             | Honored                                     |
| `REFRESH_SCHEDULE` on a view                         | Honored                                                             | Honored — becomes a workflow schedule       |
| Cancellation                                         | Job-level request-cancellation                                      | `POST /workflows/{id}/runs/{run_id}/cancel` |
| Error bodies                                         | RFC 7807                                                            | Identical, apart from `instance`            |

The statement set and the errors are unchanged. The two things that actually require code changes are reaching the job id and getting the created dataset.

## Reaching the job id

The execute response has no job id. The workflow's task creates the job shortly after the run starts, so you discover it through the jobs API:

```text theme={null}
POST /v1/nql/execute                    -> { "id": <workflow_id>, "run_id": <run_id>, ... }
GET  /jobs?workflow_run_id=<run_id>     -> { "records": [ { "job_id": ..., "state": ... } ] }
GET  /jobs/<job_id>                     -> state, result, failures
```

Three things to know:

* **The field is `job_id`, not `id`.** `/nql/run` returns the job id as `id`, so a straight port reads the wrong key and gets `undefined` rather than an error. This is the most likely source of a silent bug in this migration.
* **You may not need the job at all.** `GET /workflows/{workflow_id}/runs` reports run status (`running`, `completed`, `failed`). If you only need to know whether the statement finished, the run is enough — skip `/jobs` entirely.
* **Correlation works in both directions.** The job carries `workflow_id` and `workflow_run_id` pointing back at the workflow, and `GET /jobs?workflow_id=<workflow_id>` returns every job across all runs of that workflow. See [Filtering by workflow](/guides/sdk/tracking-jobs#filtering-by-workflow).

## Getting the created dataset

`/nql/run` returned the whole dataset inline for `CREATE MATERIALIZED VIEW`, so callers could update local state before the statement had run. `/v1/nql/execute` does not — the dataset does not exist yet when the response is sent. Two ways to get it, depending on whether you can wait:

1. **Await the run, then resolve the dataset by name.** You named the view in the statement, so the name is already known to your code. Once the run reaches `completed`, look the dataset up by name.
2. **Poll the job and read the id out of its result.** Once the job reaches `completed`:

   ```json theme={null}
   {
     "state": "completed",
     "result": { "dataset_id": 42558, "snapshot_id": 6007775799525750167 }
   }
   ```

   then `GET /datasets/42558`.

Either way the dataset arrives *after* the statement runs rather than with the response, so an optimistic "here is your new dataset" render has nothing to render until the run finishes.

## Stop sending `execution_cluster`

Placement runs on [compute pools](/concepts/primitives/compute-pools) now, and `compute_pool_id` is honored identically by both endpoints. `execution_cluster` is a legacy field that no longer decides where work runs, and it is not in the documented request schema for either endpoint. If your caller still sets it, that code is dead — the migration is a good moment to delete it.

## Errors need no changes

Both endpoints return the same RFC 7807 problem documents: same status codes, same `title`, same `detail`, same `debug`. Only `instance` (`/v1/nql/execute` instead of `/nql/run`) and the per-request `log_id` differ. Existing error handling keeps working as-is. See [Troubleshooting NQL](/guides/nql/troubleshooting) for how to read these bodies.

## Worked example

A complete migration sequence, trimmed to the interesting fields:

```text theme={null}
POST /v1/nql/execute
     {"nql": "CREATE MATERIALIZED VIEW \"qa_view\" AS SELECT 1 AS \"a\", 2 AS \"b\""}
200
     { "id": "93bba8c5-...", "run_id": "019ff635-...", "status": "active", ... }

GET /workflows/93bba8c5-.../runs
     {"runs": [{"run_id": "019ff635-...", "status": "running", "close_time": null}]}

GET /jobs?workflow_run_id=019ff635-...          # seconds after the run starts
     {"records": [{"job_id": "5622058d-...", "type": "materialize-view", "state": "pending"}]}

GET /jobs/5622058d-...                          # once the run completes
     {"job_id": "5622058d-...", "state": "completed",
      "result": {"dataset_id": 42558, "snapshot_id": 6007775799525750167}}
```

## Related content

<CardGroup cols={2}>
  <Card title="Executing NQL via the API" icon="play" href="/guides/nql/executing-nql-via-api">
    Full guide to /v1/nql/execute — parameters, tracking, and errors
  </Card>

  <Card title="Workflow Orchestration" icon="diagram-project" href="/guides/workflows/workflow-orchestration">
    Compose multi-step operations into one durable workflow
  </Card>

  <Card title="Tracking Job Status" icon="list-check" href="/guides/sdk/tracking-jobs">
    Polling patterns, backoff, and job result handling
  </Card>

  <Card title="Troubleshooting NQL" icon="wrench" href="/guides/nql/troubleshooting">
    Reading RFC 7807 error responses
  </Card>
</CardGroup>
