Background runs and live progress (SSE)

Connecting an ad account, „Jetzt abgleichen“, uploading or retrying conversions, applying a preset, rescoring, imports, the demo seed and a test report all start work in the background. Each start creates a run that the dashboard shows at the object that started it:

  • „In Warteschlange · startet gleich“ → „Läuft · 2 von 5 Zeiträumen“ → „Fertig · 14:05“, or „Fehlgeschlagen · Google-Zugang abgelaufen · Erneut versuchen“ with an error id,
  • the button stays disabled while the run is queued or running,
  • „Verzögert“ appears only when a run is really overdue: queued for more than 2 minutes without a start, or running without a heartbeat for 2 minutes.

Runs start right away; the dispatcher job runs.dispatch (every minute) and the cron tick are only the fallback for a start that got lost. Starting the same work twice returns the active run (one active run per organization, project, kind and subject) – a double click never queues anything twice.

The run object

id, kind, projectId, subject (type, id, label), status (queued · running · done · failed · canceled), progress (done, total, phase – what is counted), message, error (code, message, requestId), result, attempts, createdBy, createdAt, startedAt, heartbeatAt, finishedAt, overdue and statusText (the line above in both languages, in the project's time zone).

Kinds: ads.backfill, ads.sync_now, conversions.upload, conversions.retry, presets.apply, relevance.rescore, leads.revenue_import, gsc.sync, demo.seed, reports.test_send.

Endpoints

CallScopeWhat
GET /v1/projects/{project}/runsanalytics:readnewest first; active=true, kind, subject_type, subject_id, limit, cursor
GET /v1/orgs/{org}/runsanalytics:readorganization-level runs and those of its projects
GET /v1/runs/{id}analytics:read on its project or organizationone run
POST /v1/runs/{id}/cancelconfig:write or ads:write, or the person who started ita queued run stops at once, a running one at its next checkpoint; finished runs answer 409
GET /v1/projects/{project}/runs/eventsanalytics:readserver-sent events (below)
POST /v1/projects/{project}/runsper kind (below)start a project run: { "kind": "gsc.sync" }, relevance.rescore, demo.seed – answers { run, created }
POST /v1/orgs/{org}/runsconfig:write + project:admin on every projectstart an organization run: presets.apply with input: { presetId, version, projectIds, recompute }

MCP: runs_list, runs_get.

Starting a run

Only the kinds with a trigger in the dashboard can be started directly; the others start as part of another action (connecting an account, uploading a conversion …) and answer 422 here.

KindLevelScopeInputProgress counts
gsc.syncprojectproject:admin{ from?, to? } (YYYY-MM-DD)days („12 von 30 Tagen“)
relevance.rescoreprojectconfig:write–visits („1.200 von 8.000 Besuchen“)
demo.seedprojectconfig:write, demo projects only–days, data up to now − 15 minutes
presets.applyorganizationconfig:write + project:admin on every listed project{ presetId, version, projectIds[], recompute: none·30d·90d }projects („3 von 7 Projekten“), the result lists the outcome per project

A second start while the same subject has a queued or running run returns that run with created: false. A run that stopped reporting for 2 minutes is taken over by the next start right away. Long runs store a checkpoint and continue in a fresh invocation, so a retry never repeats finished work.

Server-sent events

GET /v1/projects/{project}/runs/events?active=true
Accept: text/event-stream
Authorization: Bearer x3s_…

The stream sends event: ready, then every active run (and runs finished in the last 2 minutes) once, then event: run with the run as JSON on every change. Each event has id: <run id>:<updated ms> – after a reconnect, send it back as Last-Event-ID and you only get newer changes. A comment line every 15 seconds keeps proxies from closing the connection. After 55 seconds the server sends event: bye and closes (serverless time limit); reconnect while something is still running. Without SSE (or when it fails twice), poll GET …/runs?active=true every 2 seconds, backing off to 5 seconds, and stop when nothing is queued or running.