Menú

SDKs

Las librerías oficiales de TypeScript y Python: un método tipado por operación, con reintentos, idempotencia, paginación y vista previa incluidos. Para otros lenguajes, el contrato OpenAPI.

LenguajePaqueteEstado
TypeScript / JavaScript@mb-suite/sdkVersión preliminar sobre el contrato 2026-11-01. Aún no se publica en npm.
Pythonmb-suiteVersión preliminar sobre el contrato 2026-11-01. Aún no se publica en PyPI. Ver SDK de Python.
El paquete todavía no está en npm: esta guía cuenta cómo funciona. El contrato OpenAPI ya se puede descargar (al final de esta página).

Instalación

npm install @mb-suite/sdk
import { MBSuite } from "@mb-suite/sdk";

// Sin apiKey, lee la variable de entorno MB_API_KEY
const mb = new MBSuite({ apiKey: process.env.MB_API_KEY });
  • Node.js 20 o posterior, o cualquier entorno con fetch global: Deno, Bun, Cloudflare Workers, funciones de Vercel. Sin dependencias.
  • Solo en tu servidor: una API key es un secreto y la API no acepta pedidos desde un navegador, así que el cliente no arranca en uno.
  • Una API key (mbs_live_…) o un token OAuth (mbo_…): ver Autenticación.

Tu primer pedido

const me = await mb.platform.me(); // GET /v1/me

console.log(me.actor.uid, me.org.id);
console.log(me.lastResponse.requestId); // req_…: inclúyelo si nos escribes

Cada operación tiene un id estable y el método lo sigue: crm.deals.create es mb.crm.deals.create(). Cada tramo va en camelCase: crm.custom_fields.list es mb.crm.customFields.list() y task_management.tasks.complete es mb.taskManagement.tasks.complete(). La Referencia muestra el id de cada una.

  1. 1Primero, los parámetros de ruta, como texto y en el orden de la ruta: mb.crm.deals.get("deal_123").
  2. 2Después, los parámetros: un objeto en snake_case, igual que la API (la query de un GET, el cuerpo del resto).
  3. 3Al final, las opciones del pedido: organization, idempotencyKey, confirmToken, timeout, maxRetries, signal y headers.

Listar, crear y editar

const { data, has_more } = await mb.crm.deals.list({ status: "open", limit: 50 });

const { deal_id } = await mb.crm.deals.create({
  title: "Plan anual — Acme",
  value: 4800,
  currency: "USD",
  pipeline_id: "PIPELINE_ID",
  stage_id: "STAGE_ID",
});

await mb.crm.deals.move(deal_id, { stage_id: "NEXT_STAGE_ID" });
await mb.crm.deals.update(deal_id, { updates: { value: 5200 } });
const deal = await mb.crm.deals.get(deal_id);

Los parámetros y los resultados están tipados desde el contrato, y cada método trae la descripción de su operación: tu editor la muestra al pasar el cursor.

Paginación

Cada lista paginada tiene un gemelo …All() que recorre todas las páginas y entrega los elementos uno por uno. Una página puede venir vacía con has_more en true: el recorrido sigue igual. Sal del bucle cuando tengas lo que necesitas. Ver Paginación.

// Sigue next_cursor hasta que has_more sea false; limit es el tamaño de cada página
for await (const deal of mb.crm.deals.listAll({ status: "open", limit: 200 })) {
  console.log(deal.id, deal.title);
}

Vista previa y confirmación

Las operaciones que piden confirmación —las masivas, las irreversibles, las que mandan o cambian algo fuera de MB Suite o tienen un costo aparte, y las que la piden siempre— tienen .dryRun(): no escribe nada y devuelve la vista previa y confirm(), que ejecuta la misma operación con los mismos parámetros y el confirm_token de la vista previa (vale 10 minutos). Tu idempotencyKey, pásala a .dryRun() o a confirm(): viaja con la confirmación, nunca con la vista previa, que no escribe nada. Ver Vista previa y confirmación.

const { preview, confirm } = await mb.crm.deals.bulk.delete.dryRun({
  deal_ids: ["DEAL_A", "DEAL_B"],
});

console.log(preview.summary, preview.counts, preview.warnings); // todavía no se borró nada

const { deleted } = await confirm(); // dentro de los 10 minutos
  • Llamarla sin token lanza ConfirmationRequiredError.
  • Si el confirm_token llega por otro camino (una aprobación, un agente), pásalo como opción: mb.crm.deals.bulk.delete(params, { confirmToken }).
  • Con una API key, el DELETE simple de un registro que va a la papelera se ejecuta directo: await mb.crm.deals.delete("deal_123"). Con un token OAuth también pasa por .dryRun().

Reintentos e idempotencia

  • Cada mutación sale con una Idempotency-Key (un UUID que genera el SDK, o la tuya) y la conserva en los reintentos: si una respuesta se pierde, el reintento devuelve la guardada en vez de ejecutar dos veces.
  • Se reintentan solos, hasta 3 veces con espera exponencial (0,5 s, 1 s, 2 s): los errores de red, los timeouts, un 429 (rate_limited, external_rate_limited, un cooldown_active corto), un 409 idempotency_in_progress y los 5xx (los 503 external_unavailable y pdf_unavailable incluidos). Si la respuesta trae Retry-After, espera exactamente eso.
  • No se reintentan: el resto de los 4xx (un 409 external_rejected, por ejemplo: la plataforma ya dijo que no; o un 409 external_outcome_unknown: un envío que puede haber salido, mira antes de repetirlo), un 429 quota_exceeded (el cupo diario vuelve a las 00:00 UTC), un 502 ai_generation_failed (repetirlo vuelve a llamar a la IA y gasta créditos si responde: lo decides tú) ni un Retry-After de más de 60 segundos (un daily_limit_reached). El error vuelve con retryAfter para que lo reprogrames.

