CRM connectors¶
Connects an agent to Salesforce or HubSpot via OAuth2, then gives it
three tools that act on that connection mid-conversation:
crm_upsert_contact, crm_create_ticket, and crm_log_call_activity
(see Tools). AiFlow holds the OAuth tokens, encrypted at
rest, and refreshes them automatically. The underlying tool-calling
mechanism doesn't change: a CRM action is just another function call the
agent can make, the same as send_webhook or a
custom tool.
Activating a provider¶
Each provider needs its own OAuth2 client credentials, set once for the
whole deployment (not per agent, and not a customer credential) as
backend environment variables. A provider with no client ID or secret
set simply can't start a new connection:
POST .../crm-connections/{id}/authorize-url still returns a URL, but
the provider rejects it at that point.
Every provider's callback URL follows the same pattern:
provider is the literal string salesforce or hubspot, and
agent_id is the numeric id of the agent you're connecting (visible in
that agent's URL in the admin dashboard). Both are known before a
connection exists, so register the exact URL for your agent up front, for
example
https://api.your-domain.com/api/v1/agents/1/crm-connections/hubspot/oauth-callback.
Every provider below also accepts more than one callback URL per app, in
case you want to pre-register more than one agent id at once.
These provider consoles change without much notice
Salesforce and HubSpot both moved to a substantially different app-creation flow between early and mid-2026. The steps below reflect each provider's current flow as of this writing, with a link to the official doc at the top of each tab as the source of truth if a step stops matching what you see.
Official doc: Create an External Client App.
Salesforce restricted creating new Connected Apps starting Spring '26, in favor of External Client Apps, a newer container for the same OAuth mechanics. If your org still shows New Connected App instead of New External Client App, that's fine too: just follow the equivalent fields.
- Log into your Salesforce org (a Developer Edition org works fine for testing, no paid license needed).
- Setup (gear icon, top right) → search App in the Quick Find box → App Manager → New External Client App.
- Fill in the basic fields: Name (e.g. "AiFlow"), API Name (auto-derived, editable), Contact Email, and leave Distribution State as Local.
- Expand API (Enable OAuth Settings):
- Check Enable OAuth.
- Callback URL: your callback URL from above (this field accepts multiple URLs, one per line, if you want to pre-register a few ids).
- Selected OAuth Scopes: add both Manage user data via APIs
(api) and Perform requests at any time (refresh_token,
offline_access). AiFlow only ever requests
api refresh_token. - Under Security: deselect Require Proof Key for Code Exchange (PKCE) (AiFlow doesn't implement PKCE), and keep Require Secret for the Web Server Flow and Require Secret for Refresh Token Flow selected.
- Save. Salesforce shows a warning that changes can take up to 10 minutes to propagate; that's normal.
- To retrieve credentials: from App Manager, find your app →
open its Settings tab → expand OAuth Settings → click
Consumer Key and Secret (opens a new tab, may ask you to
verify your identity via email first). This reveals:
- Consumer Key →
SALESFORCE_CLIENT_ID - Consumer Secret →
SALESFORCE_CLIENT_SECRET
- Consumer Key →
- If users besides System Administrators need to authorize this app themselves, look for a Policies or OAuth Policies section on the app (the External Client App equivalent of a Connected App's "Permitted Users"), and confirm it allows the users who'll actually click through the "Connect a CRM" flow later.
Official doc: Create a new app using the CLI.
This one changed the most. HubSpot retired the old "sign into developers.hubspot.com and click Create app" web flow for new OAuth apps (that button still works only for accounts that made an app before the mid-2026 cutover). A new OAuth app is created through the HubSpot CLI instead. It's still a one-time setup step, not something you need to maintain afterward.
- Install Node.js if you don't already have it, then install the CLI:
- Authenticate it with your HubSpot account (opens a browser to sign in and grant access):
- Create a new project: When prompted: Contents → App; Distribution → restrict to specific accounts, not the Marketplace, since this is a private integration; Authentication → OAuth; Features → none needed, so skip or leave it blank.
- This scaffolds a project containing an
app-hsmeta.jsonfile. Open it and set itsauthsection:("auth": { "type": "oauth", "redirectUrls": ["your callback URL from above"], "requiredScopes": ["oauth", "crm.objects.contacts.write", "tickets"] }oauthis a base scope every app needs, and it's easy to miss.hs project uploadwarns and auto-adds it if left out, but declare it explicitly rather than rely on that. Tickets aren't granular like contacts: there's nocrm.objects.tickets.write, just the singleticketsscope covering both read and write, and an unrecognized scope name here fails the whole upload withThe scope ... could not be recognized.crm.objects.contacts.writealone already covers the contact search AiFlow does before deciding to create or update one, and the call-logging tool, since HubSpot's engagement APIs ride on the contacts scope rather than having their own). - Upload it:
- Open the project in your browser, find the credentials:
Under Project Components, click your app's name → Auth
tab → Client credentials section shows:
- Client ID →
HUBSPOT_CLIENT_ID - Client secret →
HUBSPOT_CLIENT_SECRET
- Client ID →
- If your HubSpot account is part of an agency or parent account, install the app on the specific sub-account (portal) you want AiFlow's connection to act on. The OAuth consent screen asks which portal to connect during the actual "Connect a CRM" step in AiFlow, not here.
Exact CLI prompt wording can shift between CLI releases; run hs
project create --help if step 3's prompts don't match what you see.
Endpoint family¶
GET /api/v1/agents/{agent_id}/crm-connections
POST /api/v1/agents/{agent_id}/crm-connections
DELETE /api/v1/agents/{agent_id}/crm-connections/{connection_id}
GET /api/v1/agents/{agent_id}/crm-connections/{connection_id}/authorize-url
Plus GET .../crm-connections/{provider}/oauth-callback, the OAuth
redirect target itself. You don't call this directly; the provider's own
authorize page redirects a browser there after the admin approves the
connection. It's keyed by provider, not connection_id, so it's
predictable before a connection row exists. See the callback URL note
above.
Authentication and roles¶
Every endpoint here (except the callback, see below) takes an admin
session token (see
Authentication). GET is
available to every role, including Viewer; POST and DELETE require
Owner or Admin, the same bar as MCP servers and other
credential-bearing connections.
The callback endpoint takes no admin token at all: the provider's
redirect lands a browser there directly, with no Authorization header
attached. Its state query parameter, matched against a nonce generated
at authorize-url time, is the security boundary instead, the same model
virtually every OAuth integration uses for this exact reason.
Connect a provider¶
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | Yes | salesforce or hubspot. |
An agent can have at most one connection per provider (409 on a second
attempt). The response's instance_url field is populated automatically
for Salesforce from the OAuth exchange; HubSpot's API always lives at one
fixed host, so it stays null there. It's never admin-supplied.
{
"id": 3,
"agent_id": 1,
"provider": "hubspot",
"status": "pending",
"instance_url": null,
"last_error": null,
"created_at": "2026-01-15T10:00:00Z"
}
Then fetch the authorize URL and open it in a browser to complete the OAuth flow:
The provider redirects back to AiFlow's own callback once the admin
approves. A small static page confirms success or reports what went
wrong (an expired authorization code, a mismatched redirect URI, and so
on), then GET .../crm-connections reflects the connection's new
status (connected or error, with last_error set on failure).
The tools¶
crm_upsert_contact, crm_create_ticket, and crm_log_call_activity
(see Tools) all share one config field, provider, shown as
a dropdown of salesforce or hubspot in the admin dashboard's Tools
tab. Whichever provider you pick determines which of this agent's
connections the tool uses when it's called. Enabling one of these tools
without a matching connection returns a plain error string to the agent
rather than failing the call, the same defensive pattern every built-in
tool follows.