> ## 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.

# Executing NQL via the API

> Run a single NQL statement as a workflow with POST /v1/nql/execute and track it to completion

`POST /v1/nql/execute` runs a single NQL statement as a [workflow](/guides/workflows/workflow-orchestration). The platform creates a one-task workflow around your statement, starts a run immediately, and returns the workflow and run identifiers so you can track it like any other workflow run.

This guide covers what the endpoint accepts, what it returns, and how to follow an execution through to its result.

<Note>
  `/v1/nql/execute` replaces the deprecated `POST /nql/run` endpoint. If you have existing code calling `/nql/run`, see [Migrating from /nql/run to /v1/nql/execute](/guides/nql/migrating-from-nql-run).
</Note>

## Prerequisites

* A Narrative API token
* Basic familiarity with [NQL syntax](/nql/general/syntax)
* Familiarity with [workflows](/guides/workflows/workflow-orchestration) is helpful but not required

## What the endpoint does

* Runs one NQL statement per request: `CREATE MATERIALIZED VIEW`, `INSERT`, `UPDATE`, `DELETE`, or `EXPLAIN`
* Wraps the statement in a one-task workflow and starts a run immediately
* Returns `200` with the created workflow and a `run_id`
* Honors `data_plane_id`, `compute_pool_id`, and `create_as_view`
* Turns a `REFRESH_SCHEDULE` on a `CREATE MATERIALIZED VIEW` statement into a workflow schedule, so scheduled refreshes run as workflow runs

## What the endpoint does not do

* **It does not return query results.** Bare `SELECT` statements are rejected with a `400`. To query data and read the results back, create a materialized view and [request a sample](/guides/sdk/managing-datasets#sampling-data) from the resulting dataset.
* **It does not return the created dataset.** For `CREATE MATERIALIZED VIEW`, the dataset is created when the run executes, after the response is sent. See [Getting the created dataset](#getting-the-created-dataset).
* **It does not return a job id.** The workflow creates its job shortly after the run starts. See [Reaching the job](#reaching-the-job).
* **It does not run `MERGE`.** `MERGE ON` deduplication is part of [materialized view refresh behavior](/guides/nql/incremental-upserts), not a standalone statement.

## Executing a statement

```bash theme={null}
curl -X POST https://api.narrative.io/v1/nql/execute \
  -H "Authorization: Bearer $NIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"nql": "CREATE MATERIALIZED VIEW \"active_users_by_region\" AS SELECT \"user_data\".\"region\", COUNT(1) AS \"user_count\" FROM \"user_data\" GROUP BY \"user_data\".\"region\""}'
```

The response is the created workflow plus the id of the run that just started:

```json theme={null}
{
  "id": "93bba8c5-6db4-4c36-af3b-502e0bff268f",
  "name": "execute-nql-787b42e4-fe12-4a8e-a2f6-ed3d5e9b1b17",
  "run_id": "019ff635-5aee-7949-8e6a-8c47efa7fc56",
  "status": "active",
  "specification": "document:\n  dsl: 1.0.0\n  ..."
}
```

`id` is the **workflow id** and `run_id` is the **run id**. You need both to track the execution.

### Request parameters

| Parameter         | Required | Description                                                                                                                          |
| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `nql`             | Yes      | The NQL statement to run                                                                                                             |
| `data_plane_id`   | No       | The [data plane](/concepts/primitives/data-planes) to run on                                                                         |
| `compute_pool_id` | No       | The [compute pool](/concepts/primitives/compute-pools) that executes the statement                                                   |
| `create_as_view`  | No       | For `CREATE MATERIALIZED VIEW` only: register the result as a view dataset instead of materializing it. Ignored for other statements |

## Tracking the run

Poll the workflow's runs to see whether the statement has finished:

```bash theme={null}
curl https://api.narrative.io/workflows/{workflow_id}/runs \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

```json theme={null}
{
  "runs": [
    {
      "run_id": "019ff635-5aee-7949-8e6a-8c47efa7fc56",
      "status": "running",
      "start_time": "2026-08-12T13:41:59Z",
      "close_time": null
    }
  ]
}
```

If you only need to know whether the statement succeeded, the run status (`running`, `completed`, `failed`) is enough and you can stop here.

## Reaching the job

The workflow's task creates a job shortly after the run starts — typically within seconds. If you need job-level detail (state transitions, failures, the result payload), list jobs by the run id:

```bash theme={null}
curl "https://api.narrative.io/jobs?workflow_run_id={run_id}" \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

```json theme={null}
{
  "records": [
    {
      "job_id": "5622058d-38c8-4eca-8c94-ba5c9c70a87f",
      "type": "materialize-view",
      "state": "pending",
      "workflow_id": "93bba8c5-6db4-4c36-af3b-502e0bff268f",
      "workflow_run_id": "019ff635-5aee-7949-8e6a-8c47efa7fc56"
    }
  ]
}
```

<Warning>
  The job identifier field is `job_id`, not `id`. Code ported from `/nql/run` that reads `id` gets `undefined` rather than an error.
</Warning>

Because the job is created moments after the run starts, an immediate query can return an empty list — poll until the job appears. The job carries `workflow_id` and `workflow_run_id` pointing back at the workflow, so the correlation works in both directions; `workflow_id` alone returns every job across all runs of that workflow. See [Tracking Job Status](/guides/sdk/tracking-jobs) for polling patterns and backoff.

## Getting the created dataset

For `CREATE MATERIALIZED VIEW`, the dataset does not exist when the response is sent — it is created when the run executes. Two ways to get it:

1. **Await the run, then resolve the dataset by name.** You named the view in the statement, so once the run reaches `completed`, look the dataset up by that name.
2. **Poll the job and read the result.** Once the job reaches `completed`, its result carries the dataset id:

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

   Then fetch it with `GET /datasets/42558`.

## Canceling a run

Cancel an in-flight execution through the workflow run cancel endpoint:

```bash theme={null}
curl -X POST https://api.narrative.io/workflows/{workflow_id}/runs/{run_id}/cancel \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

## Errors

Errors are returned as RFC 7807 problem documents with `title`, `status`, `detail`, `instance`, `log_id`, and a `debug` object.

| Status | When                                                                                                                                                                                               |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The statement failed to compile, or is a statement type the endpoint cannot run (bare `SELECT`, `MERGE`)                                                                                           |
| `422`  | The statement parsed but is semantically invalid — for example, a materialized view output column with an [unsupported type](/guides/nql/troubleshooting/unsupported-type-error) such as `DECIMAL` |
| `403`  | The company has no usage credit for the target data plane, or the statement would create a view under a name that already exists                                                                   |

See [Troubleshooting NQL](/guides/nql/troubleshooting) for how to read these error bodies.

## Related content

<CardGroup cols={2}>
  <Card title="Migrating from /nql/run" icon="arrow-right-arrow-left" href="/guides/nql/migrating-from-nql-run">
    Port existing callers of the deprecated endpoint
  </Card>

  <Card title="Workflow Orchestration" icon="diagram-project" href="/guides/workflows/workflow-orchestration">
    Chain multiple 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="Materialized View Syntax" icon="layer-plus" href="/nql/commands/create-materialized-view">
    Complete reference for CREATE MATERIALIZED VIEW
  </Card>
</CardGroup>
