---
author: OOMOL
author_url: https://oomol.com/fr/about/
dateModified: 2026-09-10
title: Guide du SDK OOMOL Connector
description: Utilisez le SDK TypeScript pour ajouter rapidement les capacités
  OOMOL Connector au backend de votre produit.
lang: fr
canonical_url: https://oomol.com/fr/docs/connector-sdk/
markdown_url: https://oomol.com/fr/docs/connector-sdk.md
---

# Guide du SDK OOMOL Connector

[`@oomol-lab/connector`](https://github.com/oomol-lab/connector-sdk) est le client TypeScript pour la passerelle OOMOL Connector. Utilisez-le pour ajouter rapidement des intégrations d'apps au backend de votre produit sans gérer vous-même l'infrastructure connector. Votre code appelle la passerelle hébergée d'OOMOL via le SDK ; OAuth, le renouvellement des tokens et le stockage des identifiants provider restent côté passerelle.

Le package est léger et sans dépendance ; le SDK construit la requête et analyse la réponse. Les appels comme `gmail.search_threads`, `slack.post_message` et `notion.append_block` ne nécessitent ni codegen ni CLI.

Sujets principaux :

- Installer le SDK, obtenir une API key et effectuer votre premier appel.
- Les cinq mots qui décrivent tout le modèle : gateway, provider, action, connection, team.
- Les deux chemins d'appel (chaîne dynamique et namespace) et la lecture des métadonnées d'exécution.
- Activer des types précis par action sans codegen.
- Transmettre des endpoints non modélisés, inspecter le catalog et lister les apps connectées.
- Utiliser `ProjectConnector` pour connecter des comptes au nom de vos utilisateurs finaux et exécuter des actions pour eux.
- Utiliser `OpenConnector` pour exécuter les mêmes actions sur un runtime Connector open source que vous hébergez vous-même.
- La forme des erreurs, celles qui sont réessayables, et comment définir le scope, le timeout et l'annulation des appels.

## Installation

```sh
npm install @oomol-lab/connector   # ou : bun add / pnpm add / yarn add
```

Nécessite Node ≥ 18 pour `fetch` et `AbortController` intégrés. Le SDK ne publie que `dist`, n'a aucune dépendance runtime et déclare `sideEffects: false`, ce qui permet un tree-shaking propre. Il fonctionne dans Node, Bun, Deno, les runtimes edge et les apps basées navigateur avec un `fetch` standard ; dans le navigateur, injectez l'API key depuis l'hôte.

## Obtenir une API key

Vous avez besoin d'une **API key personnelle** OOMOL Connector, de la forme `api_…`. Créez-la dans OOMOL Console :

<https://console.oomol.com/api-key>

Définissez-la comme variable d'environnement. Ce guide utilise `OOMOL_API_KEY` partout. La passerelle autorise chaque requête et reçoit la clé sous la forme `Authorization: Bearer <apiKey>`.

> Une clé personnelle `api_…` exécute les actions sur **vos propres** connections. Pour connecter des comptes pour *vos* utilisateurs finaux, utilisez une clé **project** distincte (`oo_proj_…`) et le client `ProjectConnector` ; voir [Connecter des comptes pour vos utilisateurs](#connecter-des-comptes-pour-vos-utilisateurs). Pour auto-héberger le runtime au lieu d'utiliser la passerelle hébergée, le serveur open source utilise son propre runtime token optionnel (`oct_…`) et le client `OpenConnector` ; voir [Runtime auto-hébergé](#runtime-auto-hébergé).

## Démarrage rapide

Construisez un client, puis appelez une action. Les deux formes ci-dessous sont équivalentes.

```ts
import { Connector } from "@oomol-lab/connector";

const oomol = new Connector({ apiKey: process.env.OOMOL_API_KEY! });

// Chemin 1 : chaîne dynamique. Appelable pour n'importe quel action id.
const { threads } = await oomol.execute("gmail.search_threads", { query: "from:boss" });

// Chemin 2 : syntaxe namespace. Même appel en dessous.
const result = await oomol.gmail.search_threads({ query: "is:unread" });
```

`execute` renvoie directement la sortie de l'action. Si vous voulez aussi les métadonnées d'exécution, utilisez `executeRaw` :

```ts
const raw = await oomol.executeRaw("gmail.search_threads", { query: "from:ceo" });
raw.data;        // la même valeur que celle renvoyée par execute()
raw.executionId; // id d'exécution attribué par le serveur (utile pour support / corrélation de logs)
raw.actionId;    // action id renvoyé en écho
raw.message;     // message lisible de l'enveloppe de succès
```

Le flux d'appel central repose sur `execute` et `executeRaw`.

## Concepts

Le modèle du SDK a cinq concepts principaux. L'authentification, les identifiants et les appels provider sont gérés par la passerelle :

| Terme | Définition |
| --- | --- |
| **Gateway** | Le service OOMOL Connector hébergé auquel ce client parle. Il conserve les identifiants, effectue les appels provider réels et renvoie une enveloppe uniforme. Le SDK n'exécute **aucune** logique d'intégration localement. |
| **Provider / service** | Une API tierce (`gmail`, `slack`, `github`, `notion`, …). C'est le préfixe `<service>` d'un action id. |
| **Action** | Une opération sur un provider, identifiée par `"<service>.<action>"` (par exemple `gmail.search_threads`). Les actions sont fournies par la passerelle et appelées par le client. |
| **Connection** | Un identifiant stocké et déjà autorisé pour un provider. Vous ne touchez jamais les tokens ; vous indiquez quelle connection utiliser via `connectionName`. OAuth et le cycle de vie des identifiants relèvent de la passerelle. |
| **Team** | Scope de tenant optionnel, envoyé dans l'en-tête `x-oo-team-name`. |

Le même vocabulaire se retrouve dans toute l'interface du SDK : un action id est toujours `"<service>.<action>"`, `connectionName` sélectionne une connection stockée et, pour le client project, `externalUserId` identifie l'un de vos utilisateurs finaux.

## Opérations courantes

| Besoin | Utiliser | Notes |
| --- | --- | --- |
| Exécuter une action modélisée | `execute` / `executeRaw` | L'appel typé en une ligne. `executeRaw` renvoie aussi `{ executionId, actionId, message }`. |
| Appeler un endpoint qui n'est pas encore modélisé comme action | `proxy` | Transmission vers l'API amont, avec les identifiants de la connection injectés par la passerelle. |
| Fournir des actions à un LLM / construire des formulaires dynamiques | `catalog` | JSON Schema runtime (2020-12) pour n'importe quelle action ou provider. |
| Découvrir ce qui est connecté | `apps.list` | Liste en lecture seule des connections déjà liées. |
| Laisser *vos* utilisateurs connecter *leurs* comptes | `ProjectConnector` | Client distinct, scopé par project, pour connecter des comptes au nom de vos utilisateurs finaux et exécuter des actions pour eux. |

La passerelle fournit la couverture des providers et actions. Consultez l’[API du catalogue public OOMOL](https://connector.oomol.com/v1/catalog) pour connaître les totaux du catalogue, ou parcourez le [répertoire des applications](/fr/apps/directory/). Découvrez les providers de votre passerelle au runtime avec `oomol.catalog.providers()` ; les actions appelables dépendent des autorisations du compte et de la politique d’accès.

## Types précis (optionnel)

Le chemin par chaîne dynamique compile pour **n'importe quel** `actionId`. Par défaut, chaque action utilise des types souples (`Record<string, any>` en entrée et en sortie), ce qui permet d'appeler immédiatement les nouvelles actions. Pour obtenir des types précis d'entrée/sortie par action avec la JSDoc, installez le package de types compagnon et ajoutez **un import à effet de bord par provider** utilisé :

```ts
import { Connector } from "@oomol-lab/connector";
import "@oomol-lab/connector-types/gmail";   // types précis + JSDoc pour gmail.*
import "@oomol-lab/connector-types/slack";   // …et slack.*

const oomol = new Connector({ apiKey: process.env.OOMOL_API_KEY! });

await oomol.gmail.search_threads({ query: "from:boss" }); // entrée + sortie maintenant précises
await oomol.notion.append_block({ pageId, text });        // notion non importé → appel toujours souple
```

```sh
npm install -D @oomol-lab/connector-types
```

Pas de codegen, pas de fichiers générés à commit, pas de CLI. Les actions enregistrées obtiennent l'autocomplétion littérale et des types exacts d'entrée/sortie ; **les actions non enregistrées se dégradent en `Record<string, any>`**. Si le package de types est en retard sur le backend, les nouvelles actions restent appelables via le fallback souple. Le runtime principal ne dépend pas du package de types.

> Nécessite `moduleResolution` défini sur `bundler`, `node16` ou `nodenext` pour que les imports de sous-chemins (`@oomol-lab/connector-types/gmail`) se résolvent. Consultez le dépôt [`@oomol-lab/connector-types`](https://github.com/oomol-lab/connector-types) pour les détails de configuration.

## Configuration

Tous les champs sauf `apiKey` sont optionnels :

```ts
new Connector({
  apiKey: process.env.OOMOL_API_KEY!,        // obligatoire
  baseUrl: "https://connector.oomol.com/v1", // valeur par défaut
  team: "acme",                              // team par défaut → x-oo-team-name
  connectionName: "work",                    // connection par défaut (préférer par appel / using())
  timeoutMs: 30_000,                         // timeout par requête par défaut
  maxRetries: 2,                             // par défaut ; retente 429 / 5xx / réseau avec backoff + jitter
  fetch: customFetch,                        // injection pour tests / proxies / tracing
});
```

| Champ | Défaut | Notes |
| --- | --- | --- |
| `apiKey` | — | Obligatoire. Envoyé sous la forme `Authorization: Bearer <apiKey>`. |
| `baseUrl` | `https://connector.oomol.com/v1` | Remplacement manuel uniquement ; il n'y a pas de bascule automatique basée sur l'environnement. |
| `team` | — | Tenant sous lequel l'appel s'exécute. |
| `connectionName` | — | Connection stockée à utiliser quand un provider en a plusieurs. Utilisez-la comme défaut client pour les configurations simples ; avec plusieurs connections, définissez-la par appel ou via `using()`. |
| `timeoutMs` | `30_000` | Timeout par requête, en millisecondes. |
| `maxRetries` | `2` | Retente sur 429 / 5xx / erreurs réseau, avec backoff exponentiel et jitter. |
| `fetch` | `fetch` global | Injecte un `fetch` personnalisé pour les tests, agents proxy ou tracing. |

### Scopes et options par appel

Trois couches sont résolues dans cet ordre de priorité : **options par appel > scope `using()` > valeurs par défaut du client.**

`using()` renvoie un sous-client scopé immuable qui fusionne les valeurs par défaut données ; le client d'origine reste intact :

```ts
const work = oomol.using({ connectionName: "work", team: "acme" });
await work.gmail.search_threads({ query: "label:urgent" }); // s'exécute sous "work" / "acme"
```

Les options par appel ne s'appliquent qu'à cet appel et ont la priorité la plus élevée :

```ts
await oomol.execute(
  "gmail.search_threads",
  { query: "from:ceo" },
  {
    team: "acme",           // remplace la team pour cet appel
    connectionName: "alt",  // choisit une autre connection pour cet appel
    timeoutMs: 10_000,      // timeout plus strict pour cet appel
    retries: 0,             // désactive les retries pour cet appel
    signal: controller.signal, // transmet un AbortSignal
  },
);
```

`connectionName` se résout avec les mêmes couches. Il est transporté sur le fil comme en-tête `x-oo-connector-alias` (le champ passerelle s'appelle `alias`) ; l'interface du SDK utilise `connectionName`.

## Les trois clients en bref

Le package exporte trois clients : un pour les connections personnelles, un pour les connections d'utilisateurs finaux sous un project, et un pour un runtime que vous hébergez vous-même. Ils partagent le transport et le modèle d'erreur ; leurs identifiants, méthodes et types restent séparés.

| | `Connector` | `ProjectConnector` | `OpenConnector` |
| --- | --- | --- | --- |
| Auth | clé personnelle `api_…` | clé project `oo_proj_…` | runtime token optionnel `oct_…` |
| Parle à | passerelle hébergée d'OOMOL | passerelle hébergée d'OOMOL | runtime open source que **vous** exécutez |
| Agit sur | **vos propres** connections | les connections de **vos utilisateurs finaux** | **vos propres** connections sur votre serveur |
| Identifie un utilisateur | — | `externalUserId` (choisi par vous) | — |
| Interface | `execute`, `executeRaw`, `proxy`, `catalog`, `apps`, namespaces | `connect.*`, `waitForConnection`, `getUserProfile`, `execute`, `executeRaw`, `forUser` | `execute`, `executeRaw`, `proxy`, `catalog`, `apps`, `health`, namespaces |
| Usage | appeler les providers que vous avez connectés | construire un produit SaaS où chaque utilisateur relie ses propres comptes | auto-héberger tout le runtime (localhost, Docker, votre infra) |

Cette section commence avec le `Connector` personnel. `ProjectConnector` est couvert dans [Connecter des comptes pour vos utilisateurs](#connecter-des-comptes-pour-vos-utilisateurs), et `OpenConnector` dans [Runtime auto-hébergé](#runtime-auto-hébergé).

## Proxy : appeler un endpoint sans action

Quand la passerelle n'a pas modélisé un endpoint comme action, appelez-le directement avec `proxy`. La passerelle injecte toujours les identifiants de la connection ; la requête et la réponse conservent la forme de l'API amont.

```ts
// GET typé. NOTE : le champ est `endpoint`, PAS `path`.
const repos = await oomol.proxy<Array<{ name: string }>>("github", {
  endpoint: "/user/repos",
  method: "GET",
  query: { per_page: 5, sort: "updated" },
});
repos.status;               // statut HTTP amont
repos.data.map((r) => r.name);

// POST avec body et en-têtes amont (ils vont au provider, pas à la passerelle).
await oomol.proxy("github", {
  endpoint: "/repos/acme/widgets/issues",
  method: "POST",
  headers: { "X-GitHub-Api-Version": "2022-11-28" },
  body: { title: "Tracking issue", labels: ["chore"] },
});
```

`endpoint` accepte un chemin (résolu contre la base URL du provider) ou une URL complète, utile pour les providers avec des hôtes régionaux comme `https://eu.posthog.com/api/...`. `method` est l'une de `GET | POST | PUT | PATCH | DELETE`. La réponse est `{ status, headers, data }`. Le body proxy est strict côté backend : les clés de premier niveau inconnues sont rejetées avec `invalid_input`.

## Catalog : inspecter providers et actions

Le catalog fournit des métadonnées runtime en lecture seule pour les formulaires dynamiques, la validation et les définitions d'outils LLM. Les schemas d'entrée/sortie utilisent JSON Schema (2020-12) et sont indépendants du package de types compile-time.

```ts
// Liste les providers ; possibilité de réduire côté serveur.
const all = await oomol.catalog.providers();                       // tous les providers
const mailish = await oomol.catalog.providers({ q: "mail" });      // recherche texte libre → ?q=
const some = await oomol.catalog.providers({ service: ["gmail", "slack"] }); // restriction → ?service=…

// Toutes les actions d'un service.
const actions = await oomol.catalog.actions("gmail");

// Métadonnées complètes d'une action, avec JSON Schema runtime.
const meta = await oomol.catalog.action("gmail.search_threads");
meta.name;          // nom lisible
meta.requiredScopes;// scopes OAuth nécessaires à l'action
meta.inputSchema;   // JSON Schema (2020-12) pour l'entrée
meta.outputSchema;  // JSON Schema (2020-12) pour la sortie
```

Chaque provider porte `{ service, displayName, iconUrl, homepageUrl, categories, authTypes }`.

## Apps : lister vos comptes connectés

`apps.list()` renvoie une vue en lecture seule des connections que la passerelle détient déjà pour vous. La création et la suppression de connections se font dans Console.

```ts
const apps = await oomol.apps.list();
for (const app of apps) {
  // { id, service, status, connectionName, … } ; connectionName vaut null quand il n'est pas défini.
  console.log(`${app.service}: id=${app.id} status=${app.status} connectionName=${app.connectionName}`);
}

// Ciblez une connection précise en repassant son connectionName comme sélecteur par appel.
const work = apps.find((a) => a.connectionName === "work");
if (work) {
  await oomol.execute("gmail.search_threads", { query: "is:unread" }, { connectionName: "work" });
}
```

## Gestion des erreurs

Les échecs lancent une `ConnectorError` typée. L'annulation par l'appelant (un `AbortSignal` interrompu) rejette avec l'`AbortError` standard, pour pouvoir la traiter séparément des erreurs passerelle ou transport.

```ts
import { Connector, ConnectorError, isRetryable } from "@oomol-lab/connector";

try {
  await oomol.slack.post_message({ channel: "#general", text: "shipped" });
} catch (err) {
  if (err instanceof ConnectorError) {
    err.code;        // union discriminable, p. ex. "rate_limited", "credential_expired"
    err.status;      // statut HTTP (0 pour les erreurs côté client / réseau)
    err.requestId;   // id de corrélation d'échec
    err.actionId;    // si applicable
    err.executionId; // si applicable
    err.data;        // body de réponse amont, p. ex. sur provider_error
    if (isRetryable(err)) {
      // 429 / 5xx / réseau / rate_limited / proxy_upstream_timeout / request_in_progress
    }
  } else {
    throw err; // non-ConnectorError, p. ex. AbortError venant d'une annulation appelant ; relancer
  }
}
```

`err.code` est une union **ouverte** : les codes backend connus obtiennent l'autocomplétion, et les nouveaux codes backend passent quand même comme chaînes. Gardez une branche par défaut lors du traitement. Codes courants, regroupés :

| Groupe | Codes |
| --- | --- |
| Entrée / requête | `invalid_input`, `invalid_request_payload`, `invalid_request_signature` |
| App / provider | `app_not_found`, `app_not_ready`, `app_auth_type_mismatch`, `provider_not_found`, `provider_not_configured`, `provider_config_not_found`, `provider_error`, `profile_not_found` |
| Identifiant / auth | `credential_expired`, `scope_missing`, `user_oauth_client_required` |
| Sélection de connection | `connection_ambiguous`, `connection_account_conflict`, `connection_alias_conflict`, `connection_request_not_found`, `connected_account_not_found` |
| Proxy | `proxy_not_supported`, `proxy_upstream_error`, `proxy_upstream_timeout`, `proxy_response_too_large` |
| Limite / concurrence | `rate_limited`, `request_in_progress`, `request_key_conflict`, `request_key_used` |
| Client uniquement (status 0, aucune requête envoyée ou échec de transport) | `client_invalid_request`, `client_timeout`, `client_network_error`, `client_wait_timeout` |

`isRetryable(err)` renvoie `true` pour `rate_limited`, `proxy_upstream_timeout`, `request_in_progress`, HTTP 429, tout 5xx et les échecs de transport (status 0). Il renvoie `false` pour les erreurs de validation client (`client_invalid_request`) et le plafond de `waitForConnection` (`client_wait_timeout`) ; ces cas nécessitent généralement de modifier l'appel ou le flux d'attente.

### Annulation et timeouts

Transmettez un `AbortSignal` pour annuler ; définissez `timeoutMs` pour borner un appel unique. La couche de retry intégrée gère les échecs transitoires. Utilisez `retries: 0` pour une tentative unique déterministe.

```ts
const controller = new AbortController();
setTimeout(() => controller.abort(), 50);
try {
  await oomol.execute("gmail.search_threads", { query: "huge" }, { signal: controller.signal });
} catch (err) {
  (err as Error).name; // "AbortError"
}
```

## Connecter des comptes pour vos utilisateurs

`Connector` exécute des actions sur **vos propres** connections. `ProjectConnector` sert aux produits SaaS : *vos* utilisateurs finaux relient *leurs propres* comptes Gmail / Slack / GitHub / … via votre app, et votre backend exécute des actions pour eux. C'est le modèle d'auth gérée utilisé par des produits comme [Composio](https://composio.dev) et [Pipedream Connect](https://pipedream.com/docs/connect).

Avec l'auth gérée, **les identifiants ne passent jamais par votre application ni par aucun modèle**. Votre utilisateur autorise sur une page hébergée par la passerelle ; la passerelle stocke l'identifiant et renouvelle le token automatiquement. Votre code ne conserve que des identifiants opaques.

### Identifiant de l'utilisateur final

**`externalUserId`** est la clé d'isolation utilisateur pour le client project. Vous la choisissez, généralement depuis votre propre base d'utilisateurs. Les opérations project comme connecter un compte, attendre une connection et exécuter une action sont scopées par cette valeur. Passez toujours le même `externalUserId` et la passerelle garde les connections de chaque utilisateur isolées.

### Construire le client project

`ProjectConnector` est un **client séparé**, construit avec une **API key project** (`oo_proj_…`). Il expose uniquement les opérations scopées project et n'inclut pas l'interface `execute` / namespace / `proxy` / `catalog` / `apps` du client personnel.

```ts
import { ProjectConnector } from "@oomol-lab/connector";

const project = new ProjectConnector({ apiKey: process.env.OOMOL_PROJECT_API_KEY! }); // oo_proj_...
```

> **La configuration Console vient d'abord.** Avant que votre backend ne connecte des comptes, un administrateur crée un project, une provider config et une API key project dans OOMOL Console. La configuration ponctuelle et le flux REST backend correspondant sont couverts dans le [guide SaaS OOMOL Connector](/fr/docs/connector-saas/). Ce SDK est l'enveloppe typée autour de la même API runtime.

### OAuth : créer un lien, puis attendre la fin

OAuth comporte deux étapes : créer une demande de connection en attente, envoyer l'utilisateur s'autoriser, puis attendre la fin.

```ts
// 1. Créer une demande de connection en attente pour l'un de vos utilisateurs.
const request = await project.connect.oauth("user_42", {
  service: "gmail",
  connectionName: "work",                          // nom à attribuer ; réutilisez-le ensuite pour cibler ce compte
  returnUri: "https://app.example.com/connected",  // où la passerelle renvoie l'utilisateur après le callback
});

// 2. Envoyer votre utilisateur vers la page d'autorisation du provider.
redirectUserTo(request.authorizationUrl);

// 3. Poller jusqu'à ce que l'utilisateur termine (ou que cela échoue / expire). Renvoie la demande finale.
const connected = await project.waitForConnection(request);
connected.status;             // "connected" | "failed" | "expired"
connected.connectedAccountId; // id du compte stocké une fois connecté
```

`authorizationUrl` ouvre d'abord une page d'entrée hébergée par Connector qui nomme votre project et le provider que l'utilisateur va autoriser, puis envoie l'utilisateur vers la page OAuth du provider.

![Entrée d'autorisation Connector affichant my-saas qui se connecte à Gmail avant la redirection vers Gmail](/img/docs/connector-sdk/en/authorization-entry.webp)

`waitForConnection` poll jusqu'à ce que la demande quitte l'état `initiated`, puis la renvoie. Si l'utilisateur ne termine pas l'autorisation, la demande devient naturellement `expired`. Si `maxWaitMs` (par défaut `600_000` ms, correspondant à l'expiration de la demande) s'écoule d'abord, une `ConnectorError` avec le code `client_wait_timeout` est lancée ; un `signal` interrompu rejette avec l'`AbortError` standard.

Quand votre `returnUri` est appelé, la passerelle ajoute des paramètres query que vous pouvez lire sur votre page de callback :

```text
status=success
service=gmail
providerConfigId=pc-1
externalUserId=user_42
connectedAccountId=ca-1
```

…ou, en cas d'annulation / erreur provider :

```text
status=error
code=<connector-error-code>
message=<human-readable-message>
```

### API key / identifiant personnalisé : synchrone

Les connections par API key et identifiant personnalisé renvoient un compte immédiatement. Seul OAuth nécessite `waitForConnection`.

```ts
// La clé amont propre à l'utilisateur final (p. ex. une clé OpenAI sk-…), pas votre clé oo_proj_.
const account = await project.connect.apiKey("user_42", { service: "openai", apiKey: "sk-..." });
account.available; // si le compte peut exécuter des actions maintenant

// Champs d'identifiants propres au provider, validés par la passerelle contre la provider config.
await project.connect.customCredential("user_42", {
  service: "jira",
  values: { email: "user@acme.com", token: "..." },
});
```

Chaque appel `connect.*` identifie le provider par **exactement un** de `service` ou `providerConfigId`. Utilisez `providerConfigId` quand un project a plusieurs configs pour le même service ; `service` est le cas simple.

### Sous quel compte se sont-ils connectés ?

`getUserProfile` lit le titulaire du compte tiers derrière un compte connecté : qui est réellement votre utilisateur final chez le provider. La passerelle récupère l'information en direct auprès du provider et la normalise en une seule forme, identique pour tous les providers.

```ts
const { service, profile, fetchedAt } = await project.getUserProfile(account.connectedAccountId);

profile.id;          // identifiant utilisateur stable côté provider
profile.kind;        // "user" | "bot" | "service_account" | "unknown" (union ouverte)
profile.username;    // pseudo / handle, ou null
profile.displayName; // nom affiché, ou null
profile.avatarUrl;   // URL de l'avatar, ou null
profile.email;       // e-mail, ou null si les scopes accordés ne l'exposent pas
profile.metadata;    // champs propres au provider exposés par la passerelle
```

Les champs qu'un provider n'expose pas (ou que les scopes accordés ne couvrent pas, `email` le plus souvent) reviennent à `null`, jamais absents. `fetchedAt` est l'horodatage Unix en millisecondes du moment où la passerelle a lu le profil. Passez un `connectedAccountId` issu d'un résultat de `connect.apiKey` / `connect.customCredential`, ou d'une `ConnectionRequest` parvenue à `connected`. Un id inconnu est rejeté avec `connected_account_not_found` ; un provider sans capacité `userProfile` est rejeté avec `profile_not_found` ; un compte inactif ou inutilisable peut être rejeté avec `app_not_ready`, `app_auth_type_mismatch` ou `credential_expired`. Le sous-client ciblé sur un utilisateur l'expose aussi : `user.getUserProfile(connectedAccountId)`.

### Exécuter au nom de l'utilisateur

```ts
// Le provider service est déduit du préfixe actionId ("gmail").
// Sans connectionName / connectedAccountId, le dernier compte actif de l'utilisateur est utilisé.
const out = await project.execute(
  "user_42",
  "gmail.search_threads",
  { query: "is:unread" },
  { connectionName: "work" },
);
```

Priorité de sélection du compte : `connectedAccountId` (un compte précis) prime sur `connectionName` (un compte par son nom) ; sans les deux, la passerelle utilise le dernier compte actif de l'utilisateur pour ce provider. `project.executeRaw` renvoie la même enveloppe `{ data, executionId, actionId, message }` que le client personnel.

### Scoper sur un utilisateur

`forUser` lie une fois `externalUserId` pour éviter de répéter l'id dans les appels suivants :

```ts
const user = project.forUser("user_42");
const request = await user.connect.oauth({ service: "slack" });
const slack = await user.waitForConnection(request);
if (slack.status === "connected") {
  await user.execute(
    "slack.post_message",
    { channel: "#general", text: "shipped" },
    { connectedAccountId: slack.connectedAccountId },
  );
}
```

`project.execute` réutilise le même registre [`@oomol-lab/connector-types`](https://github.com/oomol-lab/connector-types) que le chemin personnel : les providers importés obtiennent des entrées/sorties précises, et les autres restent appelables de manière souple.

### Cycle de vie des connections

| Objet | Quand vous l'obtenez | Champs clés |
| --- | --- | --- |
| `ConnectionRequest` | renvoyé par `connect.oauth`, relu via `getConnectionRequest` / `waitForConnection` | `id`, `status` (`initiated` → `connected` / `failed` / `expired`), `authorizationUrl`, `connectedAccountId`, `externalUserId`, `connectionName`, `expiresAt` |
| `ConnectedAccount` | renvoyé de façon synchrone par `connect.apiKey` / `connect.customCredential` ; pointé par une demande OAuth terminée | `id` / `connectedAccountId`, `status` (`active`, `reauth_required`, `error`, `disconnected`), `available`, `externalUserId`, `connectionName`, `service` |

`available` vaut `true` uniquement quand tout ce qui est nécessaire à l'exécution est en place : la provider config est active, le compte est actif, l'app sous-jacente est active et un identifiant existe. Les deux champs status sont des unions **ouvertes** ; gardez une branche par défaut pour gérer les nouveaux statuts backend.

### Vous venez de Composio / Pipedream ?

| Composio / Pipedream | `@oomol-lab/connector` |
| --- | --- |
| `userId` / `external_user_id` | `externalUserId` |
| `connectedAccounts.initiate` / `createConnectToken` (OAuth) | `project.connect.oauth` |
| `connectedAccounts.initiate` + `AuthScheme.APIKey` | `project.connect.apiKey` |
| `waitForConnection()` | `project.waitForConnection()` |
| `tools.execute(slug, { userId, arguments })` | `project.execute(externalUserId, actionId, input)` |
| `composio.getEntity(userId)` | `project.forUser(externalUserId)` |

## Runtime auto-hébergé

Vous exécutez vous-même le serveur Connector open source, sur localhost, dans Docker ou sur votre propre infra ? **`OpenConnector`** est le client personnel pour ce runtime. Il reflète l'interface `Connector` (les deux chemins d'appel, `proxy`, `catalog`, `apps`) en la pointant vers le serveur que *vous* exécutez, de sorte que le code déjà écrit change très peu. Mettre le serveur en route est un sujet séparé ; voir le [guide d'auto-hébergement OpenConnector](/fr/docs/openconnector-self-hosting/).

Le runtime open source est l'équivalent auto-hébergeable du produit **personnel** : un serveur, un utilisateur, des actions exécutées sur les propres connections de cet utilisateur. Il n'y a ni `team` ni modèle d'utilisateur final ici ; le serveur est à vous.

```ts
import { OpenConnector } from "@oomol-lab/connector";

const open = new OpenConnector(); // par défaut http://localhost:3000 ; une instance neuve ne demande pas d'auth

await open.execute("hackernews.get_top_stories", {});             // chemin 1 — chaîne dynamique
await open.gmail.search_threads({ query: "from:boss" });          // chemin 2 — syntaxe namespace, mêmes types de registre
await open.proxy("github", { endpoint: "/user", method: "GET" }); // chemin 3 — transmission vers un endpoint non modélisé
await open.catalog.search("send email", { limit: 5 });            // extensions catalog : search, services
await open.apps.list();                                           // vue en lecture seule des connections du runtime
```

Les deux chemins d'appel et le workflow de types précis sont identiques au `Connector` personnel ; les mêmes imports à effet de bord `@oomol-lab/connector-types` s'appliquent. `proxy` fonctionne de la même manière, avec une différence côté runtime : `endpoint` doit être un **chemin relatif** commençant par `/` ; le runtime rejette les URL absolues avec `invalid_input`, alors que la passerelle hébergée accepte aussi une URL complète. Les providers sans exécuteur proxy répondent `proxy_not_supported`.

### Pointer vers votre serveur

`baseUrl` est l'**origin** du serveur, pas une URL `/v1` ; le client ajoute lui-même le préfixe de chemin. L'auth utilise un seul runtime token **optionnel** (`oct_…`), créé dans la console web du runtime (onglet Access). Une instance neuve sans token répond sans token ; dès qu'un token existe, le serveur l'impose.

```ts
const open = new OpenConnector({
  baseUrl: "https://connect.internal.example.com",       // ORIGIN du serveur — pas une URL /v1
  runtimeToken: process.env.OOMOL_CONNECT_RUNTIME_TOKEN, // oct_… ; omettre tant que l'instance n'a pas de token
  connectionName: "work",                                // connection par défaut optionnelle au niveau client
});
```

Chaque champ est optionnel. `timeoutMs`, `maxRetries` et `fetch` se comportent exactement comme sur le client hébergé.

### Interface propre au runtime

`OpenConnector` ajoute quelques membres absents du client hébergé, et retire ceux qui ne s'appliquent pas :

```ts
await open.health();                                    // { ok, runtime } — sonde de connectivité / auth
await open.catalog.services();                          // tous les service id qui ont des actions
await open.catalog.search("top stories", { limit: 3 }); // classe les actions par pertinence en texte libre
await open.apps.listByService("github");                // connections d'un service
await open.apps.authenticated(["github", "notion"]);    // lesquels ont un VRAI identifiant stocké
```

Il n'y a **pas de `using()`**. La sélection de connection n'a que deux couches : un `connectionName` par appel remplace le défaut au niveau client, et sans les deux le runtime revient à sa connection `"default"`.

> **La gestion se fait dans la console web, pas dans le SDK.** Créer des connections, configurer des clients OAuth et créer des runtime tokens relève de l'administration serveur, volontairement en dehors de ce SDK. `OpenConnector` ne fait que *consommer* ce que la console a configuré ; le [guide d'auto-hébergement](/fr/docs/openconnector-self-hosting/) couvre cet aspect. Comme sur le client hébergé, un service id qui entre en collision avec un nom de membre (`execute` / `executeRaw` / `health` / `proxy` / `catalog` / `apps`) continue de fonctionner via `execute("<service>.<action>", …)` ; seule sa syntaxe namespace est masquée.

Tour complet exécutable — [`examples/open.ts`](https://github.com/oomol-lab/connector-sdk/blob/main/examples/open.ts).

## Référence

### `Connector` (clé personnelle `api_…`)

```ts
new Connector(config: ClientConfig)

oomol.execute(actionId, input, options?)     // → sortie de l'action
oomol.executeRaw(actionId, input, options?)  // → { data, executionId, actionId, message }
oomol.<service>.<action>(input, options?)    // syntaxe namespace pour execute
oomol.using(scope)                           // → sous-client scopé immuable
oomol.proxy(service, { endpoint, method, query?, headers?, body? }, options?) // → { status, headers, data }
oomol.catalog.action(actionId, options?)     // → ActionMetadata
oomol.catalog.actions(service, options?)     // → ActionMetadata[]
oomol.catalog.providers(query?, options?)    // → ProviderMetadata[]   query: { service?: string[]; q?: string }
oomol.apps.list(options?)                    // → ConnectedApp[]
```

### `ProjectConnector` (clé project `oo_proj_…`)

```ts
new ProjectConnector(config: ProjectConnectorConfig)

project.connect.oauth(externalUserId, input, options?)            // → ConnectionRequest (en attente)
project.connect.apiKey(externalUserId, input, options?)           // → ConnectedAccount (synchrone)
project.connect.customCredential(externalUserId, input, options?) // → ConnectedAccount (synchrone)
project.getConnectionRequest(connectionRequestId, options?)       // → ConnectionRequest
project.waitForConnection(requestOrId, options?)                  // → ConnectionRequest   options: { pollIntervalMs?, maxWaitMs?, signal?, timeoutMs? }
project.getUserProfile(connectedAccountId, options?)              // → ConnectedAccountProfile
project.execute(externalUserId, actionId, input, options?)        // → sortie de l'action
project.executeRaw(externalUserId, actionId, input, options?)     // → { data, executionId, actionId, message }
project.forUser(externalUserId)                                   // → ProjectUser (mêmes méthodes, id lié)
```

L'entrée `connect.*` est `{ service | providerConfigId } & { connectionName?, … }` (exactement un de `service` / `providerConfigId`). Les options `execute` ajoutent `{ providerConfigId?, service?, connectedAccountId?, connectionName? }`.

### `OpenConnector` (runtime auto-hébergé, token `oct_…` optionnel)

```ts
new OpenConnector(config?: OpenConnectorConfig)   // tous les champs sont optionnels ; baseUrl vaut par défaut http://localhost:3000

open.execute(actionId, input, options?)       // → sortie de l'action
open.executeRaw(actionId, input, options?)    // → { data, executionId, actionId, message }
open.<service>.<action>(input, options?)      // syntaxe namespace pour execute
open.health(options?)                         // → { ok, runtime }
open.proxy(service, { endpoint, method, query?, headers?, body? }, options?) // → { status, headers, data }   (endpoint doit être un chemin relatif)
open.catalog.action(actionId, options?)       // → OpenActionMetadata
open.catalog.actions(service, options?)       // → OpenActionMetadata[]
open.catalog.services(options?)               // → string[]   (service ids qui ont des actions)
open.catalog.providers(query?, options?)      // → ProviderMetadata[]
open.catalog.search(q, query?, options?)      // → OpenActionSearchResult[]   query: { service?, limit? }
open.apps.list(options?)                      // → ConnectedApp[]
open.apps.listByService(service, options?)    // → ConnectedApp[]
open.apps.authenticated(services, options?)   // → string[]   (services avec un vrai identifiant)
```

`options` est `{ connectionName?, signal?, timeoutMs?, retries? }` (pas de `team`, pas de `using()`). `config` ajoute `{ baseUrl?, runtimeToken?, connectionName?, timeoutMs?, maxRetries?, fetch? }`.

### Exports

```ts
import {
  Connector,
  ProjectConnector,
  OpenConnector,
  ConnectorError,
  isRetryable,
} from "@oomol-lab/connector";
import type {
  ClientConfig, CallOptions, ScopeOptions, RawResult,
  ProxyRequest, ProxyResponse, ProxyMethod,
  CatalogApi, ActionMetadata, ProviderMetadata, ProviderQuery,
  AppsApi, ConnectedApp,
  ConnectorErrorCode,
  ProjectConnectorConfig, ProjectCallOptions, ProjectExecuteOptions,
  ConnectionRequest, ConnectedAccount, ProviderSelector,
  ConnectedAccountProfile, ProviderUserProfile, ProviderUserKind,
  OAuthConnectInput, ApiKeyConnectInput, CustomCredentialConnectInput,
  OpenConnectorConfig, OpenConnectorApi, OpenCallOptions, OpenExecuteOptions,
  OpenCatalogApi, OpenAppsApi, OpenHealth,
  OpenActionMetadata, OpenActionFollowUp, OpenActionAsyncLifecycle,
  OpenActionSearchResult, OpenSearchQuery,
} from "@oomol-lab/connector";
```

Des exemples exécutables et type-checkés se trouvent dans le répertoire [`examples/`](https://github.com/oomol-lab/connector-sdk/tree/main/examples) du dépôt.

## Licence

MIT, voir le dépôt [connector-sdk](https://github.com/oomol-lab/connector-sdk).
