> ## Documentation Index
> Fetch the complete documentation index at: https://docs.checkfu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect a provider account

> Embed Checkfu's provider-neutral Connect Center, complete setup, choose agent tools, and revoke without handling provider credentials.

A [Connection](/concepts/capabilities#connections) is one authorized third-party
account your agents can call without seeing its credential. Connect Center is
the preferred public workflow for creating and governing those accounts. It
uses the same operations and `@checkfu/ui` component in Checkfu's dashboard and
in your own application.

Your backend needs a Checkfu API key and Workspace. The browser does not.

<Warning>
  The Checkfu CLI and TypeScript SDK are private-alpha artifacts with no self-service public installation channel.
  Part of this flow is behind a rollout fence; a deployment that has not enabled it refuses the request rather than running it.
  Confirm [capability status](/getting-started/status) before making availability part of your application's contract.
</Warning>

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Checkfu, createConnectCenterController } from "@checkfu/sdk"

const checkfu = new Checkfu({
  apiKey: process.env.CHECKFU_API_KEY,
})

// This can be a service Principal for a shared account or an end-user
// Principal for a delegated personal account.
const center = createConnectCenterController(checkfu, ownerPrincipalId)
```

Register the embedding application's exact HTTPS origin through the public,
versioned Workspace registry. Paths may vary beneath an admitted origin, but
credentials, HTTP origins, subdomain wildcards, query-only origins, and
request-derived host headers are never redirect authority.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const currentOrigins = await checkfu.integrationGateway.returnOrigins.retrieve()

await checkfu.integrationGateway.returnOrigins.replace(
  {
    expected_version: currentOrigins.version,
    origins: ["https://app.example.com"],
  },
  { idempotencyKey: crypto.randomUUID() },
)
```

Removing an origin blocks new ceremonies immediately; an already-reserved
ceremony keeps only its frozen, non-extendable return URL until expiry. Local
end-to-end ceremonies therefore need an HTTPS development origin or tunnel—
plain loopback HTTP is intentionally not an OAuth/credential return origin.

## List what this Principal can connect

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const offerings = await center.list()
```

Each offering is already joined to only that Principal's Connections. It
includes:

* Checkfu-owned package name, revision, digest, and review tier;
* minimum scopes and exact reviewed egress destinations;
* normalized tool count and qualification expiry;
* connection flow: managed or protocol OAuth, hosted credential entry, or no
  credential step;
* opaque hosted or dedicated placement choices and their availability; and
* the Principal's account lifecycle, health, and optimistic version.

Render every returned row. `curated`, `structural`, and `unreviewed` are facts
for the person deciding what to connect, not reasons to silently hide a row.
An unavailable row or placement carries a closed reason such as
`qualification_expired` or `not_configured`.

The projection never contains a provider credential, supplier tenant id,
account handle, adapter key, placement handle, or provider authorization URL.

## Render the shared Connect Center

Use the stateful `ConnectCenterAdapter`, not a second application-specific
connection state machine. Give it the signed-in Principal id and narrow
browser-to-BFF transport functions; your BFF maps those functions to
`createConnectCenterController` while retaining the Checkfu Workspace key.
The [credential broker example](/examples/credential-broker) walks the same
transport and server boundary end to end.

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { ConnectCenterAdapter } from "@checkfu/ui/sdk"
import { checkfuConnectCenterBff } from "./checkfu-connect-center-bff"

<ConnectCenterAdapter
  principalId={signedInPrincipalId}
  transport={checkfuConnectCenterBff}
  onChanged={() => refreshAccountSummary()}
  onError={(cause) => reportConnectionError(cause)}
/>
```

The `ConnectCenterTransport` type is the BFF contract. It includes listing,
begin/reconnect/revoke, read/observe/replay of one session, source detection,
credential-free account detail, access plan/apply, and revocation impact. Each
function accepts already-scoped ids and returns the public SDK projection; it
must never return your Checkfu key, a provider credential, or a supplier URL.

