WarmblyDocs
Endpoint reference

Integrations, automations, meetings

Connect third-party apps, build automation flows, capture booked meetings, and run on-demand Google Sheets lead syncs.

These endpoints cover Warmbly's integration layer: the provider catalog and connection lifecycle, per-connection event subscriptions and field mappings, the visual automation flow builder, the booked-meetings list, and on-demand Google Sheets lead sync. Most reads are reachable by operational integration users (use_integrations / INTEGRATIONS), while connecting and configuring is a settings action (manage_settings).

Integrations are a paid-plan feature. Browsing the catalog and listing connections is open so non-paid orgs see the upsell, but any mutating call (connect, authorize, configure, push, test, automations write) returns 403 with a "Integrations are available on paid plans" message when the organization is not on a paid plan.

List shapes here are mostly ad-hoc envelopes ({"connections": [...]}, {"automations": [...]}, and so on) rather than the cursor-paginated data + pagination shape used elsewhere. The meetings search and lead-sync source list are the exceptions and are noted inline. Errors follow the standard {error, message, code, request_id} envelope (see error codes).

List the integration catalog

GET /integrations/catalog

Returns the static metadata for every provider Warmbly supports, annotated with whether each OAuth provider has server-side credentials wired (configured).

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

Response

A catalog array of provider entries.

{
  "catalog": [
    {
      "provider": "hubspot",
      "name": "HubSpot",
      "tagline": "Sync contacts and deals to your CRM",
      "category": "crm",
      "auth_method": "oauth",
      "beta": false,
      "highlights": ["Push contacts on demand", "Auto-sync on positive reply"],
      "scopes": ["crm.objects.contacts.write"],
      "events": ["email.replied", "meeting.booked"],
      "action_types": ["hubspot.upsert_contact"],
      "supports_push": true,
      "configured": true
    }
    // ... one entry per provider
  ]
}

List connections

GET /integrations/connections

Returns this org's connection rows. Secrets (access/refresh tokens, pasted API keys) are never serialized.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

Response

A connections array of IntegrationConnection objects.

{
  "connections": [
    {
      "id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
      "organization_id": "1a2b3c4d-0000-0000-0000-000000000001",
      "provider": "hubspot",
      "label": "HubSpot (Sales)",
      "status": "connected",
      "auth_method": "oauth",
      "display_fields": {},
      "sync_direction": "push",
      "external_account_name": "Acme Inc",
      "granted_scopes": ["crm.objects.contacts.write"],
      "health": "healthy",
      "last_synced_at": "2026-06-10T14:03:00Z",
      "created_at": "2026-05-01T09:00:00Z",
      "updated_at": "2026-06-10T14:03:00Z"
    }
  ]
}

Create a connection

POST /integrations/connections

Creates a credential-based connection for api_key / webhook providers (for example Close or Discord). OAuth providers are rejected here with a hint to start the authorize flow instead (POST /integrations/oauth/start, session only).

Auth: Scope INTEGRATIONS · Org permission manage_settings.

Request body

FieldTypeRequiredDescription
providerstringYesA valid provider id (for example close, discord).
labelstringNoFriendly name shown on the connection card.
configobjectNoProvider-specific config (for example the pasted API key or webhook URL).
{
  "provider": "close",
  "label": "Close (Outbound)",
  "config": { "api_key": "api_xxx" }
}

Response

201 Created with the created IntegrationConnection object (bare, not enveloped). For inbound providers (Calendly, Cal.com) the response includes inbound_webhook_url once.

{
  "id": "c0ffee00-0000-4000-8000-000000000abc",
  "provider": "close",
  "label": "Close (Outbound)",
  "status": "connected",
  "auth_method": "api_key",
  "sync_direction": "push",
  "health": "unknown",
  "created_at": "2026-06-11T10:00:00Z",
  "updated_at": "2026-06-11T10:00:00Z"
}

Get a connection

GET /integrations/connections/:id

Returns one connection plus its event subscriptions and up to 20 recent sync runs (the detail drawer payload).

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidConnection id.

Response

