El caso plataforma
Para un producto que conecta a muchos clientes finales, el modelo de datos, el alta embebida y los topes que importan a escala.
Si tu producto (un ERP, un software de gestión, una plataforma vertical) necesita leer el SII o los bancos de muchos clientes finales, este es tu mapa. El mecanismo es el mismo de Conecta tu primera empresa; lo que cambia a escala es el modelo de datos, cómo identificas a cada cliente y contra qué topes vas a operar.
El modelo: dos niveles, no tres
organización (org_…) tu cuenta. Aquí viven tus API keys y tu facturación.
└── conexión (conn_…) un cliente tuyo en un sistema. La unidad de todo lo demás.No existe una entidad «proyecto». Si vienes de un modelo cuenta → proyecto → enlace, el equivalente del
proyecto es la conexión, y el enlace de un solo uso es solo el permiso para entregar la credencial:
una vez consumido, lo que queda es la conexión. Una organización puede tener tantas conexiones del
mismo sistema como clientes tengas; toda llamada a sus tools debe nombrar cuál con
X-Connect-Connection, incluso con una sola conexión (omitirlo responde validation_error). El detalle del ciclo de vida está en Conexiones.
Identificar a tu cliente
Las conexiones no tienen un campo external_id ni metadata. El mapeo cliente ↔ conn_… lo guardas
tú, y el momento de capturarlo es el alta: el connection_id llega en el postMessage del iframe
cuando la persona completa el enlace, y en el webhook connect_session.consumed si tienes
webhooks configurados. El webhook trae también el session.id que recibiste al
crear el enlace, así que puedes guardar el par sesión ↔ cliente desde ese momento.
Para reconocer cada conexión en el panel, en la bitácora y en los cobros, ponle nombre al crear el
enlace con display_name en POST /v1/connect_sessions,
por ejemplo "Comercial Andes SpA (SII)". Sin él, la conexión se llama como su sistema (SII, SII 2).
Las que ya existen se renombran con
conexiones.conexion.renombrar. El nombre no se muestra
en la página del enlace. Para distinguir empresas por un dato que no depende del nombre,
conexiones.estado.consultar y GET /v1/connections traen el RUT de cada conexión cuando ya se conoce.
Por defecto una API key alcanza todas las conexiones de tu organización: con sii:read puede leer cualquier
conexión del SII. Para levantar muros entre clientes finales, acota cada clave a las conexiones de su cliente
(conexiones de una clave): lo que queda fuera de su lista
responde como si no existiera. Una clave acotada no crea conexiones, así que el enlace de alta lo pide una clave con
acceso a todas, y la conexión nueva se agrega a la lista editando la clave.
Con Gmail esto pesa más: una clave con gmail:readonly lee todos los buzones que alcanza. Si conectas los buzones de
varios clientes finales, acota cada clave a los de su cliente. El enlace de Gmail no recoge una clave: abre el
consentimiento de Google fuera del iframe (en una ventana nueva, o en la misma pestaña si no hay iframe), y el
resultado te llega por el mismo postMessage. Si tu iframe usa
sandbox, incluye allow-popups; si no, la página ofrece abrir el enlace en una pestaña.
El alta, dentro de tu producto
POST /v1/connect_sessions con allowed_origins devuelve una URL de un solo uso que se monta en un
iframe de tu app: tu cliente escribe su clave en la pantalla de Emisso sin salir de tu producto, la
credencial viaja directo al vault y el resultado te vuelve por postMessage, por redirect_uri y
por webhook. El contrato completo (orígenes, TTL, intentos, el modelo de amenaza) está en
el enlace hosted; el endpoint, en la
API de control. Dos datos que importan a escala:
- La emisión de enlaces tiene tope por organización (60 por hora,
429conRetry-After): un alta masiva de clientes se planifica por tandas. - La primera sincronización queda encolada sola al conectar, tanto por el enlace como desde el dashboard, salvo que falle la comprobación de la credencial.
Cuando la credencial de un cliente se rompe
Te enteras por tres señales, en este orden si tienes webhooks: llega sync.failed, las llamadas de
esa conexión empiezan a responder connection_credential_required (428, con la sugerencia del SII o
del banco en el mensaje), y, si persisten los rechazos de autenticación, la cadencia automática
se pausa y deja de encolar. La conexión sigue active y sus tools de consulta siguen
leyendo lo ya persistido; lo que se detiene es el reloj.
La reconexión es el mismo endpoint de enlaces con "mode": "reauth" y el connection_id: rota la
credencial sobre la misma conexión, sin duplicarla. Guardar la clave no demuestra que la conexión se haya recuperado: comprueba
la verificación y el trabajo que se encola. Una sincronización exitosa puede limpiar la pausa por
credencial y restaurar la cadencia anterior. Revisa cadenciaEstado antes de darla por reanudada;
no hace falta cambiar la frecuencia a mano.
Operar la flota
Los topes que vas a tocar con decenas o cientos de conexiones, todos del código:
| Tope | Valor |
|---|---|
| Cupo de trabajos pendientes por organización | Si se supera, 429 too_many_pending |
| Períodos por petición de backfill | 24 |
| Trabajos simultáneos por conexión | 1, y uno solo activo por período |
| Enlaces de conexión por hora, por organización | 60 |
La duración depende de la carga, del conector y del portal externo; no hay un rendimiento fijo ni un
plazo garantizado. Reparte el backfill en tandas y conserva los job_ids que devuelve
POST /v1/connections/{conn}/syncs. Sigue cada uno con GET /v1/syncs/{id} o los webhooks sync.*.
conexiones.estado.consultar muestra solo los cinco últimos trabajos por conexión. No existe
GET /v1/connections/{id}/estado. El ejemplo de dos meses
muestra el pedido y seguimiento completos. Si es urgente, escríbenos desde el chat y lo revisamos;
no hay un parámetro de prioridad para clientes.
Sobre la cuota: encolar no consume cuota; lo que cuenta como llamada es cada sincronización ejecutada y cada página de consulta (el detalle de categorías está en Límites y planes). El cupo es por conexión, así que la aritmética de una flota grande no se multiplica por el número de conexiones: cada una mide su propio período contra el mismo techo, y agregar clientes no acerca a los demás al corte. Lo que hay que dimensionar por conexión es sincronizaciones diarias × páginas leídas. Subir de plan no mueve ese techo; si tu cadencia lo roza, escríbenos desde connect.emisso.ai/billing y lo ampliamos para tu organización.
Lo que todavía se hace solo desde el dashboard
Hoy no hay endpoint para crear organizaciones ni para acuñar o rotar API keys. Si tu integración necesita alguna de esas operaciones por API, escríbenos: saber qué falta primero es lo que ordena el roadmap.
Renombrar, deshabilitar, habilitar y eliminar una conexión sí se pueden hacer por API, con
conexiones.conexion.renombrar,
conexiones.conexion.deshabilitar,
conexiones.conexion.habilitar y
conexiones.conexion.eliminar. Cada una exige su propio
permiso (conexiones:rename, conexiones:disable, conexiones:enable y conexiones:delete), y eliminar no se puede
deshacer: borra la credencial, los datos sincronizados, el historial y los webhooks de esa conexión.
La cadencia de sincronización no está en esa lista porque no es un pendiente: no se configura en
ninguna parte, ni por API ni desde el dashboard. La decide Emisso por conector, en los sistemas que sincronizan. Para forzar una sincronización puntual, el camino es
sistema.conexion.sincronizar (o POST /v1/connections/{conn}/syncs), que siguen disponibles. Si tu
producto necesita una frecuencia de actualización mayor que la programada, escríbenos a
contacto@tryemisso.com y te preparamos una propuesta.
Próximos pasos
- El enlace hosted: el contrato completo del alta embebida.
- API de control: sesiones, sincronizaciones y webhooks, endpoint por endpoint.
- Webhooks: enterarte de cada alta y cada sync sin polling.
Pedir boletas de honorarios a tus profesionales
Tu sistema pide la boleta por API y cada profesional la revisa y la autoriza desde un enlace, con su clave del SII. El ejemplo de una clínica, con demo animada.
Enlace hosted de conexión
Un enlace de un solo uso para que un tercero entregue la clave del SII o del banco: sin cuenta, sin acceso al dashboard y sin que la credencial pase por tu aplicación.