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.
| Field | Half | Notes |
|---|---|---|
host, port, database, user | public | The connection target. |
sslmode | public | require (default), disable, prefer, verify-ca, verify-full. |
password | secret | Encrypted at rest. |
sslrootcert, sslcert, sslkey | secret | PEM contents (not file paths) for verify-ca/verify-full against a private CA, or mutual TLS. |
ssh_host, ssh_port, ssh_user | public | Optional — set to tunnel through an SSH bastion / jump host. |
ssh_private_key, ssh_key_passphrase | secret | The 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.
| Field | Half | Notes |
|---|---|---|
host, port, database, user | public | The connection target. Port defaults to 1433. |
schema | public | Optional schema filter for source discovery. |
use_encrypt | public | TLS encryption. Defaults to true; set false only for legacy servers with no TLS support. |
trust_server_certificate | public | Defaults 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. |
password | secret | Encrypted 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.
- Sign in to HubSpot. You need Super Admin or Developer tools access permission to create a service key.
- Go to Development → Keys → Service keys, click Create service key,
and name it (for example,
Definite). - Click Add new scope, search for
.readto filter, then select the read scopes you need (crm.objects.contacts.read,crm.objects.companies.read,crm.objects.deals.read,crm.objects.tickets.read). Addsales-email-readandticketsif your syncs use them. Use read-only scopes for sync-only pipelines. - Click Create and confirm, then open the new key, click Show, and
Copy the token (it looks like
pat-na1-...). - 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
restconnectors (~80) — the connector'sconnector.jsonmanifest 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.pyalongside 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:
- 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 rejectnip.iohosts, bare IPs, and non-https URLs, so setPUBLIC_BASE_URLaccordingly. - Create an
oauth2integration 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 (noPUBLIC_BASE_URLset, 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 adeveloper_tokenhere; Salesforce lets you pick between production and sandbox). - Authorize — click Authorize now in the modal after saving, or open
GET /api/v1/oauth/authorize?name=<integration>directly. The deployment mints a CSRFstate+ 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. - 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
oauth2integration 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 recentlast_refresh_attimestamp 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
- In Salesforce (or your sandbox org), go to Setup → App Manager → New Connected App. Enable OAuth Settings.
- Set the Callback URL to the exact value shown on the Definite
integrations form (typically
https://<your-base-url>/api/v1/oauth/callback). - Add at least the
apiandrefresh_tokenscopes. The Definite profile requests these by default. - Save the Connected App and copy the Consumer Key (client ID) and Consumer Secret (client secret).
- In Definite, Add integration → OAuth 2.0. Pick provider profile
salesforce. For a sandbox org, set the auth domain totest.salesforce.com; leave it atlogin.salesforce.comfor production. Paste the Consumer Key and Consumer Secret. - 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
- 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.
- Enable the Google Ads API for the same project.
- 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-tokenheader in addition to the OAuth token. - In Definite, Add integration → OAuth 2.0. Pick provider profile
googleads. Paste the OAuth client ID and secret, plus the developer token and (optionally) thecustomer_id/login_customer_idof the MCC. - 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.