{
  "connection": {
    "id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
    "provider": "hubspot",
    "label": "HubSpot (Sales)",
    "status": "connected",
    "auth_method": "oauth",
    "sync_direction": "push",
    "health": "healthy",
    "created_at": "2026-05-01T09:00:00Z",
    "updated_at": "2026-06-10T14:03:00Z"
  },
  "events": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
      "event_type": "email.replied",
      "action": "hubspot.upsert_contact",
      "config": {},
      "enabled": true,
      "use_case": "crm_sync",
      "created_at": "2026-05-01T09:05:00Z",
      "updated_at": "2026-05-01T09:05:00Z"
    }
  ],
  "runs": [
    {
      "id": "22222222-2222-4222-8222-222222222222",
      "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
      "kind": "push",
      "status": "success",
      "detail": "pushed 12 contacts",
      "records_processed": 12,
      "started_at": "2026-06-10T14:02:00Z",
      "finished_at": "2026-06-10T14:03:00Z"
    }
  ]
}

Update connection config

PATCH /integrations/connections/:id/config

Saves a connection's onboarding/capability snapshot (selected objects, enabled use-cases) and its sync direction.

Auth: Scope INTEGRATIONS · Org permission manage_settings.

ParameterInTypeDescription
idpathuuidConnection id.

Request body

FieldTypeRequiredDescription
config_capabilitiesobjectNoPer-connection capability snapshot (picker selections, enabled use-cases).
sync_directionstringNoData-flow direction: push, pull, or both.
{
  "config_capabilities": { "objects": ["contact"], "use_cases": ["crm_sync"] },
  "sync_direction": "push"
}

Response

{
  "connection": {
    "id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
    "provider": "hubspot",
    "sync_direction": "push",
    "status": "connected",
    "health": "healthy",
    "updated_at": "2026-06-11T11:00:00Z"
  }
}

Disconnect

DELETE /integrations/connections/:id

Removes a connection row.

Auth: Scope INTEGRATIONS · Org permission manage_settings.

ParameterInTypeDescription
idpathuuidConnection id.

Response

204 No Content.

List event subscriptions

GET /integrations/connections/:id/events

Returns the event subscriptions (event-to-action routes) configured on a connection.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidConnection id.

Response

{
  "events": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
      "event_type": "email.replied",
      "action": "hubspot.upsert_contact",
      "config": {},
      "enabled": true,
      "use_case": "crm_sync",
      "automation_id": null,
      "created_at": "2026-05-01T09:05:00Z",
      "updated_at": "2026-05-01T09:05:00Z"
    }
  ]
}

Create an event subscription

POST /integrations/connections/:id/events

Routes a Warmbly event to a provider action on this connection.

Auth: Scope INTEGRATIONS · Org permission manage_settings.

ParameterInTypeDescription
idpathuuidConnection id.

Request body

FieldTypeRequiredDescription
event_typestringYesThe Warmbly event to react to (for example email.replied).
actionstringYesProvider action id (for example slack.notify, hubspot.upsert_contact).
configobjectNoAction config (for example a Slack channel or message template).
enabledbooleanNoDefaults to true when omitted.
{
  "event_type": "email.replied",
  "action": "slack.notify",
  "config": { "channel": "#sales" },
  "enabled": true
}

Response

201 Created with the created IntegrationEventSubscription (bare object).

{
  "id": "33333333-3333-4333-8333-333333333333",
  "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
  "event_type": "email.replied",
  "action": "slack.notify",
  "config": { "channel": "#sales" },
  "enabled": true,
  "use_case": "notify",
  "created_at": "2026-06-11T12:00:00Z",
  "updated_at": "2026-06-11T12:00:00Z"
}

Delete an event subscription

DELETE /integrations/connections/:id/events/:eventId

Removes one event subscription.

Auth: Scope INTEGRATIONS · Org permission manage_settings.

ParameterInTypeDescription
idpathuuidConnection id.
eventIdpathuuidEvent subscription id.

Response

204 No Content.

List field mappings

GET /integrations/connections/:id/field-mappings

Returns the Warmbly-field to provider-field maps configured for a connection.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidConnection id.

Response

{
  "mappings": [
    {
      "id": "44444444-4444-4444-8444-444444444444",
      "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
      "direction": "push",
      "object_name": "contact",
      "warmbly_field": "email",
      "external_field": "email",
      "transform": "",
      "static_value": "",
      "is_default": true,
      "created_at": "2026-05-01T09:06:00Z"
    }
  ]
}

