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

# Poll a run

> Track a run to completion, read the import report, recover a run you lost, and cancel one in flight.

```http theme={null}
GET   https://app.vern.so/api/v1/migrations/{migration_id}/runs/{run_id}
PATCH https://app.vern.so/api/v1/migrations/{migration_id}/runs/{run_id}
```

Reads the state of a [run](/migration-api/concepts#runs). Poll it on an interval
(≈2s, backing off) until `status` is terminal for its phase. For live progress,
prefer the [thread stream](/migration-api/stream-the-thread).

## Authentication

Requires an `x-api-key` header. See [Authentication](/migration-api/authentication).

## Response by status

Every response carries `run_id`, `status`, and `created_at`. Once you've answered
the agent, the response also echoes `answers` (your reply, read back). The rest
depends on `status`:

<ParamField path="status" type="string">
  One of `queued`, `running`, `blocked`, `awaiting_approval`, `completed`,
  `failed`, or `canceled`.
</ParamField>

| `status`             | Extra fields                                                                                                                                                                                     |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `queued` / `running` | `message` — a human-readable update (e.g. `"Cleaning your data"`, `"Retrying (attempt 2 of 5)"`), or `null`.                                                                                     |
| `blocked`            | `blocked_reason` — `"question"` (the agent needs a decision; carries `questions[]`) or `"credentials"` (a live source needs secrets; carries `credential_request`). See [below](#a-blocked-run). |
| `awaiting_approval`  | *(none)* — a `generate`/`update` run produced a preview. Fetch it from [`GET /preview`](/migration-api/get-the-preview).                                                                         |
| `completed`          | `report` for an `execute` run (see below); nothing extra for a `clarify` run.                                                                                                                    |
| `failed`             | `error` — the agent couldn't produce a working result even after self-healing. For an execute, no rows were inserted.                                                                            |
| `canceled`           | *(none)* — the run was stopped before it finished.                                                                                                                                               |

<ResponseField name="answers" type="object | null">
  Present once you've answered a block — a normalized read-back of your last
  reply, so a UI can show what was submitted. Absent before the first answer.
</ResponseField>

A **completed `execute`** carries a report:

```json theme={null}
{
  "run_id": "e5d4c3b2-...",
  "status": "completed",
  "created_at": "2026-06-19T15:40:02.000Z",
  "report": {
    "inserted": 1450,
    "perSheet": [
      { "templateName": "Contacts", "sheetId": "7a1c2b3d-...", "rowCount": 1450 }
    ],
    "invalidCellCount": 12,
    "coverage": null
  }
}
```

<ResponseField name="report.inserted" type="number">
  Total rows imported across all sheets.
</ResponseField>

<ResponseField name="report.perSheet" type="object[]">
  Per-template counts — `{ templateName, sheetId, rowCount }`.
</ResponseField>

<ResponseField name="report.invalidCellCount" type="number">
  The number of **rows** that still have at least one cell that failed validation
  — not a count of individual cells. The import succeeded; those rows are simply
  flagged. The default CSV download (`?filter=valid`) excludes any flagged row
  entirely, so a single bad cell drops its whole row. See
  [Export CSV](/migration-api/export-csv).
</ResponseField>

<ResponseField name="report.coverage" type="object | null">
  An optional value-completeness profile — per-column fill/distinct stats for the
  source inputs and the imported outputs — so a hollow import (right row count,
  dropped fields) is detectable programmatically. `null` when profiling wasn't
  available for the run.
</ResponseField>

## A blocked run

A `blocked` run is waiting on you. **`blocked_reason` tells you which kind of
block it is** and which sub-resource resumes it:

| `blocked_reason` | Carries              | Resume with                                                 |
| ---------------- | -------------------- | ----------------------------------------------------------- |
| `question`       | `questions[]`        | [`POST .../messages`](/migration-api/answer-questions)      |
| `credentials`    | `credential_request` | [`POST .../credentials`](/migration-api/submit-credentials) |

### A question block

`blocked_reason: "question"` carries the `questions` you **render in your own
UI**. Each is self-describing — `{ id, question, context?, options[], allowCustom }`:

```json theme={null}
{
  "run_id": "d4c3b2a1-...",
  "status": "blocked",
  "created_at": "2026-06-19T15:32:11.000Z",
  "blocked_reason": "question",
  "questions": [
    {
      "id": "q1",
      "question": "Some Status values aren't one of the allowed options (Open, Closed, Pending). How should I handle them?",
      "options": [
        { "id": "a", "label": "Map them all to 'Open'" },
        { "id": "b", "label": "Drop those rows" }
      ],
      "allowCustom": true
    }
  ]
}
```

Answer through the run's [messages](/migration-api/answer-questions) sub-resource —
not the poll. The agent resumes and may block again with a fresh question.

### A credential block

`blocked_reason: "credentials"` means a [connected live
source](/migration-api/source-connection) needs secrets. The response carries a
`credential_request` — the connection it's for, plus a `schema` and `guidance`
you render into a form — and **no question**:

```json theme={null}
{
  "run_id": "d4c3b2a1-...",
  "status": "blocked",
  "created_at": "2026-06-19T15:32:11.000Z",
  "blocked_reason": "credentials",
  "credential_request": {
    "connection_id": "9b1f2c3d-...",
    "source_key": "salesforce",
    "source_name": "Salesforce",
    "reason": "I need API access to read your contacts.",
    "guidance": { "summary": "Create a connected app and copy its OAuth credentials." },
    "schema": { "client_id": { "type": "string" }, "client_secret": { "type": "string", "secret": true } }
  }
}
```

Submit the secrets through the dedicated
[credentials](/migration-api/submit-credentials) sub-resource — **never** as a
message answer, which would leak them into the agent thread.

## Recovering a run

If you lose a `run_id` (a crash, a dropped response, a page reload), re-issue the
same [run](/migration-api/start-a-run) you were running. Because **only one run
runs at a time per migration**, the call returns `409` with the active run's ID:

```json theme={null}
{ "error": "A run is already in progress for this migration.", "run_id": "d4c3b2a1-..." }
```

Poll that `run_id` as usual.

<Note>
  A run whose engine dies without writing a terminal status is swept to `failed`
  ("Import timed out") once it goes stale — so a lookup never reports a zombie run
  as forever-running.
</Note>

## Cancel a run

```http theme={null}
PATCH https://app.vern.so/api/v1/migrations/{migration_id}/runs/{run_id}
```

Cancels an in-flight run — it stops the durable workflow and any active import
task. The only accepted body is:

```json theme={null}
{ "status": "cancel_requested" }
```

The response is the run in its updated (`canceled`) shape.

```bash theme={null}
curl -X PATCH https://app.vern.so/api/v1/migrations/c0a8012e-.../runs/d4c3b2a1-... \
  -H "x-api-key: $VERN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "cancel_requested" }'
```

## Errors

| Status | Meaning                                                |
| ------ | ------------------------------------------------------ |
| `400`  | (PATCH) body isn't `{ "status": "cancel_requested" }`. |
| `401`  | API key missing or invalid.                            |
| `404`  | No such run, or the migration isn't in your account.   |
| `429`  | Rate limit hit — back off and retry.                   |
| `500`  | Server error.                                          |

## Example

```bash theme={null}
curl https://app.vern.so/api/v1/migrations/c0a8012e-.../runs/d4c3b2a1-... \
  -H "x-api-key: $VERN_API_KEY"
```

## Next

* [Answer the agent](/migration-api/answer-questions) — resume a question block.
* [Submit credentials](/migration-api/submit-credentials) — resume a credential block.
* [Get the preview](/migration-api/get-the-preview) — read what a generate/update produced.
* [Export CSV](/migration-api/export-csv) — get the validated rows out.
