Skip to content

Guide

Syncing data

Triggering bounded extraction runs, idempotency, and what each run state means.

A sync run extracts one window from the provider, stores immutable raw evidence, loads it, and publishes the days it wins. Reading is separate: publication is what makes data visible to queries.

This route triggers runs explicitly. The controlled pilot scheduler is implemented and locally tested separately; new live automation acceptance remains pending.

POST /v1/syncs
Authorization: Bearer <key with sync:write>
Content-Type: application/json
Idempotency-Key: nightly-ga4-2026-09-06
{
"workspaceId": "your_workspace",
"connectionId": "your_connection",
"propertyId": "123456789",
"startDate": "2026-08-31",
"endDate": "2026-09-04"
}

Same five window fields as a query, without metric and dimensions.

Idempotency-Key is required: 8–128 characters from A-Za-z0-9._:-.

Reusing a key with the same window resumes or returns the existing durable run rather than starting a second one. This is what makes retrying safe: a run that crashed after committing raw evidence resumes from that evidence instead of re-fetching, so a retry does not spend provider quota twice or risk a different result.

Reusing a key with a different window is 409 idempotency_conflict. Changed intent needs a new key.

Know the boundary of this guarantee. At most one live lease can execute an exact workspace/connection/property/date window. A different key that contends while that lease is live gets retryable 409 window_in_progress before a run or provider attempt is allocated. Retry that same key after the owner finishes or expires. Partly overlapping windows remain independent; use a new key for a deliberate correction.

Runs created for an internal schedule belong to that schedule’s pipeline rather than the public sync namespace. Public create/replay/takeover and polling do not return or acquire a pipeline-owned run, even if its identifier becomes known. Ownership is rechecked at each effect, checkpoint, publication, failure/cleanup mutation and final-result boundary, so a pipeline attached after an earlier read still withholds later work and the response from the public caller.

Publication checks both quantities atomically. The immutable extraction timestamp is primary, so a genuinely later extraction may correct an earlier head even when its run was created first. Only equal-millisecond extraction timestamps use the creation sequence as a stable tie-break. Frozen older evidence cannot win merely because its run was created later.

The window must contain 1 to 31 days and end before today in the property’s returned timezone. Strict date, interval and identifier validation remains at the application boundaries; there is no earlier UTC completion gate. A day still in progress is 400 incomplete_day.

Local tests cover Oslo summer and winter midnight, and a Los Angeles date that has ended in UTC but remains incomplete locally. The earlier UTC-gate defect described by the original reference at 1274bce is corrected in stage 2 source.

POST /v1/syncs returns 200 for completed, partial or superseded, and 202 for an in-progress run. A failed POST returns a classified 4xx or 5xx error.

Poll with GET /v1/syncs/{runId}. A successful GET returns 200 for the current run record, including in-progress and failed states. Inspect state to determine progress and errorCode to diagnose a recorded failure.

The example below is a real run recorded on staging on 7 September 2026.

{
"runId": "78d95c71-2f6e-4b35-8729-6d8d4b6ecc86",
"state": "completed",
"extractionSeq": 9,
"completedAt": 1788754811781,
"timezone": "Europe/Oslo",
"quality": { "subjectToThresholding": false, "dataLossFromOtherRow": false },
"publishedDates": 5,
"usage": {
"providerRequests": 1,
"providerRows": 5,
"rawBytes": 615,
"loadRequests": 1,
"loadedRows": 5
}
}
StateMeaning
pending extracting raw_committed loadedIn progress (POST 202; polling GET 200). raw_committed means the provider result is durably stored — a retry from here will not re-fetch.
completedThe reliable run owned every requested date when its publication transaction completed.
partialProvider thresholding, sampling or other-row loss prevented trusted publication. No dates from this report replace published data.
supersededA reliable run did not own its whole requested window. It may have published some dates or none; publishedDates gives the exact count.
failedSee errorCode.

This is the state most likely to be misread, so it is worth being blunt about.

A run is marked partial when the provider report was thresholded, sampled or lossy. In that case the publication step emits no rows at all: publishedDates is 0, nothing became visible, and the previous complete result is left exactly as it was. It is a refusal, not a partial write.

The right response is to re-run the window later, once the provider returns a clean report. Data volume, GA4’s thresholding behaviour and the size of the window all affect whether it will.

A POST 200 means terminal, not successful full publication; a polling GET 200 only means the run record was read. Accept completed with the expected publishedDates for a whole-window refresh. The scheduler also checks the query’s run identities before export. A terminal replay returns the recorded result; later corrections may already own its former dates.

Only a completely fetched, unthresholded, unsampled, loss-free report may advance published data. A report that is thresholded, sampled or lossy leaves the last complete result intact and records what was seen in quality — that is the partial state above. It does not half-publish and it does not overwrite good data with worse data.

This is deliberately conservative for a first connector and does not set the tolerance policy for later ones.

usage counts what the run actually spent, including attempts rejected before the provider was called. These are per-run figures for operational visibility; they are not a billing surface.

A failed run publishes nothing. The code says which trust condition failed rather than collapsing to a generic error — provider_auth_failed, restricted_report, changing_report, row_count_mismatch, raw_integrity_failure and others are all distinct. See Errors for the full list and which are retryable.

Retry a retryable failure with the same idempotency key so the run resumes rather than duplicating work.