Skip to content
GET /connection

Exactly docs/access-model.md §4, always — connections the caller owns, plus every connection shared with them directly, through a team they belong to, or org-wide. No organization permission widens this: a private connection (no grants) never appears here for anyone but its owner, whatever permission an org owner or admin holds. There is no org-wide oversight listing for connections. Statuses are reconciled against the vault as they are read, so this is also how a `needs_reauth` connection surfaces. A `pending` row whose sign-in finished but whose redirect never came back is bound AFTER the response by a throttled background pass: it is returned `pending` and may show as bound one load later. Rows the caller may share (owner or `edit` access) also carry `can_share: true` and a bounded `access_summary` — see the schema; page `GET /connection/{id}/share` for the grants themselves.

Query Parameters

qstring

Substring match (LIKE "%value%", case-insensitive) on the connection name, the connected account label where present, the connector slug ("slack", "google-drive"), and the connector's catalog label ("Google Drive") — matching the search box's own "Search by name or connector…" placeholder against both spellings a connector can go by. connector_slug below is a different question: an exact-equality filter to only these connectors, not a search. LIKE wildcards are matched literally. Max 200 characters.

statusstring

Filter by lifecycle status. Any other value is a 400.

Possible values:
pendingactiveneeds_reauthdisconnected
connector_slugstring[]

Filter to one or more connectors. Repeat the parameter or send one comma-separated value — both are the same filter. At most 25 values per request; more is a 400.

owner_user_idstring

Filter to connections owned by one user. This narrows what the caller can already see; it is not a second access path, so it never surfaces another member’s private connections. An id that matches nobody returns an empty page rather than a 404.

Response Body

next_cursorstring,null
prev_cursorstring,null
resultobject[]
connectorobject
required·

The connector's display data, resolved server-side in one batch per page so a client needs no GET /connector/{slug} per distinct connector. On GET /connection rows and GET /connection/{id} (which adds can_view_connector, available and auth_mode, and always states single_redirect_auth); absent from the command responses, which cannot change a connection's connector.

7 properties
auth_modestring

GET /connection/{id} only, remote MCP connectors only, and only when can_view_connector: how the connector signs in, so a screen can say "No sign-in" instead of "API key".

Possible values:
oauth_autooauth_clientcustomnone
availableboolean

False only when the catalog positively does not have the connector (staff unpublished or deleted a platform connector): the connection is kept but nothing can work until it returns. On GET /connection/{id} it is true when the connector resolves; on a GET /connection row it is never true, only false or ABSENT (one batched lookup per page). ABSENT too when the catalog could not answer, so a blip never reads as an unpublished connector.

can_view_connectorboolean

GET /connection/{id} only. Whether GET /connector/{slug} would answer this caller: false for a connector the catalog no longer resolves, without connector:view, and for an org-owned connector nobody shared with them (it 404s). Link to the connector only when true.

labelstring

Catalog display name; the slug itself when the catalog cannot resolve it.

logostring,null

Logo URL; null when the connector has none.

single_redirect_authboolean

The connector-row hint of the same name: connecting is most likely a single provider sign-in, so a client reserves a popup inside the Reconnect click. Always stated on GET /connection/{id} (false when can_view_connector is). On a list row it is stated only where the caller could open the connector, and ABSENT otherwise, which reads as unknown, never false.

upstream_slugstring

Present only on a fork (an org-owned connector with a lineage): the connector it was forked from. Omitted when the caller has no reach on the fork or on a private upstream (naming it would disclose its slug), and when the lineage cannot be read.

ownerobject
required·

Who owner_user_id is, resolved server-side in the same batch as connected_by_label (one per page). Same audience as a toolbox's owner: present on a connection the caller does NOT own, absent on their own. On every branch that returns a connection — list, detail and the command responses, so a transfer names the new owner without a reload. name/email are absent for an owner who has left the organization ("Former member").

4 properties
avatar_urlstring,null

