Integrations

An integration is a stored, reusable connection to an external system — a Postgres database, a SaaS API, a Slack workspace, an LLM provider. It holds the connection details and credentials in one place so automations, syncs, and Fi can reference the source by name without anyone re-pasting a secret.

Data flows in one direction: a source integration → an automation pipeline → the lakehouse. The integration is the credential; the automation is the work that uses it to pull rows.

The integration-type system

Every integration has a type (postgres, hubspot, slack_webhook, oauth2, custom, …). The type is a schema: it declares the form fields the customer fills in, split into two halves.

  • public_fields — non-secret configuration. Stored in plaintext and visible in the UI: a database host, a port, an SSL mode, an OAuth provider profile.
  • secret_fields — credentials. Encrypted at rest (Fernet) and never returned to the UI or to Fi after they are saved: a password, an API key, an OAuth client secret, a PEM-encoded private key.

Because the type is the schema, the Settings → Integrations form is generated from it — there is no hand-written form per source. Each field carries a label, a type (string, url, email, password, number, select), a required flag, and an optional description, and the API validates the submitted payload against that schema before it stores anything.

Worked example: Postgres

The postgres type connects a Postgres database with username/password auth.

FieldHalfNotes
host, port, database, userpublicThe connection target.
sslmodepublicrequire (default), disable, prefer, verify-ca, verify-full.
passwordsecretEncrypted at rest.
sslrootcert, sslcert, sslkeysecretPEM contents (not file paths) for verify-ca/verify-full against a private CA, or mutual TLS.
ssh_host, ssh_port, ssh_userpublicOptional — set to tunnel through an SSH bastion / jump host.
ssh_private_key, ssh_key_passphrasesecretThe bastion key material.

A database inside the deployment's own VPC needs only the first five fields; the SSH-tunnel fields exist for sources reachable only through a bastion. After you save, Test connection runs a real SELECT 1 and, on a network timeout, reminds you to allowlist the deployment's egress IP — shown in Settings → Workspace → General (and as a hint on this form) when the deployment has a reserved egress address. See Network Requirements.

Postgres tables are then synced with a pg_sync automation step, which streams rows directly from the source into the lakehouse.

Worked example: SQL Server

The sqlserver type connects to Microsoft SQL Server with username/password auth and syncs through the DuckDB mssql extension.

FieldHalfNotes
host, port, database, userpublicThe connection target. Port defaults to 1433.
schemapublicOptional schema filter for source discovery.
use_encryptpublicTLS encryption. Defaults to true; set false only for legacy servers with no TLS support.
trust_server_certificatepublicDefaults to false. Set true for internal SQL Servers using a self-signed certificate or private CA that the deployment does not trust. Requires use_encrypt=true.
passwordsecretEncrypted at rest.

After saving, Test connection runs a real SELECT 1. Certificate failures are reported with the trust_server_certificate remediation instead of a raw TLS stack trace. SQL Server tables are synced with an mssql_sync automation step.

Worked example: HubSpot

HubSpot uses the hubspot integration type: a single Bearer token in the api_key secret field. HubSpot now recommends a Service Key for single-account API access like this (backend integrations, ETL or warehouse syncs, and BI tools), so that is what Definite recommends. The token behaves like the standard HubSpot Bearer token, so syncs work the same way.

  1. Sign in to HubSpot. You need Super Admin or Developer tools access permission to create a service key.
  2. Go to Development → Keys → Service keys, click Create service key, and name it (for example, Definite).
  3. Click Add new scope, search for .read to filter, then select the read scopes you need (crm.objects.contacts.read, crm.objects.companies.read, crm.objects.deals.read, crm.objects.tickets.read). Add sales-email-read and tickets if your syncs use them. Use read-only scopes for sync-only pipelines.
  4. Click Create and confirm, then open the new key, click Show, and Copy the token (it looks like pat-na1-...).
  5. In Definite, Add integration → HubSpot and paste the token into the Service key field. It is encrypted at rest and never shown to Fi.

If Service Keys are not available in your account yet, a legacy private app token still works as a fallback: create one under Development → Legacy apps and copy its access token into the same field. See the HubSpot service keys docs for details.

The connector catalog

For SaaS sources there is a connector catalog — roughly 125 pre-built connectors (HubSpot, Stripe, Salesforce, Shopify, Jira, Notion, and many more). A connector packages everything needed to sync a source: the integration form, the list of syncable objects (with primary keys and incremental watermarks), and the loader that calls the source API.

Connectors come in two flavours:

  • Declarative rest connectors (~80) — the connector's connector.json manifest fully describes the API: base URL, auth, pagination, and each object's path and record selector. These run on one shared REST loader; no per-source code.
  • Dedicated-loader connectors (~45) — sources whose API does not fit the declarative shape (unusual pagination, GraphQL, multi-step calls) ship a small purpose-built loader.py alongside the manifest.