Replace field mappings

PUT /integrations/connections/:id/field-mappings

Swaps the connection-default field map for an object wholesale. A full replace is naturally idempotent, so retries are safe without an Idempotency-Key.

Auth: Scope INTEGRATIONS · Org permission manage_settings.

ParameterInTypeDescription
idpathuuidConnection id.

Request body

FieldTypeRequiredDescription
objectstringNoThe provider object the mappings apply to (for example contact).
mappingsarrayYesThe full set of mappings to store (replaces any existing).
mappings[].external_fieldstringYesDestination field on the provider. Required for every mapping.
mappings[].warmbly_fieldstringConditionalSource Warmbly field. Required unless transform is static.
mappings[].transformstringNoOne of `` (none), none, static, uppercase, lowercase, trim.
mappings[].static_valuestringConditionalRequired when transform is static.
{
  "object": "contact",
  "mappings": [
    { "warmbly_field": "email", "external_field": "email", "transform": "" },
    { "warmbly_field": "first_name", "external_field": "firstname", "transform": "trim" },
    { "external_field": "lifecyclestage", "transform": "static", "static_value": "lead" }
  ]
}

Response

The full mapping set after the replace (same shape as the list endpoint).

{
  "mappings": [
    {
      "id": "44444444-4444-4444-8444-444444444444",
      "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
      "object_name": "contact",
      "warmbly_field": "email",
      "external_field": "email",
      "transform": "",
      "is_default": true,
      "created_at": "2026-06-11T12:30:00Z"
    }
  ]
}

List sync runs

GET /integrations/connections/:id/runs

Returns up to 50 recent observability records for a connection (connect, token refresh, event dispatch, manual push).

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidConnection id.

Response

{
  "runs": [
    {
      "id": "22222222-2222-4222-8222-222222222222",
      "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
      "kind": "push",
      "status": "success",
      "detail": "pushed 12 contacts",
      "records_processed": 12,
      "started_at": "2026-06-10T14:02:00Z",
      "finished_at": "2026-06-10T14:03:00Z"
    }
  ]
}

Get the connection webhook secret

GET /integrations/connections/:id/webhook-secret

Returns (generating on first call) the HMAC signing secret for an automation connection, so you can verify Warmbly's outbound webhook signatures.

Auth: Scope INTEGRATIONS · Org permission manage_settings.

ParameterInTypeDescription
idpathuuidConnection id.

Response

{
  "signing_secret": "whsec_live_...",
  "signature_header": "X-Warmbly-Signature",
  "scheme": "HMAC-SHA256 of \"{t}.{body}\", sent as t=<unix>,v1=<hex>"
}

Test a connection

POST /integrations/connections/:id/test

Fires a synthetic event through the connection's notify/webhook automations so you can confirm the Zap, scenario, or channel is wired.

Auth: Scope INTEGRATIONS · Org permission manage_settings.

ParameterInTypeDescription
idpathuuidConnection id.

Response

{ "sent": true }

Push contacts to a CRM

POST /integrations/connections/:id/push

Synchronously upserts the given org contacts into a connected CRM (HubSpot, Pipedrive, Salesforce, Close). Retries are naturally safe: every provider upsert is keyed by email, so a repeated push converges rather than duplicating records. No Idempotency-Key is required. Per-record results are returned.

Auth: Scope INTEGRATIONS · Org permission use_integrations.

ParameterInTypeDescription
idpathuuidConnection id.

Request body

FieldTypeRequiredDescription
contact_idsstring[]YesContact ids to push. Deduplicated server-side. At least 1, at most 500.
{
  "contact_ids": [
    "aaaaaaaa-0000-4000-8000-000000000001",
    "aaaaaaaa-0000-4000-8000-000000000002"
  ]
}

Response

{
  "provider": "hubspot",
  "pushed": 1,
  "failed": 1,
  "results": [
    { "contact_id": "aaaaaaaa-0000-4000-8000-000000000001", "email": "[email protected]", "ok": true },
    { "contact_id": "aaaaaaaa-0000-4000-8000-000000000002", "email": "bad", "ok": false, "error": "invalid email" }
  ]
}

A connection whose token can no longer be refreshed returns 409 Conflict with a "needs to be reconnected" message.

