Runs
One pull of a source, from fetch to master records. Triggers and streams, the stages a run goes through, how it ends, the cells it could not read, waiting, replaying a snapshot, and starting a run yourself.
A run (run_…) is one pull of a source: FurniOps fetches what the source holds, reads it,
notes what is missing, and updates your master records. A finished run
never changes again; a failed one is retried by starting a new run.
{
"object": "run",
"id": "run_034XqBaL2mRLTZPXyRbaUe",
"source": "src_034XqBaL2mRLTZPXyRbaUh",
"trigger": "schedule",
"stream": "catalogue",
"status": "succeeded",
"stage": "done",
"stats": {
"fetch.bytes": 482133,
"normalise.products": 1204,
"normalise.problems": 3,
"presence.absent": 2,
"master.changed": 41
},
"error": null,
"waiting": null,
"problems": [
{
"sheet": null,
"line": 418,
"column": "Width",
"field": "width",
"problem": "unparseable",
"detail": "\"see notes\" is not a length"
}
],
"created_at": "2026-10-06T06:00:00Z",
"started_at": "2026-10-06T06:00:02Z",
"finished_at": "2026-10-06T06:01:37Z"
}
What starts a run
trigger | Started by |
|---|---|
schedule | The source's schedule. |
manual | A person or a key: POST /v1/sources/{id}/runs, or Run now on the source's page. |
push | A file that arrived, such as an emailed attachment. |
replay | A replay of the current snapshot, after a mapping or rule change. |
A run pulls one stream: the whole catalogue, or only stock (quantities and arrivals) for a
connector that reads stock apart.
Stages
A run moves through four stages, each committed with its result, so a run that is retried resumes where it stopped:
stage | What happens |
|---|---|
fetch | The source is read, and what it sent is kept as a snapshot. A source that sent exactly what it sent last time ends here as unchanged. |
normalise | Rows are read through the source's mapping into products, variants and offers, cell by cell. |
presence | What the source no longer lists is noted. Absence lowers a product step by step, never straight to discontinued, and a pull far smaller than usual is treated as a broken pull, not mass removal. |
master | Master records are updated, and each change becomes an event in the change feed. |
stage is the next stage the run performs, and done once it has performed them all.
How it ends
status | Meaning |
|---|---|
queued, running | Still in progress. A source has at most one such run. |
succeeded | Done. |
unchanged | The source sent what it sent last time; nothing to do. |
needs_mapping | The file's headers are new: it waits for a person to confirm a mapping, then resumes. |
failed | It stopped; error says why. |
aborted | FurniOps stopped it, such as a run that ran past its limit. |
Errors
error.code | Why the run failed |
|---|---|
fetch_refused | The URL was refused: a private address, or a scheme FurniOps will not fetch. The source is blocked at once. |
fetch_failed | The source could not be reached. |
source_error | It answered with an error. |
credentials | It refused the stored credentials, or none are stored. |
too_large | Its file was larger than the snapshot limit. |
unreadable | Its file could not be read. |
empty | Its file was empty. |
unsupported | FurniOps cannot fetch this kind of source yet. |
no_snapshot | A replay found no snapshot to process. |
timed_out | The run took too long. |
internal | An error on FurniOps' side. It never counts against the source's health. |
error.message says what went wrong in FurniOps' words; the vendor's own answer is never echoed.
Failures count toward the source's health.
Counters
stats holds counters by stage, such as fetch.bytes, normalise.products,
normalise.problems, presence.absent and master.changed. Read them for trends; new counters
may appear.
Problems
A cell FurniOps cannot use does not fail the run: the rest of its row is used, and the cell is
recorded. problems keeps the first 100 in file order; stats["normalise.problems"] counts every
one.
problem | Meaning |
|---|---|
missing | Empty, or a placeholder where a value was needed. |
unparseable | Not readable as the field's kind ("see notes" as a width). |
ambiguous | Readable more than one way. |
implausible | Outside what the field can be. |
conflict | Two columns gave one field different values. |
no_key | The row has no item number. |
Each names its sheet (in a workbook), line, column, the field it maps to, and a detail.
With AI agents in your plan, the triage agent explains a run's problems in plain words
and says who can fix each.
Waiting
While an active run cannot work, waiting says why and until when:
waiting.reason | Meaning |
|---|---|
rate_limited | The source asked FurniOps to slow down. |
retrying | An attempt failed in a way that may pass; the run tries again (attempt counts from 1). |
Start a run
POST /v1/sources/{id}/runs queues a pull now, outside the
schedule. The body is optional: { "stream": "stock" } pulls stock alone.
// Pull now, outside the schedule; then follow the run.
let run = await furniops.sources.createRun("src_034XqBaL2mRLTZPXyRbaUc");
while (run.status === "queued" || run.status === "running") {
await new Promise((resolve) => setTimeout(resolve, 5_000));
run = await furniops.runs.get(run.id);
}
console.log(run.status, run.stats, run.error);
for (const p of run.problems) console.log(p.line, p.column, p.problem, p.detail);409while the source has a run queued or running, or is paused or disabled. A source runs one run at a time.422for a source FurniOps cannot fetch yet, or astockpull of a source whose connector does not read stock apart.- Starting a run is in the
expensiverate class. - Runs count toward the
source_runsmeter. Once a month's live quota is spent, scheduled runs wait for the next month, and asking for a run, a replay or an upload's run answers429quota_exceeded. Test mode is never capped.
Replay
POST /v1/sources/{id}/replay processes the source's current
snapshot again without fetching: after you change a mapping or category rules, so the change
reaches records you already have. Only records whose result changed are written, at the time they
were fetched, and absence is not counted. Confirming a mapping and saving category rules replay on
their own.
It answers 409 like starting a run, and 422 when the source has no snapshot its records reflect
yet.
In the dashboard
Sources › Runs lists every source's runs; a source's page lists its own latest runs. Opening a run shows its stages, counters, error and problems. Run now on a source's page starts a run; it is not offered for a push source, or while the source is paused or disabled.
Categories
The FurniOps taxonomy, and how each source's own category paths are filed into it with rules. Suggestions, inheritance, the most specific rule winning, and saving rules through the API.
Probes
Before adding a vendor, probe its site. A probe reads a URL politely and reports the platform with its evidence, any wall, the public feed, sitemaps, structured data coverage and the effort to pull it.