Skip to main content
A big upload rarely fails outright. It comes back with most rows accepted and a slice refused — a mistyped phone column, a few rows the provider could not parse, some duplicates. This guide is the loop for that slice: find it, download it, fix it, send it back. The file you get is built for exactly that. Each row carries the record you originally uploaded, unchanged, with the outcome columns appended — so you correct the bad column in place and re-upload the same file as a new record set.

Step 1 — Look at what was refused

Start with the jobs feed, scoped to your batch and the outcome you care about:
status accepts any import outcomerejected for rows the provider refused, unresolved for rows whose fate could not be determined, unprocessed for rows a cancellation stopped before they were sent. This is the quick look: page envelope, newest first, good for a screen. For the whole set — and for the provider’s own reason text, which the JSON feed does not carry — request the export.

Step 2 — Request the export

POST /flows/jobs/export takes the same filters as the listing. At least one is required.
The 202 comes back immediately — nothing has been generated yet:
One export at a time per account. A second request while one is still generating returns 409 — wait for the first to finish (or expire) rather than retrying in a loop. Generation is intentionally serialized: an export reads every matching row, and several at once would be a self-inflicted load spike.

Step 3 — Poll until it is ready

status moves pendingrunningready. While it is generating, download_url is null — that is a normal poll, not an error. Once it is ready:
A failed export says why in error_reason: row_cap_exceeded when the filters matched more rows than one export may contain (the cap is 1,000,000 — a whole bulk batch’s rejected rows always fit), source_unavailable when the underlying records are no longer retained, or generation_failed.

Step 4 — Download the file

download_url is a short-lived signed URL — about fifteen minutes — and it needs no API key of its own. Download it straight away:
Never store or share the URL. It is deliberately short-lived and freely re-fetchable: read the export again at any time for a fresh one, for as long as the export lives. Anything that would need a long-lived link should re-read the export instead.
Two lifetimes are in play and they are different: After the file is deleted, request the export again and it is regenerated from the underlying rows — for as long as those rows are inside your account’s retention window.

Step 5 — Read the file

Every row is the record you uploaded, column for column, followed by the outcome columns:
Two header details worth knowing. The record columns come from the rows in the export itself, so a column that only appears on later rows — or one whose name collides with an outcome column, such as your own status field — is preserved in the _extra_json column rather than dropped or duplicated. And group by reject_class before reading prose: it is the stable, countable field, while trouble_message is written for a person.
This file is contact data. Every cell is a record you uploaded, and trouble_message frequently repeats the offending phone number inside the provider’s own sentence. Treat the download exactly as you treat the source file.

Step 6 — Fix and re-upload

Correct the offending column in the CSV, drop the outcome columns, and send it back through the normal bulk ingestion path: create a record set, push the corrected rows as chunks, seal, and trigger.
The corrected upload is an ordinary new batch with its own outcomes — so the same loop applies again if anything is still refused.
Re-uploading corrected rows is a fresh send, and that is the point. Only rows you actually fix should go back. Rows that came back unresolved are a different decision: nobody knows whether they reached your platform the first time, so re-uploading them accepts a possible duplicate contact. Decide that deliberately.

Next steps

Import outcomes

What each outcome means, and how the counters roll up into a batch’s terminal state.

Bulk ingestion

The upload path for the corrected file — manifest, chunks, seal, trigger.