> ## Documentation Index
> Fetch the complete documentation index at: https://twenty-claude-cool-pascal-5ay683.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Подключения

> Разрешите вашему приложению действовать от имени пользователя в сторонних сервисах с помощью OAuth.

Подключения — это учетные данные, которыми пользователь располагает для внешнего сервиса (Linear, GitHub, Slack, ...). Ваше приложение определяет, **как** получают эти учетные данные — через **провайдера подключения** — и использует их во время выполнения для выполнения аутентифицированных вызовов к стороннему API.

На данный момент поддерживается только OAuth 2.0. Будущие типы учетных данных (персональные токены доступа, ключи API, базовая аутентификация) будут подключаться к тому же интерфейсу — приложения, уже использующие `defineConnectionProvider({ type: 'oauth', ... })` не потребуют миграции.

<AccordionGroup>
  <Accordion title="defineConnectionProvider" description="Определите, как в вашем приложении получаются подключения">
    Провайдер подключения описывает процедуру OAuth-обмена, которая требуется вашему приложению. Пользователь нажимает "Добавить подключение" в настройках вашего приложения, подтверждает разрешения на экране согласия провайдера, и в его рабочем пространстве создается запись `ConnectedAccount`.

    Рабочей конфигурации нужны **два файла** — провайдер подключения и соответствующее объявление `serverVariables` в `defineApplication`, которое содержит учетные данные клиента OAuth.

    ```ts src/connection-providers/linear-connection.ts theme={null}
    import { defineConnectionProvider } from 'twenty-sdk/define';

    export default defineConnectionProvider({
      universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f',
      name: 'linear',
      displayName: 'Linear',
      icon: 'IconBrandLinear',
      type: 'oauth',
      oauth: {
        authorizationEndpoint: 'https://linear.app/oauth/authorize',
        tokenEndpoint: 'https://api.linear.app/oauth/token',
        scopes: ['read', 'write'],
        // These must match keys in `defineApplication.serverVariables` below.
        clientIdVariable: 'LINEAR_CLIENT_ID',
        clientSecretVariable: 'LINEAR_CLIENT_SECRET',
        // Optional: defaults to 'json'. Some providers (Linear, Slack) want
        // 'form-urlencoded' for the token request.
        tokenRequestContentType: 'form-urlencoded',
        // Optional: defaults to true. Disable only if the provider rejects PKCE.
        usePkce: false,
        // Optional: extra query params on the authorize URL.
        // authorizationParams: { prompt: 'consent' },
        // Optional: provider's RFC 7009 token revocation endpoint, called on disconnect.
        // revokeEndpoint: 'https://example.com/oauth/revoke',
      },
      // Optional: a logic function in this app to run right after a connection is
      // established. See "Run a logic function on connect".
      // onConnectLogicFunction: { universalIdentifier: '3a2b1c0d-...-...' },
      // Optional: a logic function in this app to run right after a connection is
      // removed. See "Run a logic function on disconnect".
      // onDisconnectLogicFunction: { universalIdentifier: '4d5e6f70-...-...' },
    });
    ```

    ```ts src/application.config.ts theme={null}
    import { defineApplication } from 'twenty-sdk/define';

    export default defineApplication({
      universalIdentifier: '...',
      displayName: 'Linear',
      description: 'Connect Linear to Twenty.',
      // OAuth client credentials live on the app registration (one OAuth app per
      // Twenty server, configured by the admin) — not per-workspace. Declare them
      // as serverVariables so the admin can fill them in once for all installs.
      serverVariables: {
        LINEAR_CLIENT_ID: {
          description: 'OAuth client ID from your Linear OAuth application.',
          isSecret: false,
          isRequired: true,
        },
        LINEAR_CLIENT_SECRET: {
          description: 'OAuth client secret from your Linear OAuth application.',
          isSecret: true,
          isRequired: true,
        },
      },
    });
    ```

    Основные моменты:

    * `name` — это уникальная строка-идентификатор, используемая в `listConnections({ providerName })` (kebab-case, должна соответствовать `^[a-z][a-z0-9-]*$`).
    * `displayName` отображается на вкладке настроек приложения и в списке инструментов ИИ.
    * `clientIdVariable` / `clientSecretVariable` — это **имена**, а не значения — они должны совпадать с ключами, объявленными в `defineApplication.serverVariables`. Фактические `client_id` и `client_secret` вводятся администратором сервера через интерфейс регистрации приложения и никогда не коммитятся в ваш репозиторий.
    * Используйте `serverVariables` (не `applicationVariables`) — учетные данные OAuth являются общими для сервера, и на каждом сервере Twenty используется одно приложение OAuth.
    * Пока оба `serverVariables` не заполнены, на вкладке настроек приложения показывается подсказка "нужен администратор сервера", а кнопка "Добавить подключение" отключена.
    * `type: 'oauth'` — единственное поддерживаемое сегодня значение. Дискриминатор совместим с будущими версиями: будущие типы (`'pat'`, `'api-key'`, ...) добавят новые блоки подконфигурации рядом с `oauth`.

    URL обратного вызова OAuth, который вашему провайдеру нужно добавить в список разрешенных:

    ```
    https://<your-twenty-server>/auth/apps/callback
    ```
  </Accordion>

  <Accordion title="Запускайте логическую функцию при подключении" description="Реагируйте в момент установления соединения">
    Некоторые провайдеры передают вам данные в момент подключения, которые нужно сохранить до того, как соединение станет пригодным к использованию — классический пример — Slack, где OAuth-ответ указывает `team_id` рабочей области, по которому будут определяться входящие события. Установите `onConnectLogicFunction`, чтобы сослаться на логическую функцию в том же приложении (по её `universalIdentifier`), и она будет выполнена сразу после создания `ConnectedAccount`.

    ```ts src/connection-providers/slack-connection.ts theme={null}
    export default defineConnectionProvider({
      universalIdentifier: '...',
      name: 'slack',
      displayName: 'Slack',
      type: 'oauth',
      oauth: {
        /* ... */
      },
      // Runs claimSlackTeam after every successful Slack connection.
      onConnectLogicFunction: {
        universalIdentifier: '3a2b1c0d-1111-4222-8333-444455556666',
      },
    });
    ```

    Хук выполняется **асинхронно в подключающейся рабочей области** (он ставится в очередь, а не ожидается), поэтому медленный или падающий хук никогда не блокирует и не ломает OAuth-callback — сделайте его идемпотентным и обеспечьте собственную обработку повторных попыток. Обработчик получает:

    ```ts theme={null}
    type OnConnectPayload = {
      connectionProviderId: string;
      connectionProviderName: string; // e.g. 'slack'
      connectedAccountId: string;
    };
    ```

    Оттуда используйте `getConnection(connectedAccountId)`, чтобы прочитать новый access token и вызвать API провайдера (например, Slack `auth.test`) или сохранить отображение с помощью [key-value store](/l/ru/developers/extend/apps/logic/key-value-store).
  </Accordion>

  <Accordion title="Запускайте логическую функцию при отключении" description="Выполняйте очистку при удалении подключения">
    Всё, на что приложение заявляет права при подключении, должно быть освобождено, когда подключение прекращается. Интеграция со Slack, которая, например, заявляет `team_id` при подключении, должна освободить это право, чтобы другое рабочее пространство могло подключить ту же Slack-команду. Установите `onDisconnectLogicFunction`, чтобы сослаться на логическую функцию в том же приложении, и она будет выполнена сразу после удаления `ConnectedAccount`.

    ```ts src/connection-providers/slack-connection.ts theme={null}
    export default defineConnectionProvider({
      universalIdentifier: '...',
      name: 'slack',
      displayName: 'Slack',
      type: 'oauth',
      oauth: {
        /* ... */
      },
      // Runs releaseSlackTeam after every Slack disconnection.
      onDisconnectLogicFunction: {
        universalIdentifier: '4470aba8-5ff5-4800-88db-2a427cd8677c',
      },
    });
    ```

    Как и hook on-connect, он запускается **асинхронно в отключаемом рабочем пространстве** и никогда не блокирует отключение. Обработчик получает тот же формат полезной нагрузки:

    ```ts theme={null}
    type OnDisconnectPayload = {
      connectionProviderId: string;
      connectionProviderName: string; // e.g. 'slack'
      connectedAccountId: string;
    };
    ```

    `ConnectedAccount` уже удалён к моменту запуска hook, поэтому `getConnection(connectedAccountId)` больше не возвращает результат. Всё, что требуется для очистки (например, `team_id`, внешний идентификатор подписки), должно быть записано в [key-value store](/l/ru/developers/extend/apps/logic/key-value-store) в момент подключения, с ключом `connectedAccountId`.

    Hook срабатывает, когда соединение удаляется само по себе. При удалении приложения его соединения удаляются каскадом на уровне базы данных, поэтому hook в этом случае не запускается. Объявите `uninstallLogicFunction` в `defineApplication` для этого сценария: она запускается до удаления метаданных приложения, поэтому всё ещё может вызвать `listConnections` и выполнить оставшуюся очистку.
  </Accordion>

  <Accordion title="listConnections / getConnection" description="Используйте подключения из логической функции">
    Внутри обработчика логической функции `listConnections({ providerName })` возвращает записи `ConnectedAccount` этого приложения для указанного провайдера с обновленными токенами доступа.

    ```ts src/logic-functions/handlers/create-linear-issue-handler.ts theme={null}
    import { listConnections } from 'twenty-sdk/logic-function';

    export const createLinearIssueHandler = async (input: {
      teamId?: string;
      title?: string;
    }) => {
      if (!input.teamId || !input.title) {
        return { success: false, error: 'teamId and title are required' };
      }

      const connections = await listConnections({ providerName: 'linear' });

      // Workspace-shared credentials win when present; fall back to the first
      // user-visibility one. For HTTP-route triggers you typically pick the
      // request user's connection via event.userWorkspaceId instead.
      const connection =
        connections.find((c) => c.visibility === 'workspace') ?? connections[0];

      if (!connection) {
        return {
          success: false,
          error:
            'Linear is not connected. Open the app settings and click "Add connection".',
        };
      }

      // Use connection.accessToken to call the third-party API.
      const response = await fetch('https://api.linear.app/graphql', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${connection.accessToken}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`,
        }),
      });

      return { success: response.ok };
    };
    ```

    Каждое подключение имеет:

    | Поле              | Описание                                                                                                                                                                                                                                                    |
    | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `id`              | Уникальный идентификатор записи; передайте его в `getConnection(id)`, чтобы повторно получить одну запись                                                                                                                                                   |
    | `visibility`      | `'user'` (приватно для одного участника рабочего пространства) или `'workspace'` (доступно всем участникам)                                                                                                                                                 |
    | `scopes`          | Разрешения OAuth, предоставленные внешним провайдером (отличаются от `visibility` — это несвязанные вещи)                                                                                                                                                   |
    | `userWorkspaceId` | Идентификатор userWorkspace владельца — полезно для выбора "подключения пользователя запроса" в триггерах HTTP-маршрутов                                                                                                                                    |
    | `accessToken`     | Актуальный токен доступа OAuth (обновляется автоматически при истечении срока действия)                                                                                                                                                                     |
    | `name`            | Отображаемое имя подключения (автоматически определяется при обратном вызове OAuth, может быть переименовано пользователем)                                                                                                                                 |
    | «Обработка»       | Подключенная почта учётной записи в потоке, обновляется при каждом переподключении; решил из OIDC `id_token` возвратился при обмене токенов, возвращаясь обратно к подключению электронной почты Twenty пользователей, когда провайдер не возвращает одного |
    | `authFailedAt`    | Устанавливается, если последняя попытка обновления не удалась; пользователю нужно переподключиться                                                                                                                                                          |

    Основные моменты:

    * Передайте `{ providerName }`, чтобы отфильтровать по провайдеру; опустите, чтобы получить все подключения этого приложения у всех провайдеров.
    * Сервер прозрачно обновляет токен доступа перед возвратом. Ваш обработчик всегда получает рабочий токен (или установлено `authFailedAt`).
    * `getConnection(id)` — эквивалент для одной записи.
  </Accordion>

  <Accordion title="Индивидуальная и общая для рабочего пространства видимость" description="Как пользователи выбирают между приватными и общими учетными данными">
    Когда пользователь нажимает "Добавить подключение", ему предлагается выбрать видимость:

    * **Только для меня** — учетные данные приватны для подключившегося пользователя. Любая логическая функция, вызываемая от его имени (триггер HTTP-маршрута с `isAuthRequired: true`), видит их; триггеры cron и события базы данных — нет.
    * **Общее для рабочего пространства** — любой участник рабочего пространства может использовать эти учетные данные. Триггеры cron/базы данных также видят их, поскольку у них нет пользователя запроса.

    Используйте подходящий вариант для каждого обработчика:

    ```ts theme={null}
    // HTTP-route trigger — prefer the request user's own connection.
    const conn =
      connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ??
      connections.find((c) => c.visibility === 'workspace');

    // Cron trigger — no request user; only shared credentials are sensible.
    const conn = connections.find((c) => c.visibility === 'workspace');
    ```

    Допускается несколько подключений на пару (пользователь, провайдер), поэтому один и тот же пользователь может иметь "Personal Linear" и "Work Linear" одновременно.
  </Accordion>

  <Accordion title="Единоразовая настройка провайдера" description="Зарегистрируйте свое приложение OAuth у стороннего сервиса">
    Для каждого провайдера подключения администратору сервера сначала нужно зарегистрировать у стороннего сервиса приложение OAuth.

    1. Перейдите в настройки разработчика провайдера (например, [https://linear.app/settings/api/applications/new](https://linear.app/settings/api/applications/new)).
    2. Установите **Redirect URI** в значение `\<SERVER_URL>/auth/apps/callback`.
    3. Скопируйте сгенерированные **Client ID** и **Client Secret**.
    4. Откройте установленное приложение в Twenty под учетной записью администратора сервера → задайте значения в соответствующих `serverVariables`.
    5. Затем участники рабочего пространства смогут добавлять подключения в разделе **Подключения** конкретного приложения.
  </Accordion>
</AccordionGroup>
