For the complete documentation index, see llms.txt. This page is also available as Markdown.

Agent quickstart

Guide for AI agents building syncs with the Whalesync API

This page is for AI agents driving the Whalesync API on someone's behalf, and for the people setting them up. Full endpoint documentation is in the API reference. Every error code is in the Error reference.

If your runtime supports MCP, prefer the MCP server. It wraps the same API, signs in through the browser instead of an API key, and helps guide you through the right steps.

Connection details

Base URL:  https://api.whalesync.com/v1
Spec:      https://api.whalesync.com/v1/openapi.json   (describes every endpoint)
Auth:      Authorization: Bearer ws_tok_…
Scopes:    read (monitor) · readwrite (build and change)

Fetch the OpenAPI spec first. Everything else can be read from it. Paths in this guide are relative to the base URL: GET /sync/connectors means GET https://api.whalesync.com/v1/sync/connectors.

Ask a human for an API key

There is no endpoint that creates an API key. API keys are created manually by a human in the Whalesync app. Do not probe for POST /keys or similar; no such endpoint exists.

A request without a usable key returns a 401 with a required_action containing the key-creation link to give to your user. You can also ask before starting:

To set this up I need a Whalesync API key. I can't create one myself.

  1. Open https://app.whalesync.com/settings/api-keys

  2. Click Create key

  3. Choose Read and write so I can build the sync (Read only is enough if you just want me to watch it)

  4. Copy the key and paste it here. It's shown only once.

Treat it like a password: it can read and change your syncs, including the contents of synced records.

Request the read scope if you only need monitoring. A read key can call every GET and POST …/validate; anything that changes state needs readwrite. Scope is fixed at creation.

Keys can be revoked at any time from the same page. Revocation is immediate.

Steps requiring human intervention

The API can create a sync, read the tables and fields on both sides, create tables and fields on a side where they don't exist yet, write and validate mappings, pause, activate, and delete syncs, read the operations log and open issues, look up a single record's sync state, and list the deletes waiting for review.

Three steps require a human. All are deliberate design decisions, so there is no API path around them.

Connecting a side in the browser. A person connects a side whenever its credentials aren't sent inline. That is always the case for apps that sign in through a browser — Airtable, HubSpot, Salesforce, Webflow, and similar OAuth connectors, whose credentials cannot be sent over the API at all. It is also a choice for connectors that take credentials inline (Postgres, Supabase, and others): if your user would rather not paste an app's credentials — a database password, a service API key — into the conversation, you can defer that side to a person too. Either way, declare that side with connector only and omit auth and base. The created sync has that side null and a pending_actions entry with a link for a human, who signs in, connects the app, and picks its base.

Any side can be left for a person to connect in the browser: declare it by connector alone, omitting auth and base, and a human completes the connection. For OAuth apps this is the only way. For API-key apps it is a choice, so your user never has to paste an app's credentials into the conversation.

Starting a sync. A sync only runs under mappings a human has reviewed and started in the app. Build the sync, then hand over its review_url. Any later mappings edit returns the sync to draft, which requires a new review.

Deciding a pending delete. On a sync with delete_approval: "review_required", a record that goes missing on one side waits for a person before the delete reaches the other side. Approving or ignoring one is irreversible, so no endpoint does it. GET /sync/pending-deletes?sync=… lists what's waiting; relay each entry's review_url.

How human steps appear in the API

The same object appears on the sync as pending_actions and on a blocked call's 409 as required_action:

Relay instruction and url to your user. Do not fetch the URL; it is a login-protected page for a human. Poll the sync until the step is complete.

Typical sync creation flow

  1. GET /sync/connectors to find your two apps and how each authenticates.

  2. POST /sync/syncs with credentials inline for the app that takes them, and connector alone for the browser one — or connector alone for any app whose credentials the user prefers to enter themselves.

  3. Relay the sync's user_authorization pending action. Poll until both sides are non-null.

  4. Follow tables_url, then each table's fields_url, to read the real tables and fields on both sides. Present these for your user to pick from; don't ask them to list tables and fields up front. Once a side is connected you can read its whole schema yourself.

  5. POST …/validate your mappings document, fix issues by code and path, then PUT …/mappings. Map to existing tables and fields, or have Whalesync create them on a side with {"create": {"name": "…"}} (see Creating tables and fields).

  6. Relay the sync's user_confirmation pending action. Poll until status is active.

  7. Monitor with GET …/status, /sync/operations?sync=…, and /sync/issues?sync=…. Issues carry remediation written to be acted on; POST /sync/issues/{id}/retry once the cause is fixed.

  8. For a question about one record ("why hasn't this contact arrived?"), GET /sync/records?sync=…&query=… to find it, then follow the hit's url for its status: both sides, its open issues, what's queued for it, and whether a delete is waiting on a person.

Creating tables and fields

You don't need a human to build the destination table or columns first. Whalesync can create them for you as part of writing mappings. In the mappings document, reference an existing object by string (a remote_id, a Whalesync id, or an exact name when it's unique on that side), or use {"create": {"name": "…"}} to have Whalesync create it on that side, typed from its mapped counterpart:

Creation happens when you PUT …/mappings: the response returns the document with real ids filled in, and retries safely adopt already-created objects. Only one side of a pair may be a create — the other must be an existing table or field to copy the type from.

Best practices

  • Follow the *_url fields in responses instead of constructing URLs. They point at the next valid steps for the resource's current state.

  • Read the schema; don't ask your user for it. Once a side is connected you can list its tables and fields yourself and present them to pick from. A destination table or field that doesn't exist yet doesn't have to be built by hand either — create it with {"create": {"name": "…"}}.

  • Send Idempotency-Key on POST /sync/syncs and the mappings PUT, the two calls that create objects, so retries are safe.

  • Use If-Match on the mappings PUT with the revision you read, so a concurrent edit made in the app fails with 412 revision_mismatch instead of being overwritten.

  • Prefer remote_id over names when referencing bases, tables, and fields. Names are not unique and can be renamed.

  • Don't ask the user to paste an app's credentials into the conversation unless they offer — create that side without auth and hand over the connect link.

  • Branch on code, not message. Messages are written for people and may change.

Last updated

Was this helpful?