{"openapi":"3.1.0","paths":{"/v1/sync/syncs":{"get":{"description":"Every sync on the API key's account, newest first.","operationId":"PublicApiSyncsController_list","parameters":[{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","required":false,"in":"query","description":"The `next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of syncs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List syncs","tags":["Syncs"]},"post":{"description":"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.","operationId":"PublicApiSyncsController_create","parameters":[{"name":"Idempotency-Key","in":"header","description":"Makes retries safe: the same key replays the original response instead of creating twice.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncCreate"}}}},"responses":{"201":{"description":"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`.","headers":{"Idempotent-Replayed":{"description":"`true` when this is a stored replay of an earlier identical request.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sync"}}}},"400":{"description":"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).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"An identical request with this `Idempotency-Key` is still running (`idempotency_key_in_use`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Create a sync","tags":["Syncs"]}},"/v1/sync/syncs/{id}":{"get":{"description":"A single sync, with its two sides and open-issue count.","operationId":"PublicApiSyncsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"200":{"description":"The sync.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sync"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Retrieve a sync","tags":["Syncs"]},"patch":{"description":"Rename the sync, update a side's credentials (`auth`, any time — revalidated live), or re-point a side's `connector`/`base` (draft syncs only; rebuilds that side).","operationId":"PublicApiSyncsController_patch","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncUpdate"}}}},"responses":{"200":{"description":"The updated sync.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sync"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The change needs a draft sync (`sync_not_draft`), or — `requires_action` — the side being changed is still unconnected and needs its browser sign-in first (the error carries `required_action`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Update a sync","tags":["Syncs"]},"delete":{"description":"Stops the sync and deletes it.","operationId":"PublicApiSyncsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"200":{"description":"Deletion confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncDeleted"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Delete a sync","tags":["Syncs"]}},"/v1/sync/syncs/{sync_id}/sides/{side}/bases":{"get":{"description":"The bases/workspaces/schemas the side's credentials can reach, fetched live from the connected service. Use after a `base_not_found`/`base_ambiguous` error, or to browse before re-pointing a side.","operationId":"PublicApiSideBasesController_list","parameters":[{"name":"sync_id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}},{"name":"side","required":true,"in":"path","schema":{"enum":["left","right"],"type":"string"}}],"responses":{"200":{"description":"The reachable bases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SideBaseList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A person has to act first (`requires_action`): the error carries `required_action` — relay its `instruction` and `url` to a human, then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List a side's reachable bases","tags":["Syncs"]}},"/v1/sync/syncs/{sync_id}/tables":{"get":{"description":"The tables of the base one side is connected to, from the cache the app also uses; the first call fetches them from the connected app. `202` means another request is already loading them — retry the same GET.","operationId":"PublicApiSchemaController_listTables","parameters":[{"name":"sync_id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}},{"name":"side","required":true,"in":"query","schema":{"enum":["left","right"],"type":"string"}},{"name":"refresh","required":false,"in":"query","description":"Refetch from the connected app instead of serving the cache. Needs the sync to be off.","schema":{"type":"string"}}],"responses":{"200":{"description":"The tables on that side.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableList"}}}},"202":{"description":"Another request is already loading this schema; retry the same GET.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaLoading"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A person has to act first (`requires_action`): the error carries `required_action` — relay its `instruction` and `url` to a human, then retry. Also `sync_active` when `refresh=true` is asked of an active sync.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List a side's tables","tags":["Schema"]}},"/v1/sync/syncs/{sync_id}/tables/{table_id}/fields":{"get":{"description":"The fields of one table, with the connector-native `type` and capability flags. Fetched from the connected app on first use and cached; `202` means another request is already loading them — retry the same GET.","operationId":"PublicApiSchemaController_listFields","parameters":[{"name":"sync_id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}},{"name":"table_id","required":true,"in":"path","description":"A prefixed table id, or the remote id from the connected app.","schema":{"type":"string"}},{"name":"side","required":false,"in":"query","description":"Restrict resolution to one side. Useful when the same string is a remote id on one side and a table name on the other (Postgres remote ids are bare table names); without it, the remote id wins.","schema":{"enum":["left","right"],"type":"string"}},{"name":"refresh","required":false,"in":"query","description":"Refetch from the connected app instead of serving the cache. Needs the sync to be off.","schema":{"type":"string"}}],"responses":{"200":{"description":"The table's fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldList"}}}},"202":{"description":"Another request is already loading this schema; retry the same GET.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaLoading"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync or table on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A person has to act first (`requires_action`): the error carries `required_action` — relay its `instruction` and `url` to a human, then retry. Also `sync_active` when `refresh=true` is asked of an active sync.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List a table's fields","tags":["Schema"]}},"/v1/sync/syncs/{sync_id}/mappings":{"get":{"description":"The declarative mappings document, referencing tables and fields by remote id. The `ETag` header carries the revision — send it back as `If-Match` on the PUT for a compare-and-set update. A pair that has a sync filter or a non-default advanced setting also reports them read-only, under `filter` and `advanced_settings`.","operationId":"PublicApiMappingsController_get","parameters":[{"name":"sync_id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"200":{"description":"The current document.","headers":{"ETag":{"description":"The mappings revision; send back as `If-Match`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingsDocument"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A person has to act first (`requires_action`): the error carries `required_action` — relay its `instruction` and `url` to a human, then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Retrieve the mappings document","tags":["Mappings"]},"put":{"description":"Full replace: anything omitted is unmapped, omitted settings revert to defaults — fetch-modify-put is the intended editing pattern. `{\"create\": {\"name\": \"…\"}}` placeholders create the table/field on that side, typed from the mapped counterpart; retries safely adopt already-created objects. Referenced tables whose fields were never fetched are fetched during the call, so a first PUT can take a few seconds. Any change returns the sync to `draft` for a human to review and start. The read-only `filter` and `advanced_settings` a GET reports are ignored here and keep their stored values — they are edited in the app.","operationId":"PublicApiMappingsController_put","parameters":[{"name":"sync_id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Makes retries safe.","required":false,"schema":{"type":"string"}},{"name":"If-Match","in":"header","description":"The revision the edit was based on; `412` on mismatch.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingsDocument"}}}},"responses":{"200":{"description":"The resolved document, with created objects under their real remote ids.","headers":{"ETag":{"description":"The new mappings revision.","schema":{"type":"string"}},"Idempotent-Replayed":{"description":"`true` when this is a stored replay of an earlier identical request.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingsDocument"}}}},"400":{"description":"The document is invalid: a structural problem or unknown reference names its JSON-pointer `path` in the message (`unknown_table`, `unknown_field`, `table_ambiguous`, `view_required`, `view_not_found`, `view_not_supported`); `invalid_mappings` carries the per-field problems in `details.issues`; a `create` that the connector refused is `create_failed`; a mapped table the connector could not prepare for syncing (some add a Whalesync ID column first) is `table_setup_failed`; an `Idempotency-Key` reused with a different body is `idempotency_key_reused`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A person has to act first (`requires_action`): the error carries `required_action` — relay its `instruction` and `url` to a human, then retry. Also `sync_active` (pause before editing) and `idempotency_key_in_use` (the first request is still running).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"412":{"description":"The `If-Match` revision is no longer current (`revision_mismatch`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Replace the mappings document","tags":["Mappings"]}},"/v1/sync/syncs/{sync_id}/validate":{"post":{"description":"The authoritative compatibility check: field type compatibility, required and orphaned fields, linked-record constraints, create-placeholder feasibility, and everything the mappings PUT would enforce — as one issue list with stable codes and document paths. Side-effect-free: `create` placeholders are checked, never executed. Send a candidate document, or an empty body to validate the stored one. Referenced tables whose fields were never fetched are fetched during the call, so a first validate can take a few seconds. Two failure channels: a structurally malformed document (wrong types, bad `direction`, a create on both sides) is a `400 invalid_mappings` naming the path — everything semantic about a well-formed document comes back as a `200` with issues.","operationId":"PublicApiValidateController_validate","parameters":[{"name":"sync_id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateRequest"}}}},"responses":{"200":{"description":"The issues found; empty means valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationResult"}}}},"400":{"description":"The candidate document is structurally malformed (`invalid_mappings`, with the path).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A person has to act first (`requires_action`): the error carries `required_action` — relay its `instruction` and `url` to a human, then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Validate a mappings document","tags":["Mappings"]}},"/v1/sync/syncs/{id}/pause":{"post":{"description":"Turns the sync off. Safe to call when it is already off, except while its initial sync runs.","operationId":"PublicApiSyncLifecycleController_pause","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"200":{"description":"The sync, now paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sync"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`initial_sync_running`: the sync is working through its initial sync, which turns the sync on when it finishes and cannot be paused over the API. A person can stop it from the sync in Whalesync.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Pause a sync","tags":["Syncs"]}},"/v1/sync/syncs/{id}/activate":{"post":{"description":"Turns a paused sync back on. A draft sync must first be reviewed and started by a human in Whalesync — this returns `409 requires_action` with a `review_url` to hand them.","operationId":"PublicApiSyncLifecycleController_activate","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"202":{"description":"The sync, now activating.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sync"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`requires_action`: a person has to act first — `confirmation_required` (a draft must be reviewed and started in Whalesync; `required_action.url` is the review page) or `auth_required` (a side is still unconnected). Relay `required_action` to a human, then retry. Also `initial_sync_running`, which needs no one: the sync is working through its initial sync and turns itself on at the end. And `initial_sync_preparing`: the initial sync is still being prepared for review; retry once that finishes, or a person can review and start it in Whalesync.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Activate a sync","tags":["Syncs"]}},"/v1/sync/syncs/{sync_id}/status":{"get":{"description":"A cached live snapshot: pending pushes, per-side polling state, and change detection.","operationId":"PublicApiSyncStatusController_get","parameters":[{"name":"sync_id","required":true,"in":"path","schema":{"example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"200":{"description":"The current status snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncStatus"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such sync on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Get sync status","tags":["Syncs"]}},"/v1/sync/connectors":{"get":{"description":"Every connector, with its auth method and capabilities. Connectors above your plan are listed with `available: false` and the plan that unlocks them.","operationId":"PublicApiConnectorsController_list","parameters":[],"responses":{"200":{"description":"All connectors, by name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List connectors","tags":["Connectors"]}},"/v1/sync/operations":{"get":{"description":"One sync's record changes, newest first. `sync` is required; narrow further with `table`, `record` and `since`.","operationId":"PublicApiOperationsController_list","parameters":[{"name":"sync","required":true,"in":"query","description":"The sync id to list operations for.","schema":{"type":"string"}},{"name":"table","required":false,"in":"query","description":"Only operations for this table id.","schema":{"type":"string"}},{"name":"record","required":false,"in":"query","description":"Only operations for this `rec_` record id, both sides.","schema":{"type":"string"}},{"name":"since","required":false,"in":"query","description":"Only operations at or after this ISO-8601 time.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","required":false,"in":"query","description":"The `next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of operations, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List operations","tags":["Operations"]}},"/v1/sync/operations/{id}":{"get":{"description":"A single operation, including its field changes.","operationId":"PublicApiOperationsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"op_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"200":{"description":"The operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such operation on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Retrieve an operation","tags":["Operations"]}},"/v1/sync/issues":{"get":{"description":"One sync's unresolved issues, newest first. `sync` is required; narrow further with `type`.","operationId":"PublicApiIssuesController_list","parameters":[{"name":"sync","required":true,"in":"query","description":"The sync id to list issues for.","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","schema":{"enum":["connection","record","webhook","sync_preview","other"],"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","required":false,"in":"query","description":"The `next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of issues, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List issues","tags":["Issues"]}},"/v1/sync/issues/{id}":{"get":{"description":"A single issue, with its remediation guidance.","operationId":"PublicApiIssuesController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"iss_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"200":{"description":"The issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Issue"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such issue on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Retrieve an issue","tags":["Issues"]}},"/v1/sync/issues/{id}/retry":{"post":{"description":"Clears the issue and re-queues the records it covers.","operationId":"PublicApiIssuesController_retry","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"iss_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}}],"responses":{"202":{"description":"The retry was queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueRetry"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such issue on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Retry an issue","tags":["Issues"]}},"/v1/sync/records":{"get":{"description":"Finds records in one sync by the record's id in the connected app (e.g. an Airtable \"rec…\" id), a Whalesync `rec_` id, or display text. Exact id matches come first. Results are capped, not paginated — when `has_more` is true, narrow the query. Each hit links to its status document.","operationId":"PublicApiRecordsController_search","parameters":[{"name":"sync","required":true,"in":"query","description":"The sync id to search in.","schema":{"type":"string"}},{"name":"query","required":true,"in":"query","description":"A record id, `rec_` id, or display text to match.","schema":{"type":"string"}},{"name":"table","required":false,"in":"query","description":"Only records in this table id.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":25}}],"responses":{"200":{"description":"The matching records, best matches first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Search records","tags":["Records"]}},"/v1/sync/records/{record_id}":{"get":{"description":"Everything known about one record's sync state: open issues, a pending delete awaiting review, operations queued right now, per-side flags and links, and its most recent operations. `record_id` is a `rec_` id, or the record's id in the connected app — then `sync` is required, and `table` picks a table when the same id exists in more than one.","operationId":"PublicApiRecordsController_get","parameters":[{"name":"record_id","required":true,"in":"path","description":"A `rec_` id, or the record's id in the connected app.","schema":{"example":"rec_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}},{"name":"sync","required":false,"in":"query","description":"The sync to look in. Required for a connected-app id.","schema":{"type":"string"}},{"name":"table","required":false,"in":"query","description":"The table to look in, when the id alone is ambiguous.","schema":{"type":"string"}}],"responses":{"200":{"description":"The record's status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"The id matches records in more than one table (`ambiguous_record`), or `sync` is missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such record on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Retrieve a record's status","tags":["Records"]}},"/v1/sync/records/{record_id}/refetch":{"post":{"description":"Fetches the record again from the connected app and syncs whatever changed, as the app's \"Refetch record\" button does. Pass `side` to fetch one side only; otherwise every side with a copy is fetched. The fetch runs after any operations already queued for the record. `record_id` takes the same ids as retrieving the record.","operationId":"PublicApiRecordsController_refetch","parameters":[{"name":"record_id","required":true,"in":"path","description":"A `rec_` id, or the record's id in the connected app.","schema":{"example":"rec_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","type":"string"}},{"name":"sync","required":false,"in":"query","description":"The sync to look in. Required for a connected-app id.","schema":{"type":"string"}},{"name":"table","required":false,"in":"query","description":"The table to look in, when the id alone is ambiguous.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"side":{"type":"string","enum":["left","right"],"description":"The side to fetch."}}}}}},"responses":{"202":{"description":"The fetch was queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordRefetch"}}}},"400":{"description":"The record has no copy to fetch on that side (`no_copy_to_refetch`), `side` is not `left` or `right`, or the id is ambiguous.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such record on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Refetch a record","tags":["Records"]}},"/v1/sync/pending-deletes":{"get":{"description":"One sync's deletes waiting on a person, newest first. When a record goes missing on one side, Whalesync holds the delete instead of applying it to the other side, so an accidental or API-caused disappearance cannot destroy data. Approving or ignoring a delete is deliberately not in this API: it is irreversible, so it stays a decision a person makes in the app — relay `review_url`.","operationId":"PublicApiPendingDeletesController_list","parameters":[{"name":"sync","required":true,"in":"query","description":"The sync id to list pending deletes for.","schema":{"type":"string"}},{"name":"state","required":false,"in":"query","description":"Defaults to `awaiting_review`. `ignored` lists the deletes a person chose to keep the record for.","schema":{"enum":["awaiting_review","ignored"],"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","required":false,"in":"query","description":"The `next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of pending deletes, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingDeleteList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The sync auto-approves deletes (`delete_approval_disabled`), so it has no queue. A sync reports this as `delete_approval` before you call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List pending deletes","tags":["Pending deletes"]}},"/v1/live-export/live-exports":{"post":{"description":"Pick the source and destination connectors. An `api_key` connector may include `auth` inline (validated before anything persists); an `oauth` connector comes back unconnected with a pending action a person finishes in the browser. The export starts as a resumable draft.","operationId":"PublicApiLiveExportsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportCreate"}}}},"responses":{"201":{"description":"The new draft live export.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExport"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Create a live export","tags":["Live Exports"]},"get":{"description":"Your live exports, newest first. A live export copies tables from a source app into a destination app on a schedule — one direction, source unchanged.","operationId":"PublicApiLiveExportsController_list","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of live exports, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List live exports","tags":["Live Exports"]}},"/v1/live-export/live-exports/{id}":{"get":{"operationId":"PublicApiLiveExportsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExport"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Get a live export","tags":["Live Exports"]},"patch":{"description":"Rename it, complete an `api_key` side's withheld `auth`, or set the destination's `location`.","operationId":"PublicApiLiveExportsController_patch","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportUpdate"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExport"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Update a live export","tags":["Live Exports"]},"delete":{"description":"Deletes the live export and its schedule. Tables already created in the destination stay put.","operationId":"PublicApiLiveExportsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportDeleted"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Delete a live export","tags":["Live Exports"]}},"/v1/live-export/live-exports/{id}/schedule":{"get":{"description":"The export's cadence, daily run time and on/off switch, plus the cadences your plan can set. Before the first save this is a 409 `not_provisioned`; after saving, 404 until a PATCH creates the schedule.","operationId":"PublicApiLiveExportsController_getSchedule","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportScheduleDetail"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it has no schedule yet."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Get the schedule","tags":["Live Exports"]},"patch":{"description":"Turn the export on or off and set its cadence — the only lifecycle switch (there is no pause/activate). A daily export also takes `time_of_day`, in `timezone`. Creates the schedule on first use; missing fields keep their current values. Requires a saved export.","operationId":"PublicApiLiveExportsController_patchSchedule","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportScheduleUpdate"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportScheduleDetail"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Update the schedule","tags":["Live Exports"]}},"/v1/live-export/live-exports/{id}/trigger":{"post":{"description":"Run the export now, regardless of the schedule. One run at a time: a trigger while one is active returns the `run_in_progress` conflict. Unsaved mapping edits return the `unsaved_changes` conflict — the run would use the last-saved pipeline, not them — save first, or send `{\"force\": true}` to run anyway. The run writes into the destination tables.","operationId":"PublicApiLiveExportsController_trigger","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportTrigger"}}}},"responses":{"201":{"description":"The new (pending) run. Poll its `url` or `…/status`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Run"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Trigger a run","tags":["Live Exports"]}},"/v1/live-export/live-exports/{id}/status":{"get":{"description":"Whether a run is in flight right now, and when the next scheduled run fires.","operationId":"PublicApiLiveExportsController_getStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportStatus"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Get live status","tags":["Live Exports"]}},"/v1/live-export/live-exports/{live_export_id}/source/tables":{"get":{"description":"The exportable tables in the source app. Live — the connector enumerates them on request.","operationId":"PublicApiLiveExportMappingsController_listSourceTables","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportSourceTableList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List source tables","tags":["Live Exports"]}},"/v1/live-export/live-exports/{live_export_id}/source/tables/{table_id}/fields":{"get":{"description":"One table's exportable fields, each with the name and type it would get in the destination. Pick from these when writing the mappings document — the export never takes field types from you.","operationId":"PublicApiLiveExportMappingsController_listSourceFields","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}},{"name":"table_id","required":true,"in":"path","description":"An id from `GET …/source/tables`.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportSourceFieldList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List source fields","tags":["Live Exports"]}},"/v1/live-export/live-exports/{live_export_id}/destination/locations":{"get":{"description":"Where the destination can host created tables (an Airtable base, a Notion parent page). Pick one with PATCH `{destination: {location}}` on the live export. Only destinations with pickable locations return any.","operationId":"PublicApiLiveExportMappingsController_listLocations","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}},{"name":"search","required":false,"in":"query","description":"Server-side search, for destinations with capped lists.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportLocationList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List destination locations","tags":["Live Exports"]}},"/v1/live-export/live-exports/{live_export_id}/mappings":{"get":{"description":"The working mappings document. The document is {\"tables\": [{\"source_table\", \"destination_table\": {\"create\": {\"name\"}}, \"fields\": [{\"source_field\", \"destination_field\": {\"create\": {\"name\", \"primary?\"}}}]}]}. Reference source tables and fields by the ids the discovery endpoints return; destination tables and fields are created by the export, so they take names, never types — types come from the plan behind …/fields.","operationId":"PublicApiLiveExportMappingsController_getMappings","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportMappings"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Get mappings","tags":["Live Exports"]},"put":{"description":"Declarative full replace of the working document. Nothing is created in the destination until `POST …/save`. Send `If-Match` (or `revision` awareness via a fresh GET) to avoid clobbering a concurrent edit.","operationId":"PublicApiLiveExportMappingsController_putMappings","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}},{"name":"if-match","required":true,"in":"header","schema":{"type":"string"}},{"name":"If-Match","in":"header","description":"The `ETag` from the last GET; mismatch is a 412.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The document as now stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportMappings"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Replace mappings","tags":["Live Exports"]}},"/v1/live-export/live-exports/{live_export_id}/mappings/validate":{"post":{"description":"The checks a PUT would run, as a report instead of a rejection. Changes nothing.","operationId":"PublicApiLiveExportMappingsController_validate","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportMappingsValidation"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Validate mappings","tags":["Live Exports"]}},"/v1/live-export/live-exports/{live_export_id}/save":{"post":{"description":"Create the mapped tables and fields in the destination, then apply the runnable pipeline — as one background job. This is the apply point: mapping edits change nothing until a save lands. Poll `GET …/save` until `succeeded` (which also means `unsaved_changes` is false on the export — the authoritative confirmation), then set the schedule or trigger a run. Single-flight and per-item retryable: POST again after a failure and everything already created is skipped. This writes to the destination app.","operationId":"PublicApiLiveExportMappingsController_save","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}}],"responses":{"202":{"description":"The save, now pending or running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportSave"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Save the export","tags":["Live Exports"]},"get":{"description":"The latest save: its phase and progress while running, or how it ended. `succeeded` here is authoritative — it is only reported when no edits remain unsaved. Pass `wait` to long-poll: the response comes back as soon as the save settles, or with the current in-flight state when the wait runs out.","operationId":"PublicApiLiveExportMappingsController_getSave","parameters":[{"name":"live_export_id","required":true,"in":"path","schema":{"example":"lex_8f3kq0vz","type":"string"}},{"name":"wait","required":false,"in":"query","description":"Seconds to hold the request while the save runs (max 50).","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportSave"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Never saved, no live export with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Poll the save","tags":["Live Exports"]}},"/v1/live-export/runs":{"get":{"description":"Recent runs of one live export, newest first. History is retention-bounded: the last page means the end of retained history, not of all runs ever.","operationId":"PublicApiLiveExportRunsController_list","parameters":[{"name":"live_export","required":true,"in":"query","schema":{"example":"lex_8f3kq0vz","type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of recent runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List runs","tags":["Live Exports"]}},"/v1/live-export/runs/{id}":{"get":{"description":"One run with its ordered steps.","operationId":"PublicApiLiveExportRunsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"run_8f3kq0vz.2mv91xk","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Run"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No run with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Get a run","tags":["Live Exports"]}},"/v1/live-export/runs/{id}/cancel":{"post":{"description":"Stop an in-flight run. A run that already finished returns the `run_not_active` conflict.","operationId":"PublicApiLiveExportRunsController_cancel","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":"run_8f3kq0vz.2mv91xk","type":"string"}}],"responses":{"200":{"description":"The run, now cancelling or cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Run"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No run with that id, or it belongs to someone else."},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"Cancel a run","tags":["Live Exports"]}},"/v1/live-export/connectors":{"get":{"description":"The connectors usable on the Live Export surface, with the roles each can play. A separate registry from `GET /v1/sync/connectors`: the same brand may exist on both surfaces with different auth. The `type` slugs share one namespace — the same slug always means the same brand.","operationId":"PublicApiLiveExportConnectorsController_list","parameters":[],"responses":{"200":{"description":"All live export connectors, by name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveExportConnectorList"}}}},"401":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this call (`insufficient_scope` on a mutation with a `read` key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded. Wait for `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds until the per-key budget refills.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]}],"summary":"List live export connectors","tags":["Live Exports"]}}},"info":{"title":"Whalesync API","description":"Create and monitor Whalesync syncs. Everything under `/v1/sync/…` is the sync feature; other Whalesync features will get their own prefix. Authenticate every request with an API key from Settings > API keys: `Authorization: Bearer ws_tok_…`. All fields are snake_case (credential field ids inside `auth` are the exception: they are the ids the connector registry declares, e.g. Postgres's `connectionString`). List endpoints take `limit` and `cursor` and return `{data, has_more, next_cursor}`. Every authenticated response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` headers (one per-key budget); a `429` adds `Retry-After`. Errors that need a person first come back `409` with `type: requires_action` and a `required_action` object — relay its `instruction` and `url` to a human.","version":"1.0.0","contact":{}},"tags":[],"servers":[{"url":"https://api.whalesync.com"}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API key","type":"http","description":"An API key from Settings > API keys, e.g. `ws_tok_…`."}},"schemas":{"PendingAction":{"type":"object","properties":{"type":{"type":"string","enum":["user_authorization","user_confirmation","user_api_key"]},"audience":{"type":"string","enum":["end_user"],"description":"Who has to do this. `end_user` means a person — not the agent, and not the API."},"action":{"type":"string","enum":["open_in_browser"],"description":"What to do with `url`: open it in a browser. It is a page for a person, never an endpoint to call."},"side":{"type":["string","null"],"enum":["left","right","source","destination",null],"description":"The side this is about, when it is one side. Syncs have `left`/`right`; live exports have `source`/`destination`."},"connector":{"type":["string","null"],"example":"airtable","description":"The app to connect, when we know which one was asked for."},"url":{"type":"string","format":"uri","description":"The page to hand to the person."},"instruction":{"type":"string","description":"Plain-language instruction, safe to relay to the person verbatim."}},"required":["type","audience","action","side","connector","url","instruction"]},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["authentication_error","permission_error","invalid_request_error","rate_limit_error","requires_action","api_error"],"example":"invalid_request_error","description":"How to handle the failure, independent of the HTTP status. `requires_action` means a person has to do something first and always carries `required_action`; everything wrong with the request itself — malformed, unknown resource, or a state the resource is not in — is `invalid_request_error`."},"code":{"type":"string","description":"Stable identifier for this failure. Branch on this, never on the message."},"message":{"type":"string"},"doc_url":{"type":"string","format":"uri"},"required_action":{"description":"The step a person has to take in the browser before this call can succeed — the same object as the sync's `pending_actions` entries. Relay its `instruction` and `url` to a human. Present on every `requires_action` error, and on the two `authentication_error` codes, where it points at creating a key.","allOf":[{"$ref":"#/components/schemas/PendingAction"}]},"details":{"type":"object","description":"Machine-readable extras for codes that carry them (e.g. the candidate bases on `base_ambiguous`)."}},"required":["type","code","message","doc_url"]},"ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"}},"required":["error"]},"SyncSideBase":{"type":"object","properties":{"remote_id":{"type":"string","example":"appXk3ePqW9zHt5Lm","description":"The base id in the connector, stable across renames."},"name":{"type":"string","example":"CRM","description":"The base name as shown by the connector."},"url":{"type":["string","null"],"format":"uri","description":"A link to open the base in its own service, if any."}},"required":["remote_id","name","url"]},"SyncSide":{"type":"object","properties":{"side":{"type":"string","enum":["left","right"]},"connector":{"type":"string","example":"airtable"},"base":{"$ref":"#/components/schemas/SyncSideBase"},"auth_status":{"type":"string","enum":["connected","error"],"description":"`error` means the side needs reconnecting."},"auth_error":{"type":["string","null"],"description":"A generic code for the auth failure when `auth_status` is `error`."},"other_active_syncs":{"type":"number","description":"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":{"type":"string","format":"uri","description":"This side's tables."}},"required":["side","connector","base","auth_status","auth_error","other_active_syncs","tables_url"]},"Sync":{"type":"object","properties":{"id":{"type":"string","example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"name":{"type":"string","example":"CRM sync"},"status":{"type":"string","enum":["draft","active","paused"]},"left":{"description":"The left side, or null while unset.","oneOf":[{"$ref":"#/components/schemas/SyncSide"},{"type":"null"}]},"right":{"description":"The right side, or null while unset.","oneOf":[{"$ref":"#/components/schemas/SyncSide"},{"type":"null"}]},"mappings_revision":{"type":"string","description":"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":{"type":"number","description":"Unresolved issues across both sides. Read the detail from `GET /v1/sync/issues?sync=…`."},"delete_approval":{"type":"string","enum":["review_required","auto_approve"],"description":"`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":{"type":"number","description":"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":{"type":"boolean","description":"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":{"type":"boolean","description":"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":{"type":["string","null"],"format":"date-time","description":"When the sync last polled, or null if never."},"created_at":{"type":"string","format":"date-time"},"review_url":{"type":"string","format":"uri","description":"The sync's page in the app, where a human reviews the mappings and starts it."},"app_url":{"type":"string","format":"uri","description":"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":{"description":"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":"array","items":{"$ref":"#/components/schemas/PendingAction"}},"url":{"type":"string","format":"uri","description":"Canonical URL of this sync in the API."},"status_url":{"type":"string","format":"uri","description":"This sync's live status snapshot."},"mappings_url":{"type":"string","format":"uri","description":"This sync's mappings document."},"operations_url":{"type":"string","format":"uri","description":"This sync's operations, pre-filtered."},"issues_url":{"type":"string","format":"uri","description":"This sync's issues, pre-filtered."},"pending_deletes_url":{"type":"string","format":"uri","description":"This sync's delete queue, pre-filtered."}},"required":["id","name","status","left","right","mappings_revision","open_issues","delete_approval","pending_deletes","initial_sync_running","initial_sync_preparing","last_synced_at","created_at","review_url","app_url","pending_actions","url","status_url","mappings_url","operations_url","issues_url","pending_deletes_url"]},"SyncList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Sync"}},"has_more":{"type":"boolean","description":"Whether more syncs follow this page."},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to fetch the next page."}},"required":["data","has_more","next_cursor"]},"SyncSideInput":{"type":"object","properties":{"connector":{"type":"string","example":"postgres","description":"A connector type from `GET /v1/sync/connectors`."},"auth":{"type":"object","example":{"connectionString":"postgres://…"},"description":"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":{"type":"string","example":"appXk3ePqW9zHt5Lm","description":"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."}},"required":["connector"]},"SyncCreate":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"example":"CRM sync"},"left":{"$ref":"#/components/schemas/SyncSideInput"},"right":{"$ref":"#/components/schemas/SyncSideInput"}},"required":["name","left","right"]},"SyncSideUpdate":{"type":"object","properties":{"connector":{"type":"string","description":"Replace the connector on this side (draft syncs only; requires `auth` and `base` too)."},"auth":{"type":"object","description":"New credentials, revalidated live. Credential fields for an API-key connector, keyed by the field ids from `GET /v1/sync/connectors`. Never returned by any endpoint. Rejected for OAuth connectors — a person reconnects those in the app."},"base":{"type":"string","description":"Point this side at a different base (draft syncs only). The base/workspace/schema in the connected app: its remote id, or an exact display name when unambiguous."}}},"SyncUpdate":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"example":"CRM sync"},"left":{"$ref":"#/components/schemas/SyncSideUpdate"},"right":{"$ref":"#/components/schemas/SyncSideUpdate"}}},"SyncDeleted":{"type":"object","properties":{"id":{"type":"string","example":"sync_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"deleted":{"type":"boolean","example":true}},"required":["id","deleted"]},"SideBaseList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SyncSideBase"}}},"required":["data"]},"TableViewOption":{"type":"object","properties":{"value":{"type":"string","example":"viwActive","description":"Pass this as the side's `view` in the mappings document."},"name":{"type":"string","example":"Active","description":"The view's name in the connected app."}},"required":["value","name"]},"TableViews":{"type":"object","properties":{"required":{"type":"boolean","example":true,"description":"Whether a mapping for this table must set that side's `view`. A write without one is rejected."},"options":{"description":"The values `view` accepts.","type":"array","items":{"$ref":"#/components/schemas/TableViewOption"}}},"required":["required","options"]},"Table":{"type":"object","properties":{"id":{"type":"string","example":"table_7d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"remote_id":{"type":"string","example":"tblContacts","description":"The table id in the connector, stable across renames."},"name":{"type":"string","example":"Contacts"},"fields_fetched_at":{"type":["string","null"],"format":"date-time","description":"When this table's fields were last fetched, or null if never (the fields endpoint fetches them)."},"views":{"description":"The views this table can be synced through, when its connector has them (Airtable, Salesforce), and whether picking one is required. Null when the connector has no such concept, in which case the mapping's `view` must be omitted.","oneOf":[{"$ref":"#/components/schemas/TableViews"},{"type":"null"}]},"fields_url":{"type":"string","format":"uri","description":"This table's fields."}},"required":["id","remote_id","name","fields_fetched_at","views","fields_url"]},"TableList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Table"}},"fetched_at":{"type":["string","null"],"format":"date-time","description":"When this listing was last fetched from the connected app. Pass `?refresh=true` to refetch."}},"required":["data","fetched_at"]},"SchemaLoading":{"type":"object","properties":{"loading":{"type":"object","default":true,"example":true,"description":"The schema is loading. Retry the same GET after `Retry-After` seconds."}},"required":["loading"]},"Field":{"type":"object","properties":{"id":{"type":"string","example":"field_9c0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"remote_id":{"type":"string","example":"fldXYZ","description":"The field id in the connector, stable across renames."},"name":{"type":"string","example":"Amount"},"type":{"type":"string","example":"currency","description":"The connector's own field type. Every connector has its own set; `POST …/validate` is the compatibility authority."},"read":{"type":"boolean","description":"Whether Whalesync can read values from this field."},"write":{"type":"boolean","description":"Whether Whalesync can write values into this field."},"required":{"type":"boolean","description":"Whether this field must be mapped when its table is mapped."},"primary":{"type":"boolean","description":"Whether this is the table's primary/display field."},"type_details":{"type":["object","null"],"description":"Sparse extras: `display_name`, `allows_multiple_values`, `write_once`, `mapping_disabled`."}},"required":["id","remote_id","name","type","read","write","required","primary","type_details"]},"FieldList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Field"}},"fetched_at":{"type":["string","null"],"format":"date-time","description":"When these fields were last fetched from the connected app. Pass `?refresh=true` to refetch."}},"required":["data","fetched_at"]},"CreatePlaceholder":{"type":"object","properties":{"create":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}},"required":["create"]},"MappingAdvancedSetting":{"type":"object","properties":{"setting":{"type":"string","enum":["delay_before_syncing","row_level_sync","connector_option"],"description":"`delay_before_syncing` = changes wait out a quiet period before syncing; `row_level_sync` = only records whose sync-enabled field is true sync out of this table; `connector_option` = a per-table choice the connector asks for."},"side":{"type":"string","enum":["left","right"],"description":"The table the setting is on."},"value":{"oneOf":[{"type":"number"},{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Seconds for `delay_before_syncing`, the sync-enabled field's remote id for `row_level_sync`, the chosen values otherwise."},"description":{"type":"string","description":"What the setting does here, in one sentence — safe to relay to a person."},"docs_url":{"type":"string","format":"uri","description":"The docs page for this setting, where one exists."}},"required":["setting","side","value","description"]},"MappingFilterCondition":{"type":"object","properties":{"left_field":{"type":["string","null"],"example":"fldStage","description":"The tested field on the left side; null when the field mapping it referenced is gone."},"right_field":{"type":["string","null"],"example":"stage","description":"The same field on the right side."},"operator":{"type":"string","enum":["equals","not_equals","greater_than","greater_than_or_equal","less_than","less_than_or_equal","contains","not_contains","is_empty","is_not_empty","is_any_of","is_none_of"]},"value":{"description":"Absent for `is_empty`/`is_not_empty`; several values for `is_any_of`/`is_none_of`.","type":"array","items":{"type":"string"}},"value_type":{"type":"string","enum":["string","number","boolean","date","datetime"],"description":"How the values are compared."}},"required":["left_field","right_field","operator","value_type"]},"MappingFilterGroup":{"type":"object","properties":{"match":{"type":"string","enum":["all","any"],"description":"Whether every condition must pass, or just one."},"conditions":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/MappingFilterCondition"},{"$ref":"#/components/schemas/MappingFilterGroup"}]},"description":"Conditions, or nested groups."}},"required":["match","conditions"]},"MappingFilter":{"type":"object","properties":{"match":{"type":"string","enum":["all","any"],"description":"Whether every condition must pass, or just one."},"conditions":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/MappingFilterCondition"},{"$ref":"#/components/schemas/MappingFilterGroup"}]},"description":"Conditions, or nested groups."},"summary":{"type":"string","example":"Only records where Stage is \"Won\" and Amount is at least \"100\" are synced.","description":"The whole filter as one sentence — safe to relay to a person."},"docs_url":{"type":"string","format":"uri","example":"https://docs.whalesync.com/features/filters"}},"required":["match","conditions","summary","docs_url"]},"MappingSideSettings":{"type":"object","properties":{"record_delete_behavior":{"type":"string","enum":["sync","do_nothing"],"description":"What a deletion on the other side does to records in this table. Omitted = `sync`. `do_nothing` is Delete Protection for this table — records here survive the other side deleting theirs. See https://docs.whalesync.com/features/additional-features/delete-protection."},"view":{"type":"string","example":"viwActive","description":"The view to sync, by value from the table's `views.options`. REQUIRED whenever the table reports `views` (Airtable, Salesforce); use `\"ALL_RECORDS\"` for the whole table. Omitting it on a PUT clears any prior selection rather than keeping it."}}},"MappingField":{"type":"object","properties":{"left_field":{"oneOf":[{"type":"string","description":"An existing table/field: remote id, prefixed id, or exact name when unambiguous."},{"$ref":"#/components/schemas/CreatePlaceholder"}]},"right_field":{"oneOf":[{"type":"string","description":"An existing table/field: remote id, prefixed id, or exact name when unambiguous."},{"$ref":"#/components/schemas/CreatePlaceholder"}]},"direction":{"type":"string","enum":["left_to_right","right_to_left","two_way"],"description":"Defaults to the table's direction; must be a subset of it."}},"required":["left_field","right_field"]},"MappingTable":{"type":"object","properties":{"left_table":{"oneOf":[{"type":"string","description":"An existing table/field: remote id, prefixed id, or exact name when unambiguous."},{"$ref":"#/components/schemas/CreatePlaceholder"}]},"right_table":{"oneOf":[{"type":"string","description":"An existing table/field: remote id, prefixed id, or exact name when unambiguous."},{"$ref":"#/components/schemas/CreatePlaceholder"}]},"direction":{"type":"string","enum":["left_to_right","right_to_left","two_way"],"description":"Writes take these three values. A read can additionally show `\"invalid\"` when schema drift has disabled every allowed direction for the pair — fix the mapping and PUT it back with a real direction."},"left":{"$ref":"#/components/schemas/MappingSideSettings"},"right":{"$ref":"#/components/schemas/MappingSideSettings"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/MappingField"}},"advanced_settings":{"readOnly":true,"description":"Read-only. The pair's advanced settings that are off their default and cannot be set through the API — delay before syncing, row-level sync, and connector-specific per-table options. Absent when there are none, and ignored on write: these are edited on the Advanced settings tab in the app. Delete protection is not here; it is `record_delete_behavior` above.","type":"array","items":{"$ref":"#/components/schemas/MappingAdvancedSetting"}},"filter":{"readOnly":true,"description":"Read-only. The sync filter on this pair: records failing it are not synced in either direction, and show as filtered on the record. Absent when no filter is set, and ignored on write — filters are edited in the app, before the first sync.","allOf":[{"$ref":"#/components/schemas/MappingFilter"}]}},"required":["left_table","right_table","direction","fields"]},"MappingsDocument":{"type":"object","properties":{"tables":{"description":"The mapped table pairs. Anything omitted is unmapped; omitted settings revert to defaults.","type":"array","items":{"$ref":"#/components/schemas/MappingTable"}}},"required":["tables"]},"ValidateRequest":{"type":"object","properties":{"tables":{"description":"A candidate mappings document to check (same shape as the mappings PUT). Omit to validate the stored one.","type":"array","items":{"$ref":"#/components/schemas/MappingTable"}}}},"ValidationIssue":{"type":"object","properties":{"severity":{"type":"string","enum":["error","warning"],"description":"Errors block starting the sync; warnings never block."},"code":{"type":"string","example":"incompatible_field_types","description":"Stable identifier — branch on this."},"path":{"type":["string","null"],"example":"/tables/0/fields/1","description":"JSON pointer into the document, or null when the issue has no single home."},"side":{"type":["string","null"],"enum":["left","right",null]},"message":{"type":"string"},"doc_url":{"type":"string","format":"uri"}},"required":["severity","code","path","side","message","doc_url"]},"ValidationResult":{"type":"object","properties":{"issues":{"description":"Empty when the document is fully valid.","type":"array","items":{"$ref":"#/components/schemas/ValidationIssue"}}},"required":["issues"]},"SyncStatusSide":{"type":"object","properties":{"side":{"type":"string","enum":["left","right"]},"polling":{"type":"boolean","description":"Whether this side is polling for or fetching changes right now."},"initializing":{"type":"boolean","description":"Whether this side is still running its first sync."},"change_detection":{"type":"string","enum":["webhooks","polling"],"description":"How changes on this side are picked up."}},"required":["side","polling","initializing","change_detection"]},"SyncStatus":{"type":"object","properties":{"sync_id":{"type":"string","example":"sync_9f2c…"},"status":{"type":"string","enum":["draft","active","paused"]},"pending_pushes":{"type":"number","description":"Records waiting to be written, across both sides."},"last_synced_at":{"type":["string","null"],"format":"date-time","description":"When the sync last polled, or null if never."},"left":{"oneOf":[{"$ref":"#/components/schemas/SyncStatusSide"},{"type":"null"}],"description":"The left side, or null."},"right":{"oneOf":[{"$ref":"#/components/schemas/SyncStatusSide"},{"type":"null"}],"description":"The right side, or null."},"fetched_at":{"type":"string","format":"date-time","description":"When this snapshot was taken."}},"required":["sync_id","status","pending_pushes","last_synced_at","left","right","fetched_at"]},"ConnectorCredentialFieldOption":{"type":"object","properties":{"label":{"type":"string","example":"US"},"value":{"type":"string","example":"accounts.zoho.com"}},"required":["label","value"]},"ConnectorCredentialFieldHelp":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string","format":"uri"}},"required":["label","url"]},"ConnectorCredentialField":{"type":"object","properties":{"id":{"type":"string","example":"connectionString","description":"The key to send under the side's `auth`, exactly as given — connectors declare their own ids."},"label":{"type":"string","example":"Postgres connection string"},"optional":{"type":"boolean","description":"When false, the field is required."},"placeholder":{"type":"string","example":"postgres://user:pass@host:5432/db"},"options":{"description":"Valid values, if fixed.","type":["array","null"],"items":{"$ref":"#/components/schemas/ConnectorCredentialFieldOption"}},"help":{"oneOf":[{"$ref":"#/components/schemas/ConnectorCredentialFieldHelp"},{"type":"null"}]}},"required":["id","label","optional","placeholder","options","help"]},"ConnectorAuth":{"type":"object","properties":{"method":{"type":"string","enum":["oauth","api_key"]},"fields":{"description":"Credential fields for `api_key`.","type":["array","null"],"items":{"$ref":"#/components/schemas/ConnectorCredentialField"}}},"required":["method","fields"]},"ConnectorCapabilities":{"type":"object","properties":{"read":{"type":"boolean"},"write":{"type":"boolean"},"create_tables":{"type":"boolean","description":"Whether the connector can create destination tables."},"create_fields":{"type":"boolean","description":"Whether the connector can create destination fields."},"webhooks":{"type":"boolean","description":"Whether the connector can receive real-time change webhooks."}},"required":["read","write","create_tables","create_fields","webhooks"]},"Connector":{"type":"object","properties":{"type":{"type":"string","example":"postgres","description":"The slug to pass as a side's `connector`."},"name":{"type":"string","example":"Postgres"},"auth":{"$ref":"#/components/schemas/ConnectorAuth"},"capabilities":{"$ref":"#/components/schemas/ConnectorCapabilities"},"available":{"type":"boolean","description":"Whether the caller's plan includes this connector."},"required_plan":{"type":["string","null"],"example":"Pro","description":"The plan that unlocks this connector when unavailable."}},"required":["type","name","auth","capabilities","available","required_plan"]},"ConnectorList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Connector"}}},"required":["data"]},"OperationTable":{"type":"object","properties":{"id":{"type":"string","example":"table_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"name":{"type":"string","example":"Contacts"}},"required":["id","name"]},"OperationRecord":{"type":"object","properties":{"remote_id":{"type":"string","example":"recXYZ"},"name":{"type":["string","null"],"description":"A display name for the record, if one is known."},"url":{"type":["string","null"],"format":"uri"}},"required":["remote_id","name","url"]},"OperationSummary":{"type":"object","properties":{"id":{"type":"string","example":"op_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"occurred_at":{"type":"string","format":"date-time"},"sync_id":{"type":"string","example":"sync_9f2c…"},"action":{"type":"string","enum":["created","updated","deleted"]},"trigger":{"type":"string","enum":["push","poll"],"description":"`push` = written by Whalesync; `poll` = detected."},"table":{"$ref":"#/components/schemas/OperationTable"},"record":{"$ref":"#/components/schemas/OperationRecord"},"app_url":{"type":"string","format":"uri","description":"The operation in the Whalesync app, for a person looking closer — nothing here needs their attention."}},"required":["id","occurred_at","sync_id","action","trigger","table","record","app_url"]},"OperationList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OperationSummary"}},"has_more":{"type":"boolean","description":"Whether more operations follow this page."},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to fetch the next page."}},"required":["data","has_more","next_cursor"]},"FieldChange":{"type":"object","properties":{"field":{"type":"string","example":"Stage"},"before":{"type":"string"},"after":{"type":"string"}},"required":["field","before","after"]},"Operation":{"type":"object","properties":{"id":{"type":"string","example":"op_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"occurred_at":{"type":"string","format":"date-time"},"sync_id":{"type":"string","example":"sync_9f2c…"},"action":{"type":"string","enum":["created","updated","deleted"]},"trigger":{"type":"string","enum":["push","poll"],"description":"`push` = written by Whalesync; `poll` = detected."},"table":{"$ref":"#/components/schemas/OperationTable"},"record":{"$ref":"#/components/schemas/OperationRecord"},"app_url":{"type":"string","format":"uri","description":"The operation in the Whalesync app, for a person looking closer — nothing here needs their attention."},"field_changes":{"description":"The fields that changed. Empty if none did.","type":"array","items":{"$ref":"#/components/schemas/FieldChange"}}},"required":["id","occurred_at","sync_id","action","trigger","table","record","app_url","field_changes"]},"IssueRecord":{"type":"object","properties":{"id":{"type":"string","example":"rec_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","description":"The record's status document id."},"remote_id":{"type":["string","null"],"example":"recXYZ","description":"The record id in the connected app."},"name":{"type":["string","null"],"description":"A display name for the record, if one is known."},"url":{"type":["string","null"],"format":"uri","description":"Opens the record in the connected app."}},"required":["id","remote_id","name","url"]},"IssueRemediationLink":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string","format":"uri"}},"required":["name","url"]},"IssueRemediation":{"type":"object","properties":{"explanation":{"type":"string","description":"What the issue means. May contain Markdown."},"suggested_action":{"type":["string","null"],"description":"The concrete step to take, if there is one. May contain Markdown."},"links":{"type":"array","items":{"$ref":"#/components/schemas/IssueRemediationLink"}}},"required":["explanation","suggested_action","links"]},"IssueTable":{"type":"object","properties":{"id":{"type":"string","example":"table_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"remote_id":{"type":"string","example":"tblContacts","description":"The table id in the connector, stable across renames."},"name":{"type":"string"}},"required":["id","remote_id","name"]},"IssueField":{"type":"object","properties":{"id":{"type":"string","example":"field_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"remote_id":{"type":"string","example":"fldEmail","description":"The field id in the connector, stable across renames."},"name":{"type":"string"}},"required":["id","remote_id","name"]},"Issue":{"type":"object","properties":{"id":{"type":"string","example":"iss_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"sync_id":{"type":["string","null"],"example":"sync_9f2c…"},"type":{"type":"string","enum":["connection","record","webhook","sync_preview","other"]},"code":{"type":"string","example":"authentication_error","description":"A generic category code."},"message":{"type":["string","null"]},"connector":{"type":["string","null"],"example":"airtable"},"remediation":{"oneOf":[{"$ref":"#/components/schemas/IssueRemediation"},{"type":"null"}]},"table":{"oneOf":[{"$ref":"#/components/schemas/IssueTable"},{"type":"null"}]},"record":{"oneOf":[{"$ref":"#/components/schemas/IssueRecord"},{"type":"null"}],"description":"The record this issue is about, or null for issues that are not about one record."},"fields":{"description":"The fields the connected app rejected, when it said which ones. Empty when it did not, or for issues that are not about one record.","type":"array","items":{"$ref":"#/components/schemas/IssueField"}},"occurrence_count":{"type":"number","description":"How many times this issue has recurred."},"first_seen_at":{"type":"string","format":"date-time"},"last_seen_at":{"type":"string","format":"date-time"},"app_url":{"type":"string","format":"uri","description":"The issue in the Whalesync app, for a person looking closer — nothing here needs their attention."}},"required":["id","sync_id","type","code","message","connector","remediation","table","record","fields","occurrence_count","first_seen_at","last_seen_at","app_url"]},"IssueList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Issue"}},"has_more":{"type":"boolean","description":"Whether more issues follow this page."},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to fetch the next page."}},"required":["data","has_more","next_cursor"]},"IssueRetry":{"type":"object","properties":{"retried":{"type":"boolean","description":"Always true — the issue was cleared and the affected records re-queued."}},"required":["retried"]},"RecordTable":{"type":"object","properties":{"id":{"type":"string","example":"table_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"remote_id":{"type":"string","example":"tblContacts","description":"The table id in the connector, stable across renames."},"name":{"type":"string","example":"Contacts"}},"required":["id","remote_id","name"]},"RecordSummary":{"type":"object","properties":{"id":{"type":"string","example":"rec_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"sync_id":{"type":"string","example":"sync_9f2c…"},"side":{"type":"string","enum":["left","right"],"description":"The side of the sync this match was found on."},"remote_id":{"type":["string","null"],"example":"recXYZ","description":"The record id in the connected app."},"name":{"type":["string","null"],"description":"A display name for the record, if one is known."},"table":{"$ref":"#/components/schemas/RecordTable"},"is_deleted":{"type":"boolean","description":"Whether this copy of the record has been deleted."},"is_sync_disabled":{"type":"boolean","description":"Whether syncing is turned off for this record via a sync-enabled field."},"url":{"type":"string","format":"uri","description":"The record's status document — the next call to make."},"browser_url":{"type":["string","null"],"format":"uri","description":"Opens this copy of the record in the connected app, when the app has record links. Named apart from `url`, which is this record in the API."},"app_url":{"type":"string","format":"uri","description":"The record's status page in the Whalesync app, for a person looking closer — nothing here needs their attention."}},"required":["id","sync_id","side","remote_id","name","table","is_deleted","is_sync_disabled","url","browser_url","app_url"]},"RecordList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RecordSummary"}},"has_more":{"type":"boolean","description":"Whether more records matched than were returned. Narrow the query to see them."},"next_cursor":{"type":["string","null"],"description":"Always null — search results are not paginated; refine the query."}},"required":["data","has_more","next_cursor"]},"RecordQueuedOperation":{"type":"object","properties":{"type":{"type":"string","enum":["push","refetch","verify_delete","other"],"description":"`push` = a write to the connected app; `refetch` = a re-read of the record from the app; `verify_delete` = a check that a record detected as missing is really gone."},"position":{"type":"number","description":"Position in the queue, lowest first."}},"required":["type","position"]},"RecordSide":{"type":"object","properties":{"remote_id":{"type":["string","null"],"example":"recXYZ","description":"The record id in the connected app."},"name":{"type":["string","null"],"description":"A display name for the record, if one is known."},"url":{"type":["string","null"],"format":"uri","description":"Opens the record in the connected app."},"table":{"$ref":"#/components/schemas/RecordTable"},"is_deleted":{"type":"boolean","description":"Whether this copy of the record has been deleted."},"is_filtered":{"type":"boolean","description":"Whether a sync filter currently excludes this record, so it isn't synced."},"is_sync_disabled":{"type":"boolean","description":"Whether syncing is turned off for this record via a sync-enabled field."},"queued_operations":{"description":"Operations waiting in the queue for this record. An empty list means nothing is queued; null means the queue could not be consulted just now, not that it is empty.","type":["array","null"],"items":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/RecordQueuedOperation"}},{"type":"null"}]}}},"required":["remote_id","name","url","table","is_deleted","is_filtered","is_sync_disabled","queued_operations"]},"RecordPendingDelete":{"type":"object","properties":{"state":{"type":"string","enum":["awaiting_review","ignored"],"description":"`awaiting_review` = waiting on a person; `ignored` = a person chose to keep the record, so the delete is dormant and stops being raised. Neither is resolved."},"missing_side":{"type":"string","enum":["left","right"],"description":"The side whose record went missing."},"delete_from_side":{"type":"string","enum":["left","right"],"description":"The side the record will be deleted from if the delete is approved."},"pending_since":{"type":"string","format":"date-time","description":"When the record was first detected as missing."},"ignored_at":{"type":["string","null"],"format":"date-time","description":"When a person chose to ignore this delete, or null while it is still awaiting review."},"review_url":{"type":"string","format":"uri","description":"The page where a person approves or ignores this delete. Relay it to them — the API deliberately has no endpoint that decides a delete."}},"required":["state","missing_side","delete_from_side","pending_since","ignored_at","review_url"]},"RecordPendingDeleteEvent":{"type":"object","properties":{"event":{"type":"string","enum":["detected","approved","ignored"]},"side":{"type":"string","enum":["left","right"],"description":"The side whose record went missing."},"occurred_at":{"type":"string","format":"date-time"}},"required":["event","side","occurred_at"]},"Record":{"type":"object","properties":{"id":{"type":"string","example":"rec_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"sync_id":{"type":"string","example":"sync_9f2c…"},"left":{"oneOf":[{"$ref":"#/components/schemas/RecordSide"},{"type":"null"}],"description":"Null when this side has no copy of the record (or the sync has no second side yet)."},"right":{"oneOf":[{"$ref":"#/components/schemas/RecordSide"},{"type":"null"}],"description":"The right side, or null."},"issues":{"description":"Open issues attached to this record. An issue here usually blocks the record from syncing.","type":"array","items":{"$ref":"#/components/schemas/Issue"}},"pending_delete":{"oneOf":[{"$ref":"#/components/schemas/RecordPendingDelete"},{"type":"null"}],"description":"A delete awaiting review, or null when none is."},"pending_delete_events":{"description":"Pending-delete history, oldest first.","type":"array","items":{"$ref":"#/components/schemas/RecordPendingDeleteEvent"}},"operations":{"description":"The most recent operations on this record, both sides merged, newest first.","type":"array","items":{"$ref":"#/components/schemas/OperationSummary"}},"operations_has_more":{"type":"boolean","description":"Whether older operations exist beyond the ones embedded here."},"operations_url":{"type":"string","format":"uri","description":"The full paginated operation history for this record."},"app_url":{"type":["string","null"],"format":"uri","description":"The record's status page in the Whalesync app, for a person looking closer — nothing here needs their attention."}},"required":["id","sync_id","left","right","issues","pending_delete","pending_delete_events","operations","operations_has_more","operations_url","app_url"]},"RecordRefetch":{"type":"object","properties":{"record_id":{"type":"string","example":"rec_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41"},"sides":{"type":"array","description":"The sides whose copy will be fetched again, after any operations already queued for the record. Changes found are synced as usual.","items":{"type":"string","enum":["left","right"]}}},"required":["record_id","sides"]},"PendingDeleteRecord":{"type":"object","properties":{"remote_id":{"type":["string","null"],"example":"recXYZ","description":"The record id in the connected app."},"name":{"type":["string","null"],"description":"A display name for the record, if one is known."},"url":{"type":["string","null"],"format":"uri","description":"Opens the record in the connected app, so a person can look at it before deciding."},"table":{"$ref":"#/components/schemas/RecordTable"}},"required":["remote_id","name","url","table"]},"PendingDelete":{"type":"object","properties":{"record_id":{"type":"string","example":"rec_1d0b06b0-6f5e-4a3e-9d15-3a7b0e2f9a41","description":"The record this delete is about."},"sync_id":{"type":"string","example":"sync_9f2c…"},"state":{"type":"string","enum":["awaiting_review","ignored"],"description":"`awaiting_review` = waiting on a person; `ignored` = a person chose to keep the record, so the delete is dormant and stops being raised. Neither is resolved: an ignored delete can be restored in the app."},"missing_side":{"type":"string","enum":["left","right"],"description":"The side whose record went missing."},"delete_from_side":{"type":"string","enum":["left","right"],"description":"The side the record would be deleted from if a person approves."},"pending_since":{"type":"string","format":"date-time","description":"When the record was first detected as missing."},"ignored_at":{"type":["string","null"],"format":"date-time","description":"When a person chose to ignore this delete, or null while it is still awaiting review."},"record":{"oneOf":[{"$ref":"#/components/schemas/PendingDeleteRecord"},{"type":"null"}],"description":"The record that would be deleted, or null when the other side no longer holds a copy of it."},"record_url":{"type":"string","format":"uri","description":"The record's status document in the API."},"review_url":{"type":"string","format":"uri","description":"The page where a person approves or ignores this delete. Relay it to them — the API deliberately has no endpoint that decides a delete."}},"required":["record_id","sync_id","state","missing_side","delete_from_side","pending_since","ignored_at","record","record_url","review_url"]},"PendingDeleteList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PendingDelete"}},"has_more":{"type":"boolean","description":"Whether more pending deletes follow this page."},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to fetch the next page."}},"required":["data","has_more","next_cursor"]},"LiveExportSideInput":{"type":"object","properties":{"connector":{"type":"string","example":"hubspot","description":"A connector type from `GET /v1/live-export/connectors`."},"auth":{"type":"object","example":{"apiKey":"pat-…"},"description":"Credential fields for an `api_key` connector, keyed by the field ids from `GET /v1/live-export/connectors`. Never returned by any endpoint. Optional even for those connectors: omit it and the export 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 in the browser."}},"required":["connector"]},"LiveExportCreate":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"example":"HubSpot to Notion","description":"Defaults to \"<Source> to <Destination>\"."},"source":{"description":"The app records are read from.","allOf":[{"$ref":"#/components/schemas/LiveExportSideInput"}]},"destination":{"description":"The app tables are written into.","allOf":[{"$ref":"#/components/schemas/LiveExportSideInput"}]}},"required":["source","destination"]},"LiveExportLocation":{"type":"object","properties":{"id":{"type":"string","description":"The location id, as listed by `GET …/destination/locations`."},"name":{"type":"string","example":"CRM Exports"}},"required":["id","name"]},"LiveExportSide":{"type":"object","properties":{"connector":{"type":"string","example":"hubspot","description":"The connected app, as a connector slug."},"auth_status":{"type":"string","enum":["connected","error"],"description":"Whether the connection is currently healthy."},"auth_error":{"type":["string","null"],"description":"The connection's failure message when `auth_status` is `error`."},"location":{"description":"Destination side only: where this export hosts its tables (e.g. a Notion parent page, a Postgres schema) — the pick made via PATCH `{destination: {location}}`, or, once tables exist, the container they actually live in. Null means no pick recorded and nothing created yet, or the destination uses its own default — not that the destination is unset (that is what a null `destination` side means). Only consulted when creating new tables.","oneOf":[{"$ref":"#/components/schemas/LiveExportLocation"},{"type":"null"}]}},"required":["connector","auth_status","auth_error"]},"LiveExportSchedule":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the cadence is on. This is the on/off switch for the whole export."},"cadence":{"type":"string","enum":["every_10_minutes","every_30_minutes","hourly","daily","custom"]},"time_of_day":{"type":["string","null"],"example":"09:00","description":"When a `daily` export runs: a whole hour in 24-hour time, in `timezone` (UTC when no timezone is set). Null for the other cadences."},"cron":{"type":"string","description":"The exact schedule, as a cron expression. Read-only; set `cadence` and `time_of_day` instead."},"timezone":{"type":["string","null"],"example":"America/New_York"},"next_run_at":{"type":["string","null"],"format":"date-time","description":"When the next scheduled run fires."},"last_triggered_at":{"type":["string","null"],"format":"date-time"}},"required":["enabled","cadence","time_of_day","cron","timezone","next_run_at","last_triggered_at"]},"RunSummary":{"type":"object","properties":{"id":{"type":"string","example":"run_8f3kq0vz.2mv91xk"},"status":{"type":"string","enum":["pending","running","completed","failed","cancelled"]},"trigger":{"type":"string","enum":["manual","schedule"],"description":"Whether a person/API started it, or the cadence did."},"started_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"summary":{"type":["string","null"],"description":"The one-line story: what the run is doing right now, or how it ended."},"warning":{"type":["string","null"],"description":"Set when the run completed but a step finished with a warning."},"error":{"type":["string","null"],"description":"The rolled-up error for a failed run."},"app_url":{"type":"string","format":"uri","description":"The run in the Whalesync app, for a person who wants to watch or share it — like every app_url, nothing here needs their attention."}},"required":["id","status","trigger","started_at","finished_at","summary","warning","error","app_url"]},"LiveExport":{"type":"object","properties":{"id":{"type":"string","example":"lex_8f3kq0vz"},"name":{"type":"string","example":"HubSpot to Notion"},"status":{"type":"string","enum":["draft","active","manual_only"],"description":"`draft` = not yet saved (no pipeline exists). After the first save: `active` = the schedule is on, `manual_only` = it is off and the export runs only when triggered."},"source":{"description":"The app records are read from. Null while it awaits its browser sign-in (see `pending_actions`).","oneOf":[{"$ref":"#/components/schemas/LiveExportSide"},{"type":"null"}]},"destination":{"description":"The app tables are written into. Null while it awaits its browser sign-in.","oneOf":[{"$ref":"#/components/schemas/LiveExportSide"},{"type":"null"}]},"schedule":{"description":"Null until the first save provisions the pipeline.","oneOf":[{"$ref":"#/components/schemas/LiveExportSchedule"},{"type":"null"}]},"last_run":{"oneOf":[{"$ref":"#/components/schemas/RunSummary"},{"type":"null"}]},"unsaved_changes":{"type":"boolean","description":"True when mappings were edited over the API after the last save — `trigger` would run the last-applied pipeline, not the edits. Save to clear it. (An edit saved in the app clears it on the next save poll.)"},"created_at":{"type":"string","format":"date-time"},"app_url":{"type":"string","format":"uri","description":"The export in the Whalesync app — a convenience link, never a required step (anything that needs a person arrives as a pending action)."},"pending_actions":{"type":"array","items":{"$ref":"#/components/schemas/PendingAction"}},"url":{"type":"string","format":"uri"},"status_url":{"type":"string","format":"uri"},"mappings_url":{"type":"string","format":"uri","description":"The working mappings document — edit, then save."},"save_url":{"type":"string","format":"uri","description":"The save saga: POST to apply the mappings, GET to poll."},"schedule_url":{"type":"string","format":"uri","description":"The on/off switch and cadence."},"runs_url":{"type":"string","format":"uri","description":"The run history, pre-filtered to this export."}},"required":["id","name","status","source","destination","schedule","last_run","unsaved_changes","created_at","app_url","pending_actions","url","status_url","mappings_url","save_url","schedule_url","runs_url"]},"LiveExportList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LiveExport"}},"has_more":{"type":"boolean","description":"Whether more live exports follow this page."},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to fetch the next page."}},"required":["data","has_more","next_cursor"]},"LiveExportSideUpdate":{"type":"object","properties":{"auth":{"type":"object","description":"Credential fields for an `api_key` connector, keyed by the field ids from `GET /v1/live-export/connectors`. Never returned by any endpoint. Completes a side that was created without credentials."},"location":{"type":"string","description":"Destination side only: where created tables live (an id from `GET …/destination/locations`, e.g. a Notion parent page). Only needed for destinations that host tables somewhere pickable."}}},"LiveExportUpdate":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"source":{"$ref":"#/components/schemas/LiveExportSideUpdate"},"destination":{"$ref":"#/components/schemas/LiveExportSideUpdate"}}},"LiveExportDeleted":{"type":"object","properties":{"id":{"type":"string","example":"lex_8f3kq0vz"},"deleted":{"type":"boolean","default":true,"enum":[true]}},"required":["id","deleted"]},"LiveExportScheduleDetail":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the cadence is on. This is the on/off switch for the whole export."},"cadence":{"type":"string","enum":["every_10_minutes","every_30_minutes","hourly","daily","custom"]},"time_of_day":{"type":["string","null"],"example":"09:00","description":"When a `daily` export runs: a whole hour in 24-hour time, in `timezone` (UTC when no timezone is set). Null for the other cadences."},"cron":{"type":"string","description":"The exact schedule, as a cron expression. Read-only; set `cadence` and `time_of_day` instead."},"timezone":{"type":["string","null"],"example":"America/New_York"},"next_run_at":{"type":["string","null"],"format":"date-time","description":"When the next scheduled run fires."},"last_triggered_at":{"type":["string","null"],"format":"date-time"},"allowed_cadences":{"type":"array","description":"The cadences your plan can set. Pick from these to avoid a `plan_required` error.","items":{"type":"string","enum":["every_10_minutes","every_30_minutes","hourly","daily"]}}},"required":["enabled","cadence","time_of_day","cron","timezone","next_run_at","last_triggered_at","allowed_cadences"]},"LiveExportScheduleUpdate":{"type":"object","properties":{"enabled":{"type":"boolean","description":"The on/off switch for the whole export. `trigger` still works while off."},"cadence":{"type":"string","enum":["every_10_minutes","every_30_minutes","hourly","daily"],"description":"How often scheduled runs fire. Cadences above your plan are rejected — see `allowed_cadences`."},"time_of_day":{"type":"string","pattern":"TIME_OF_DAY_PATTERN","example":"09:00","description":"When a `daily` export runs: a whole hour in 24-hour time, in `timezone`. Only with the daily cadence. Left out, a daily schedule keeps its current time (midnight for a new one)."},"timezone":{"type":"string","maxLength":64,"example":"America/New_York","description":"The IANA timezone `time_of_day` is in. UTC when never set."}}},"LiveExportTrigger":{"type":"object","properties":{"force":{"type":"boolean","description":"Run even though the mappings have unsaved edits (the run uses the last-saved pipeline, not them). Without it, that state is the `unsaved_changes` conflict."}}},"RunStep":{"type":"object","properties":{"action":{"type":"string","enum":["prepare","pull","map","write_plan","write"],"description":"What the step does to your data. `write` is the step that changes the destination."},"name":{"type":"string","example":"Pull Contacts"},"status":{"type":"string","enum":["pending","running","completed","failed","skipped"]},"started_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]}},"required":["action","name","status","started_at","finished_at","error"]},"Run":{"type":"object","properties":{"id":{"type":"string","example":"run_8f3kq0vz.2mv91xk"},"status":{"type":"string","enum":["pending","running","completed","failed","cancelled"]},"trigger":{"type":"string","enum":["manual","schedule"],"description":"Whether a person/API started it, or the cadence did."},"started_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"summary":{"type":["string","null"],"description":"The one-line story: what the run is doing right now, or how it ended."},"warning":{"type":["string","null"],"description":"Set when the run completed but a step finished with a warning."},"error":{"type":["string","null"],"description":"The rolled-up error for a failed run."},"app_url":{"type":"string","format":"uri","description":"The run in the Whalesync app, for a person who wants to watch or share it — like every app_url, nothing here needs their attention."},"live_export_id":{"type":"string","example":"lex_8f3kq0vz","description":"The Live Export this run belongs to."},"steps":{"description":"The ordered phases of the run. Detail reads only.","type":"array","items":{"$ref":"#/components/schemas/RunStep"}},"url":{"type":"string","format":"uri"}},"required":["id","status","trigger","started_at","finished_at","summary","warning","error","app_url","live_export_id","steps","url"]},"LiveExportStatus":{"type":"object","properties":{"live_export_id":{"type":"string","example":"lex_8f3kq0vz"},"status":{"type":"string","enum":["draft","active","manual_only"]},"running":{"type":"boolean","description":"Whether a run is in flight right now — independent of the schedule."},"current_run":{"description":"The in-flight run, if any.","oneOf":[{"$ref":"#/components/schemas/RunSummary"},{"type":"null"}]},"next_run_at":{"type":["string","null"],"format":"date-time","description":"When the next scheduled run fires."},"last_run":{"description":"The most recent finished run.","oneOf":[{"$ref":"#/components/schemas/RunSummary"},{"type":"null"}]},"fetched_at":{"type":"string","format":"date-time"}},"required":["live_export_id","status","running","current_run","next_run_at","last_run","fetched_at"]},"LiveExportSourceTable":{"type":"object","properties":{"id":{"type":"string","description":"The id to use as `source_table` in the mappings document."},"name":{"type":"string","example":"Contacts"}},"required":["id","name"]},"LiveExportSourceTableList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LiveExportSourceTable"}}},"required":["data"]},"LiveExportSourceField":{"type":"object","properties":{"id":{"type":"string","description":"The id to use as `source_field` in the mappings document."},"name":{"type":"string","example":"Full name","description":"The destination column name the server suggests."},"type":{"type":"string","example":"text","description":"The destination type the server planned. When the source and destination are the same kind of database, this is the destination's exact column type (e.g. `text[]`, `numeric(12,2)`). Otherwise it's one of `text`, `longText`, `number`, `boolean`, `date`, `select`, `multiSelect`, `url`, `email`, `phone`, `currency` or `json`, with ` list` appended for a field that holds several values (e.g. `text list`). Informational — not settable."},"suggested_primary":{"type":"boolean","description":"True for the server's suggested primary (title) field, when the destination needs one."},"source_record_id":{"type":"boolean","description":"True for the source-record-id field — how re-runs update a record instead of duplicating it. Always included in created tables, picked or not."}},"required":["id","name","type","suggested_primary","source_record_id"]},"LiveExportSourceFieldList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LiveExportSourceField"}}},"required":["data"]},"LiveExportLocationList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LiveExportLocation"}},"has_more":{"type":"boolean","description":"True when the destination has more locations than it returns — pass `?search=` to find one."}},"required":["data","has_more"]},"LiveExportMappings":{"type":"object","properties":{"revision":{"type":"string","description":"Compare-and-set token for PUT (also sent as the `ETag` header)."},"tables":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The document is {\"tables\": [{\"source_table\", \"destination_table\": {\"create\": {\"name\"}}, \"fields\": [{\"source_field\", \"destination_field\": {\"create\": {\"name\", \"primary?\"}}}]}]}. Reference source tables and fields by the ids the discovery endpoints return; destination tables and fields are created by the export, so they take names, never types — types come from the plan behind …/fields."}},"required":["revision","tables"]},"LiveExportMappingIssue":{"type":"object","properties":{"code":{"type":"string","example":"unknown_source_field"},"message":{"type":"string","description":"What is wrong, in caller terms."},"path":{"type":["string","null"],"example":"tables[0]","description":"Where in the document, when it is one place."}},"required":["code","message","path"]},"LiveExportMappingsValidation":{"type":"object","properties":{"valid":{"type":"boolean","description":"True when a PUT of this document would be accepted."},"issues":{"type":"array","items":{"$ref":"#/components/schemas/LiveExportMappingIssue"}}},"required":["valid","issues"]},"LiveExportSaveError":{"type":"object","properties":{"name":{"type":"string","example":"Contacts","description":"The table or field that failed, by name."},"error":{"type":"string","description":"The destination connector's error."}},"required":["name","error"]},"LiveExportSave":{"type":"object","properties":{"state":{"type":"string","enum":["pending","running","succeeded","failed"]},"phase":{"type":["string","null"],"enum":["create_tables","apply",null],"description":"What the save is doing: creating destination tables/fields, then applying the pipeline."},"progress":{"type":"object","properties":{"resolved":{"type":"number"},"total":{"type":"number"}},"required":["resolved","total"]},"errors":{"description":"Per-item failures. Fix and POST again.","type":"array","items":{"$ref":"#/components/schemas/LiveExportSaveError"}},"message":{"type":"string","description":"The state as one human sentence, ready to show to a person."},"live_export_url":{"type":"string","format":"uri"}},"required":["state","phase","progress","errors","message","live_export_url"]},"RunList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RunSummary"}},"has_more":{"type":"boolean","description":"Whether more retained runs follow this page."},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to fetch the next page."}},"required":["data","has_more","next_cursor"]},"LiveExportConnectorRoles":{"type":"object","properties":{"source":{"type":"boolean","description":"Usable as the app records are read from."},"destination":{"type":"boolean","description":"Usable as the app tables are written into."}},"required":["source","destination"]},"LiveExportCredentialField":{"type":"object","properties":{"id":{"type":"string","example":"apiKey","description":"The key to use for this field inside `auth`."},"label":{"type":"string","example":"API key"},"optional":{"type":"boolean","description":"Whether the field may be omitted."},"placeholder":{"type":["string","null"],"description":"Example or hint text for the value."}},"required":["id","label","optional","placeholder"]},"LiveExportConnectorAuth":{"type":"object","properties":{"method":{"type":"string","enum":["oauth","api_key"],"description":"`oauth` connects in the browser (a person signs in; the export carries a pending action). `api_key` may also take `auth` inline at create."},"fields":{"description":"The credential fields for `api_key` connectors; null for `oauth`.","type":["array","null"],"items":{"$ref":"#/components/schemas/LiveExportCredentialField"}}},"required":["method","fields"]},"LiveExportConnector":{"type":"object","properties":{"type":{"type":"string","example":"hubspot","description":"The slug to pass as a side's `connector`."},"name":{"type":"string","example":"HubSpot"},"roles":{"$ref":"#/components/schemas/LiveExportConnectorRoles"},"auth":{"$ref":"#/components/schemas/LiveExportConnectorAuth"},"available":{"type":"boolean","description":"False when the connector needs a higher plan (see `required_plan`)."},"required_plan":{"type":["string","null"],"example":"Plus","description":"The plan that unlocks it, when `available` is false."}},"required":["type","name","roles","auth","available","required_plan"]},"LiveExportConnectorList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LiveExportConnector"}}},"required":["data"]}}}}