- You hold it — a database password, an API key, a service-account JSON.
There is nobody to send anywhere, so you pass it and the connection exists when
the call returns. This is true for native integrations and for SaaS apps whose
auth type is
keys. - You don’t hold it — the app authorizes with OAuth, which means the provider
mints the credential during the authorization. There is nothing you could
pass, because it does not exist yet. You get back a
connect_url, somebody opens it in a browser, and a status check confirms the result.
erdo_connect_integration MCP tool, and
POST /v1/integrations-connect — run the same code, so everything below behaves
identically whichever one you drive.
Which organization gets the connection
A connection belongs to one organization, and every surface that starts one names that organization before you commit to it. This matters because the destination is never something you type: it comes from the org the CLI is pinned to, or the workspace you are looking at in the browser, or theX-Organization-ID header your integration sends — and most people at a company
using Erdo can reach more than one. A production Stripe account attached to the
wrong organization is invisible until something starts syncing there.
In the web app, the connectors table says whose it is above the list, and each
connect dialog repeats it beside the credential fields. Over the API and the CLI
the answer comes back with the result, as organization_name and
organization_slug, and the next_step line names it too:
erdo --org <slug> integrations connect <app>. erdo whoami answers in the same
shape, so the two agree.
Find the app and how it authorizes
Search the catalog before connecting. The result tells you the identifier to pass and the auth type that decides your flow:native or pipedream), its
auth types, and its display name. Over REST the same catalog is
GET /v1/integration-apps?query=postgres. An empty query lists native
integrations plus popular connectable apps.
When a native integration and a catalog app share an identifier, the native one
wins — so the identifier you pass to connect always resolves the same way it
did in apps.
Connect with credentials
CLI
-c takes one key=value per credential field and repeats:
api_key for a key-auth app,
the connection fields for a database. Get one wrong and the connect call fails
rather than half-succeeding; for a catalog app the provider’s own message about
the offending field is passed straight back to you.
MCP
erdo_connect_integration takes the same thing as a credentials object:
REST
app, integration_id, status, and a
next_step line saying what is actually known:
Verified, or merely stored
The distinction matters when something later fails, so the two cases say different things. A native credential integration is created, exercised against the provider, and activated in the one call. Credentials that don’t work fail the call, and the half-made integration is removed rather than left behind for you to trip over — sostatus: "active" means Erdo has talked to the provider with those exact
credentials.
A keys catalog app is different, and its next_step says so plainly: the
credentials are stored and the integration is ready to use; they were not
validated against the provider — the first action run is what confirms them. The
connector platform saves what it is handed without calling the vendor, so a
success here means “stored”, not “correct”. Treat the first action you run as the
real test.
Connect an OAuth app
Connect it with no credentials. You getstatus: "pending" and a URL:
return_url sends the person’s browser back to your own page
after they authorize instead of Erdo’s data page — worth setting when you are
embedding the connect flow in a product of your own. It must be an absolute
http(s) URL with no embedded credentials, and it is ignored by apps that
connect in one call.
Check status
GET /v1/integrations-connect/{app}. Either way you get the app, a
connected boolean, the matching integrations, and an optional note counting
connections confirmed by this call.
“Not connected” is an ordinary answer, not a failure:
503. Our connector platform does
not materialise a record for an end user until that user’s first connection
completes, and asking about a user it has never seen answers not found — which
is the same fact as “you have connected nothing”, not an outage. Every status
check for every catalog app failed that way for anyone starting from zero, which
is exactly when you are most likely to be checking. A real provider failure still
surfaces as an error, so the signal you want kept its meaning.
Reconnect a connection that has expired
A provider can withdraw a grant at any time — a password change, an administrator revoking app access, a refresh token that quietly aged out — and the connection then reads Needs reauth wherever Erdo shows it. Nothing about the connection is wrong except its credential, so the fix is to authorize it again rather than to remove it and start over. Open Data → Connectors, find the row reading Needs reauth, and press Reconnect. If that connector holds several accounts, expanding the row gives you one Reconnect per account so you repair the right one. The button re-runs the same authorization you did originally against the connection you already have, which is what keeps its datasets, its schedules and its chosen provider account intact — disconnecting and connecting again would leave all of that behind. Where Erdo mentions the problem elsewhere — a page whose data has gone stale, a sync failure notification — it links to the same place. Reconnecting is also how a connection picks up permissions Erdo has since started asking for: the authorization requests the app’s current set of scopes, not the set granted the day you first connected, so the provider shows you the consent screen again and the new permission is granted with the rest. If the person who originally authorized the connection has left, or the credentials belong to someone outside your organization, hand the reconnect to them with a connect link instead of sharing a login.Disconnect
erdo integrations list (or GET /v1/integrations) shows. The stored credential is deleted and datasets built on
the integration are cleaned up — the same thing removing the connection in the
web app does. The response names what was removed (id, app, name), and an
id your organization does not hold answers 404.
Set account identity
account_id — which provider account the connection is — and, for
Google Ads agency (MCC) connections, login_customer_id — the manager account
every request against a client account is sent in the context of. GET /v1/integrations returns both; this endpoint sets them.
Most connections never need this. A Google Ads OAuth connection whose token can
reach exactly one account, and that account is a manager, gets its
login_customer_id detected and stored at authorization time. But a token that
can reach several accounts is ambiguous — no detector can know which one you
mean — and connections made before identity stamping existed carry no
account_id at all. In both cases you state the identity here.
At least one field is required; a field you omit is left untouched, so you can
set one id without knowing the other. Ids are normalized before storage: spaces
and dashes are stripped, because Google renders customer ids as 530-201-2239
while its API wants 5302012239. Only login_customer_id has to be numeric,
being a Google Ads manager id; account_id takes whatever shape its provider
issues, so a Meta act_1234567890 and a Reddit a2_bzv32cgqp are both stored
as given. There are no delete semantics — a wrong id is corrected by writing the
right one. Every other credential the connection stores (tokens, expiry) is
untouched by this write.
Setting account_id on a connection that chooses a provider account also
records it as the account that connection
operates — one account level, one unambiguous id — so
requests actually go there rather than the id being stored and ignored. A
provider reached through an owner (a Reddit business, a GA4 account) needs the
whole path, so use the connection-scope endpoint for those.
The response is the integration’s summary in the same shape GET /v1/integrations returns, carrying the identity now stored — never any token
or secret material. An id your organization does not hold answers 404,
indistinguishable from an id that never existed.
Apollo, end to end
Apollo is the worked example of a key-authenticated integration: you paste a key and it is connected, with no browser step anywhere in the flow.X-Api-Key header, never as a URL
parameter — a key in a URL ends up in every log line that records one.
Connecting confirms Apollo is reachable; it cannot confirm your key. Apollo’s
key-test endpoint answers
200 even to a key that is not a key at all, so
verification has nothing to fail on. The first enrich_person call is what names
a bad key, and it says so explicitly when Apollo rejects it.enrich_person
Apollo has one action. It takes an email address and returns what Apollo knows about that person professionally:found: false and the address, and nothing else.
found is the field to branch on. A miss is a successful answer, not an
error — Apollo simply has no record of that address. Because a miss returns no
other keys, writing the result straight into a dataset cannot quietly produce a
row of empty strings that looks like a person nobody knows anything about.
Genuine failures — a rejected key, a rate limit, an Apollo outage — come back as
errors, never disguised as found: false.
Each successful match costs one Apollo credit. Personal email addresses and phone
numbers are never requested and never returned: asking for them turns a
one-credit lookup into as many as nine and pulls contact details into Erdo that
nobody asked for, so the request omits them and the result has no field to carry
them.
Calling it unattended
enrich_person reads and changes nothing — not in Apollo, not in Erdo — so it
needs no approval, which is what lets an automation running at 3am use it. A
scripted automation or an
event pipeline step invokes it directly:
run_integration_action.
Related
CLI
The
erdo integrations commands in context with the rest of the CLI.MCP
The integration tools an AI assistant drives, and their REST mirrors.
Automations
Scripted work that calls a connected app on a schedule or a trigger.
Data
Turning a connected database or warehouse into a queryable dataset.

