Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions packages/twenty-docs/developers/extend/apps/logic/connections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,18 @@ export const createLinearIssueHandler = async (input: {

Each connection has:

| Field | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------- |
| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one |
| `visibility` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) |
| `scopes` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) |
| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers |
| `workspaceMemberId` | The owner's workspace member id, or `null` if they've since left the workspace |
| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) |
| `name` | The connection's display name (auto-derived at OAuth callback, user-renameable) |
| `handle` | The connected upstream account's email, refreshed on each reconnect; resolved from the OIDC `id_token` returned at token exchange, falling back to the connecting Twenty user's email when the provider doesn't return one |
| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect |
| Field | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one |
| `visibility` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) |
| `scopes` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) |
| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers |
| `workspaceMemberId` | The owner's workspace member id, or `null` if they've since left the workspace |
| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) |
| `name` | The connection's display name (auto-derived at OAuth callback, user-renameable) |
| `handle` | The connected upstream account's email, refreshed on each reconnect; resolved from the OIDC `id_token` returned at token exchange, falling back to the connecting Twenty user's email when the provider doesn't return one |
| `authFailedAt` | Set when the most recent refresh failed, or when the app reported the credential dead via `reportConnectionAuthFailure`; the user must reconnect |
| `authFailedReason` | Human-readable explanation for `authFailedAt`, when the reporter gave one; shown on the connection row in settings next to the Reconnect button |

Key points:

Expand All @@ -232,6 +233,26 @@ Key points:

</Accordion>

<Accordion title="reportConnectionAuthFailure" description="Tell the platform a credential is dead">

The platform sets `authFailedAt` on its own only when an OAuth refresh fails. Providers whose tokens are never refreshed (a Slack bot token, for example) fail only at call time inside your handlers, so the connection row in settings would keep showing **Connected** while every call dies. When your code hits a definitive auth rejection, report it:

```ts
import { reportConnectionAuthFailure } from 'twenty-sdk/logic-function';

await reportConnectionAuthFailure({
connectionId: connection.id,
reason:
'Slack rejected the stored token (invalid_auth). Reconnect to restore the integration.',
});
```

This sets `authFailedAt` (and the optional reason, capped at 1000 characters) on the connection. The settings row flips to **Reconnect needed** with a Reconnect button, the detail page shows the reason, and `getConnection` starts throwing `AppConnectionAuthFailedError` for that row. The flag and reason clear automatically when the user reconnects.

Only report failures that a reconnect would actually fix (an `invalid_auth`-class rejection from the provider), never transient network errors. An app can only report its own connections, and a request user can only report their own user-visibility credentials.

</Accordion>

<Accordion title="Per-user vs workspace-shared visibility" description="How users choose between private and shared credentials">

When a user clicks "Add connection," they're prompted to pick a visibility:
Expand Down
Loading
Loading