The adapter owns search, account labels, minimum-scope display, stable retry
keys, one secret-free active-session locator per Principal, reload recovery,
bounded observation, tool/agent selection, and inventory refresh. Its default
storage is the current tab's `sessionStorage`, so a pending ceremony can resume
after reload without sharing recovery state across tabs. It owns no API key,
provider credential, supplier coordinate, or durable Checkfu authority.

## Begin setup

Choose one exact offering and one available opaque placement. Keep one
idempotency key for the logical attempt until the server responds; reusing it
after an ambiguous timeout prevents a duplicate Connection.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const attemptKey = crypto.randomUUID() // retain for retries

const session = await center.begin(
  {
    offering_id: selectedOffering.id,
    placement_id: selectedPlacement.id,
    label: "work",
    return_url: "https://app.example.com/settings/connections",
  },
  { idempotencyKey: attemptKey },
)
```

Omitting scopes and egress rules adopts the qualified package defaults. A
caller may request a narrower subset; widening is rejected before adapter I/O.
The placement id is only a public choice—the server resolves the frozen runtime
route and private coordinates.

Before custody begins, the server resolves one exact published
`ProviderPackage` and enforces its credential flow, minimum and allowed scopes,
and egress ceiling. The browser never asserts those binding facts, and
caller-authored settings cannot bypass a missing or ambiguous package.

The result owns one durable `ConnectionSession` and may carry a short-lived
Checkfu-origin `nextAction`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
if (session.nextAction !== null) {
  session.open((url) => window.open(url, "_blank", "noopener,noreferrer"))
}
```

That hosted step handles OAuth consent, pasted API keys, or service-account
material. Secret bytes terminate inside the selected trusted
custody boundary — Checkfu's sealed native store, or the selected
IntegrationRuntime adapter's own store — and at that boundary's final outbound
edge. They do not cross the UI kit, your browser payloads, the model, the
sandbox, the secret-free IntegrationRuntime port, or an audit event.

For managed OAuth suppliers that require their own confirmation page, Checkfu
wraps the supplier URL in a one-time Checkfu-origin handoff and completes the
supplier's custom verifier on the control plane. The raw supplier URL is never
part of the public session JSON.

## Observe or replay the exact session

`ConnectionSession` is the durable cursor. Observe it instead of searching a
environment-wide account list by label:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const settled = await session.observe({
  pollIntervalMs: 2_000,
  timeoutMs: 120_000,
  onChange: (current) => renderProgress(current),
})
```

The observer reconciles through the session's frozen runtime route. It settles
as `connected`, `revoked`, `failed`, `expired`, or `canceled`; it never returns
a provider response or credential. There is no Connection-lifecycle webhook
today, so this helper uses bounded public polling.

If a live human action was lost, replay that exact session under another
caller-owned retry key. Replay cannot create a Connection or broaden scope,
egress, package, placement, or provider authority.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const replayed = await session.replay({ idempotencyKey: retainedReplayKey })
```

## Detect a remote MCP or API source

