> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tupana.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tupana Link

> El widget con el que tus usuarios conectan su empresa — API v2

# Tupana Link

El widget que abres en tu app para que **tu usuario final** conecte su
empresa. La clave del SII (o del facturador, o del banco) viaja a Tupana.
Tu backend nunca la ve.

En v2 la clave no se manda por API: conectas con este widget y guardas el
`connection_id` (`cred_…`), que es lo único que tu SaaS conoce de la empresa.

<Frame caption="Esto ve tu usuario. Tú solo recibes el cred_….">
  <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/02-sii-gratuito.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=78db33de13529c881e7deb04b568e05f" alt="Tupana Link — SII gratuito" width="952" height="1170" data-path="images/connect/02-sii-gratuito.png" />
</Frame>

## Integra en tres llamadas

<Steps>
  <Step title="Tu backend abre una sesión">
    `POST /api/v2/connect-sessions/` con tu API key. Te devuelve un
    `client_token` de 30 minutos. Si ya conoces el RUT, mándalo como `tax_id`
    y el widget no lo vuelve a pedir.

    ```bash theme={null}
    curl -X POST "https://app.tupana.ai/api/v2/connect-sessions/" \
      -H "Authorization: Api-Key TU_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "capabilities": ["invoicing"],
        "app_name": "Mi SaaS",
        "tax_id": "78.152.641-K",
        "external_customer_id": "cus_912"
      }'
    ```

    ```json theme={null}
    {
      "connect_session_id": "cses_7391028471023",
      "client_token": "cst_9f2b…",
      "expires_at": "2026-09-25T18:30:00Z"
    }
    ```
  </Step>

  <Step title="Tu frontend abre el widget">
    Iframe o ventana. El `token` es el `client_token`. `tax_id` en la URL
    gana sobre el de la sesión.

    ```html theme={null}
    <iframe src="https://developers.tupana.ai/connect?token=cst_9f2b…&tax_id=78.152.641-K"
            width="460" height="660" frameborder="0"></iframe>
    ```

    ```js theme={null}
    window.addEventListener('message', (event) => {
      if (event.origin !== 'https://developers.tupana.ai') return;
      if (event.data.source !== 'tupana-connect') return;

      if (event.data.event === 'success') {
        // event.data.connection_id → "cred_…" (es la credencial)
        closeModal();
      }
      if (event.data.event === 'exit') closeModal();
    });
    ```

    | `event` | Para qué |
    | - | - |
    | `open` | El widget cargó |
    | `success` | El flujo terminó. Cierra el modal. **No** es la fuente de verdad |
    | `exit` | El usuario cerró antes de terminar |

    <Warning>
      Igual que el widget de Fintoc: `success` y `exit` solo mueven tu
      interfaz. La conexión existe cuando tu backend recibe
      `connection.active` o
      `GET /api/v2/connections/enrollments/{enrollment_id}/` la muestra lista.
    </Warning>
  </Step>

  <Step title="Tu backend guarda la conexión">
    Cuando el SII valida la clave, el webhook trae el `cred_…`. Con eso emites.
    También puedes listarlas con
    [`GET /connections/`](/api-reference/v2/connections/list).

    ```json theme={null}
    {
      "event": "connection.active",
      "data": {
        "connection_id": "cred_5028174639201",
        "company_id": "ment_4829301923847",
        "external_customer_id": "cus_912"
      }
    }
    ```
  </Step>
</Steps>

### Body de la sesión

| Campo | Requerido | Qué hace |
| - | - | - |
| `capabilities` | | `invoicing`, `sii_data`, `banking`. Default `["invoicing"]` |
| `tax_id` | | RUT de la empresa. El widget salta ese paso |
| `app_name` | | El nombre que ve el usuario adentro |
| `external_customer_id` | | Tu id de cliente. Vuelve en el webhook |
| `environment` | | `sandbox` o `production`. Default: el de la API key |
| `return_url` | | A dónde vuelve al terminar |

### Query del widget

| Parámetro | Requerido | Qué hace |
| - | - | - |
| `token` | Sí | El `client_token` |
| `tax_id` | | RUT. Si viene acá y en la sesión, gana la URL |
| `rut` | | Alias de `tax_id` |

<Warning>
  El `client_token` es de un solo uso y dura 30 minutos. Créalo cuando el
  usuario va a conectar, no antes. Una API key de sandbox no abre producción.
</Warning>

## Lo que ve tu usuario

