---
title: "Create a sync"
method: POST
path: "/v1/sync/syncs"
tags: ["Syncs"]
---

# Create a sync

`POST /v1/sync/syncs`

## Request

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

Creates a sync with both sides declared inline. API-key connectors can take their credentials in `auth`, validated live against the connected service; `base` resolves by remote id or exact display name. Any side declared by connector alone — an OAuth side always, an API-key side whenever credentials should not pass through the caller — is left for a person, who connects it in the app via the returned pending action. A `201` means everything sent was validated — on any error, nothing is kept.

## Headers

- `Idempotency-Key` string

## Request body

- SyncCreate
  - `name` string, required
  - `left` SyncSideInput, required
    - `connector` string, required: A connector type from `GET /v1/sync/connectors`.
    - `auth` object: Credential fields for an API-key connector, keyed by the field ids from `GET /v1/sync/connectors`. Never returned by any endpoint. Optional even for those connectors: omit it (together with `base`) and the sync comes back with this side null plus a pending action linking a person to the page where they enter the credentials — use that whenever credentials should not pass through the caller. Rejected for OAuth connectors, which always connect that way.
    - `base` string: The base/workspace/schema in the connected app: its remote id, or an exact display name when unambiguous. Required alongside `auth`; when a person connects the side instead, they pick its base, so omit both.
  - `right` SyncSideInput, required
    - `connector` string, required: A connector type from `GET /v1/sync/connectors`.
    - `auth` object: Credential fields for an API-key connector, keyed by the field ids from `GET /v1/sync/connectors`. Never returned by any endpoint. Optional even for those connectors: omit it (together with `base`) and the sync comes back with this side null plus a pending action linking a person to the page where they enter the credentials — use that whenever credentials should not pass through the caller. Rejected for OAuth connectors, which always connect that way.
    - `base` string: The base/workspace/schema in the connected app: its remote id, or an exact display name when unambiguous. Required alongside `auth`; when a person connects the side instead, they pick its base, so omit both.

## Response `201`

The new sync, in `draft`. A side left for a person — OAuth, or API-key without `auth` — comes back `null` with a `user_authorization` entry in `pending_actions`.

- Sync
  - `id` string, required
  - `name` string, required
  - `status` 'draft' | 'active' | 'paused', required
  - `left` SyncSide, required
    - `side` 'left' | 'right', required
    - `connector` string, required
    - `base` SyncSideBase, required
      - `remote_id` string, required: The base id in the connector, stable across renames.
      - `name` string, required: The base name as shown by the connector.
      - `url` string, uri, nullable, required: A link to open the base in its own service, if any.
    - `auth_status` 'connected' | 'error', required: `error` means the side needs reconnecting.
    - `auth_error` string, nullable, required: A generic code for the auth failure when `auth_status` is `error`.
    - `other_active_syncs` number, required: How many other active syncs in the account use this same base. Two syncs writing to one base can overwrite each other and create duplicates.
    - `tables_url` string, uri, required: This side's tables.
  - `right` SyncSide, required
    - `side` 'left' | 'right', required
    - `connector` string, required
    - `base` SyncSideBase, required
      - `remote_id` string, required: The base id in the connector, stable across renames.
      - `name` string, required: The base name as shown by the connector.
      - `url` string, uri, nullable, required: A link to open the base in its own service, if any.
    - `auth_status` 'connected' | 'error', required: `error` means the side needs reconnecting.
    - `auth_error` string, nullable, required: A generic code for the auth failure when `auth_status` is `error`.
    - `other_active_syncs` number, required: How many other active syncs in the account use this same base. Two syncs writing to one base can overwrite each other and create duplicates.
    - `tables_url` string, uri, required: This side's tables.
  - `mappings_revision` string, required: Opaque revision of the mappings document; changes on every mappings edit. Doubles as the mappings ETag — compare it across polls to detect edits made in the app during a handoff.
  - `open_issues` number, required: Unresolved issues across both sides. Read the detail from `GET /v1/sync/issues?sync=…`.
  - `delete_approval` 'review_required' | 'auto_approve', required: `review_required` means a record that goes missing on one side waits for a person to approve the delete before it is applied to the other side; `auto_approve` means deletes are applied as soon as they are detected, and there is no queue to read.
  - `pending_deletes` number, required: Deletes waiting on a person right now. Always 0 under `auto_approve`, where nothing waits. Read the detail from `GET /v1/sync/pending-deletes?sync=…`.
  - `initial_sync_running` boolean, required: Whether the initial sync is running: the first match and copy of records between the two apps, started when a person activates the sync. `status` is `paused` until it finishes and the sync turns on.
  - `initial_sync_preparing` boolean, required: Whether the initial sync is being prepared: Whalesync is downloading records and planning the match. When that finishes it waits for a person to review and start it. Activating the sync meanwhile fails with `initial_sync_preparing`.
  - `last_synced_at` string, date-time, nullable, required: When the sync last polled, or null if never.
  - `created_at` string, date-time, required
  - `review_url` string, uri, required: The sync's page in the app, where a human reviews the mappings and starts it.
  - `app_url` string, uri, required: The sync in the Whalesync app, for a person browsing — unlike review_url, nothing here needs their attention (a first-time draft is started from review_url).
  - `pending_actions` PendingAction[], required: What a person still has to do in the browser before this sync can run: connect a side whose app signs in there, then review and start it. Empty when nothing is waiting on anyone. Agents cannot perform these — relay the `instruction` and `url`.
    - `type` 'user_authorization' | 'user_confirmation' | 'user_api_key', required
    - `audience` 'end_user', required: Who has to do this. `end_user` means a person — not the agent, and not the API.
    - `action` 'open_in_browser', required: What to do with `url`: open it in a browser. It is a page for a person, never an endpoint to call.
    - `side` 'left' | 'right' | 'source' | 'destination' | 'null', nullable, required: The side this is about, when it is one side. Syncs have `left`/`right`; live exports have `source`/`destination`.
    - `connector` string, nullable, required: The app to connect, when we know which one was asked for.
    - `url` string, uri, required: The page to hand to the person.
    - `instruction` string, required: Plain-language instruction, safe to relay to the person verbatim.
  - `url` string, uri, required: Canonical URL of this sync in the API.
  - `status_url` string, uri, required: This sync's live status snapshot.
  - `mappings_url` string, uri, required: This sync's mappings document.
  - `operations_url` string, uri, required: This sync's operations, pre-filtered.
  - `issues_url` string, uri, required: This sync's issues, pre-filtered.
  - `pending_deletes_url` string, uri, required: This sync's delete queue, pre-filtered.

## Other responses

- `400`: Nothing was created: `unknown_connector`, `invalid_auth` (credentials failed the live check), `missing_auth`/`missing_base` (an API-key side sent only one of `auth`/`base` — send both, or neither to leave the side for a person), `auth_not_supported`/`base_not_supported` on an OAuth side, `base_not_found`/`base_ambiguous` (with the reachable bases in `details.bases`), or `idempotency_key_reused` (same `Idempotency-Key`, different body).
- `401`: The API key is missing, invalid, or revoked (`missing_api_key`, `invalid_api_key`). Carries a `required_action` linking a person to key creation, naming the smallest scope this endpoint needs.
- `403`: The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).
- `409`: An identical request with this `Idempotency-Key` is still running (`idempotency_key_in_use`).
- `429`: Rate limit exceeded. Wait for `Retry-After` seconds.

---

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