List meeting bookings (integrations view)

GET /integrations/bookings

Returns up to 50 recent booked meetings, surfaced on the integrations page. For the full Meetings page list with filters and pagination, use GET /meetings.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

Response

{
  "bookings": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "source": "calendly",
      "status": "booked",
      "invitee_email": "[email protected]",
      "invitee_name": "Lead Person",
      "event_name": "Intro call",
      "scheduled_for": "2026-06-15T16:00:00Z",
      "join_url": "https://meet.example/abc",
      "contact_id": "aaaaaaaa-0000-4000-8000-000000000001",
      "created_at": "2026-06-11T08:00:00Z",
      "updated_at": "2026-06-11T08:00:00Z"
    }
  ]
}

List automations

GET /automations

Returns this org's automation flows (the visual flow builder).

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

Response

{
  "automations": [
    {
      "id": "66666666-6666-4666-8666-666666666666",
      "organization_id": "1a2b3c4d-0000-0000-0000-000000000001",
      "name": "Notify on positive reply",
      "enabled": true,
      "trigger_event": "email.replied",
      "graph": { "nodes": [], "edges": [] },
      "created_at": "2026-06-01T09:00:00Z",
      "updated_at": "2026-06-01T09:00:00Z"
    }
  ]
}

Create an automation

POST /automations

Creates a new automation flow: a trigger event plus a graph of condition and action nodes.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

Request body

FieldTypeRequiredDescription
namestringYesDisplay name.
enabledbooleanNoWhether the automation runs on matching events.
trigger_eventstringYesThe event that fires the flow (for example email.replied).
filterobjectNoOptional automation-wide gate (for example intents / min_confidence) applied to every action.
graphobjectYesThe flow graph: nodes and edges.
graph.nodes[]objectYesA node. type is trigger (exactly one, id trigger), condition, or action. Action nodes carry action, optional connection_id, and config; condition nodes carry condition. Each node has x / y canvas coordinates.
graph.edges[]objectYesAn edge: source, target, and when ("" for plain edges, true / false for the two outgoing edges of a condition).
{
  "name": "Notify on positive reply",
  "enabled": true,
  "trigger_event": "email.replied",
  "graph": {
    "nodes": [
      { "id": "trigger", "type": "trigger", "x": 0, "y": 0 },
      {
        "id": "a1",
        "type": "action",
        "action": "slack.notify",
        "connection_id": "9b2c1f7e-4d6a-4f2b-bb11-9d0e2a7c5f33",
        "config": { "channel": "#sales" },
        "x": 240,
        "y": 0
      }
    ],
    "edges": [
      { "id": "e1", "source": "trigger", "target": "a1", "when": "" }
    ]
  }
}

Response

201 Created with the created automation under an automation key.

{
  "automation": {
    "id": "66666666-6666-4666-8666-666666666666",
    "name": "Notify on positive reply",
    "enabled": true,
    "trigger_event": "email.replied",
    "graph": { "nodes": [ /* ... */ ], "edges": [ /* ... */ ] },
    "created_at": "2026-06-11T13:00:00Z",
    "updated_at": "2026-06-11T13:00:00Z"
  }
}

Get an automation

GET /automations/:id

Returns one automation with its full graph.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidAutomation id.

Response

{
  "automation": {
    "id": "66666666-6666-4666-8666-666666666666",
    "name": "Notify on positive reply",
    "enabled": true,
    "trigger_event": "email.replied",
    "graph": { "nodes": [ /* ... */ ], "edges": [ /* ... */ ] },
    "created_at": "2026-06-01T09:00:00Z",
    "updated_at": "2026-06-01T09:00:00Z"
  }
}

Update an automation

PATCH /automations/:id

Replaces an automation's name, enabled state, trigger, filter, and graph. The body shape matches the create payload.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidAutomation id.

Request body

Same fields as create an automation.

{
  "name": "Notify on positive reply",
  "enabled": false,
  "trigger_event": "email.replied",
  "graph": { "nodes": [ /* ... */ ], "edges": [ /* ... */ ] }
}

Response

{
  "automation": {
    "id": "66666666-6666-4666-8666-666666666666",
    "name": "Notify on positive reply",
    "enabled": false,
    "trigger_event": "email.replied",
    "graph": { "nodes": [ /* ... */ ], "edges": [ /* ... */ ] },
    "updated_at": "2026-06-11T13:30:00Z"
  }
}

