> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conversion.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Fivetran

> Export Conversion contacts and email engagement events into your warehouse with our open-source Fivetran connector.

The Conversion Fivetran connector loads your contacts and email engagement data into a destination warehouse on a schedule. It's a [Fivetran Connector SDK](https://fivetran.com/docs/connector-sdk) connector built on top of the [Conversion public API export endpoints](/api-reference/export-contacts), so Fivetran handles the scheduling, paging, and loading while the API supplies the data.

The connector is **open source**. You can deploy it as-is, read exactly how it works, or fork it to fit your needs.

<Card title="Reference implementation" icon="github" href="https://github.com/tapp-ai/fivetran-connector">
  github.com/tapp-ai/fivetran-connector: the full connector source, licensed under Apache-2.0.
</Card>

## Before You Begin

Make sure you have the following before starting:

* **A Conversion API key:** Generate one from **Settings → API Keys** in the Conversion dashboard. The connector authenticates with this key; see [Authentication](/product-docs/workspace-settings/integrations/api).
* **A Fivetran account and a configured destination:** The warehouse Fivetran will load into (BigQuery, Snowflake, Redshift, Databricks, etc.).
* **A Fivetran deploy key:** Used to deploy the connector to your Fivetran account.
* **[uv](https://docs.astral.sh/uv/) installed locally:** Used to run and deploy the connector.

***

## Tables

The connector exports ten tables, all upserted by primary key so re-emitted rows update in place. Each email table requests a single `eventType` from `POST /v2/exports/email-events`, and the destination table name is the lowercased event type:

| Table                     | Source                                         | Notes                                                                                                                                |
| :------------------------ | :--------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
| `contacts`                | `POST /v2/exports/contacts`                    | One row per contact. Every contact field is flattened in as its own column keyed by its common name (e.g. `first_name`, `owner_id`). |
| `email_send`              | `POST /v2/exports/email-events` (`EMAIL_SEND`) | One row per send event.                                                                                                              |
| `email_delivery`          | `…` (`EMAIL_DELIVERY`)                         | The email was accepted by the recipient's mail server.                                                                               |
| `email_open`              | `…` (`EMAIL_OPEN`)                             | One row per open event.                                                                                                              |
| `email_click`             | `…` (`EMAIL_CLICK`)                            | Includes `link`, the clicked URL.                                                                                                    |
| `email_bounce`            | `…` (`EMAIL_BOUNCE`)                           | Hard (permanent) bounces.                                                                                                            |
| `email_soft_bounce`       | `…` (`EMAIL_SOFT_BOUNCE`)                      | Soft (transient) bounces.                                                                                                            |
| `email_complaint`         | `…` (`EMAIL_COMPLAINT`)                        | The recipient marked the email as spam.                                                                                              |
| `email_unsubscribe_all`   | `…` (`EMAIL_UNSUBSCRIBE_ALL`)                  | The recipient unsubscribed from all email.                                                                                           |
| `email_topic_unsubscribe` | `…` (`EMAIL_TOPIC_UNSUBSCRIBE`)                | The recipient unsubscribed from specific topics.                                                                                     |

<Info>
  Core contact columns (`id`, `email`, `subscription_status`, `created_at`, `updated_at`, and the Salesforce IDs) are declared up front. Contact field columns are left undeclared so Fivetran infers them automatically. That's what lets every field surface as its own column without hardcoding the set as it grows.
</Info>

***

## How It Works

### Authentication

Each request carries your API key in the `X-API-Key` header (`sk_live_<id>_<secret>`). Because the API scopes every response to the business that owns the key, the connector never sends a business ID.

### Incremental Sync

Each table keeps its own **opaque cursor** in connector `state`, keyed by table (`contacts_cursor`, `email_send_cursor`, …). The connector never interprets the cursor. It stores whatever the API last returned and sends it back on the next request.

Every request posts `{"limit": 1000, "cursor": <saved cursor>}` (the email tables also send `eventType`). The connector reads rows from the response's `data` and the next cursor from `pagination.nextCursor`.

<Warning>
  Paging is driven by `pagination.nextCursor`, **not** by page length. A short page is **not** end-of-stream. The connector keeps paging as long as the cursor advances and stops only when it's exhausted (`null`) or stops advancing.
</Warning>

The connector checkpoints `state` after **every page**, so progress is durable and the next sync resumes from the stored cursor. Rows are upserted by primary key (`id` / `event_id`), so any row the API re-emits updates in place.

### Resilience

Transient failures (network errors, `5xx`, and `429` rate limits) are retried with exponential backoff. The connector fails fast on `4xx` responses and on any structured API error returned in the response envelope.

***

## Setup

### 1. Get the Connector

Clone the [reference implementation](https://github.com/tapp-ai/fivetran-connector):

```bash theme={null}
git clone https://github.com/tapp-ai/fivetran-connector.git
cd fivetran-connector
uv sync --extra dev
```

### 2. Configure It

Copy the example configuration and fill in your API key. `configuration.json` is gitignored, so your key is never committed:

```bash theme={null}
cp configuration.example.json configuration.json
```

```json configuration.json theme={null}
{
  "base_url": "https://pub-api.conversion.ai/api",
  "api_key": "sk_live_<id>_<secret>"
}
```

### 3. Run It Locally

Debug against the API before deploying. This runs the sync against a local DuckDB warehouse so you can inspect the tables and confirm cursors advance:

```bash theme={null}
uv run fivetran debug --configuration configuration.json
```

### 4. Deploy to Fivetran

Deploy the connector to your Fivetran account, targeting your configured destination:

```bash theme={null}
uv run fivetran deploy \
  --api-key <FIVETRAN_DEPLOY_KEY> \
  --destination <DESTINATION> \
  --connection conversion
```

Once deployed, Fivetran manages the sync schedule and loads each table into your destination warehouse.

***

## Customizing the Connector

Because the connector is open source and licensed under Apache-2.0, you're free to fork it and adapt it. Common customizations:

* **Add or rename columns** by editing `schema()` and the row mappers in `connector.py`.
* **Select a subset of tables** by trimming the `EMAIL_STREAMS` list or removing the contacts stream.
* **Change the page size** via `PAGE_LIMIT` (the server applies its own hard cap).
* **Tune retries** with `MAX_RETRIES` and `BACKOFF_BASE_SECONDS`.

See [`CONTRIBUTING.md`](https://github.com/tapp-ai/fivetran-connector/blob/main/CONTRIBUTING.md) in the repository for development guidelines.

***

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="Does Conversion host the connector for me?">
    No. The connector is open source and runs in your own Fivetran account, loading into your own destination. You stay in full control of your data and credentials. Conversion provides the API the connector reads from.
  </Accordion>

  <Accordion title="How are timestamps formatted?">
    The API emits RFC-3339 timestamps with nanosecond precision. The connector truncates the fractional seconds to microseconds so they parse cleanly into Fivetran's `UTC_DATETIME` type. Values without a sub-second fraction pass through unchanged.
  </Accordion>

  <Accordion title="Why do new contact fields appear automatically as columns?">
    Contact fields are left undeclared in the connector's schema, so Fivetran infers them from the data it loads. As you define new fields in Conversion, they surface as new columns on the `contacts` table on the next sync. No connector change required.
  </Accordion>

  <Accordion title="Can I build my own pipeline instead?">
    Yes. The connector is just a client of the [public API export endpoints](/api-reference/export-contacts). If you'd rather use a different ETL tool or write your own loader, you can call the same endpoints directly.
  </Accordion>
</AccordionGroup>
