---
title: Referencia del SDK de TypeScript de OOMOL
description: Consulta la configuración compartida, los tipos, los errores y la
  superficie de API de Connector, ProjectConnector y OpenConnector.
lang: es
canonical_url: https://oomol.com/es/docs/connector-sdk/
markdown_url: https://oomol.com/es/docs/connector-sdk.md
---

# Referencia del SDK de TypeScript de OOMOL

[`@oomol-lab/connector`](https://github.com/oomol-lab/connector-sdk) contiene tres clientes de TypeScript. Elige el cliente que coincida con la ruta del producto antes de usar la configuración compartida y la referencia de API de esta página:

- [`Connector`](/es/docs/connector-client/) llama a cuentas conectadas a tu propia cuenta de OOMOL a través del gateway alojado.
- [`ProjectConnector`](/es/docs/project-connector/) conecta y llama a cuentas propiedad de usuarios de tu producto SaaS.
- [`OpenConnector`](/es/docs/openconnector-sdk/) llama a un entorno de ejecución de OpenConnector que tú operas.

Los clientes comparten el comportamiento de transporte, tipos precisos de acciones y el modelo de errores. Sus claves, límites de cuentas y métodos disponibles difieren.

El paquete es ligero y sin dependencias; el SDK construye cada solicitud y analiza la respuesta. TypeScript puede llamar a acciones como `gmail.search_threads`, `slack.post_message` y `notion.append_block` directamente.

## Instalación

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

Requiere Node ≥ 18, para `fetch` y `AbortController` integrados. El SDK solo incluye `dist`, no tiene dependencias en tiempo de ejecución y es `sideEffects: false`, por lo que se puede tree-shake limpiamente. Usa `Connector` y `ProjectConnector` desde un entorno de ejecución de confianza del lado del servidor como Node, Bun, Deno o un edge worker. Las aplicaciones de navegador deben llamar a tu backend; nunca incluyas una clave de API personal o de Proyecto en el código del cliente.

## Tres clientes de un vistazo

| | `Connector` | `ProjectConnector` | `OpenConnector` |
| --- | --- | --- | --- |
| Autenticación | Clave `api_…` personal | Clave `oo_proj_…` de Proyecto | Token de entorno de ejecución opcional `oct_…` |
| Se conecta a | Gateway alojado por OOMOL | Gateway alojado por OOMOL | Un entorno de ejecución de OpenConnector que tú operas |
| Recurso de cuenta | Conexiones personales o de Equipo | Cuentas conectadas para usuarios externos | Conexiones en el entorno de ejecución |
| Caso de uso | Llamar a cuentas conectadas por un individuo o Equipo | Conectar y llamar a cuentas para usuarios de productos SaaS | Llamar a un entorno de ejecución autoalojado |
| Guía de integración | [SDK de Connector](/es/docs/connector-client/) | [ProjectConnector](/es/docs/project-connector/) | [SDK de OpenConnector](/es/docs/openconnector-sdk/) |

## `Connector`: llamar a conexiones personales o de Equipo

### Obtener una clave de API

Necesitas una **clave de API personal** de OOMOL Connector, con el formato `api_…`. Crea una en OOMOL Console:

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

Establécela como variable de entorno. Esta guía usa `OOMOL_API_KEY` en todo momento. El gateway autoriza cada solicitud y recibe la clave como `Authorization: Bearer <apiKey>`.

> Una clave `api_…` personal ejecuta acciones en conexiones personales o de Equipo. Para conectar cuentas para usuarios finales, usa una clave de Proyecto (`oo_proj_…`) y `ProjectConnector`; consulta la [guía de integración de ProjectConnector](/es/docs/project-connector/). Para llamar a un entorno de ejecución autoalojado, usa su token de entorno de ejecución opcional (`oct_…`) y `OpenConnector`; consulta la [guía del SDK de OpenConnector](/es/docs/openconnector-sdk/).

### Inicio rápido

Construye un cliente y luego llama a una acción. Las dos formas a continuación son equivalentes.

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

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

// Path 1: dynamic string. Callable for any action id.
const { threads } = await oomol.execute("gmail.search_threads", { query: "from:boss" });

// Path 2: namespace sugar. Same call underneath.
const result = await oomol.gmail.search_threads({ query: "is:unread" });
```

`execute` devuelve la salida de la acción directamente. Cuando también quieras los metadatos de ejecución, usa `executeRaw`:

```ts
const raw = await oomol.executeRaw("gmail.search_threads", { query: "from:ceo" });
raw.data;        // the same value execute() returns
raw.executionId; // server-assigned execution id (useful for support / log correlation)
raw.actionId;    // echoed action id
raw.message;     // human-readable message from the success envelope
```

El flujo de llamada principal es `execute` y `executeRaw`.

### Conceptos de `Connector`

El modelo del SDK tiene cinco conceptos principales. La autenticación, las credenciales y las llamadas al proveedor son gestionadas por el gateway:

| Término | Qué es |
| --- | --- |
| **Gateway** | El servicio alojado de OOMOL Connector con el que habla este cliente. Contiene credenciales, realiza las llamadas reales al proveedor y devuelve un sobre uniforme. El SDK ejecuta **ninguna** lógica de integración localmente. |
| **Proveedor / servicio** | Una API de terceros (`gmail`, `slack`, `github`, `notion`, …). Es el prefijo `<service>` de un id de acción. |
| **Acción** | Una operación en un proveedor, identificada como `"<service>.<action>"` (p. ej. `gmail.search_threads`). Las acciones son proporcionadas por el gateway y llamadas por el cliente. |
| **Conexión** | Una credencial almacenada y ya autorizada para un proveedor. Nunca tocas los tokens; nombras qué conexión usar mediante `connectionName`. OAuth y el ciclo de vida de las credenciales son responsabilidad del gateway. |
| **Equipo** | Alcance de inquilino opcional, enviado como el encabezado `x-oo-team-name`. |

El mismo vocabulario se traslada a la superficie del SDK: un id de acción es siempre `"<service>.<action>"`, `connectionName` selecciona una conexión almacenada y, para el cliente de proyecto, `externalUserId` identifica a uno de tus usuarios finales.

### Operaciones comunes

| Quieres… | Usa | Notas |
| --- | --- | --- |
| Ejecutar una acción modelada | `execute` / `executeRaw` | La línea tipada. `executeRaw` también devuelve `{ executionId, actionId, message }`. |
| Acceder a un endpoint que aún no está modelado como acción | `proxy` | Passthrough a la API ascendente, con las credenciales de la conexión inyectadas por el gateway. |
| Alimentar acciones a un LLM / crear formularios dinámicos | `catalog` | JSON Schema en tiempo de ejecución (2020-12) para cualquier acción o proveedor. |
| Descubrir qué está conectado | `apps.list` | Lista de solo lectura de las conexiones que ya has enlazado. |
| Permitir que *tus* usuarios conecten *sus* cuentas | `ProjectConnector` | Un cliente separado, con alcance de proyecto, para conectar cuentas en nombre de tus usuarios finales y ejecutar acciones para ellos. |

La cobertura de proveedores y acciones es proporcionada por el gateway. Actualmente admite más de 600 proveedores y sigue creciendo; descúbrela en tiempo de ejecución con `oomol.catalog.providers()`.

### Tipos precisos (opcional)

La ruta de cadena dinámica compila para **cualquier** `actionId`. De forma predeterminada, cada acción está tipada de forma flexible (`Record<string, any>` de entrada y salida), lo que mantiene las nuevas acciones invocables de inmediato. Para tipos precisos de entrada/salida por acción más JSDoc, instala el paquete de tipos complementario y añade **una importación de efectos secundarios por proveedor** que uses:

```ts
import { Connector } from "@oomol-lab/connector";
import "@oomol-lab/connector-types/gmail";   // precise types + JSDoc for gmail.*
import "@oomol-lab/connector-types/slack";   // …and slack.*

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

await oomol.gmail.search_threads({ query: "from:boss" }); // input + output now precise
await oomol.notion.append_block({ pageId, text });        // notion not imported → still loosely callable
```

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

Los tipos precisos provienen de `@oomol-lab/connector-types` y las importaciones de efectos secundarios, dejando el proyecto libre de archivos generados. Las acciones registradas obtienen autocompletado literal y tipos exactos de entrada/salida; **las no registradas degradan a `Record<string, any>`**. Cuando el paquete de tipos va por detrás del backend, las nuevas acciones siguen siendo invocables a través del fallback flexible. El entorno de ejecución principal y el paquete de tipos se publican por separado.

> Requiere `moduleResolution` establecido en `bundler`, `node16` o `nodenext` para que se resuelvan las importaciones de subruta (`@oomol-lab/connector-types/gmail`). Consulta el [repositorio `@oomol-lab/connector-types`](https://github.com/oomol-lab/connector-types) para detalles de configuración.

### Configuración

Todos los campos excepto `apiKey` son opcionales:

```ts
new Connector({
  apiKey: process.env.OOMOL_API_KEY!,        // required
  baseUrl: "https://connector.oomol.com/v1", // default
  team: "acme",                              // default team → x-oo-team-name
  connectionName: "work",                    // default connection (prefer per-call / using())
  timeoutMs: 30_000,                         // default per-request timeout
  maxRetries: 2,                             // default; retries 429 / 5xx / network with backoff + jitter
  fetch: customFetch,                        // inject for tests / proxies / tracing
});
```

| Campo | Predeterminado | Notas |
| --- | --- | --- |
| `apiKey` | — | Obligatorio. Se envía como `Authorization: Bearer <apiKey>`. |
| `baseUrl` | `https://connector.oomol.com/v1` | Anúlalo explícitamente en la configuración del cliente. |
| `team` | — | Bajo qué inquilino se ejecuta la llamada. |
| `connectionName` | — | Qué conexión almacenada usar cuando un proveedor tiene más de una. Úsalo como valor predeterminado del cliente para configuraciones simples; para varias conexiones, establécelo por llamada o mediante `using()`. |
| `timeoutMs` | `30_000` | Tiempo de espera por solicitud en milisegundos. |
| `maxRetries` | `2` | Reintentos en 429 / 5xx / errores de red, con retroceso exponencial y jitter. |
| `fetch` | `fetch` global | Inyecta un `fetch` personalizado para pruebas, agentes de proxy o trazabilidad. |

Para acciones con efectos secundarios, como `gmail.send_email` o `slack.post_message`, pasa `{ retries: 0 }` a menos que esa acción proporcione un mecanismo de idempotencia que hayas verificado. Puede ocurrir un fallo de red después de que el proveedor haya aceptado la solicitud, por lo que un reintento automático puede repetir la operación.

#### Ámbitos y opciones por llamada

Tres capas se resuelven en este orden de precedencia: **opciones por llamada > ámbito de `using()` > valores predeterminados del cliente.**

`using()` devuelve un subcliente con ámbito inmutable que combina los valores predeterminados dados; el cliente original no se modifica:

```ts
const work = oomol.using({ connectionName: "work", team: "acme" });
await work.gmail.search_threads({ query: "label:urgent" }); // runs under "work" / "acme"
```

Las opciones por llamada se aplican solo a esa llamada y tienen la mayor precedencia:

```ts
await oomol.execute(
  "gmail.search_threads",
  { query: "from:ceo" },
  {
    team: "acme",           // override team for this call
    connectionName: "alt",  // pick a different connection for this call
    timeoutMs: 10_000,      // tighter timeout for this call
    retries: 0,             // disable retries for this call
    signal: controller.signal, // forward an AbortSignal
  },
);
```

`connectionName` se resuelve con la misma estratificación. Se transporta en la red como el encabezado `x-oo-connector-alias` (el nombre del campo de la puerta de enlace es `alias`); la superficie del SDK usa `connectionName`.

### Proxy: llamar directamente a un endpoint ascendente

`proxy` llama directamente a un endpoint de API ascendente. La puerta de enlace inyecta credenciales de la conexión seleccionada, mientras que la solicitud y la respuesta mantienen la forma de la API ascendente.

```ts
// Typed GET. The request path uses the `endpoint` field.
const repos = await oomol.proxy<Array<{ name: string }>>("github", {
  endpoint: "/user/repos",
  method: "GET",
  query: { per_page: 5, sort: "updated" },
});
repos.status;               // upstream HTTP status
repos.data.map((r) => r.name);

// POST with a body and upstream headers; these headers go to the provider.
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` acepta una ruta (resuelta contra la URL base del proveedor) o una URL completa, lo cual es útil para proveedores con hosts regionales como `https://eu.posthog.com/api/...`. `method` es uno de `GET | POST | PUT | PATCH | DELETE`. La respuesta es `{ status, headers, data }`. El cuerpo del proxy es estricto en el backend: las claves de nivel superior desconocidas se rechazan como `invalid_input`.

### Catálogo: inspeccionar proveedores y acciones

El catálogo proporciona metadatos de tiempo de ejecución de solo lectura para formularios dinámicos, validación y definiciones de herramientas de LLM. Los esquemas de entrada/salida usan JSON Schema (2020-12) y son independientes del paquete de tipos en tiempo de compilación.

```ts
// List providers; optionally narrow server-side.
const all = await oomol.catalog.providers();                       // every provider
const mailish = await oomol.catalog.providers({ q: "mail" });      // free-text search → ?q=
const some = await oomol.catalog.providers({ service: ["gmail", "slack"] }); // restrict → ?service=…

// All actions of one service.
const actions = await oomol.catalog.actions("gmail");

// Full metadata for one action, including runtime JSON Schema.
const meta = await oomol.catalog.action("gmail.search_threads");
meta.name;          // human-readable name
meta.requiredScopes;// OAuth scopes the action needs
meta.inputSchema;   // JSON Schema (2020-12) for the input
meta.outputSchema;  // JSON Schema (2020-12) for the output
```

Cada proveedor incluye `{ service, displayName, iconUrl, homepageUrl, categories, authTypes }`.

### Apps: enumera tus cuentas conectadas

`apps.list()` devuelve una vista de solo lectura de las conexiones que la puerta de enlace ya tiene para ti. La creación y eliminación de conexiones se realiza en Console.

```ts
const apps = await oomol.apps.list();
for (const app of apps) {
  // { id, service, status, connectionName, … }; connectionName is null when none is set.
  console.log(`${app.service}: id=${app.id} status=${app.status} connectionName=${app.connectionName}`);
}

// Target a specific connection by passing its connectionName back as the per-call selector.
const work = apps.find((a) => a.connectionName === "work");
if (work) {
  await oomol.execute("gmail.search_threads", { query: "is:unread" }, { connectionName: "work" });
}
```

### Manejo de errores

Los fallos lanzan un `ConnectorError` tipado. La cancelación por parte del llamador (un `AbortSignal` abortado) rechaza con el `AbortError` estándar, por lo que se puede manejar por separado de los errores de la puerta de enlace o de transporte.

```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;        // discriminable union, e.g. "rate_limited", "credential_expired"
    err.status;      // HTTP status (0 for client-side / network errors)
    err.requestId;   // failure-correlation id
    err.actionId;    // when applicable
    err.executionId; // when applicable
    err.data;        // upstream response body, e.g. on provider_error
    if (isRetryable(err)) {
      // 429 / 5xx / network / rate_limited / proxy_upstream_timeout / request_in_progress
    }
  } else {
    throw err; // non-ConnectorError, e.g. AbortError from caller cancellation; rethrow
  }
}
```

`err.code` es una unión **abierta**: los códigos de backend conocidos obtienen autocompletado, y los nuevos códigos de backend siguen pasando como cadenas. Mantén una rama predeterminada al manejarla. Códigos comunes, agrupados:

| Grupo | Códigos |
| --- | --- |
| Entrada / solicitud | `invalid_input`, `invalid_request_payload`, `invalid_request_signature` |
| App / proveedor | `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` |
| Credencial / autenticación | `credential_expired`, `scope_missing`, `user_oauth_client_required` |
| Selección de conexión | `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` |
| Tasa / concurrencia | `rate_limited`, `request_in_progress`, `request_key_conflict`, `request_key_used` |
| Solo cliente (estado 0, sin solicitud enviada o fallo de transporte) | `client_invalid_request`, `client_timeout`, `client_network_error`, `client_wait_timeout` |

`isRetryable(err)` devuelve `true` para `rate_limited`, `proxy_upstream_timeout`, `request_in_progress`, HTTP 429, cualquier 5xx y fallos de transporte (estado 0). Devuelve `false` para errores de validación del cliente (`client_invalid_request`) y el límite de `waitForConnection` (`client_wait_timeout`); esos casos suelen requerir un cambio de llamada o de flujo de espera.

#### Cancelación y tiempos de espera

Reenvía un `AbortSignal` para cancelar; establece `timeoutMs` para limitar una sola llamada. La capa de reintentos integrada maneja fallos transitorios. Usa `retries: 0` para un único intento determinista.

```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"
}
```

## `ProjectConnector`: conecta cuentas para los usuarios del producto

`Connector` ejecuta acciones sobre **tus propias** conexiones. `ProjectConnector` es para productos SaaS: *tus* usuarios finales vinculan *sus propias* cuentas de Gmail / Slack / GitHub / … a través de tu app, y tu backend ejecuta acciones en su nombre. Este es el modelo de autenticación gestionada que usan productos como [Composio](https://composio.dev) y [Pipedream Connect](https://pipedream.com/docs/connect).

Con OAuth, el usuario autoriza en una página alojada por el gateway; los tokens del proveedor permanecen en el gateway y tu código maneja identificadores opacos. `project.connect.apiKey` y `project.connect.customCredential` son diferentes: tu backend de confianza recibe el secreto del usuario y lo envía al gateway. Mantén esos secretos fuera de navegadores, prompts, contexto del modelo y logs.

### El identificador de usuario final

**`externalUserId`** es la clave de aislamiento de usuario para el cliente de proyecto. Tú la eliges, normalmente desde tu propia base de datos de usuarios. Las operaciones del proyecto, como conectar una cuenta, esperar una conexión y ejecutar una acción, están limitadas a ella. Pasa el mismo `externalUserId` de forma consistente y el gateway mantiene aisladas las conexiones de cada usuario.

### Construir el cliente de proyecto

`ProjectConnector` es un cliente independiente que se construye con una **Project API key** (`oo_proj_…`). Proporciona operaciones con alcance de proyecto como `connect.*`, `waitForConnection`, `getUserProfile`, `execute`, `executeRaw` y `forUser`.

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

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

> **La configuración en Console va primero.** Antes de que tu backend conecte cuentas, un administrador crea un Project, una configuración de proveedor y una Project API key en OOMOL Console. La configuración inicial y el flujo REST de backend correspondiente se cubren en la [guía de Connector for SaaS](/es/docs/connector-saas/). Este SDK es el wrapper tipado sobre la misma API en tiempo de ejecución.

### OAuth: crear un enlace y esperar a que se complete

OAuth tiene dos pasos: crear una solicitud de conexión pendiente, enviar al usuario a autorizar, y luego esperar a que se complete.

```ts
// 1. Create a pending connection request for one of your users.
const request = await project.connect.oauth("user_42", {
  service: "gmail",
  connectionName: "work",                          // the name to assign; reuse it later to target this account
  returnUri: "https://app.example.com/connected",  // where the gateway returns the user after the callback
});

// 2. Send your user to the provider's authorization page.
redirectUserTo(request.authorizationUrl);

// 3. Poll until the user finishes (or it fails / expires). Returns the final connection request.
const connected = await project.waitForConnection(request);
connected.status;             // "connected" | "failed" | "expired"
connected.connectedAccountId; // the stored account id once connected
```

El `authorizationUrl` primero abre una página de entrada alojada por Connector que indica tu proyecto y el proveedor que el usuario está a punto de autorizar, y luego envía al usuario a la página de OAuth del proveedor.

![Entrada de autorización de Connector mostrando my-saas conectando con Gmail antes de redirigir a Gmail](/img/docs/connector-sdk/en/authorization-entry.png)

`waitForConnection` consulta hasta que la solicitud sale del estado `initiated` y lo devuelve. Si el usuario no completa la autorización, la solicitud pasa naturalmente a `expired`. Si `maxWaitMs` (por defecto `600_000`ms, coincidiendo con la expiración de la solicitud) transcurre primero, lanza un `ConnectorError` con código `client_wait_timeout`; un `signal` abortado rechaza con el `AbortError` estándar.

Cuando se alcanza tu `returnUri`, el gateway añade parámetros de consulta que puedes leer en tu página de callback:

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

…o, en caso de cancelación / error del proveedor:

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

### API key / credencial personalizada: síncrono

Las conexiones por API key y credencial personalizada devuelven una cuenta inmediatamente. Solo OAuth necesita `waitForConnection`.

```ts
// The end user's own upstream key (e.g. an OpenAI sk-…), not your oo_proj_ key.
const account = await project.connect.apiKey("user_42", { service: "openai", apiKey: "sk-..." });
account.available; // whether the account can execute actions right now

// Provider-specific credential fields, validated by the gateway against the provider config.
await project.connect.customCredential("user_42", {
  service: "jira",
  values: { email: "user@acme.com", token: "..." },
});
```

Cada llamada a `connect.*` identifica el proveedor por **exactamente uno** de `service` o `providerConfigId`. Usa `providerConfigId` cuando un proyecto tenga más de una configuración para el mismo servicio; `service` es el caso simple.

### ¿Como quién se conectaron?

`getUserProfile` lee el titular de la cuenta de terceros detrás de una cuenta conectada: quién es realmente tu usuario final en el proveedor. El gateway lo obtiene en vivo del proveedor y lo normaliza en una única forma entre proveedores.

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

profile.id;          // stable provider-side user id
profile.kind;        // "user" | "bot" | "service_account" | "unknown" (open union)
profile.username;    // handle, or null
profile.displayName; // display name, or null
profile.avatarUrl;   // avatar URL, or null
profile.email;       // email, or null when the granted scopes do not expose it
profile.metadata;    // provider-specific fields the gateway exposes
```

Los campos que un proveedor omite o que los scopes concedidos no cubren (`email` con mayor frecuencia) permanecen presentes en la respuesta con un valor `null`. `fetchedAt` es la marca de tiempo Unix en milisegundos en la que el gateway leyó el perfil. Pasa un `connectedAccountId` de un resultado `connect.apiKey` / `connect.customCredential`, o de un `ConnectionRequest` que alcanzó `connected`. Un id desconocido rechaza con `connected_account_not_found`; un proveedor que carece de la capacidad `userProfile` rechaza con `profile_not_found`; una cuenta inactiva o inutilizable puede rechazar con `app_not_ready`, `app_auth_type_mismatch` o `credential_expired`. El subcliente con alcance también lo tiene: `user.getUserProfile(connectedAccountId)`.

### Ejecutar en nombre del usuario

```ts
// The provider service is derived from the actionId prefix ("gmail").
// Without connectionName / connectedAccountId, the user's latest active account is used.
const out = await project.execute(
  "user_42",
  "gmail.search_threads",
  { query: "is:unread" },
  { connectionName: "work" },
);
```

Precedencia de selección de cuenta: `connectedAccountId` (una cuenta específica) tiene prioridad sobre `connectionName` (una cuenta por su nombre); sin ninguno de los dos, el gateway usa la última cuenta activa del usuario para ese proveedor. `project.executeRaw` devuelve el mismo sobre `{ data, executionId, actionId, message }` que el cliente personal.

### Limitar el alcance a un usuario

`forUser` vincula el `externalUserId` una vez para que las llamadas posteriores no repitan el id:

```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` reutiliza el mismo registro [`@oomol-lab/connector-types`](https://github.com/oomol-lab/connector-types) que la ruta personal: los proveedores importados obtienen entradas/salidas precisas, y el resto permanece invocable de forma flexible.

### Ciclo de vida de la solicitud de autorización y la cuenta

| Objeto | Cuándo lo obtienes | Campos clave |
| --- | --- | --- |
| `ConnectionRequest` | devuelto por `connect.oauth`, releído mediante `getConnectionRequest` / `waitForConnection` | `id`, `status` (`initiated` → `connected` / `failed` / `expired`), `authorizationUrl`, `connectedAccountId`, `externalUserId`, `connectionName`, `expiresAt` |
| `ConnectedAccount` | devuelto de forma síncrona por `connect.apiKey` / `connect.customCredential`; señalado por una solicitud OAuth completada | `id` / `connectedAccountId`, `status` (`active`, `reauth_required`, `error`, `disconnected`), `available`, `externalUserId`, `connectionName`, `service` |

`available` es `true` solo cuando todo lo necesario para ejecutar está en su sitio: la configuración del proveedor está activa, la cuenta está activa, la app subyacente está activa y existe una credencial. Ambos campos de estado son uniones **abiertas**; mantén una rama por defecto para gestionar nuevos estados del backend.

### ¿Vienes 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)` |

## `OpenConnector`: llamar a un runtime autoalojado

¿Ejecutas el servidor Connector de código abierto por tu cuenta — en localhost, en Docker o en tu propia infraestructura? **`OpenConnector`** es el cliente personal para ello. Refleja la superficie de `Connector` (ambas rutas de llamada, `proxy`, `catalog`, `apps`) apuntando al servidor que *tú* ejecutas, por lo que el código que ya escribiste apenas cambia. Poner en marcha el servidor es un tema aparte — consulta la [guía de autoalojamiento de OpenConnector](/es/docs/openconnector-self-hosting/).

El cliente `OpenConnector` apunta a un runtime que tú operas. La configuración de ese runtime determina la organización de cuentas, la selección de conexiones y la política de acceso.

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

const open = new OpenConnector(); // local/private setup only; defaults to http://localhost:3000

await open.execute("hackernews.get_top_stories", {});             // path 1 — dynamic string
await open.gmail.search_threads({ query: "from:boss" });          // path 2 — namespace sugar, same registry types
await open.proxy("github", { endpoint: "/user", method: "GET" }); // path 3 — passthrough to an un-modeled endpoint
await open.catalog.search("send email", { limit: 5 });            // catalog extras: search, services
await open.apps.list();                                           // read-only view of the runtime's connections
```

Ambas rutas de llamada y el flujo de tipos precisos coinciden con el `Connector` personal; se aplican las mismas importaciones de efectos secundarios de `@oomol-lab/connector-types`. OpenConnector acepta una **ruta relativa** que comienza con `/` para `proxy.endpoint`; una URL absoluta devuelve `invalid_input`. Un proveedor al que le falta un ejecutor de proxy devuelve `proxy_not_supported`.

### Apúntalo a tu servidor

`baseUrl` acepta el **origen** del servidor, y el cliente añade el prefijo de ruta de la API. Un runtime sin tokens solo es adecuado para localhost o una red que de otro modo sea privada. Antes de exponerlo a través de una URL pública, crea un token de runtime (`oct_…`) en la Web Console bajo Access y exígelo a todos los clientes `/v1` y `/mcp`.

```ts
const open = new OpenConnector({
  baseUrl: "https://connect.internal.example.com",       // the server origin
  runtimeToken: process.env.OOMOL_CONNECT_RUNTIME_TOKEN!, // oct_…; required for a public runtime
  connectionName: "work",                                // optional client-level default connection
});
```

Todos los campos son opcionales. `timeoutMs`, `maxRetries` y `fetch` se comportan exactamente igual que en el cliente alojado.

### Superficie específica del runtime

`OpenConnector` proporciona estos métodos específicos del runtime:

```ts
await open.health();                                    // { ok, runtime } — connectivity / auth probe
await open.catalog.services();                          // every service id that has actions
await open.catalog.search("top stories", { limit: 3 }); // rank actions by free-text relevance
await open.apps.listByService("github");                // one service's connections
await open.apps.authenticated(["github", "notion"]);    // which have a REAL credential stored
```

La selección de conexiones tiene dos capas: un `connectionName` por llamada anula el valor predeterminado a nivel de cliente, y omitir ambos selecciona la conexión `"default"` del runtime.

> **Usa la Web Console para la gestión.** Crea conexiones, configura clientes OAuth y acuña tokens de runtime en la consola; el SDK `OpenConnector` llama al runtime configurado. Consulta la [guía de autoalojamiento](/es/docs/openconnector-self-hosting/) para la configuración. Cuando un id de servicio colisiona con un nombre de miembro (`execute` / `executeRaw` / `health` / `proxy` / `catalog` / `apps`), llámalo a través de `execute("<service>.<action>", …)`.

Recorrido completo ejecutable — [`examples/open.ts`](https://github.com/oomol-lab/connector-sdk/blob/main/examples/open.ts).

## Referencia

### `Connector` (clave `api_…` personal)

```ts
new Connector(config: ClientConfig)

oomol.execute(actionId, input, options?)     // → action output
oomol.executeRaw(actionId, input, options?)  // → { data, executionId, actionId, message }
oomol.<service>.<action>(input, options?)    // namespace sugar for execute
oomol.using(scope)                           // → immutable scoped sub-client
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` (clave `oo_proj_…` del proyecto)

```ts
new ProjectConnector(config: ProjectConnectorConfig)

project.connect.oauth(externalUserId, input, options?)            // → ConnectionRequest (pending)
project.connect.apiKey(externalUserId, input, options?)           // → ConnectedAccount (synchronous)
project.connect.customCredential(externalUserId, input, options?) // → ConnectedAccount (synchronous)
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?)        // → action output
project.executeRaw(externalUserId, actionId, input, options?)     // → { data, executionId, actionId, message }
project.forUser(externalUserId)                                   // → ProjectUser (same methods, id bound)
```

La entrada de `connect.*` es `{ service | providerConfigId } & { connectionName?, … }` (exactamente uno de `service` / `providerConfigId`). Las opciones de `execute` añaden `{ providerConfigId?, service?, connectedAccountId?, connectionName? }`.

### `OpenConnector` (runtime autoalojado, token `oct_…` opcional)

```ts
new OpenConnector(config?: OpenConnectorConfig)   // every field optional; baseUrl defaults to http://localhost:3000

open.execute(actionId, input, options?)       // → action output
open.executeRaw(actionId, input, options?)    // → { data, executionId, actionId, message }
open.<service>.<action>(input, options?)      // namespace sugar for execute
open.health(options?)                         // → { ok, runtime }
open.proxy(service, { endpoint, method, query?, headers?, body? }, options?) // → { status, headers, data }   (endpoint must be a relative path)
open.catalog.action(actionId, options?)       // → OpenActionMetadata
open.catalog.actions(service, options?)       // → OpenActionMetadata[]
open.catalog.services(options?)               // → string[]   (service ids that have 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 with a real credential)
```

`options` es `{ connectionName?, signal?, timeoutMs?, retries? }` (sin `team`, sin `using()`). `config` añade `{ baseUrl?, runtimeToken?, connectionName?, timeoutMs?, maxRetries?, fetch? }`.

### Exportaciones

```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";
```

En el repositorio hay ejemplos ejecutables y verificados de tipos en el directorio [`examples/`](https://github.com/oomol-lab/connector-sdk/tree/main/examples).

## Licencia

MIT, consulta el repositorio [connector-sdk](https://github.com/oomol-lab/connector-sdk).