Delete an automation

DELETE /automations/:id

Removes an automation. Returns 409 Conflict when the automation is still referenced by campaign steps.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidAutomation id.

Response

{ "deleted": true }

Test an automation

POST /automations/:id/test

Runs the automation against sample (or provided) data without side effects and returns the walked trace plus per-action previews (the builder's "Test" button).

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidAutomation id.

Request body

The body is optional. When omitted, the server builds a sample event from the trigger.

FieldTypeRequiredDescription
dataobjectNoSample event payload to evaluate the flow against.
{
  "data": { "email": "[email protected]", "intent": "positive" }
}

Response

A dry-run trace plus the resolved event data.

{
  "trace": [
    { "node_id": "trigger", "type": "trigger", "status": "success" },
    {
      "node_id": "a1",
      "type": "action",
      "action": "slack.notify",
      "label": "Slack · #sales",
      "status": "success",
      "preview": { "channel": "#sales", "text": "Lead replied: [email protected]" }
    }
  ],
  "data": { "email": "[email protected]", "intent": "positive" }
}

List automation runs

GET /automations/:id/runs

Returns recent run history for an automation (per fired event or manual launch), with per-node outcomes.

Auth: Scope INTEGRATIONS (or session with Org permission manage_settings or use_integrations).

ParameterInTypeDescription
idpathuuidAutomation id.
limitqueryintMax runs to return. Defaults to 50.

Response

{
  "runs": [
    {
      "id": "77777777-7777-4777-8777-777777777777",
      "automation_id": "66666666-6666-4666-8666-666666666666",
      "trigger_event": "email.replied",
      "status": "success",
      "node_results": [
        { "node_id": "a1", "type": "action", "action": "slack.notify", "status": "success" }
      ],
      "started_at": "2026-06-10T15:00:00Z",
      "finished_at": "2026-06-10T15:00:01Z"
    }
  ]
}

Search meetings

GET /meetings

The Meetings page list: booked calls from connected scheduling providers (Calendly, Cal.com) plus manually logged meetings, filtered by timeframe, status, and text, with offset pagination.

Auth: Scope READ_CONTACTS · Org permission view_contacts.

ParameterInTypeDescription
timeframequerystringOne of upcoming, past, or empty for all. Anything else returns 400.
statusquerystringExact status filter (booked, rescheduled, canceled, completed, no_show), or empty for any.
qquerystringMatches invitee name/email or event name.
limitqueryintPage size (clamped 1..200 server-side).
cursorquerystringOpaque cursor from a previous page's pagination.next_cursor. Omit for the first page.

Response

A data array plus the standard pagination envelope with an opaque next_cursor and an exact total (offset-paginated under the hood, identical shape to every other list).

{
  "data": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "source": "calendly",
      "status": "booked",
      "invitee_email": "[email protected]",
      "invitee_name": "Lead Person",
      "event_name": "Intro call",
      "scheduled_for": "2026-06-15T16:00:00Z",
      "join_url": "https://meet.example/abc",
      "contact_id": "aaaaaaaa-0000-4000-8000-000000000001",
      "contact_name": "Lead Person",
      "created_at": "2026-06-11T08:00:00Z",
      "updated_at": "2026-06-11T08:00:00Z"
    }
  ],
  "pagination": {
    "total": 42,
    "next_cursor": null,
    "has_more": false
  }
}

Meetings summary

GET /meetings/summary

Returns the counts the Meetings page header and sidebar show.

Auth: Scope READ_CONTACTS · Org permission view_contacts.

Response

{
  "upcoming": 7,
  "today": 2,
  "total": 42,
  "canceled": 3
}

Create a meeting

POST /meetings

Logs a meeting by hand (source manual). It lives alongside auto-captured Calendly/Cal.com bookings on the Meetings page and the contact timeline. The contact is attributed by an explicit (verified) id or by an org-scoped email match.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

Request body

