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,
      "rank": 1
    }
    // ... one entry per provider
  ]
}

rank is the provider's popularity on this instance, 1 being the most used: the number of workspaces with a live connection, ties broken by a curated order so a new instance still has one. The array keeps catalog order; sort by rank to show the most used first.

List community apps

GET /integrations/community

Returns the community directory listings shown in discovery: featured apps first, then published apps installed in at least 25 workspaces other than the publisher's, each at least two weeks old, most installed first. A link-only app is never returned here.

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

ParameterInTypeDescription
limitqueryintegerBetween 1 and 200, defaults to 100. Out-of-range values return 400.
cursorquerystringOpaque cursor from pagination.next_cursor. A cursor this API did not issue returns 400.

Response

{
  "data": [
    {
      "application_id": "5a8f0c1e-2b3d-4e5f-8a9b-0c1d2e3f4a5b",
      "slug": "acme-sync",
      "name": "Acme Sync",
      "tagline": "Push positive replies into Acme as deals",
      "description": "",
      "category": "crm",
      "logo_url": "",
      "website_url": "https://acme.com",
      "install_url": "https://acme.com/warmbly/install",
      "support_url": "https://acme.com/support",
      "privacy_url": "",
      "developer": "Acme Inc",
      "scopes": 1,
      "permissions": [
        { "name": "READ_EMAILS", "value": 1, "description": "View email accounts and settings", "category": "read" }
      ],
      "status": "featured",
      "listed": true,
      "installs": 12,
      "installed": false,
      "published_at": "2026-10-03T09:00:00Z"
    }
  ],
  "pagination": { "total": 1, "next_cursor": null, "has_more": false }
}

status is featured or published, and listed says whether it shows in discovery. developer is the publishing workspace's name. installs counts workspaces on this instance with an active grant, and installed says whether anyone in the caller's workspace has one. No credential, redirect URI or webhook setting is ever included.

Get a community app

GET /integrations/community/{slug}

Returns one listing by its slug, listed or link only, in the same shape as a list item. This is how a shared link resolves. A hidden or unpublished listing, or one whose app is disabled, returns 404.

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

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. For Salesforce, each contact is matched to its Lead or Contact, created as the connection's Create as when missing, and updated through its field rules.

Auth: Scope INTEGRATIONS · Org permission use_integrations.

ParameterInTypeDescription
idpathuuidConnection id.

Request body

FieldTypeRequiredDescription
contact_idsstring[]Yes, unless allContact ids to push. Deduplicated server-side. At least 1, at most 500.
all, filters, exclude—NoA filter selection instead of a list. It still has to resolve to 500 contacts or fewer: the push calls the CRM once per contact inside the request.
{
  "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.

Salesforce sync

Every route below takes the Salesforce connection id as :id and is scoped to the caller's organization. See the Salesforce guide for what each setting does.

MethodPathOrg permissionPurpose
GET/integrations/salesforce/:id/overviewreadHealth, API usage and budget, outbox counts, last pull. ?checks=1 also probes what the connected user can create and edit
GET/integrations/salesforce/:id/settingsread{ settings, warmbly_fields, defaults }
PUT/integrations/salesforce/:id/settingsmanage_settingsReplace the settings. A whole-document write, so a retry converges
GET/integrations/salesforce/:id/metadatareadLead and Contact fields, Lead Status and Lead Source picklists
GET/integrations/salesforce/:id/users?q=readActive Salesforce users, for owner pickers
GET/integrations/salesforce/:id/list-views?object=Lead|ContactreadList views the connected user can see
GET/integrations/salesforce/:id/campaigns?q=readSalesforce Campaigns
POST/integrations/salesforce/:id/import/previewuse_integrations{ source_kind, object, source_id } to { total, sample }. Writes nothing
GET/integrations/salesforce/:id/import-sourcesreadSaved imports with their last result
POST/integrations/salesforce/:id/import-sourcesmanage_settingsSave an import and start its first run
PATCH/integrations/salesforce/:id/import-sources/:sourceIdmanage_settingsEdit name, campaign_id (null clears it), category_ids, recurring, enabled
POST/integrations/salesforce/:id/import-sources/:sourceId/runuse_integrationsRun now. 409 import_running while one is running
DELETE/integrations/salesforce/:id/import-sources/:sourceIdmanage_settingsRemove the import. Contacts it brought in stay
GET/integrations/salesforce/:id/activity?status=&contact_id=&cursor=&limit=readThe activity log, newest first, { data, pagination }
POST/integrations/salesforce/:id/activity/retrymanage_settings{ ids } re-queues those failed or skipped rows; no ids re-queues every failed row
POST/integrations/salesforce/:id/sync-nowuse_integrationsLog pending activity and pull changes now
GET/contacts/:id/salesforceview_contactsThe contact's Salesforce card: linked records, owner, status, opportunities, recent Tasks, sync state
POST/contacts/:id/salesforce/syncuse_integrations{ connection_id?, create_as? }: match (or create as lead or contact) and push mapped fields; returns the card
DELETE/contacts/:id/salesforce/links/:linkIduse_integrationsUnlink the contact from a record. Nothing changes in Salesforce

"read" is any of manage_settings or use_integrations. API keys need the INTEGRATIONS permission, except GET /contacts/:id/salesforce, which shows opportunities and needs READ_CRM.

Creating an import is not deduplicated by request: a retried create saves a second import, whose run brings in nobody new because people are matched by address.

List meeting bookings (integrations view)

GET /integrations/bookings

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

Auth: Scope READ_CONTACTS · Org permission view_contacts.

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 · Org permission manage_settings.

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 · Org permission manage_settings.

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 · Org permission manage_settings.

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. The mapping is suggested exactly as the file import preview suggests it, including headers matched to the workspace's existing custom fields.

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 or a segment.

Auth: Scope WRITE_CONTACTS · Org permission manage_contacts.

ParameterInTypeDescription
campaign_idqueryuuidOptional. Restricts to sources targeting that campaign. Invalid ids return 400.
segment_idqueryuuidOptional. Restricts to sources that pin their rows into that segment. Invalid ids return 400. Combines with campaign_id.

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": [],
      "segment_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). Validated on save, not on the next sync: a mapping with no email column, or a custom field whose name Warmbly cannot use, is a 400 here.
dedupstringNoCollision strategy: skip, update, or create_duplicate.
target_campaign_iduuidNoEnrol new/updated leads into this campaign on each sync.
category_idsstring[]NoLabel IDs to put on synced leads.
segment_idsstring[]NoSegments every synced row is pinned into as a manual include override, on every run. Each id must name a segment in the organization or the save is a 400; a segment deleted later is dropped from the run instead of failing it.
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": [],
  "segment_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": [],
  "segment_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 set of label IDs.
segment_idsstring[]NoReplacement segment target set. An unknown id is a 400; an empty array clears the targets.
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