Skip to main content
Preview. Part of the Migration API — in active design and not yet generally available. Endpoints and payloads may change. See the overview.
When an import can’t validate some rows on its own, it pauses at needs_attention instead of finishing dirty. The poll response then carries everything you need to render the decision in your own product — a set of plain-language questions and a proposed_patch Vern would apply — and this endpoint is how you send the answer back. Nothing is changed while a run waits here. The import is suspended; your answer (or rejection) is what resumes it.
POST https://app.vern.so/api/v1/companies/{id}/imports/{run_id}/decisions

Authentication

Requires an x-api-key header. See Authentication.

What you get to render

A needs_attention poll response looks like this:
{
  "status": "needs_attention",
  "report": {
    "inserted": 1438,
    "perSheet": [{ "templateName": "Contacts", "sheetId": "7a1c2b3d-4e5f-4061-8a9b-0c1d2e3f4a5b", "rowCount": 1438 }],
    "invalidCellCount": 15
  },
  "needs_attention": {
    "questions": [
      {
        "id": "fq0",
        "prompt": "Some Status values aren't one of the allowed options. How should Vern handle them?",
        "context": "12 rows in Contacts have a Status outside Open / Closed / Pending.",
        "target": {
          "sheet_id": "7a1c2b3d-4e5f-4061-8a9b-0c1d2e3f4a5b",
          "column": "Status",
          "affected_count": 12,
          "sample_values": ["open", "in progress", "DONE"],
          "error": "Does not match allowed values: Open, Closed, Pending"
        },
        "relevant_columns": ["Status"],
        "options": [
          { "id": "a", "label": "Map all to 'Open'" },
          { "id": "b", "label": "Remove these rows" }
        ],
        "allow_custom": true
      }
    ],
    "proposed_patch": {
      "summary": "Map 12 out-of-range Status values to 'Open'; drop the rows missing Email.",
      "actions": [
        { "sheet_id": "7a1c2b3d-4e5f-4061-8a9b-0c1d2e3f4a5b", "column": "Status", "action": "set_value", "value": "Open" },
        { "sheet_id": "7a1c2b3d-4e5f-4061-8a9b-0c1d2e3f4a5b", "column": "Email", "action": "delete_invalid_rows" }
      ]
    }
  }
}

The questions

Each question is self-describing — you can render it in your UI without knowing anything about Vern’s internals.
id
string
Stable identifier you echo back in your answer.
prompt
string
The question, in plain language. Show it as the field label.
context
string
One line on why it’s being asked — good as helper text under the prompt.
target
object
What the question is about: sheet_id, column, affected_count (how many rows), sample_values (a few real offending values), and error (the validation rule that failed). Use it to show the customer exactly which data is in question.
relevant_columns
string[]
The column(s) this question concerns. Use it to highlight or scope a data preview — the same way Vern’s own import UI focuses the customer on exactly the cells in question.
options
object[]
Suggested answers, each { id, label }. Render them as choices (radio buttons, a select, buttons).
allow_custom
boolean
When true, let the customer type a free-text answer instead of picking an option.

The proposed_patch

Vern’s best-guess fix, ready to apply as-is. summary is a human-readable sentence describing the whole fix (good for an “Apply suggested fix” button); actions[] is the per-column breakdown — one entry per flagged group. Each action is one of:
actionEffect
set_valueSet the flagged cells in column to value (use "" to clear a non-required column).
delete_invalid_rowsDrop the rows whose column is invalid.
dedup_keep_firstKeep the first row of each duplicate group on key_column, drop the rest.
keepLeave the rows as-is — they stay flagged (and excluded from a valid-only download).
You can present the proposed patch as a single “Apply suggested fix” button (approve with no answers) and the questions as the “let me decide” path.
The patch can contain destructive actions — delete_invalid_rows and dedup_keep_first drop rows. The actions[] list isn’t always one-to-one with the questions: an action can drop rows the questions never asked about. If your end-customer is the one approving, render the full actions[] list (not just the summary) so they can see exactly what will be removed before they click Apply suggested fix.

Request body

decision
string
required
approve to apply a fix and re-run, or reject to end the run.
answers
object[]
Only with approve, and optional. Omit it to accept the proposed_patch unchanged. To override, send one entry per question you want to answer, each with the question_id and either an option_id (a choice you picked) or a custom string (free text, when allow_custom is true).
A fix you approve here applies to this run only and never changes how other customers’ imports behave. To make a fix part of a source’s default logic for every future migration, re-author and re-publish the plan in the Vern UI — see Core concepts.

Approve

{
  "decision": "approve",
  "answers": [
    { "question_id": "fq0", "option_id": "a" },
    { "question_id": "fq1", "custom": "Map to the company domain" }
  ]
}
Vern applies the answers (or the proposed patch, if answers is omitted) to this run and re-runs the import. If the fix clears every flagged cell the run goes to completed; if some rows still fail, it parks at needs_attention again with fresh questions.

Reject

{ "decision": "reject" }
The run ends failed with its invalid-cell report intact. Use this when you’d rather fix the source data and re-import, or download just the valid rows.

Response

200 OK
{
  "run_id": "d4c3b2a1-9f8e-47d6-b5a4-c3d2e1f0a9b8",
  "status": "running",
  "poll_url": "/api/v1/companies/c0a8012e-4f1b-4d3a-9b2c-7e6f5a4b3c2d/imports/d4c3b2a1-9f8e-47d6-b5a4-c3d2e1f0a9b8"
}
Resume polling poll_url until the run reaches a terminal status.

Errors

StatusMeaning
400Malformed body, or an answer references an unknown question_id.
401API key missing or invalid.
404No such run, or the company isn’t in your account.
409The run isn’t in needs_attention (already resolved, or never paused).
429Rate limit hit — back off and retry.
500Server error.

Example

curl -X POST \
  https://app.vern.so/api/v1/companies/c0a8012e-4f1b-4d3a-9b2c-7e6f5a4b3c2d/imports/d4c3b2a1-9f8e-47d6-b5a4-c3d2e1f0a9b8/decisions \
  -H "x-api-key: $VERN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "decision": "approve",
    "answers": [{ "question_id": "fq0", "option_id": "a" }]
  }'

Next