---
title: "Trigger a sync"
method: POST
path: "/api/v1/sync"
tags: ["sync"]
---

# Trigger a sync

`POST /api/v1/sync`

## Request

- Base URL: `https://api.growomat.com`
- URL: `https://api.growomat.com/api/v1/sync`
- Auth: HTTP bearer

Pushes the active Space's campaigns to the connected ad platforms (Google Ads, Microsoft Advertising, Reddit Ads, Meta Ads, ChatGPT Ads and LinkedIn Ads) and imports changes made on the platforms. This is the only operation that makes campaigns live, and it can start real spend.

The sync runs in the background: the response arrives immediately with a `syncId` and `status: started`, or `status: nothing_to_sync`. Poll *Get a sync* until it finishes.

Recommended flow: call *Preview a sync plan* first, review it, then pass its `planHash` here so the sync executes exactly what you reviewed. Scope it with `campaignIds`, choose directions per platform with `platformModes`, and use `force` to re-export unchanged campaigns.

The platform connection belongs to the account; the campaigns synced are the active Space's.

**Errors**

- `409 SYNC_ALREADY_ACTIVE`: a sync is already running in this Space; `details` carries `activeSyncId` and `startedAt`.
- `409 SYNC_SCOPE_EXCEEDED`: the plan would touch campaigns outside `campaignIds`; set `confirmFullSpace` to allow it.
- `409 SYNC_PLAN_DRIFT`: the plan changed since the `planHash` you passed was computed; preview again.
- `400 INVALID_PLATFORM_MODES`: `platformModes` names an unknown platform or direction.

## Headers

- `X-Space-Id` string

## Request body

- SyncTriggerRequest: What to sync. All fields optional; an empty body syncs every connected platform. Each campaign is only ever exported to the platforms it runs on (`_platformBudgets`). `campaignIds`, `force`, `confirmFullSpace`, `planHash` and `platformModes` are accepted at the top level or inside `options`.
  - `platforms` string[]: Platform ids to sync, for example `["google-ads"]`. Defaults to every connected platform.
  - `campaignIds` string[]: Sync only these campaigns, on every platform and in every phase (import, export, remove). The scope is never widened silently: a plan that would touch a campaign outside this list is refused with `409 SYNC_SCOPE_EXCEEDED` unless `confirmFullSpace` is set. Omit to sync the whole Space.
  - `force` boolean: Re-export the in-scope campaigns even when nothing changed, including targeting, extensions and labels. To recreate a campaign that was deleted on the platform, use *Unlink campaigns from a platform* and then sync.
  - `confirmFullSpace` boolean: Acknowledge a large change. Required when an unscoped sync would change more than 10 campaigns, and to override a `SYNC_SCOPE_EXCEEDED` refusal.
  - `planHash` string: Run exactly the plan you previewed: pass the `planHash` from *Preview a sync plan*. If the plan has changed since, the sync is refused with `409 SYNC_PLAN_DRIFT` instead of running something you did not review.
  - `platformModes` object: Per-platform direction: map a platform id to the phases it may run. A platform absent from the map runs all three. Combines with `campaignIds` (which campaigns) and `planHash` (the hash covers the full previewed plan; holding a direction back is a choice, not drift). Work held back is reported in the response under `scope.excludedByMode`. Example, to keep importing from a platform while holding its exports: `{"google-ads": ["import", "remove"]}`. Unknown platform ids or directions fail with `400 INVALID_PLATFORM_MODES`.
  - `options` object: Which entity types to include. Also accepts `force`, `campaignIds`, `confirmFullSpace` and `planHash`.
    - `includeAdGroups` boolean: Sync ad groups.
    - `includeKeywords` boolean: Sync keywords.
    - `includeAds` boolean: Sync ads.
    - `force` boolean: Same as the top-level `force`.
    - `campaignIds` string[]: Same as the top-level `campaignIds`.

## Response `200`

OK. The result is in `data`. List responses also carry `meta` with `nextCursor` and `total`.

- SuccessEnvelope: The shape of every successful response.
  - `data` unknown, required
  - `meta` object: Present on list responses.
    - `nextCursor` union: Pass as `cursor` to get the next page. `null` on the last page.
      - string
      - number
    - `total` integer: Number of items in this page.

## Other responses

- `201`: Created. The new resource is in `data`.
- `400`: The request is malformed or fails validation. `error.code` is `VALIDATION_ERROR` (with `details.issues[]` naming each field), `INVALID_REQUEST` (the body is not valid JSON or has the wrong shape), or one of the specific codes listed in this operation's description.
- `401`: The bearer token is missing, invalid, expired or revoked (`INVALID_TOKEN`).
- `403`: The token lacks the scope this operation needs (`INSUFFICIENT_SCOPE`), the plan is read-only (`READ_ONLY_MODE`), or the caller is not a member of the Space named in `X-Space-Id` (`NOT_A_MEMBER`).
- `404`: No such resource in the active Space (`NOT_FOUND`), or `X-Space-Id` names a Space that does not exist (`SPACE_NOT_FOUND`).
- `409`: The request conflicts with the current state, for example a delete blocked by dependents or a sync already running. This operation's description names the `error.code` values it can return.
- `429`: Too many requests (`RATE_LIMITED`). Wait for the number of seconds in the `Retry-After` header, then retry.

---

[API](https://skmtc.dev/growomat/apis/growomat-public-api.md) · [All operations](https://skmtc.dev/growomat/apis/growomat-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/growomat/apis/growomat-public-api/revisions/f02611b531aa?raw)