Para que una operación lógica sea segura entre tus propios reintentos, dale tu propia clave (vale 24 horas). La misma clave con otro cuerpo lanza IdempotencyError. Ver Idempotencia.

// La misma clave en un reintento tuyo (otro proceso, otro deploy) devuelve la respuesta guardada
await mb.crm.deals.create(newDeal, { idempotencyKey: `import-${row.id}` });

Errores

Todo error hereda de MBSuiteError y trae el formato único de la API: type, code (estable: decide con él), message, status, param, requestId, docUrl y details. La clase depende del type:

ClasetypeHTTP
InvalidRequestErrorinvalid_request400
AuthenticationErrorauthentication401
PaymentRequiredErrorpayment_required402
PermissionErrorpermission403
NotFoundErrornot_found404
ConflictErrorconflict409
ConfirmationRequiredErrorconfirmation_required409
IdempotencyErroridempotency422
RateLimitErrorrate_limit429
QuotaExceededErrorquota429
UpstreamErrorupstream502
ServiceUnavailableErrorunavailable503
MaintenanceErrormaintenance503
InternalServerErrorinternal5xx
ConnectionError, TimeoutErrorconnection—
import { MBSuiteError, NotFoundError, RateLimitError } from "@mb-suite/sdk";

try {
  await mb.crm.deals.get(dealId);
} catch (err) {
  if (err instanceof NotFoundError) return null;
  if (err instanceof RateLimitError) console.log(`Reintenta en ${err.retryAfter} s`);
  if (err instanceof MBSuiteError) console.error(err.code, err.requestId, err.docUrl);
  throw err;
}

InvalidRequestError suma issues (qué campo y por qué), ConfirmationRequiredError suma reason (expired, mismatch), RateLimitError y ServiceUnavailableError suman retryAfter, QuotaExceededError es un RateLimitError con quota y MaintenanceError es un ServiceUnavailableError. Todos los códigos están en Errores.

Cuentas

Una API key de workspace trabaja en las Cuentas de su alcance con MB-Organization. Elige la Cuenta para todo el cliente, para un pedido o con un cliente atado a ella. mb.platform.orgs.list() devuelve las que alcanza la API key. Ver Workspaces y Cuentas.

// Para todos los pedidos del cliente
const mb = new MBSuite({ apiKey: process.env.MB_API_KEY, organization: "ACCOUNT_ID" });

// Para un pedido (null = la organización de la API key)
await mb.crm.deals.list({}, { organization: "OTHER_ACCOUNT_ID" });

// Un cliente para esa Cuenta; el original no cambia
const account = mb.withOptions({ organization: "ACCOUNT_ID" });

Lo que trae cada respuesta

Cada resultado lleva lastResponse, que no aparece en JSON.stringify ni al copiar el objeto: el request_id, la versión, la ráfaga y el cupo del día (para frenar antes de un 429, ver Límites) y si fue la respuesta guardada de un pedido anterior.

const page = await mb.crm.deals.list();

page.lastResponse.requestId;          // "req_…"
page.lastResponse.rateLimit;          // { limit, remaining, reset }: la ráfaga de la API key (10 s)
page.lastResponse.quota;              // { limit, remaining, reset }: las llamadas de hoy
page.lastResponse.idempotentReplayed; // true si es la respuesta guardada de un pedido anterior

Versiones

El SDK manda MB-Version: 2026-11-01 en cada pedido: la versión que describen sus tipos, sin importar en cuál quedó fijada tu organización. Cuando sale una versión nueva de la API sale una del SDK; la anterior sigue viva 12 meses, y si una respuesta llega de una versión en retiro, el SDK lo avisa una vez con su fecha de Sunset. Ver Versiones y el Registro de cambios.

Tipos y pedidos a mano

import type { Item, Params } from "@mb-suite/sdk";

type NewDeal = Params<"crm.deals.create">; // el cuerpo de POST /v1/crm/deals
type Deal = Item<"crm.deals.list">;        // un elemento de data[]

// Cualquier endpoint por método y ruta, con los mismos reintentos y errores
await mb.request({ method: "POST", path: "/crm/deals/DEAL_ID/archive", body: { archived: true } });

Por defecto el cliente habla con https://api.mb-suite.com/v1; baseUrl lo apunta a otra dirección (la misma API responde en https://mb-suite.com/api/platform/v1). Los tipos crudos del contrato se exportan como paths, operations y components.

Otros lenguajes: el contrato OpenAPI

El contrato completo, en OpenAPI 3.1, está en GET /v1/openapi.json (con tu API key o tu token, como cualquier pedido). Es de donde salen los SDKs de TypeScript y Python, y sirve para generar un cliente en otro lenguaje.

curl https://api.mb-suite.com/v1/openapi.json \
  -H "Authorization: Bearer $MB_API_KEY" \
  -o mb-suite-openapi.json

Cada operación del contrato trae extensiones x-mb-*: la clase (x-mb-kind), si es idempotente, el lote, los eventos que emite, la tool del MCP y el permiso. Dentro de una versión, el contrato solo suma: ver Versiones.