Detection is credential-free and informative. It never publishes, enables, or
connects a source.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const detection = await center.detect("https://tools.example.com/mcp")
// matched | qualification_required | unavailable
```

A recognized MCP/OpenAPI source that has no qualified offering reports its
kind and bounded tool count with `qualification_required`. Network, document,
size, and protocol failures are closed reasons rather than guessed defaults.

## Choose tools and agent access

A connected account is still inert. Load its credential-free detail, review
the selected tools and agents, then plan before applying.

If a native account's tool catalog is not prepared in this Workspace, detail or
tool-list reads return `409 validation.conflict`. This differs from
`404 validation.not_found` for a missing or inaccessible Connection. Keep the
failure visible instead of showing an empty tool picker: these reads do not
discover or publish tools.

For a supported native MCP OAuth account, explicitly prepare its catalog with
an authorized Workspace API key. Read the current Connection first with
`GET /v1/connections/{id}`; the CLI and HTTP examples below use version `3`. Set `CHECKFU_CONNECTION_ID` to that
Connection's ID and retain `CHECKFU_PREPARATION_KEY` for this logical attempt.
[CLI access](/getting-started/cli-access) and the
[TypeScript SDK](/reference/typescript-sdk) require an invited alpha artifact.

<CodeGroup>
  ```sh CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  checkfu operation invoke connections.getConnection \
    --path "{\"id\":\"$CHECKFU_CONNECTION_ID\"}"

  checkfu operation invoke connections.prepareConnectionTools \
    --path "{\"id\":\"$CHECKFU_CONNECTION_ID\"}" \
    --body '{"expected_version":3}' \
    --idempotency-key "$CHECKFU_PREPARATION_KEY"

  checkfu operation invoke connections.listConnectionTools \
    --path "{\"id\":\"$CHECKFU_CONNECTION_ID\"}"
  ```

  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --fail-with-body --request POST \
    "https://api.checkfu.com/v1/connections/$CHECKFU_CONNECTION_ID/tools/prepare" \
    --header "Authorization: Bearer $CHECKFU_API_KEY" \
    --header "Checkfu-Version: 2026-08-31" \
    --header "Idempotency-Key: $CHECKFU_PREPARATION_KEY" \
    --header "Content-Type: application/json" \
    --data '{"expected_version":3}'
  ```

  ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const connection = await checkfu.connections.retrieve(connectionId)
  const preparationKey = crypto.randomUUID() // retain for this logical attempt
  await checkfu.connections.tools.prepare(
    connection.id,
    { expected_version: connection.version },
    { idempotencyKey: preparationKey },
  )
  const tools = await checkfu.connections.tools.list(connection.id)
  ```
</CodeGroup>

Use the version returned by the Connection read instead of copying `3`. Create
one unique preparation key before the first request. After an ambiguous timeout,
retry the same body with that same key. If the server reports a stale version,
read the Connection again and review its state before starting a new attempt
with its current version and a new key. After preparation, make a fresh tool-list
read with `GET /v1/connections/{id}/tools` before choosing Agent access.

For an API-key MCP client, search for `prepare connected account tools` with
`checkfu_search_operations`. Call the returned `connections_prepareConnectionTools`
tool using its advertised `path`, `body` and `idempotency_key` schema, then
read tools through `connections_listConnectionTools`. Principal MCP credentials
expose selected reads and the separately authorized Managed Tool invocation
operation; they cannot perform this connected-account setup mutation. See
[MCP discovery](/reference/mcp#discover-before-calling).

Preparation uses the account's stored credential inside server-owned custody.
It binds discovery to the reviewed provider descriptor and publishes only a
complete catalog while that exact Connection version remains active. An
already healthy, complete catalog is reused. It invokes no tools and grants no
Agent access. In the dashboard's Agent editor, choose the connected account and
click **Set up account tools**, then review the fresh tool list before adding it
to the draft. Public ToolSource registration and sync remain credential-free
discovery; Integration Runtime accounts use their capability-refresh workflow.

For an account with a prepared catalog:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const detail = await center.detail(settled.connection_id)

const plan = await center.planAccess(
  detail.connection.id,
  selectedAgentIds,
  selectedToolNames,
)

const applied = await center.applyAccess(
  detail.connection.id,
  selectedAgentIds,
  selectedToolNames,
)
```

The compiler creates explicit Connection and tool authority and reports
`installation_required`, `publish_required`, or `ready`. “All current agents”
is an explicit reconciled set, never ambient authority for future agents.

Access and an Agent's `always_allow` tool setting do not bypass ActionPolicy.
The generic OAuth MCP package installs a `require_approval` recommendation;
that decision wins over a separate allow rule. For unattended calls, review
the installed recommendation and explicitly scope the intended policy to the
acting Principal, Agent, Connection, and its published tools. Do not remove unrelated
policies or treat successful access setup as proof that execution is allowed.

During a Run, the harness calls the exact `connection_id:tool_name` through
CapabilityGateway. Checkfu rechecks PermissionAssignments, ActionPolicy and approval, Connection health
and revision, package/binding digest, exact egress, budget, and invocation
idempotency before credentials are injected server-side.

## Reconnect and revoke