FieldTypeRequiredDescription
titlestringNoMeeting title. Defaults to Call when empty.
invitee_namestringConditionalA name or email is required.
invitee_emailstringConditionalA name or email is required. Lowercased server-side.
scheduled_forstringYesRFC 3339 date-time. Required and validated.
duration_minutesintNoWhen > 0, sets the end time.
locationstringNoFree-text location.
join_urlstringNoVideo/join link.
contact_idstringNoExplicit contact id to link (verified against the org).
{
  "title": "Intro call",
  "invitee_name": "Lead Person",
  "invitee_email": "[email protected]",
  "scheduled_for": "2026-06-15T16:00:00Z",
  "duration_minutes": 30,
  "join_url": "https://meet.example/abc"
}

Response

201 Created with the created booking under a meeting key.

{
  "meeting": {
    "id": "55555555-5555-4555-8555-555555555555",
    "source": "manual",
    "status": "booked",
    "invitee_email": "[email protected]",
    "invitee_name": "Lead Person",
    "event_name": "Intro call",
    "scheduled_for": "2026-06-15T16:00:00Z",
    "end_time": "2026-06-15T16:30:00Z",
    "contact_id": "aaaaaaaa-0000-4000-8000-000000000001",
    "created_at": "2026-06-11T14:00:00Z",
    "updated_at": "2026-06-11T14:00:00Z"
  }
}

Delete a meeting

DELETE /meetings/:id

Removes a meeting booking (used for manually created ones).

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

ParameterInTypeDescription
idpathuuidMeeting booking id.

Response

{ "deleted": true }

Get the lead-sync Google connection

GET /lead-sync/google/connection

Reports whether the org has a connected Google account usable for lead sync (the hidden google_sheets OAuth connection).

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

Response

{
  "connected": true,
  "connection": {
    "id": "88888888-8888-4888-8888-888888888888",
    "external_account_name": "[email protected]",
    "status": "connected"
  }
}

When no Google account is connected, connected is false and connection is null.

Get spreadsheet metadata

POST /lead-sync/google/spreadsheet

Returns a sheet's title and tabs so the UI can render a tab picker before mapping columns.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

Request body

FieldTypeRequiredDescription
connection_idstringYesThe google_sheets connection id.
sheet_idstringYesThe Google spreadsheet id.
{
  "connection_id": "88888888-8888-4888-8888-888888888888",
  "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz"
}

Response

{
  "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz",
  "title": "Q2 Leads",
  "tabs": [
    { "title": "Inbound", "index": 0 },
    { "title": "Outbound", "index": 1 }
  ]
}

Preview a lead sync

POST /lead-sync/google/preview

Returns an import-preview-shaped payload (columns, sample rows, total rows, header detection, suggested mapping) so the frontend reuses its contact-import column mapper verbatim.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

Request body

FieldTypeRequiredDescription
connection_idstringYesThe google_sheets connection id.
sheet_idstringYesThe Google spreadsheet id.
tab_titlestringNoThe tab to read. Defaults to the first tab.
{
  "connection_id": "88888888-8888-4888-8888-888888888888",
  "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz",
  "tab_title": "Inbound"
}

Response

{
  "filename": "Q2 Leads / Inbound",
  "format": "google_sheets",
  "total_rows": 124,
  "columns": ["Email", "First name", "Company"],
  "has_header": true,
  "sample_rows": [
    ["[email protected]", "Jane", "Acme"]
  ],
  "suggested_mapping": [
    { "index": 0, "target": "email" },
    { "index": 1, "target": "first_name" },
    { "index": 2, "target": "company" }
  ]
}

List lead-sync sources

GET /lead-sync/sources

Lists this org's saved sync sources, optionally filtered to a campaign.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

ParameterInTypeDescription
campaign_idqueryuuidOptional. Restricts to sources targeting that campaign. Invalid ids return 400.

Response

A data array (no pagination envelope on this list).

{
  "data": [
    {
      "id": "99999999-9999-4999-8999-999999999999",
      "provider": "google_sheets",
      "connection_id": "88888888-8888-4888-8888-888888888888",
      "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz",
      "sheet_title": "Q2 Leads",
      "tab_title": "Inbound",
      "has_header": true,
      "column_mapping": [
        { "index": 0, "target": "email" },
        { "index": 1, "target": "first_name" }
      ],
      "dedup": "update",
      "category_ids": [],
      "subscribed_default": true,
      "status": "idle",
      "last_synced_at": "2026-06-10T12:00:00Z",
      "created_at": "2026-06-01T09:00:00Z",
      "updated_at": "2026-06-10T12:00:00Z"
    }
  ]
}

