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

# Links

> Un link es la conexión persistente entre un usuario tuyo y un banco.

## Qué es un link

Un **link** representa la conexión que un usuario tuyo tiene con uno de sus bancos. Es persistente: una vez creado, vive en tu DB asociado a tu user. Cada link tiene un `link_id` con prefijo `link_` + 16 chars hex.

```json theme={null}
{
  "object": "link",
  "id": "link_8a2f4c6e1b9d5037",
  "bank_id": "banco_estado",
  "holder_type": "individual",
  "holder_id": "12.345.678-9",
  "holder_name": "JANE DOE EXAMPLE",
  "status": "active",
  "created_at": "2026-05-15T01:50:22Z",
  "last_synced_at": "2026-06-03T12:00:00Z",
  "next_sync_at": "2026-06-03T16:00:00Z",
  "realtime": false
}
```

### Tipo de titular

`holder_type` distingue banca personal de empresa:

| `holder_type`          | `holder_id`           | Cuándo         |
| ---------------------- | --------------------- | -------------- |
| `individual` (default) | RUT de la persona     | Banca personal |
| `business`             | RUT de la **empresa** | Banca empresa  |

Lo defines al emitir el `widget_token` (`holder_type: "business"`). Ver
[Banca empresa](/guides/banca-empresa) para el flujo completo.

## Cómo se crea

**Solo vía widget.** No hay endpoint público que reciba credenciales en plano. El flow:

1. Tu backend pide `wt_*` con `POST /v1/widget_tokens`.
2. El widget se abre en el frontend con ese token.
3. User mete RUT + clave + MFA dentro del widget.
4. Widget cierra y devuelve `et_*` al frontend.
5. Tu backend canjea `et_*` por `link_id` con `POST /v1/exchange_tokens/{id}`.

## Estados

| `status`              | Significa                                                                                                                                                                                                                                   |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending`             | Recién creado, esperando primer sync.                                                                                                                                                                                                       |
| `mfa_required`        | El sync se quedó esperando un código MFA del user. Resuelve via widget.                                                                                                                                                                     |
| `active`              | Funcionando. Syncs corren automáticos cada 4h.                                                                                                                                                                                              |
| `failed`              | Último sync falló transient. Reintentamos en el próximo cron tick (backoff 1h → 6h → 12h → 24h).                                                                                                                                            |
| `credentials_invalid` | El user cambió la clave en el banco. El cron NO retoca este link. Inicia reconnect con `POST /v1/widget_tokens { intent: 'reconnect', link_id }` para que el user reingrese la clave. El link conserva accounts, transactions y holder\_id. |
| `expired`             | 3 fallos consecutivos sin haber validado nunca el login. Webhook `link.expired` disparado. User debe re-conectar (link nuevo).                                                                                                              |
| `revoked`             | Borrado con `DELETE /v1/links/{id}`. Read-only desde acá; no se reintenta.                                                                                                                                                                  |

## Modo realtime (en vivo)

Algunos links (banca empresa, planes premium) se sincronizan en **tiempo real**: en vez del cron cada 4h, un worker dedicado mantiene la sesión del banco viva y hace *poll* continuo. El campo `realtime` te dice si un link está en este modo:

| `realtime`        | Significa                                                                                        |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| `false` (default) | Sync **programado** — cron cada 4h (o el intervalo de `sync_interval_minutes`).                  |
| `true`            | Sync en **tiempo real** — sesión viva + poll continuo; los datos se actualizan casi al instante. |

<Warning>
  **En tu UI, no muestres "cargando" para un link `realtime: true`.** Un sync en curso es su estado **normal** (siempre se está actualizando), no una carga inicial. Muestra algo como **"En vivo"** + "actualizado hace X" usando `last_sync_at`. Interpretar un link realtime como "cargando" confunde al usuario final.
</Warning>

## Listar y leer

```bash theme={null}
# Todos los links del cliente (default: hasta 50, sort por created_at desc)
curl https://api.rail.cl/v1/links \
  -H "Authorization: Bearer rail_sk_live_…"

# Un link específico
curl https://api.rail.cl/v1/links/link_8a2f4c6e1b9d5037 \
  -H "Authorization: Bearer rail_sk_live_…"
```

### Query params

| Param              | Tipo     | Default           | Notas                                                                                       |
| ------------------ | -------- | ----------------- | ------------------------------------------------------------------------------------------- |
| `external_user_id` | string   | —                 | Filtra por el ID que tú asignaste al user en `POST /v1/links`.                              |
| `bank_id`          | enum     | —                 | Uno de los `bank_id` soportados.                                                            |
| `status`           | enum     | —                 | `pending`, `mfa_required`, `active`, `expired`, `failed`, `revoked`, `credentials_invalid`. |
| `last_sync_at_gte` | ISO 8601 | —                 | Solo links con `last_sync_at >= ` este timestamp.                                           |
| `last_sync_at_lte` | ISO 8601 | —                 | Solo links con `last_sync_at <= ` este timestamp.                                           |
| `sort`             | enum     | `created_at:desc` | `created_at:asc`, `created_at:desc`, `last_sync_at:asc`, `last_sync_at:desc`.               |
| `limit`            | integer  | `50`              | Max `100`.                                                                                  |

### Casos típicos

**Encontrar links atrasados** — los que llevan más tiempo sin sincronizar primero (incluye los que nunca sincronizaron):

```bash theme={null}
curl "https://api.rail.cl/v1/links?sort=last_sync_at:asc&limit=20" \
  -H "Authorization: Bearer rail_sk_live_…"
```

`last_sync_at:asc` usa `NULLS FIRST` automáticamente: los links que nunca sincronizaron salen antes que los más viejos.

**Detectar cambios desde tu último poll** — recently synced after a timestamp:

```bash theme={null}
curl "https://api.rail.cl/v1/links?last_sync_at_gte=2026-06-07T10:00:00Z&sort=last_sync_at:desc" \
  -H "Authorization: Bearer rail_sk_live_…"
```

**Audit de links rotos** — los que pasaron a `credentials_invalid` (el user cambió la clave en el banco y hay que iniciar reconnect):

```bash theme={null}
curl "https://api.rail.cl/v1/links?status=credentials_invalid" \
  -H "Authorization: Bearer rail_sk_live_…"
```

Para cada uno de esos, puedes disparar el flow de reconnect: `POST /v1/widget_tokens { intent: 'reconnect', link_id }`.

### Sort por `last_sync_at` y NULLs

* `sort=last_sync_at:asc` → `NULLS FIRST`. Útil para encontrar los más atrasados (nunca sincronizados + los más viejos primero).
* `sort=last_sync_at:desc` → `NULLS LAST`. Útil para encontrar los recién actualizados sin que los nunca-sincronizados inflen el head.

## Pausar / despausar

```bash theme={null}
curl https://api.rail.cl/v1/links/link_xxx/pause \
  -X POST \
  -H "Authorization: Bearer rail_sk_live_…"

curl https://api.rail.cl/v1/links/link_xxx/resume \
  -X POST \
  -H "Authorization: Bearer rail_sk_live_…"
```

Útil si el user pausó la cuenta en tu app — no quieres seguir haciendo syncs.

## Borrar

```bash theme={null}
curl https://api.rail.cl/v1/links/link_xxx \
  -X DELETE \
  -H "Authorization: Bearer rail_sk_live_…"
```

Borra el link, todas sus cuentas y movimientos asociados, y revoca sus credenciales del vault. Irreversible.