Reconnect reserves a distinct immutable successor revision. The predecessor
remains the admitted revision until the successor completes.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const reconnect = await center.reconnect(
  detail.connection,
  "https://app.example.com/settings/connections",
  { idempotencyKey: retainedReconnectKey },
)
```

Revocation denies locally before supplier cleanup. It is version-fenced and
also requires one retained logical-attempt key:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const revoked = await center.revoke(detail.connection, {
  idempotencyKey: retainedRevokeKey,
})
```

The returned session may be terminal `revoked` with `cleanup_pending: true`.
New ToolInvocations are denied throughout asynchronous supplier cleanup.

## Drive the same lifecycle from the CLI

The private-alpha CLI composes the same generated operations; it has no
supplier-specific connector command or separate state. Start with the
Principal-scoped offering wall:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu connection offerings --principal prin_... --json
```

Begin setup with one selected offering and opaque placement. The CLI reserves
a new mode-`0600` file before it sends the request, writes the complete
human-completion receipt there, and prints only the secret-free session
summary:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu connection connect \
  --principal prin_... \
  --offering ioff_... \
  --placement hosted \
  --label "Engineering Slack" \
  --return-url https://app.example.com/settings/connections \
  --output ./slack-connect-receipt.json
```

Open `next_action.url` from that protected file outside model context. Retain
the returned `cns_...` identity for passive reads, explicit runtime
reconciliation, or a lost-handoff replay:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu connection session cns_... --principal prin_... --json
checkfu connection refresh cns_... --principal prin_... --json
checkfu connection replay cns_... --principal prin_... \
  --output ./slack-replay-receipt.json
```

Here `refresh` means reconcile that durable `ConnectionSession`; it is not a
capability-snapshot refresh. Read an Integration Gateway account's
credential-free health and drift from the Connection projection. Do not route
it through the legacy custody health mutation:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu connection health conn_... --principal prin_... --json
```

Review the credential-free tool catalog, then compile the complete desired
Agent tool set before applying it. Repeated `--agent` and `--tool`
flags are explicit selections; use `--no-agents` or `--no-tools` when an empty
set is intentional. Omitting `--tool` selects every currently discovered tool.

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu connection tools conn_... --principal prin_... --json
checkfu connection access plan conn_... \
  --principal prin_... \
  --agent agent_... \
  --tool messages.send \
  --json
checkfu connection access apply conn_... \
  --principal prin_... \
  --agent agent_... \
  --tool messages.send \
  --confirm --json
```

The plan changes nothing. Apply reconciles the complete Agent set,
may change draft tool pins and PermissionAssignments, never publishes an Agent automatically,
and therefore requires confirmation.

Reconnect uses the version from that reviewed Connection read and protects its
new handoff in the same way. Revocation has a separate impact preview and
requires both the returned CAS version and explicit confirmation:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu connection reconnect conn_... \
  --principal prin_... \
  --expected-version 3 \
  --return-url https://app.example.com/settings/connections \
  --output ./slack-reconnect-receipt.json

checkfu connection impact conn_... --principal prin_... --json
checkfu connection revoke conn_... \
  --principal prin_... \
  --expected-version 3 \
  --confirm --json
```

## Ownership modes

<Columns cols={2}>
  <Card title="Workspace / service">
    Use a service Principal for one shared account. Its blast radius is every
    explicitly authorized agent acting through that Principal.
  </Card>

  <Card title="Personal / delegated">
    Map each application user to an end-user Principal. The Principal-scoped
    offering read and session routes keep divergent accounts at one provider
    separate.
  </Card>
</Columns>

The flow is otherwise identical. A foreign Principal cannot list, observe,
replay, reconnect, or revoke another Principal's account or session.

## Lower-level compatibility operations

The generated SDK still exposes direct Connection authorize, provision,
health, and revoke operations for advanced and migration clients. They are not
the Connect Center contract and require callers to compose more lifecycle and
catalog state themselves. New embedded products should use the provider-neutral
controller above. The former duplicate connector path has been removed; these
lower-level operations remain only for native custody-backed Connections and
advanced lifecycle clients.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.