The person's picture — the same one the console's user menu draws: their stored profile picture, else a Gravatar URL (d=404, 96px) derived server-side from their email (the address itself is not sent), else null. Draw initials when it is null or the image fails to load.

emailstring · email
idstring

User id (usr_…) — same value as owner_user_id.

namestring

Display name, else the email.

restricted_inherited_fromobject,null
required·

Set when this connector is restricted for the caller ONLY because the connector it was forked from is — a fork inherits its upstream's restrictions on purpose (forking a blocked connector is not a bypass), but nothing was set on the fork itself, so this says where the block came from. Null when not restricted, when a rule names this connector directly, or when the lineage could not be walked. Asking for access still targets THIS connector: approving the fork's own request lifts the inherited block for that fork only. Sibling of restricted_by (which stays the precedence layer); computed server-side in the same batch as the restriction evaluation — clients must not derive it from lineage.

2 properties
labelstring,null

The upstream's display name — null, together with slug, when the caller may not be told it. The UI then says "a connector this was forked from".

slugstring,null

The upstream connector the restriction is inherited from. For API consumers; never render it. Null — together with label — when the caller may not be told the upstream (an org-owned upstream they hold no grant on, or one that was deleted): a slug names the connector as surely as its label does.

accessstring

The caller's relationship to this connection: owner, or a granted view/use/edit level. There is no oversight fallback for connections — this is never "oversight"; a connection the caller neither owns nor holds a grant on is not visible to them at all, whatever organization permission they hold.

access_summaryobject

Present only when can_share is true. Bounded by design — page GET /connection/{id}/share for the grants themselves.

5 properties
org_levelstring,null
Possible values:
viewusenull
previewobject[]

At most 5 grantees, broadest first (org, then teams, then members), for a hover preview.

4 properties
grantee_idstring,null
grantee_typestring
Possible values:
userteamorg
levelstring
Possible values:
viewuse
namestring,null

Display name; null for org grants and for grantees no longer in the org.

team_countinteger
totalinteger

Every grant, the org-wide one included.

user_countinteger
access_viastring

How the CALLER reaches this connection — not who else can. owner — they own it. direct — a grant naming them personally. team — a grant to a team they belong to (or, per §6.2, one they administer), named in access_via_team. org — an organization-wide grant. When several sources apply the BROADEST wins and the caller's access level is not consulted: owner, else org, else team, else direct. So a caller granted edit personally AND view org-wide reads org — a narrower grant must never mask org-wide exposure, since this field says how far the connection reaches, not what the caller may do with it. Deliberately NOT gated on can_see_shares, and deliberately not a widening of it: this is the caller's OWN grant and their OWN team memberships, so a view/use grantee receives it while the grantee list — information about colleagues — stays closed to them. No other member is ever named. ABSENT when nothing reaches the caller — a transfer answering the ex-owner of a resource that was never shared, or a catalog row browsed with no grant behind it. Absent means "no source to name", never "not permitted", and never an implied org.

Possible values:
ownerdirectteamorg
access_via_teamobject

Present exactly when access_via is team, absent otherwise. The team the caller reaches this connection through — one of their own teams, never a disclosure about anybody else.

2 properties
idstring

Team id (team_…).

namestring,null

Team display name, or null when the team no longer resolves in the directory — the same null contract every resolved grantee name carries.

can_manageboolean

Present on every list and detail row. Whether the caller may edit this connection's own settings — owner, or edit access. No connection:manage fallback. Computed server-side; same can_manage name used by toolbox and connector for the identical capability.

can_revoke_shareboolean

can_share, verbatim — a THIRD formula, deliberately not an alias for can_manage: an edit grantee whose role omits connection:share (Auditor, Guest, any custom role) can reconfigure or reconnect the connection but was never meant to grant or revoke someone else's access to it, so can_manage: true on that row must not imply this control too. Mirrors DELETE /connection/{id}/share/{aclId}'s own gate exactly. Present on every list and detail row.

can_shareboolean

