# Integration, automation and meeting endpoints

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

Source: https://docs.ayinbox.com/api/reference/integrations/

These endpoints cover AY Inbox'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](/api/error-codes/)).

List the integration catalog [#list-the-integration-catalog]

`GET /integrations/catalog`

Returns the static metadata for every provider AY Inbox 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 [#response]

A `catalog` array of provider entries.

```json
{
  "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 [#list-community-apps]

`GET /integrations/community`

Returns the [community directory](/guides/integrations/#community-apps) 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`).

| Parameter | In    | Type    | Description                                                                                 |
| --------- | ----- | ------- | ------------------------------------------------------------------------------------------- |
| `limit`   | query | integer | Between 1 and 200, defaults to 100. Out-of-range values return `400`.                       |
| `cursor`  | query | string  | Opaque cursor from `pagination.next_cursor`. A cursor this API did not issue returns `400`. |

Response [#response-1]

```json
{
  "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/ayinbox/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-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 [#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 [#response-2]

A `connections` array of `IntegrationConnection` objects.

```json
{
  "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 [#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 [#request-body]

| Field      | Type   | Required | Description                                                               |
| ---------- | ------ | -------- | ------------------------------------------------------------------------- |
| `provider` | string | Yes      | A valid provider id (for example `close`, `discord`).                     |
| `label`    | string | No       | Friendly name shown on the connection card.                               |
| `config`   | object | No       | Provider-specific config (for example the pasted API key or webhook URL). |

```json
{
  "provider": "close",
  "label": "Close (Outbound)",
  "config": { "api_key": "api_xxx" }
}
```

Response [#response-3]

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

```json
{
  "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-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`).

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Response [#response-4]

```json
{
  "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 [#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`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Request body [#request-body-1]

| Field                 | Type   | Required | Description                                                                |
| --------------------- | ------ | -------- | -------------------------------------------------------------------------- |
| `config_capabilities` | object | No       | Per-connection capability snapshot (picker selections, enabled use-cases). |
| `sync_direction`      | string | No       | Data-flow direction: `push`, `pull`, or `both`.                            |

```json
{
  "config_capabilities": { "objects": ["contact"], "use_cases": ["crm_sync"] },
  "sync_direction": "push"
}
```

Response [#response-5]

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

Disconnect [#disconnect]

`DELETE /integrations/connections/:id`

Removes a connection row.

Auth: **Scope** `INTEGRATIONS` · **Org permission** `manage_settings`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Response [#response-6]

`204 No Content`.

List event subscriptions [#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`).

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Response [#response-7]

```json
{
  "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 [#create-an-event-subscription]

`POST /integrations/connections/:id/events`

Routes an AY Inbox event to a provider action on this connection.

Auth: **Scope** `INTEGRATIONS` · **Org permission** `manage_settings`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Request body [#request-body-2]

| Field        | Type    | Required | Description                                                                |
| ------------ | ------- | -------- | -------------------------------------------------------------------------- |
| `event_type` | string  | Yes      | The AY Inbox event to react to (for example `email.replied`).              |
| `action`     | string  | Yes      | Provider action id (for example `slack.notify`, `hubspot.upsert_contact`). |
| `config`     | object  | No       | Action config (for example a Slack channel or message template).           |
| `enabled`    | boolean | No       | Defaults to `true` when omitted.                                           |

```json
{
  "event_type": "email.replied",
  "action": "slack.notify",
  "config": { "channel": "#sales" },
  "enabled": true
}
```

Response [#response-8]

`201 Created` with the created `IntegrationEventSubscription` (bare object).

```json
{
  "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-an-event-subscription]

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

Removes one event subscription.

Auth: **Scope** `INTEGRATIONS` · **Org permission** `manage_settings`.

| Parameter | In   | Type | Description            |
| --------- | ---- | ---- | ---------------------- |
| `id`      | path | uuid | Connection id.         |
| `eventId` | path | uuid | Event subscription id. |

Response [#response-9]

`204 No Content`.

List field mappings [#list-field-mappings]

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

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

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

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Response [#response-10]

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

Replace field mappings [#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`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Request body [#request-body-3]

| Field                       | Type   | Required    | Description                                                             |
| --------------------------- | ------ | ----------- | ----------------------------------------------------------------------- |
| `object`                    | string | No          | The provider object the mappings apply to (for example `contact`).      |
| `mappings`                  | array  | Yes         | The full set of mappings to store (replaces any existing).              |
| `mappings[].external_field` | string | Yes         | Destination field on the provider. Required for every mapping.          |
| `mappings[].ayinbox_field`  | string | Conditional | Source AY Inbox field. Required unless `transform` is `static`.         |
| `mappings[].transform`      | string | No          | One of \`\` (none), `none`, `static`, `uppercase`, `lowercase`, `trim`. |
| `mappings[].static_value`   | string | Conditional | Required when `transform` is `static`.                                  |

```json
{
  "object": "contact",
  "mappings": [
    { "ayinbox_field": "email", "external_field": "email", "transform": "" },
    { "ayinbox_field": "first_name", "external_field": "firstname", "transform": "trim" },
    { "external_field": "lifecyclestage", "transform": "static", "static_value": "lead" }
  ]
}
```

Response [#response-11]

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

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

List sync runs [#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`).

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Response [#response-12]

```json
{
  "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-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 AY Inbox's outbound webhook signatures.

Auth: **Scope** `INTEGRATIONS` · **Org permission** `manage_settings`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Response [#response-13]

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

Test a connection [#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`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Response [#response-14]

```json
{ "sent": true }
```

Push contacts to a CRM [#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](/guides/salesforce/#field-mapping).

Auth: **Scope** `INTEGRATIONS` · **Org permission** `use_integrations`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Connection id. |

Request body [#request-body-4]

| Field                       | Type      | Required          | Description                                                                                                                                                                                                          |
| --------------------------- | --------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contact_ids`               | string\[] | Yes, unless `all` | Contact ids to push. Deduplicated server-side. At least 1, at most 500.                                                                                                                                              |
| `all`, `filters`, `exclude` | —         | No                | A [filter selection](/api/reference/contacts/#selecting-contacts-for-a-bulk-action) 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. |

```json
{
  "contact_ids": [
    "aaaaaaaa-0000-4000-8000-000000000001",
    "aaaaaaaa-0000-4000-8000-000000000002"
  ]
}
```

Response [#response-15]

```json
{
  "provider": "hubspot",
  "pushed": 1,
  "failed": 1,
  "results": [
    { "contact_id": "aaaaaaaa-0000-4000-8000-000000000001", "email": "jane@acme.com", "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 [#salesforce-sync]

Every route below takes the Salesforce connection id as `:id` and is scoped to the caller's organization. See the [Salesforce guide](/guides/salesforce/) for what each setting does.

| Method | Path                                                                       | Org permission     | Purpose                                                                                                                     |
| ------ | -------------------------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| GET    | `/integrations/salesforce/:id/overview`                                    | read               | Health, API usage and budget, outbox counts, last pull. `?checks=1` also probes what the connected user can create and edit |
| GET    | `/integrations/salesforce/:id/settings`                                    | read               | `{ settings, ayinbox_fields, defaults }`                                                                                    |
| PUT    | `/integrations/salesforce/:id/settings`                                    | `manage_settings`  | Replace the settings. A whole-document write, so a retry converges                                                          |
| GET    | `/integrations/salesforce/:id/metadata`                                    | read               | Lead and Contact fields, Lead Status and Lead Source picklists                                                              |
| GET    | `/integrations/salesforce/:id/users?q=`                                    | read               | Active Salesforce users, for owner pickers                                                                                  |
| GET    | `/integrations/salesforce/:id/list-views?object=Lead\|Contact`             | read               | List views the connected user can see                                                                                       |
| GET    | `/integrations/salesforce/:id/campaigns?q=`                                | read               | Salesforce Campaigns                                                                                                        |
| POST   | `/integrations/salesforce/:id/import/preview`                              | `use_integrations` | `{ source_kind, object, source_id }` to `{ total, sample }`. Writes nothing                                                 |
| GET    | `/integrations/salesforce/:id/import-sources`                              | read               | Saved imports with their last result                                                                                        |
| POST   | `/integrations/salesforce/:id/import-sources`                              | `manage_settings`  | Save an import and start its first run                                                                                      |
| PATCH  | `/integrations/salesforce/:id/import-sources/:sourceId`                    | `manage_settings`  | Edit name, `campaign_id` (`null` clears it), `category_ids`, `recurring`, `enabled`                                         |
| POST   | `/integrations/salesforce/:id/import-sources/:sourceId/run`                | `use_integrations` | Run now. `409 import_running` while one is running                                                                          |
| DELETE | `/integrations/salesforce/:id/import-sources/:sourceId`                    | `manage_settings`  | Remove the import. Contacts it brought in stay                                                                              |
| GET    | `/integrations/salesforce/:id/activity?status=&contact_id=&cursor=&limit=` | read               | The activity log, newest first, `{ data, pagination }`                                                                      |
| POST   | `/integrations/salesforce/:id/activity/retry`                              | `manage_settings`  | `{ ids }` re-queues those failed or skipped rows; no ids re-queues every failed row                                         |
| POST   | `/integrations/salesforce/:id/sync-now`                                    | `use_integrations` | Log pending activity and pull changes now                                                                                   |
| GET    | `/contacts/:id/salesforce`                                                 | `view_contacts`    | The contact's Salesforce card: linked records, owner, status, opportunities, recent Tasks, sync state                       |
| POST   | `/contacts/:id/salesforce/sync`                                            | `use_integrations` | `{ connection_id?, create_as? }`: match (or create as `lead` or `contact`) and push mapped fields; returns the card         |
| DELETE | `/contacts/:id/salesforce/links/:linkId`                                   | `use_integrations` | Unlink 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) [#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 [#response-16]

```json
{
  "bookings": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "source": "calendly",
      "status": "booked",
      "invitee_email": "lead@acme.com",
      "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 [#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 [#response-17]

```json
{
  "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 [#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 [#request-body-5]

| Field           | Type    | Required | Description                                                                                                                                                                                                                            |
| --------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`          | string  | Yes      | Display name.                                                                                                                                                                                                                          |
| `enabled`       | boolean | No       | Whether the automation runs on matching events.                                                                                                                                                                                        |
| `trigger_event` | string  | Yes      | The event that fires the flow (for example `email.replied`).                                                                                                                                                                           |
| `filter`        | object  | No       | Optional automation-wide gate (for example intents / `min_confidence`) applied to every action.                                                                                                                                        |
| `graph`         | object  | Yes      | The flow graph: `nodes` and `edges`.                                                                                                                                                                                                   |
| `graph.nodes[]` | object  | Yes      | A 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[]` | object  | Yes      | An edge: `source`, `target`, and `when` (`""` for plain edges, `true` / `false` for the two outgoing edges of a condition).                                                                                                            |

```json
{
  "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 [#response-18]

`201 Created` with the created automation under an `automation` key.

```json
{
  "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-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`).

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Automation id. |

Response [#response-19]

```json
{
  "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 [#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`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Automation id. |

Request body [#request-body-6]

Same fields as [create an automation](#create-an-automation).

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

Response [#response-20]

```json
{
  "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-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`.

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Automation id. |

Response [#response-21]

```json
{ "deleted": true }
```

Test an automation [#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`).

| Parameter | In   | Type | Description    |
| --------- | ---- | ---- | -------------- |
| `id`      | path | uuid | Automation id. |

Request body [#request-body-7]

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

| Field  | Type   | Required | Description                                        |
| ------ | ------ | -------- | -------------------------------------------------- |
| `data` | object | No       | Sample event payload to evaluate the flow against. |

```json
{
  "data": { "email": "lead@acme.com", "intent": "positive" }
}
```

Response [#response-22]

A dry-run trace plus the resolved event data.

```json
{
  "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: lead@acme.com" }
    }
  ],
  "data": { "email": "lead@acme.com", "intent": "positive" }
}
```

List automation runs [#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`).

| Parameter | In    | Type | Description                         |
| --------- | ----- | ---- | ----------------------------------- |
| `id`      | path  | uuid | Automation id.                      |
| `limit`   | query | int  | Max runs to return. Defaults to 50. |

Response [#response-23]

```json
{
  "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 [#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`.

| Parameter   | In    | Type   | Description                                                                                          |
| ----------- | ----- | ------ | ---------------------------------------------------------------------------------------------------- |
| `timeframe` | query | string | One of `upcoming`, `past`, or empty for all. Anything else returns `400`.                            |
| `status`    | query | string | Exact status filter (`booked`, `rescheduled`, `canceled`, `completed`, `no_show`), or empty for any. |
| `q`         | query | string | Matches invitee name/email or event name.                                                            |
| `limit`     | query | int    | Page size (clamped 1..200 server-side).                                                              |
| `cursor`    | query | string | Opaque cursor from a previous page's `pagination.next_cursor`. Omit for the first page.              |

Response [#response-24]

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).

```json
{
  "data": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "source": "calendly",
      "status": "booked",
      "invitee_email": "lead@acme.com",
      "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 [#meetings-summary]

`GET /meetings/summary`

Returns the counts the Meetings page header and sidebar show.

Auth: **Scope** `READ_CONTACTS` · **Org permission** `view_contacts`.

Response [#response-25]

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

Create a meeting [#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 [#request-body-8]

| Field              | Type   | Required    | Description                                             |
| ------------------ | ------ | ----------- | ------------------------------------------------------- |
| `title`            | string | No          | Meeting title. Defaults to `Call` when empty.           |
| `invitee_name`     | string | Conditional | A name or email is required.                            |
| `invitee_email`    | string | Conditional | A name or email is required. Lowercased server-side.    |
| `scheduled_for`    | string | Yes         | RFC 3339 date-time. Required and validated.             |
| `duration_minutes` | int    | No          | When > 0, sets the end time.                            |
| `location`         | string | No          | Free-text location.                                     |
| `join_url`         | string | No          | Video/join link.                                        |
| `contact_id`       | string | No          | Explicit contact id to link (verified against the org). |

```json
{
  "title": "Intro call",
  "invitee_name": "Lead Person",
  "invitee_email": "lead@acme.com",
  "scheduled_for": "2026-06-15T16:00:00Z",
  "duration_minutes": 30,
  "join_url": "https://meet.example/abc"
}
```

Response [#response-26]

`201 Created` with the created booking under a `meeting` key.

```json
{
  "meeting": {
    "id": "55555555-5555-4555-8555-555555555555",
    "source": "manual",
    "status": "booked",
    "invitee_email": "lead@acme.com",
    "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-a-meeting]

`DELETE /meetings/:id`

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

Auth: **Scope** `WRITE_CONTACTS` · **Org permission** `manage_contacts`.

| Parameter | In   | Type | Description         |
| --------- | ---- | ---- | ------------------- |
| `id`      | path | uuid | Meeting booking id. |

Response [#response-27]

```json
{ "deleted": true }
```

Get the lead-sync Google connection [#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 [#response-28]

```json
{
  "connected": true,
  "connection": {
    "id": "88888888-8888-4888-8888-888888888888",
    "external_account_name": "ops@acme.com",
    "status": "connected"
  }
}
```

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

Get spreadsheet metadata [#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 [#request-body-9]

| Field           | Type   | Required | Description                        |
| --------------- | ------ | -------- | ---------------------------------- |
| `connection_id` | string | Yes      | The `google_sheets` connection id. |
| `sheet_id`      | string | Yes      | The Google spreadsheet id.         |

```json
{
  "connection_id": "88888888-8888-4888-8888-888888888888",
  "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz"
}
```

Response [#response-29]

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

Preview a lead sync [#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](/api/reference/contacts/#preview-an-import) suggests it, including headers matched to the workspace's existing custom fields.

Auth: **Scope** `WRITE_CONTACTS` · **Org permission** `manage_contacts`.

Request body [#request-body-10]

| Field           | Type   | Required | Description                                 |
| --------------- | ------ | -------- | ------------------------------------------- |
| `connection_id` | string | Yes      | The `google_sheets` connection id.          |
| `sheet_id`      | string | Yes      | The Google spreadsheet id.                  |
| `tab_title`     | string | No       | The tab to read. Defaults to the first tab. |

```json
{
  "connection_id": "88888888-8888-4888-8888-888888888888",
  "sheet_id": "1AbCdEfGhIjKlMnOpQrStUvWxYz",
  "tab_title": "Inbound"
}
```

Response [#response-30]

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

List lead-sync sources [#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`.

| Parameter     | In    | Type | Description                                                                                                                  |
| ------------- | ----- | ---- | ---------------------------------------------------------------------------------------------------------------------------- |
| `campaign_id` | query | uuid | Optional. Restricts to sources targeting that campaign. Invalid ids return `400`.                                            |
| `segment_id`  | query | uuid | Optional. Restricts to sources that pin their rows into that segment. Invalid ids return `400`. Combines with `campaign_id`. |

Response [#response-31]

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

```json
{
  "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 [#create-a-lead-sync-source]

`POST /lead-sync/sources`

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

Auth: **Scope** `WRITE_CONTACTS` · **Org permission** `manage_contacts`.

Request body [#request-body-11]

| Field                | Type      | Required | Description                                                                                                                                                                                                                         |
| -------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_id`      | uuid      | Yes      | The org's `google_sheets` OAuth connection.                                                                                                                                                                                         |
| `sheet_id`           | string    | Yes      | The Google spreadsheet id.                                                                                                                                                                                                          |
| `sheet_title`        | string    | No       | Spreadsheet title (for display).                                                                                                                                                                                                    |
| `tab_title`          | string    | No       | Tab to read.                                                                                                                                                                                                                        |
| `has_header`         | boolean   | No       | Whether the first row is a header.                                                                                                                                                                                                  |
| `column_mapping`     | array     | Yes      | Column-to-target mappings ([same shape as contact import](/api/reference/contacts/)). Validated on save, not on the next sync: a mapping with no `email` column, or a custom field whose name AY Inbox cannot use, is a `400` here. |
| `dedup`              | string    | No       | Collision strategy: `skip`, `update`, or `create_duplicate`.                                                                                                                                                                        |
| `target_campaign_id` | uuid      | No       | Enrol new/updated leads into this campaign on each sync.                                                                                                                                                                            |
| `category_ids`       | string\[] | No       | Label IDs to put on synced leads.                                                                                                                                                                                                   |
| `segment_ids`        | string\[] | No       | Segments 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_default` | boolean   | No       | Default subscription state for new contacts.                                                                                                                                                                                        |
| `label`              | string    | No       | Friendly name.                                                                                                                                                                                                                      |

```json
{
  "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 [#response-32]

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

```json
{
  "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-a-lead-sync-source]

`GET /lead-sync/sources/:id`

Returns one saved source.

Auth: **Scope** `WRITE_CONTACTS` · **Org permission** `manage_contacts`.

| Parameter | In   | Type | Description |
| --------- | ---- | ---- | ----------- |
| `id`      | path | uuid | Source id.  |

Response [#response-33]

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

Update a lead-sync source [#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`.

| Parameter | In   | Type | Description |
| --------- | ---- | ---- | ----------- |
| `id`      | path | uuid | Source id.  |

Request body [#request-body-12]

| Field                | Type      | Required | Description                                                                                  |
| -------------------- | --------- | -------- | -------------------------------------------------------------------------------------------- |
| `sheet_id`           | string    | No       | New spreadsheet id.                                                                          |
| `sheet_title`        | string    | No       | New title.                                                                                   |
| `tab_title`          | string    | No       | New tab.                                                                                     |
| `has_header`         | boolean   | No       | Header toggle.                                                                               |
| `column_mapping`     | array     | No       | Replacement mappings.                                                                        |
| `dedup`              | string    | No       | Collision strategy.                                                                          |
| `target_campaign_id` | uuid      | No       | New target campaign.                                                                         |
| `clear_campaign`     | boolean   | No       | When `true`, unsets the target campaign.                                                     |
| `category_ids`       | string\[] | No       | Replacement set of label IDs.                                                                |
| `segment_ids`        | string\[] | No       | Replacement segment target set. An unknown id is a `400`; an empty array clears the targets. |
| `subscribed_default` | boolean   | No       | Default subscription state.                                                                  |
| `label`              | string    | No       | New label.                                                                                   |

```json
{
  "label": "Q2 inbound (paused)",
  "dedup": "skip",
  "clear_campaign": true
}
```

Response [#response-34]

The updated `LeadSyncSource` (bare object).

Delete a lead-sync source [#delete-a-lead-sync-source]

`DELETE /lead-sync/sources/:id`

Removes a saved source.

Auth: **Scope** `WRITE_CONTACTS` · **Org permission** `manage_contacts`.

| Parameter | In   | Type | Description |
| --------- | ---- | ---- | ----------- |
| `id`      | path | uuid | Source id.  |

Response [#response-35]

`204 No Content`.

Run a lead-sync source now [#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`.

| Parameter | In   | Type | Description |
| --------- | ---- | ---- | ----------- |
| `id`      | path | uuid | Source id.  |

Response [#response-36]

The source id plus the underlying contact-import counts.

```json
{
  "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"
  }
}
```
