Skip to content

Twenty CRM ​

Twenty is an open-source CRM. It runs both as a hosted workspace (api.twenty.com) and self-hosted on your own domain; Bruin supports both.

Bruin supports Twenty CRM as a source for Ingestr assets, and you can use it to ingest data from Twenty into your data platform.

To set up a Twenty connection, you must add a configuration item in the .bruin.yml and asset file. You need an API key, which you can create in the workspace under Settings → API & Webhooks. The key is shown only once and covers one workspace.

Follow the steps below to set up Twenty correctly as a data source and run ingestion.

Configuration ​

Step 1: Add a connection to the .bruin.yml file ​

yaml
connections:
    twenty:
        - name: "twenty"
          host: "api.twenty.com"
          api_key: "your_api_key"
  • host (required): The workspace host. api.twenty.com for Twenty Cloud, or your own domain for a self-hosted instance (e.g. crm.example.com).
  • api_key (required): Created in the workspace under Settings → API & Webhooks.
  • scheme (optional): The transport used to reach the workspace, https or http. Defaults to https.
  • base_path (optional): Where the REST API is mounted. Defaults to /rest.
  • page_size (optional): Rows per request. Defaults to and is capped at 200.
  • rate_limit (optional): Requests per second. Defaults to 1.33 (80% of Twenty's documented 100 requests/minute).
  • include_deleted (optional): Whether a second pass re-reads soft-deleted records so deletions are reflected downstream. Defaults to true.

Step 2: Create an asset file for data ingestion ​

To ingest data from Twenty, you need to create an asset configuration file. This file defines the data flow from the source to the destination. Create a YAML file (e.g., twenty_ingestion.yml) inside the assets folder and add the following content:

yaml
name: public.twenty
type: ingestr

parameters:
  source_connection: twenty
  source_table: 'people'

  destination: postgres
  • name: The name of the asset.
  • type: Specifies the asset's type. Set this to ingestr to use the ingestr data pipeline. For Twenty, it will always be ingestr.
  • source_connection: The name of the Twenty connection defined in .bruin.yml.
  • source_table: The name of the table in Twenty to ingest. See the available tables below.
  • destination: The destination platform/type, for example postgres.

Step 3: Run asset to ingest data ​

bash
bruin run assets/twenty_ingestion.yml

As a result of this command, Bruin will ingest data from the given Twenty table into your Postgres database.

Available Source Tables ​

TablePrimary KeyIncremental KeyIncremental StrategyDetails
companiesidupdatedAtmergeCompanies in the workspace
notesidupdatedAtmergeNotes attached to workspace records
opportunitiesidupdatedAtmergeSales opportunities
peopleidupdatedAtmergePeople in the workspace
tasksidupdatedAtmergeTasks attached to workspace records
workspaceMembersidupdatedAtmergeMembers of the workspace
custom:<object_name>idupdatedAtmergeA custom object, using its plural API name

Custom objects ​

Twenty exposes custom objects through the same REST API as standard objects. To ingest one, prefix its plural API name with custom:, for example custom:leads. The connector reads the object's metadata at runtime, so custom fields are included automatically.

yaml
name: public.twenty_leads
type: ingestr

parameters:
  source_connection: twenty
  source_table: 'custom:leads'

  destination: postgres

Incremental loading and deletions ​

Every Twenty object carries updatedAt, which is used as the incremental key with the merge strategy, filtered server-side. Only the start of the interval is applied — merge is idempotent, so a wider window costs requests rather than correctness.

Twenty soft-deletes records: a deleted record keeps its row with deletedAt set and is excluded from every list response by default. With include_deleted left at true, Bruin makes a second pass that re-reads exactly those records so their deletedAt lands populated; filter on deletedAt IS NULL downstream to see the live set. Set include_deleted: false to skip that pass at the cost of never learning about a deletion.