Create a lead-sync source

POST /lead-sync/sources

Saves a new on-demand sync source binding a Google Sheet tab to Warmbly's contact importer.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

Request body

FieldTypeRequiredDescription
connection_iduuidYesThe org's google_sheets OAuth connection.
sheet_idstringYesThe Google spreadsheet id.
sheet_titlestringNoSpreadsheet title (for display).
tab_titlestringNoTab to read.
has_headerbooleanNoWhether the first row is a header.
column_mappingarrayYesColumn-to-target mappings (same shape as contact import).
dedupstringNoCollision strategy: skip, update, or create_duplicate.
target_campaign_iduuidNoEnrol new/updated leads into this campaign on each sync.
category_idsstring[]NoCategories to assign to synced leads.
subscribed_defaultbooleanNoDefault subscription state for new contacts.
labelstringNoFriendly name.
{
  "connection_id": "88888888-8888-4888-8888-888888888888",
  "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz",
  "sheet_title": "Q2 Leads",
  "tab_title": "Inbound",
  "has_header": true,
  "column_mapping": [
    { "index": 0, "target": "email" },
    { "index": 1, "target": "first_name" }
  ],
  "dedup": "update",
  "target_campaign_id": null,
  "category_ids": [],
  "subscribed_default": true,
  "label": "Q2 inbound leads"
}

Response

201 Created with the created LeadSyncSource (bare object, same shape as a list item).

{
  "id": "99999999-9999-4999-8999-999999999999",
  "provider": "google_sheets",
  "connection_id": "88888888-8888-4888-8888-888888888888",
  "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz",
  "sheet_title": "Q2 Leads",
  "tab_title": "Inbound",
  "has_header": true,
  "column_mapping": [
    { "index": 0, "target": "email" },
    { "index": 1, "target": "first_name" }
  ],
  "dedup": "update",
  "category_ids": [],
  "subscribed_default": true,
  "status": "idle",
  "created_at": "2026-06-11T15:00:00Z",
  "updated_at": "2026-06-11T15:00:00Z"
}

Get a lead-sync source

GET /lead-sync/sources/:id

Returns one saved source.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

ParameterInTypeDescription
idpathuuidSource id.

Response

A bare LeadSyncSource object (same shape as a list item).

Update a lead-sync source

PATCH /lead-sync/sources/:id

Edits a saved source. All fields are optional; omitting a field leaves the stored value untouched.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

ParameterInTypeDescription
idpathuuidSource id.

Request body

FieldTypeRequiredDescription
sheet_idstringNoNew spreadsheet id.
sheet_titlestringNoNew title.
tab_titlestringNoNew tab.
has_headerbooleanNoHeader toggle.
column_mappingarrayNoReplacement mappings.
dedupstringNoCollision strategy.
target_campaign_iduuidNoNew target campaign.
clear_campaignbooleanNoWhen true, unsets the target campaign.
category_idsstring[]NoReplacement category set.
subscribed_defaultbooleanNoDefault subscription state.
labelstringNoNew label.
{
  "label": "Q2 inbound (paused)",
  "dedup": "skip",
  "clear_campaign": true
}

Response

The updated LeadSyncSource (bare object).

Delete a lead-sync source

DELETE /lead-sync/sources/:id

Removes a saved source.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

ParameterInTypeDescription
idpathuuidSource id.

Response

204 No Content.

Run a lead-sync source now

POST /lead-sync/sources/:id/sync

Runs the source on demand: reads the sheet and upserts contacts through the contact importer. New rows create contacts; rows matching an existing contact by email are updated. Naturally idempotent (email upsert), so retries are safe without an Idempotency-Key.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

ParameterInTypeDescription
idpathuuidSource id.

Response

The source id plus the underlying contact-import counts.

{
  "source_id": "99999999-9999-4999-8999-999999999999",
  "result": {
    "total": 124,
    "imported": 18,
    "updated": 100,
    "skipped": 6,
    "failed": 0,
    "started_at": "2026-06-11T16:00:00Z",
    "ended_at": "2026-06-11T16:00:04Z"
  }
}

On this page