<Tabs>
  <Tab title="SII gratuito">
    <Columns cols={2}>
      <Frame caption="1. RUT de la empresa — no aparece si mandaste tax_id.">
        <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/01-empresa.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=14faed4c37a6d5cdc869ce24e0f75f6f" alt="Widget: RUT de la empresa" width="952" height="806" data-path="images/connect/01-empresa.png" />
      </Frame>

      <Frame caption="2. Detectamos SII. El RUT del representante se formatea solo.">
        <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/02-sii-gratuito.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=78db33de13529c881e7deb04b568e05f" alt="Widget: SII gratuito" width="952" height="1170" data-path="images/connect/02-sii-gratuito.png" />
      </Frame>
    </Columns>
  </Tab>

  <Tab title="Facturador de mercado">
    <Columns cols={2}>
      <Frame caption="1. Detectamos Haulmer, Bsale, Nubox…">
        <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/03-mercado-detectado.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=c8d4a32fa0c2685ade66f011255d50f1" alt="Widget: facturador detectado" width="952" height="868" data-path="images/connect/03-mercado-detectado.png" />
      </Frame>

      <Frame caption="2. Usuario y clave del facturador — no la del SII.">
        <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/04-mercado-usuario.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=b55a5c913fd44c9a2e0d419b56b7dbfb" alt="Widget: usuario de Haulmer" width="952" height="958" data-path="images/connect/04-mercado-usuario.png" />
      </Frame>
    </Columns>

    <Frame caption="3. Respaldo XML/PDF y migración a Tupana: 1 día hábil, aviso por correo.">
      <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/05-mercado-migrar.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=65991ee468be3ce9b7e0e00a3062e7be" alt="Widget: respaldar y migrar" width="952" height="1202" data-path="images/connect/05-mercado-migrar.png" />
    </Frame>
  </Tab>

  <Tab title="Banco">
    <Columns cols={2}>
      <Frame caption="1. Elige el banco.">
        <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/06-banco.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=687e9cf72a86d3e2c68c0489128dcbe6" alt="Widget: selector de banco" width="952" height="1140" data-path="images/connect/06-banco.png" />
      </Frame>

      <Frame caption="2. RUT de la persona y clave.">
        <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/07-banco-clave.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=1f637a596d120b07edd2ae267de46212" alt="Widget: clave del banco" width="952" height="918" data-path="images/connect/07-banco-clave.png" />
      </Frame>
    </Columns>

    <Frame caption="3. El banco aceptó la clave: cuentas y saldos. Ahí sale success.">
      <img src="https://mintcdn.com/tupana/qIPLgEE43o4mTOY0/images/connect/08-cuentas.png?fit=max&auto=format&n=qIPLgEE43o4mTOY0&q=85&s=0ce19592b74adca05f174e08aebe80ba" alt="Widget: cuentas conectadas" width="952" height="1096" data-path="images/connect/08-cuentas.png" />
    </Frame>
  </Tab>
</Tabs>

## Cuándo el enrolamiento es exitoso

El widget de Fintoc no avisa éxito al recibir la clave. Se queda en
pantalla mientras el banco responde. Si la clave está mala, el error queda
adentro y el usuario reintenta: no hay `onSuccess`. `onSuccess` sale cuando
el flujo terminó (el banco aceptó y existe el Link). `onExit` sale solo si
la persona cierra antes. Fintoc pide no usar esos callbacks para saber si el
Link existe: eso lo confirma el webhook `link.created` en el `webhookUrl`,
que trae el `link_token`.

Tupana Link funciona igual. El `postMessage` mueve tu modal. El webhook
dice si la credencial quedó.

| Qué ves | Qué significa |
| - | - |
| `success` | El flujo terminó. Cierra el modal y guarda el `connection_id` |
| `exit` | Cerró antes de terminar. No hay conexión nueva |
| `connection.active` | La institución aceptó la clave. Esta es la confirmación |
| `connection.requires_action` | La clave no sirvió. El widget deja reintentar |

### SII

El login no termina en el request. La respuesta trae `status: "connecting"`
y un `enrollment_id` (`cenr_…`). El widget espera en
`ws/connect/{client_token}/` y manda `success` solo con `valid`.

```json theme={null}
{
  "enrollment_id": "cenr_8420193847261",
  "company_id": "ment_4829301923847",
  "status": "connecting"
}
```

Puedes esperar el webhook o consultar
`GET /api/v2/connections/enrollments/{enrollment_id}/` hasta `valid`,
`invalid` o `error`.

### Banco

El widget no manda `success` cuando guarda la clave. Se queda en
“estamos leyendo tus cuentas” hasta que el banco contesta.

* El banco aceptó y hay cuentas: el widget las muestra y ahí manda
  `success` con el `cred_…`. Tu backend recibe `connection.active`.
* La clave es inválida: el error queda en el widget, el usuario puede
  corregirla, y no hay `success`. Tu backend recibe
  `connection.requires_action`.
* El usuario cierra antes: `exit`. Eso no confirma la conexión. Si el banco igual acepta la clave, llega `connection.active`.

Guardar la clave con estado interno `VALID` no es la confirmación. La
confirmación es `connection.active`, cuando el banco ya listó las cuentas.

Los movimientos no vienen en ese aviso. Cuando la cartola está sincronizada
se leen como documentos tipo 100:
`GET /api/v2/documents/?master_entity_id={empresa}&dte_type__code__in=100`.

Responde 2xx al webhook y procesa después. Puede llegarte dos veces.

Para ver o cortar conexiones usa
[`GET /connections/`](/api-reference/v2/connections/list) y
[`DELETE /connections/{connection_id}/`](/api-reference/v2/connections/delete),
que la deja `inactive` y suelta la clave.

## Qué viaja por tu app

| | Tu aplicación | Tupana |
| - | - | - |
| Clave del SII / facturador / banco | Nunca | Cifrada |
| Credencial (`cred_…`) | Sí | Sí |
| Documentos, movimientos, registros | Sí | Sí |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.