Whether the caller may share this connection — owner, edit access, or a team admin on a team-granted one. No connection:manage fallback. Computed server-side (CLAUDE.md's API-first rule); the console reads this rather than re-deriving it from owner_user_id/permissions.

can_transferboolean

Present on every list and detail row. Whether the caller may transfer or delete this connection — owner, full stop. No connection:manage fallback. Deliberately narrower than can_manage: an edit grant lets someone use and reconfigure a connection, never give it away or destroy it.

connected_by_labelstring,null

Display name for connected_by_user_id, resolved server-side one batch per page (name, else email). Present on every branch that returns a connection, commands included. A null HERE beside a NON-null connected_by_user_id is a fact about the person, not a failed lookup: they have left the organization, and clients render "Former member" — the same word the GET /audit-log actor summary uses. Compare the two fields, never one alone: a null id means "not recorded", a null label means "departed".

connected_by_user_idstring,null

Who AUTHENTICATED this account (usr_…) — the member whose third-party credential the vault actually holds. Written once at creation and never rewritten. Distinct from owner_user_id on purpose: POST /connection/{id}/transfer moves the owner and deliberately does NOT touch the vault account, so after a transfer these are two different people and only this one may be rendered as "Connected by". Null means NOT RECORDED, never "nobody" — every connection created before this field existed is null, because nothing server-side could tell a never-transferred row (where owner_user_id would have been the honest answer) from a transferred one, and a fabricated attribution is worse than an absent one. Render nothing for a null.

connector_slugstring

Which connector this account is on.

created_atstring · date-time
idstring

Connection id (conn_…).

last_errorstring,null
namestring
owner_user_idstring

Owning member (usr_…); calls run as this identity. Owner is implicit edit, plus delete and transfer — the two things no share confers. Move it with POST /connection/{id}/transfer. This is who is ACCOUNTABLE for the connection now, which after a transfer is not who authenticated it — see connected_by_user_id.

saffron_account_idstring,null

Vault account holding the credential. Null until the connect flow completes.

sharesobject[]

Present only when the caller may see the ACL: owner or edit access. A view/use recipient is never told who else the connection is shared with. Absent (not empty) when the caller may not see it.

8 properties
created_atstring · date-time
grantee_idstring,null

User id (usr_…) or team id (team_…). Null for a grantee_type: "org" grant.

grantee_typestring

user and team grants target one grantee_id; org applies to every member of the organization and takes no id.

Possible values:
userteamorg
idstring

ACL entry id — the :aclId a DELETE .../share/{aclId} call takes.

levelstring

Same ladder for every shareable resource, low to high. view — see it exists, read its metadata/config; cannot exercise it. use — view + exercise it, resource-specific: run tools through a connection; execute a toolbox's tools through its bound connections (this DELEGATES — the caller runs through each entry's pinning editor's own authority, not necessarily their own); stamp a new toolbox by copying a template's entries; create connections from a connector. edit — use + change its settings, entries and its own grants.

Possible values:
viewuseedit
resource_idstring

The shared resource's id (a connector's slug, for that type).

resource_typestring
Possible values:
connectiontoolboxtemplateconnectorfile
updated_atstring · date-time
statusstring

pending = created but not yet authenticated (open connect_url); active = usable; needs_reauth = the credential expired or was revoked upstream, call reconnect; disconnected = no longer usable. Refreshed lazily against the vault when connections are read.

Possible values:
pendingactiveneeds_reauthdisconnected
updated_atstring · date-time
curl -X GET 'https://api.elaichi.ai/connection' \
  -H 'Authorization: Bearer $ELAICHI_API_TOKEN' \
  -H 'Content-Type: application/json'
const response = await fetch('https://api.elaichi.ai/connection', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer ' + process.env.ELAICHI_API_TOKEN,
    'Content-Type': 'application/json',
  },
});

const data = await response.json();
console.log(data);
import os
import requests

url = "https://api.elaichi.ai/connection"
headers = {
    "Authorization": f"Bearer {os.environ['ELAICHI_API_TOKEN']}",
    "Content-Type": "application/json",
}

response = requests.get(url, headers=headers)
print(response.json())