Either way the customer experience is the same: pick the connector, pick the objects, schedule it. To add one, ask Fi to set up the pipeline ("sync my HubSpot contacts") or use the Settings → Integrations page directly. Fi's pipeline-builder skill collects the credentials in a secure panel, creates the integration, and builds a scheduled automation that loads the chosen objects.

OAuth connectors

Sources that authenticate with OAuth 2.0 — Google Sheets, Salesforce, QuickBooks, Xero, LinkedIn, Microsoft, and the rest of the authorization-code providers — use the generic oauth2 integration type. One type backs every such provider; per-provider differences (token endpoints, default scopes, refresh quirks) live in provider profiles.

Mode B — bring-your-own OAuth app (available today)

The mode that ships today is Mode B: you register your own OAuth application with the provider, and the deployment runs the consent and token-refresh flow itself. No credential ever leaves your network.

Connecting an OAuth source, end to end:

  1. Register an OAuth app with the provider (e.g. in the Google Cloud Console). Set its redirect URI to <your-base-url>/api/v1/oauth/callback. Use the deployment's stable public hostname — most providers reject nip.io hosts, bare IPs, and non-https URLs, so set PUBLIC_BASE_URL accordingly.
  2. Create an oauth2 integration in Settings → Integrations → Add integration → OAuth 2.0. The form shows the exact callback URL to paste into your provider's OAuth app and flags common misconfigurations (no PUBLIC_BASE_URL set, callback host on a provider-rejected URL pattern). Pick the provider profile, enter your app's client ID and client secret (both encrypted at rest), and any provider-specific extras (Google Ads needs a developer_token here; Salesforce lets you pick between production and sandbox).
  3. Authorize — click Authorize now in the modal after saving, or open GET /api/v1/oauth/authorize?name=<integration> directly. The deployment mints a CSRF state + PKCE pair and redirects you to the provider's consent screen. On approval the provider calls back to /api/v1/oauth/callback, the code is exchanged for tokens, and the access and refresh tokens are stored encrypted on the integration.
  4. Tokens auto-refresh. A fetch-time refresh-on-read hook checks the access token whenever the integration is used. A background worker also passes over every oauth2 integration on a 15-minute cadence so integrations referenced only by infrequent automations don't lapse past a provider's refresh-token expiry window (Outreach rotates with a ~100-day life; an unverified Google app's refresh tokens die in 7 days). When refresh becomes impossible the integration surfaces a "Reconnect" state — re-run the authorize step to fix it. The most recent last_refresh_at timestamp is visible under Test in the integration detail.

A few providers are not authorization-code flows at all. NetSuite uses its own Token-Based Authentication integration type; ADP, Amazon Selling Partner, and Microsoft Ads are out of scope for the oauth2 type entirely.

Worked example: Salesforce

  1. In Salesforce (or your sandbox org), go to Setup → App Manager → New Connected App. Enable OAuth Settings.
  2. Set the Callback URL to the exact value shown on the Definite integrations form (typically https://<your-base-url>/api/v1/oauth/callback).
  3. Add at least the api and refresh_token scopes. The Definite profile requests these by default.
  4. Save the Connected App and copy the Consumer Key (client ID) and Consumer Secret (client secret).
  5. In Definite, Add integration → OAuth 2.0. Pick provider profile salesforce. For a sandbox org, set the auth domain to test.salesforce.com; leave it at login.salesforce.com for production. Paste the Consumer Key and Consumer Secret.
  6. Click Authorize now to complete the consent flow. After approval, the integration stores the access token, refresh token, and the org's instance_url (used by syncs as the REST API base).

Worked example: Google Ads

  1. In the Google Cloud Console, create an OAuth 2.0 client of type Web application under APIs & Services → Credentials. Add the Definite callback URL as an authorized redirect URI.
  2. Enable the Google Ads API for the same project.
  3. In your Google Ads MCC (manager account), apply for an Approved developer token (Basic access or higher). Every Google Ads API request needs this developer-token header in addition to the OAuth token.
  4. In Definite, Add integration → OAuth 2.0. Pick provider profile googleads. Paste the OAuth client ID and secret, plus the developer token and (optionally) the customer_id / login_customer_id of the MCC.
  5. Click Authorize now. Google will only return a refresh token if the consent screen is fully re-approved with access_type=offline + prompt=consent — the Definite profile requests both, so the first consent works; do not skip Google's "Are you sure?" page.

Syncs run as automations

An integration on its own moves no data — it is the credential. The actual ingestion runs as an automation pipeline: a pg_sync step for Postgres, or a python step (driven by a connector loader) for SaaS sources. Pipelines can run on a cron schedule, so a configured integration plus a scheduled automation gives you a source that refreshes into the lakehouse on its own. See Automations for step types, scheduling, and run history.