Documentación de la API
Fintable se conecta a tus bancos y sincroniza tus cuentas, saldos, transacciones y posiciones de inversión con hojas de cálculo como Google Sheets o Airtable. La API V2 de Fintable abre esos mismos datos (¡y más!) a tu propio código: todo lo almacenado en Fintable —conexiones bancarias, cuentas, transacciones, posiciones de inversión, el categorizador y tus integraciones de hojas de cálculo— está disponible a través de una interfaz REST limpia.
Pero la API de Fintable no es solo para tus datos financieros privados. Es también una API pública de información financiera pública, como los tipos de cambio de divisas y las cotizaciones de acciones. La API de Fintable está pensada para ser una ventanilla única con la que construir desde cero tu propia aplicación de finanzas (para ti, no para revenderla).
API de datos privados
Sirve para recuperar tus datos financieros privados, como los saldos y las transacciones de tus cuentas bancarias.
API de datos públicos
Incluye datos financieros públicos muy útiles para construir aplicaciones y paneles de finanzas completos, como tipos de cambio de divisas y cotizaciones de acciones en vivo.
API de panel / gestión
Sirve para gestionar el propio Fintable, crear nuevas conexiones bancarias y consultar su estado de sincronización, de modo que ni siquiera tengas que entrar en el panel de Fintable.
Sin acceso de terceros para plataformas o aplicaciones: solo tus datos
La API de Fintable es estrictamente de primera parte, para cuentas bancarias que poseas o que estés autorizado a controlar directamente (como las cuentas de tus clientes si eres contable). No es una plataforma de agregación de datos como Plaid: no puede ni debe usarse para crear aplicaciones de finanzas destinadas a la reventa, solo para ti.
Primeros pasos
¿Nunca has usado Fintable? Aquí tienes el recorrido completo, desde cero hasta tu primera llamada a la API sobre tus propios datos bancarios.
| URL base | https://fintable.io/api/v2 |
| Descripción OpenAPI 3.1 | https://fintable.io/api/v2/openapi.json |
| Servidor MCP para asistentes de IA | https://fintable.io/mcp |
| Skill de IA o documentación para LLM (llms.txt) | https://fintable.io/llms.txt |
| Gestionar tokens | Panel → API |
1. Crea una cuenta de Fintable
Regístrate aquí: empezar es gratis, y el plan gratuito incluye acceso a la API, así que puedes desarrollar contra ella antes de pagar nada.
2. Crea un token de acceso personal
Abre Panel → API y crea un token de acceso personal, eligiendo acceso de solo lectura o de lectura y escritura. El token se muestra una sola vez, así que cópialo en un lugar seguro y trátalo como una contraseña. Es lo que tus scripts enviarán para autenticarse en tu nombre.
3. Conecta una cuenta bancaria
Genera el enlace, visita la URL que te devuelve y sigue los pasos en un navegador para completar el flujo de conexión bancaria:
curl -X POST https://fintable.io/api/v2/connections/link \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"
{
"data": {
"url": "https://fintable.io/api-link/eyJpdiI6...",
"expires_at": "2026-07-26T15:42:00Z"
}
}
El enlace es de un solo uso y caduca en 30 minutos; una vez completado, Fintable empieza
a sincronizar las cuentas y transacciones del banco. Todos los detalles (preselección de
institución, reconexiones, elegibilidad) están en
POST /connections/link.
4. Recupera tus saldos y transacciones
Cuando llegue la primera sincronización —normalmente en un par de minutos— tus datos ya estarán ahí. Saldos:
curl https://fintable.io/api/v2/accounts \
-H "Authorization: Bearer YOUR_TOKEN"
{
"data": [
{
"id": "acc_01J9V5R9WQD3M8Y2KXN4T7PB6C",
"name": "Chase Total Checking",
"balance": "5240.12",
"balance_available": "5190.12",
"currency": "USD",
"...": "..."
}
]
}
Y transacciones:
curl "https://fintable.io/api/v2/transactions?limit=5" \
-H "Authorization: Bearer YOUR_TOKEN"
{
"data": [
{
"id": "tx_01JB2M9QK4R7X3W8N5PDY6TF2H",
"date": "2026-07-24",
"amount": "-4.50",
"currency": "USD",
"description": "BLUE BOTTLE COFFEE",
"...": "..."
}
],
"next_cursor": null
}
Las formas completas, los filtros y la paginación están en Cuenta y
Transacción — y toda la Referencia de la API
sigue el mismo patrón. ¿Prefieres clientes generados? Apunta Swagger UI, Postman o tu
generador de código a la descripción OpenAPI 3.1:
https://fintable.io/api/v2/openapi.json.
Autenticación
Hay dos formas de entrar, según lo que estés construyendo:
- Tokens de acceso personal: para tus propios scripts y herramientas. Crea uno en el panel, ponlo en una cabecera y listo.
- OAuth 2.0: para aplicaciones y asistentes de IA que se conectan a tu cuenta mediante un flujo de autorización en condiciones (es lo que usa el servidor MCP por debajo).
Tokens de acceso personal
Crea y revoca tokens en la página Panel → API.
Los tokens son válidos durante 1 año y llevan los ámbitos que elijas al crearlos:
solo lectura (read) o lectura y escritura (read + write). Envíalos como cabecera
bearer:
Authorization: Bearer YOUR_TOKEN
La revocación surte efecto de inmediato.
OAuth 2.0
Fintable ejecuta un servidor de autorización OAuth 2.0 estándar, así que funcionará cualquier biblioteca cliente de OAuth convencional:
| Endpoint | URL |
|---|---|
| Autorización | https://fintable.io/oauth/authorize |
| Token | https://fintable.io/oauth/token |
| Registro dinámico de clientes | https://fintable.io/oauth/register |
| Descubrimiento | https://fintable.io/.well-known/oauth-authorization-server |
Algunos detalles que conviene conocer:
- El grant admitido es Authorization Code + PKCE.
- Los tokens de acceso duran 1 hora; los de actualización, 30 días.
- Autorizar siempre requiere iniciar sesión y volver a confirmar la contraseña de la cuenta. La pantalla de consentimiento indica exactamente qué podrá hacer la aplicación. Esta fricción es deliberada: son tus datos bancarios.
Ámbitos
| Ámbito | Qué permite |
|---|---|
read |
Leer todos los datos de la cuenta |
write |
Modificar datos: renombrar, categorizar, activar/desactivar, eliminar, sincronizar |
mcp:use |
Acceso completo de lectura y escritura, para clientes MCP (Claude, ChatGPT) |
mcp:use es un superconjunto: se acepta en todos los sitios donde valen read o
write. Los tokens con solo read/write son rechazados por el endpoint MCP.
Referencia de la API
Todo lo que sigue usa la misma autenticación bearer, y los comportamientos comunes —envoltorios, importes como cadenas, errores, límites de tasa, paginación— se describen en Convenciones de la API y Paginación más abajo. Cada sección cubre un tipo de recurso: qué es, su forma exacta (un ejemplo realista y cada campo explicado) y después los endpoints que operan sobre él.
Perfil
| Endpoint | Qué hace |
|---|---|
GET /me |
Tu perfil y metadatos de facturación |
El Perfil es tu cuenta tal como la ve la API: quién eres, en qué plan estás y cuánto margen te queda. Consúltalo antes de añadir una conexión o lanzar una sincronización: los límites que informa son los que aplican los endpoints de escritura.
{
"data": {
"name": "Jamie",
"tier": "personal",
"plan_period": "monthly",
"connection_limit": 10,
"connections_used": 3,
"tx_365_limit_usd": null,
"can_sync": true,
"renews_at": "2026-08-14T00:00:00Z",
"renewal_amount": "9.00",
"renewal_currency": "USD",
"will_renew": true,
"expires_at": "2026-08-14T00:00:00Z"
}
}
| Campo | Tipo | Significado |
|---|---|---|
name |
string | Tu nombre visible |
tier |
string | Nivel del plan: free, trial, personal, office o enterprise |
plan_period |
string | null | Ciclo de facturación: monthly, annual, lifetime, trial o manual; null en cuentas gratuitas |
connection_limit |
integer | Tope de conexiones bancarias que permite tu plan en total. Añadir una conexión falla cuando connections_used ya ha alcanzado este número. Las cuentas gratuitas informan su recuento actual como límite (sin huecos libres). |
connections_used |
integer | Cuántas conexiones bancarias tienes ahora mismo. Compáralo con connection_limit para saber el margen restante: connection_limit - connections_used. Desconectar un banco lo reduce; el límite en sí no cambia salvo que cambie tu plan. |
tx_365_limit_usd |
integer | null | Límite móvil de volumen de transacciones a 365 días en USD; null significa ilimitado |
can_sync |
boolean | Si las sincronizaciones están disponibles (false en cuentas gratuitas) |
renews_at |
string | null | Momento ISO-8601 en que se renueva la suscripción activa |
renewal_amount |
string | null | Precio de renovación, como cadena decimal |
renewal_currency |
string | null | Código de moneda de la renovación |
will_renew |
boolean | Si la suscripción se renovará automáticamente |
expires_at |
string | null | Cuándo termina el derecho de uso actual |
Las cuentas gratuitas obtienen "tier": "free", "can_sync": false y su recuento actual
de conexiones como límite.
GET /me
Devuelve tu Perfil: el objeto de arriba. Sin parámetros:
curl https://fintable.io/api/v2/me \
-H "Authorization: Bearer YOUR_TOKEN"
Conexión
| Endpoint | Qué hace |
|---|---|
GET /connections |
Lista todas las conexiones |
GET /connections/{id} |
Una conexión |
PATCH /connections/{id} |
Renombrar o fijar la fecha de inicio de sincronización |
DELETE /connections/{id} |
Desconectar el banco y purgar sus datos |
POST /connections/link |
Generar un enlace de navegador para conectar un banco nuevo |
POST /connections/{id}/link |
Generar un enlace de navegador para reconectar este banco |
Una Conexión es un banco vinculado: un inicio de sesión en una institución. Una conexión posee una o más cuentas y lleva el estado de salud y de sincronización de esa relación bancaria.
{
"data": {
"id": "conn_plaid_1771845993762884095",
"provider": "PLAID",
"institution_name": "Chase",
"name": null,
"healthy": true,
"status_text": "OK",
"needs_reconnect": false,
"last_successful_update": "2026-07-26T09:12:44Z",
"created_at": "2025-11-02T18:20:11Z",
"accounts_count": 3,
"sync_status": {
"state": "finished",
"progress_now": 4,
"progress_max": 4,
"stage": "Sync complete",
"started_at": "2026-07-26T09:11:58Z",
"finished_at": "2026-07-26T09:12:44Z"
}
}
}
| Campo | Tipo | Significado |
|---|---|---|
id |
string | Id de la conexión, conn_{provider}_{number} |
provider |
string | El agregador detrás de esta conexión, p. ej. PLAID, NORDIGEN, AKOYA, FINICITY, MERCURY, SNAPTRADE |
institution_name |
string | El nombre del banco (tu nombre personalizado, si lo has puesto) |
name |
string | null | Tu nombre personalizado para la conexión |
healthy |
boolean | Señal de salud almacenada: false significa que la conexión necesita atención |
status_text |
string | Estado legible: OK, o el mensaje de error del proveedor |
needs_reconnect |
boolean | true cuando el banco exige que vuelvas a autenticarte |
last_successful_update |
string | null | Momento ISO-8601 de la última sincronización correcta |
created_at |
string | Cuándo se conectó el banco |
accounts_count |
integer | Número de cuentas bajo esta conexión |
sync_status |
object | null | El trabajo de sincronización más reciente: un objeto Estado de sincronización |
GET /connections
Lista todas tus conexiones. Sin parámetros.
GET /connections/{id}
Una conexión por id.
PATCH /connections/{id}
Renombra la conexión o fija su fecha de inicio de sincronización. Acepta uno o ambos de:
name: tu nombre personalizado, máximo 64 caracteres;nulllo borra.sync_start_date:YYYY-MM-DD;nulllo borra. Fintable solo sincronizará transacciones a partir de esta fecha.
curl -X PATCH https://fintable.io/api/v2/connections/conn_plaid_1771845993762884095 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Chase (Jamie)", "sync_start_date": "2026-01-01"}'
La respuesta es el objeto de conexión actualizado. Dos detalles: la fecha de inicio se aplica solo a las cuentas activadas de la conexión (las desactivadas conservan su fecha hasta que se reactiven), y la fecha se valida contra el histórico mínimo del proveedor y el límite de 30 días de las cuentas de prueba (422 si está fuera de rango).
DELETE /connections/{id}
Desconecta el banco y purga sus cuentas y transacciones de Fintable. Como la purga implica al proveedor, se completa de forma asíncrona: la respuesta es un 202:
{
"data": {
"id": "conn_plaid_1771845993762884095",
"status": "deleting"
}
}
La conexión y sus datos desaparecen en cuestión de minutos.
POST /connections/link
Conectar un banco significa iniciar sesión en él, y las páginas de login bancario necesitan un navegador real, así que este es el único flujo que la API no puede completar por sí sola. En su lugar, este endpoint genera una URL de un solo uso, válida 30 minutos, que abres tú (o le pasas al titular de la cuenta: es su cuenta):
curl -X POST https://fintable.io/api/v2/connections/link \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"institution": "12913262_chase"}'
{
"data": {
"url": "https://fintable.io/api-link/eyJpdiI6...",
"expires_at": "2026-07-26T15:42:00Z"
}
}
El campo institution es opcional: es un slug del
directorio de Instituciones y preselecciona el banco en el flujo. Quien
abra la URL verá a qué cuenta de Fintable se está conectando, confirmará la contraseña
de la cuenta y aterrizará en el flujo de selección de banco del proveedor. La URL se
consume en la primera confirmación correcta.
Generarla requiere un plan activo con margen: una suscripción o prueba activa, por debajo del límite de conexiones, por debajo del límite mensual de intentos y por debajo del límite de volumen de transacciones; en caso contrario, 422 con una explicación (y las mismas comprobaciones se repiten cuando el banco se crea realmente).
POST /connections/{id}/link
Funciona exactamente como POST /connections/link, pero genera
un enlace de reconexión para una conexión existente, y está exento de las
comprobaciones de conexión nueva.
Cuenta
| Endpoint | Qué hace |
|---|---|
GET /accounts |
Lista todas las cuentas, incluidas las desactivadas |
GET /accounts/{id} |
Una cuenta |
PATCH /accounts/{id} |
Actualiza display_name, sync_start_date y/o enabled |
Una Cuenta es una cuenta bancaria concreta dentro de una conexión: una cuenta corriente, una de ahorro, una de bróker. Las cuentas son donde viven los saldos y a las que pertenecen las transacciones.
{
"data": {
"id": "acc_01J9V5R9WQD3M8Y2KXN4T7PB6C",
"connection_id": "conn_plaid_1771845993762884095",
"name": "Chase Total Checking",
"display_name": "Household checking",
"type": "depository / checking",
"currency": "USD",
"balance": "5240.12",
"balance_available": "5190.12",
"sync_start_date": "2026-01-01",
"last_tx_date": "2026-07-25",
"enabled": true,
"created_at": "2025-11-02T18:20:14Z",
"updated_at": "2026-07-26T09:12:40Z"
}
}
| Campo | Tipo | Significado |
|---|---|---|
id |
string | Id de la cuenta: opaco, normalmente acc_... |
connection_id |
string | La Conexión a la que pertenece esta cuenta |
name |
string | El nombre que el banco da a la cuenta |
display_name |
string | null | Tu nombre personalizado: es lo que muestran tus hojas de cálculo |
type |
string | Texto libre con sabor del proveedor, como depository / checking o investment / brokerage: muéstralo, no ramifiques según él |
currency |
string | Código de moneda efectivo (respeta cualquier anulación que hayas puesto) |
balance |
string | null | Saldo actual como cadena decimal |
balance_available |
string | null | Saldo disponible, cuando el banco lo informa |
sync_start_date |
string | null | YYYY-MM-DD: las transacciones solo se sincronizan a partir de esta fecha |
last_tx_date |
string | null | Fecha de la transacción sincronizada más reciente |
enabled |
boolean | Si la cuenta se sincroniza; las cuentas desactivadas siguen apareciendo aquí con enabled: false |
created_at / updated_at |
string | Marcas de tiempo ISO-8601 |
GET /accounts
Lista todas las cuentas, incluidas las desactivadas (enabled: false). Filtros:
connection_id, ids[] y enabled.
GET /accounts/{id}
Una cuenta por id.
PATCH /accounts/{id}
Actualiza display_name, sync_start_date y/o enabled.
Aviso: desactivar una cuenta elimina sus transacciones. Poner
"enabled": falseelimina permanentemente todas las transacciones de esa cuenta en Fintable, igual que el interruptor del panel. Reactivarla no las restaura; una sincronización posterior tendrá que recuperarlas del proveedor. No desactives una cuenta salvo que sea exactamente lo que quieres.
Posición
| Endpoint | Qué hace |
|---|---|
GET /accounts/{id}/holdings |
Una instantánea de las posiciones de una cuenta |
Una Posición es una participación en una cuenta de inversión: una acción, un fondo u
otro valor. Fintable registra las posiciones como instantáneas diarias: qué tenías,
a qué precio, una vez al día. Una respuesta de posiciones es el conjunto de filas de una
fecha de instantánea, con la fecha en el envoltorio como snapshot_date.
{
"data": [
{
"id": "hol_01JB7Q2M5X8R4T6W9NKZP3VD1F",
"name": "Vanguard Total Stock Market ETF",
"symbol": "VTI",
"quantity": "42.0000",
"price": "279.35",
"value": "11732.70",
"cost_basis": "9450.00",
"currency": "USD",
"updated_at": "2026-07-26T09:12:41Z"
}
],
"snapshot_date": "2026-07-26"
}
| Campo | Tipo | Significado |
|---|---|---|
id |
string | Id de la posición: opaco, normalmente hol_... |
name |
string | El nombre del valor |
symbol |
string | null | Símbolo bursátil, cuando el proveedor lo informa |
quantity |
string | null | Unidades en cartera, como cadena decimal |
price |
string | null | Precio por unidad |
value |
string | null | Valor de mercado actual de la posición |
cost_basis |
string | null | Coste total de la posición, no por acción: una peculiaridad del proveedor que trasladamos tal cual en vez de adivinar |
currency |
string | La moneda efectiva de la cuenta |
updated_at |
string | null | Cuándo se escribió esta fila por última vez |
snapshot_date (envoltorio) |
string | null | El día de la instantánea que describe esta respuesta; null cuando la cuenta no tiene posiciones |
GET /accounts/{id}/holdings
Devuelve la instantánea más reciente por defecto; ?date=YYYY-MM-DD selecciona una
concreta. No hay paginación de histórico: recupera fecha a fecha.
Transacción
| Endpoint | Qué hace |
|---|---|
GET /transactions |
Todas las transacciones, paginadas por cursor |
GET /accounts/{id}/transactions |
Las transacciones de una cuenta |
GET /transactions/{id} |
Una transacción |
PATCH /transactions/{id} |
Fijar o borrar la categoría |
PATCH /transactions/bulk |
Categorizar muchas a la vez |
El corazón de la API. Una Transacción es un movimiento de dinero en una cuenta: una compra, un ingreso, una transferencia, una comisión. Las transacciones llevan el estado de categorización: tanto la categoría asignada como si se fijó a mano o por una regla.
{
"data": {
"id": "tx_01JB2M9QK4R7X3W8N5PDY6TF2H",
"account_id": "acc_01J9V5R9WQD3M8Y2KXN4T7PB6C",
"date": "2026-07-24",
"datetime": "2026-07-24T16:41:02Z",
"auth_date": "2026-07-23",
"amount": "-4.50",
"currency": "USD",
"description": "BLUE BOTTLE COFFEE",
"merchant": "Blue Bottle Coffee",
"pending": false,
"check_num": null,
"external_memo": null,
"account_owner": null,
"category": {
"id": "dining-out_aB3xY9k2Lm",
"name": "Dining Out",
"header": "Expenses"
},
"category_manual_override": false,
"created_at": "2026-07-24T18:03:12Z",
"updated_at": "2026-07-25T06:14:09Z"
}
}
| Campo | Tipo | Significado |
|---|---|---|
id |
string | Id de la transacción: opaco, normalmente tx_... |
account_id |
string | La Cuenta a la que pertenece esta transacción |
date |
string | Fecha de la transacción, YYYY-MM-DD |
datetime |
string | null | Hora ISO-8601 exacta, cuando el proveedor la facilita |
auth_date |
string | null | Fecha de autorización, cuando difiere de la de contabilización |
amount |
string | Cadena decimal exacta; negativo es dinero que sale |
currency |
string | Código de moneda efectivo |
description |
string | La descripción del extracto |
merchant |
string | null | Nombre del comercio ya limpio, cuando se conoce |
pending |
boolean | true mientras la transacción no se ha contabilizado; las filas pendientes pueden sustituirse al contabilizarse |
check_num |
string | null | Número de cheque, para pagos con cheque |
external_memo |
string | null | Texto adicional de nota del banco |
account_owner |
string | null | Nombre del titular en cuentas compartidas o con varios titulares |
category |
object | null | La Categoria asignada —{id, name, header}— o null si no está categorizada |
category_manual_override |
boolean | true cuando la categoría se fijó a mano; las reglas nunca tocan filas anuladas |
created_at / updated_at |
string | Marcas de tiempo ISO-8601; updated_at es lo que impulsa la sincronizacion incremental |
raw |
object | El JSON en bruto del proveedor: solo presente con ?include=raw; los mismos datos que los campos **Raw de tus hojas de cálculo |
GET /transactions
Todas tus transacciones, paginadas por cursor, de más reciente a más antigua por defecto. Filtros:
| Filtro | Significado |
|---|---|
date_from, date_to |
Rango de fechas (YYYY-MM-DD, inclusive) |
account_ids[] |
Limitar a cuentas concretas |
category_ids[] |
Limitar a categorías: incluye el literal uncategorized para las filas sin categorizar |
pending |
true o false |
amount_min, amount_max |
Rango de importes |
q |
Búsqueda de subcadena sin distinguir mayúsculas sobre descripción + comercio |
description |
Coincidencia exacta de descripción |
updated_since, order |
Para la sincronizacion incremental; order es date o updated |
GET /accounts/{id}/transactions
Las transacciones de una cuenta: mismos filtros, paginación y forma que
GET /transactions.
GET /transactions/{id}
Una transacción por id. ?include=raw también funciona aquí.
PATCH /transactions/{id}
Hace exactamente una cosa: fijar la categoría.
curl -X PATCH https://fintable.io/api/v2/transactions/tx_01JB2M9QK4R7X3W8N5PDY6TF2H \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"category_id": "dining-out_aB3xY9k2Lm"}'
Fijar una categoría marca la transacción como anulada manualmente
(category_manual_override: true): las reglas no volverán a tocarla. Poner
"category_id": null la descategoriza y borra la anulación, así que las reglas pueden
volver a aplicarse en la siguiente pasada. La respuesta es la transacción actualizada.
PATCH /transactions/bulk
Aplica un category_id (o null) a muchas transacciones a la vez. Elige los objetivos
con exactamente uno de estos dos selectores:
ids[]: hasta 10.000 ids (los ids que no sean tuyos se omiten en silencio), ofilters: las mismas claves que el endpoint de listado. Para que una errata no recategorice todo tu histórico, el filtro debe incluir al menos una clave restrictiva:date_from,date_to,account_ids,category_ids,qodescription(pendingyamount_*pueden afinar, pero no cuentan por sí solos). Si coinciden más de 10.000 transacciones, la petición falla con 422: acota y reintenta.
¿No sabes a qué va a afectar un filtro? Haz primero una prueba en seco:
curl -X PATCH https://fintable.io/api/v2/transactions/bulk \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filters": {"q": "whole foods", "date_from": "2026-01-01"},
"category_id": "groceries_x7Pq2Rv9Zn",
"dry_run": true
}'
{
"data": {
"dry_run": true,
"matched_count": 37,
"sample": [
"... up to 10 matching transactions ..."
]
}
}
¿Contento con la coincidencia? Envía la misma petición sin dry_run:
{
"data": {
"dry_run": false,
"updated_count": 37
}
}
La ejecución actualiza todas las filas coincidentes y sincroniza las categorías con tus hojas de cálculo una sola vez, como una única exportación.
Sincronización
| Endpoint | Qué hace |
|---|---|
GET /sync |
Programación, sincronizaciones en curso y estado por conexión |
POST /sync |
Sincronizar todas las conexiones ahora |
POST /sync/{connection_id} |
Sincronizar una conexión ahora |
Una Sincronización es una ejecución que trae datos frescos de una conexión bancaria a Fintable. Fintable las programa automáticamente: cada cuenta entra en un barrido aleatorizado que se ejecuta cada 6–23 horas, de modo que deliberadamente no existe una "hora exacta de la próxima sincronización". La API te permite inspeccionar la programación, observar las sincronizaciones en curso y (en planes de pago) lanzar una bajo demanda.
La forma recurrente aquí es el objeto Estado de sincronización: aparece en cada
Conexión y a lo largo de GET /sync:
{
"state": "finished",
"progress_now": 4,
"progress_max": 4,
"stage": "Sync complete",
"started_at": "2026-07-26T09:11:58Z",
"finished_at": "2026-07-26T09:12:44Z"
}
| Campo | Tipo | Significado |
|---|---|---|
state |
string | queued, executing, finished, failed o retrying |
progress_now |
integer | null | Pasos completados hasta ahora |
progress_max |
integer | null | Pasos totales de esta ejecución |
stage |
string | null | Descripción legible de la etapa actual |
started_at |
string | null | Cuándo empezó la ejecución |
finished_at |
string | null | Cuándo terminó; null mientras sigue en curso |
GET /sync
Envuelve los objetos de Estado de sincronización en el cuadro completo: tu programación más el estado por conexión.
| Campo | Tipo | Significado |
|---|---|---|
schedule.type |
string | default (el barrido aleatorizado) o custom (existen programaciones específicas de proveedor) |
schedule.last_sync_at |
string | null | Cuándo despachó el barrido tus sincronizaciones por última vez |
schedule.next_sync_window |
object | null | Ventana aproximada {earliest, latest} del próximo barrido: no existe una hora exacta |
schedule.custom_schedules |
array | Programaciones específicas de proveedor: {provider, cron, timezone, next_run_at} |
schedule.default_sweep_applies |
boolean | El barrido por defecto se aplica a todas las cuentas, haya programaciones personalizadas o no |
active_syncs |
array | Trabajos en curso (o atascados, o fallidos): {connection_id, sync_status} |
connections |
array | El último {connection_id, sync_status} de cada conexión |
curl https://fintable.io/api/v2/sync \
-H "Authorization: Bearer YOUR_TOKEN"
{
"data": {
"schedule": {
"type": "default",
"last_sync_at": "2026-07-26T09:11:55Z",
"next_sync_window": {
"earliest": "2026-07-26T15:23:00Z",
"latest": "2026-07-27T09:11:55Z"
},
"custom_schedules": [],
"default_sweep_applies": true
},
"active_syncs": [],
"connections": [
{
"connection_id": "conn_plaid_1771845993762884095",
"sync_status": {
"state": "finished",
"progress_now": 4,
"progress_max": 4,
"stage": "Sync complete",
"started_at": "2026-07-26T09:11:58Z",
"finished_at": "2026-07-26T09:12:44Z"
}
}
]
}
}
POST /sync
La versión API del botón "Sincronizar todas las conexiones" del panel. Trae los datos cacheados del proveedor: no es una actualización en tiempo real desde el banco:
{
"data": [
{
"connection_id": "conn_plaid_1771845993762884095",
"status": "started"
},
{
"connection_id": "conn_nordigen_1802214467911184310",
"status": "already_syncing"
}
]
}
Una sincronización ya en curso se informa como already_syncing, nunca como error.
Requiere una suscripción o prueba activa: las cuentas gratuitas reciben un 403 antes
de que empiece nada. Sigue el progreso con GET /sync o con el
sync_status de cada conexión.
POST /sync/{connection_id}
Sincroniza solo una conexión: misma forma de respuesta que POST /sync
(un elemento) y mismo requisito de plan.
Categoría
| Endpoint | Qué hace |
|---|---|
GET /categorizer/categories |
Listar categorías |
GET /categorizer/categories/{id} |
Una categoría |
POST /categorizer/categories |
Crear una categoría (201) |
PATCH /categorizer/categories/{id} |
Renombrar, cambiar de grupo, recolorear |
DELETE /categorizer/categories/{id} |
Eliminar una categoría |
Una Categoría es una etiqueta para transacciones: el bloque básico del categorizador, que es como se etiquetan las transacciones. Las categorías son las etiquetas, y las Reglas las aplican automáticamente conforme entran las transacciones. Todo lo que puedes hacer en el panel del categorizador puedes hacerlo aquí. Límite: 1.000 categorías por cuenta.
{
"data": {
"id": "groceries_x7Pq2Rv9Zn",
"name": "Groceries",
"header": "Expenses",
"color": "green",
"created_at": "2026-07-26T14:02:33Z",
"updated_at": "2026-07-26T14:02:33Z"
}
}
| Campo | Tipo | Significado |
|---|---|---|
id |
string | Id de la categoría, {name-slug}_{10 alfanuméricos}: estable ante renombrados |
name |
string | La etiqueta que se muestra en las transacciones y en tus hojas de cálculo |
header |
string | El grupo bajo el que aparece la categoría, como Expenses o Income |
color |
string | Un nombre de la paleta del panel: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose |
created_at / updated_at |
string | Marcas de tiempo ISO-8601 |
GET /categorizer/categories
Lista todas tus categorías.
GET /categorizer/categories/{id}
Una categoría por id.
POST /categorizer/categories
Crea una categoría (201): name, header y un color opcional.
curl -X POST https://fintable.io/api/v2/categorizer/categories \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Groceries", "header": "Expenses", "color": "green"}'
La respuesta es la categoría creada (como arriba).
PATCH /categorizer/categories/{id}
Actualiza name, header y/o color. Los renombrados se propagan automáticamente a tu
Airtable y a tus Google Sheets.
DELETE /categorizer/categories/{id}
El borrado está protegido: si alguna regla sigue referenciando la categoría, la petición falla con 409 listando los ids de las reglas implicadas; actualiza o elimina esas reglas primero. En caso contrario, todas las transacciones de la categoría quedan sin categorizar y la categoría se elimina:
{
"data": {
"deleted": true,
"uncategorized_count": 118
}
}
Regla
| Endpoint | Qué hace |
|---|---|
GET /categorizer/rules |
Listar reglas: solo metadatos, sin logic |
GET /categorizer/rules/{id} |
Una regla, incluida su logic completa |
POST /categorizer/rules |
Crear una regla (202) |
PATCH /categorizer/rules/{id} |
Actualizar name, priority y/o logic |
DELETE /categorizer/rules/{id} |
Eliminar una regla |
POST /categorizer/sync |
Encolar una pasada completa de reglas + exportación a hojas (202) |
GET /categorizer/status |
En qué punto está la tubería |
Una Regla categoriza transacciones automáticamente conforme se sincronizan: la otra mitad del categorizador. Límite: 1.000 reglas por cuenta.
{
"data": {
"id": "a25a374d-e4d7-4652-aca7-5dd3c3d02d15",
"name": "Big grocery runs",
"type": "advanced",
"priority": 7,
"category_ids": [
"groceries_x7Pq2Rv9Zn"
],
"logic": {
"if": [
"..."
]
},
"created_at": "2026-07-26T14:10:05Z",
"updated_at": "2026-07-26T14:10:05Z"
}
}
| Campo | Tipo | Significado |
|---|---|---|
id |
string | Id de la regla: un UUID |
name |
string | Nombre visible (autogenerado para las reglas simples) |
type |
string | simple (la descripción contiene un texto) o advanced (JSONLogic en bruto) |
priority |
integer | Las reglas se ejecutan en orden (priority, id); cuando coinciden varias, gana la de mayor prioridad |
category_ids |
array of strings | Las Categorias que pueden asignar las ramas de esta regla |
logic |
object | El JSONLogic completo: presente en GET /categorizer/rules/{id} y en las respuestas de creación/actualización, omitido en el listado |
created_at / updated_at |
string | Marcas de tiempo ISO-8601 |
Cómo se comportan las reglas, conviene leerlo una vez:
- Las reglas se ejecutan en orden (priority, id). Cuando varias reglas coinciden con
la misma transacción, gana la de mayor prioridad (se aplica la última).
priorityes editable vía PATCH. - Las transacciones categorizadas a mano (
category_manual_override: true) nunca son tocadas por las reglas. - Una pasada de reglas preserva las categorizaciones antiguas. Solo sobrescribe las transacciones que coinciden con el conjunto de reglas actual: eliminar o cambiar una regla no descategoriza las transacciones que antes coincidían. Esto refleja el comportamiento del panel y es intencionado. Para reiniciar de verdad, descategoriza en bloque y vuelve a ejecutar.
GET /categorizer/rules
Lista todas tus reglas: solo metadatos (id, name, type, priority,
category_ids[]), sin logic.
GET /categorizer/rules/{id}
Una regla por id, incluida su logic completa.
POST /categorizer/rules
Hay dos tipos. Las reglas simples cubren el caso habitual: "si la descripción contiene X, archívalo en Y" (sin distinguir mayúsculas, 3–128 caracteres):
curl -X POST https://fintable.io/api/v2/categorizer/rules \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type": "simple", "text": "STARBUCKS", "category_id": "dining-out_aB3xY9k2Lm"}'
Las reglas avanzadas son JSONLogic en bruto: condiciones
arbitrarias sobre el importe, las fechas, la cuenta, la descripción e incluso los campos
en bruto del proveedor (consulta la
referencia de JSONLogic más abajo). Ten en
cuenta que logic es una cadena codificada en JSON, no un objeto anidado:
curl -X POST https://fintable.io/api/v2/categorizer/rules \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "advanced",
"name": "Big grocery runs",
"logic": "{\"if\": [{\"and\": [{\"in\": [\"WHOLE FOODS\", {\"var\": \"transaction.description\"}]}, {\"<\": [{\"var\": \"transaction.amount\"}, -100]}]}, \"groceries_x7Pq2Rv9Zn\", null]}"
}'
Crear o actualizar una regla devuelve 202 con el objeto de la regla (como arriba) más
un "application": "queued" de primer nivel. Ese 202 te está diciendo algo real: la
regla está guardada y se ha encolado una pasada ordenada completa de todas tus
reglas sobre todas tus transacciones, junto con una exportación agrupada a tus hojas
de cálculo. Consulta GET /categorizer/status para ver cuándo
aterriza. Muchas ediciones rápidas producen una sola pasada y una sola exportación, no
una por edición.
PATCH /categorizer/rules/{id}
Actualiza name, priority y/o logic. Los cambios de comportamiento (lógica o
prioridad) devuelven 202 y encolan una pasada de reglas, igual que al crear; un
simple renombrado devuelve 200 con "application": "none".
DELETE /categorizer/rules/{id}
Elimina la regla. Las transacciones que categorizó previamente conservan sus categorías (consulta las notas de comportamiento de arriba).
Escribir reglas avanzadas en JSONLogic
logic debe ser un objeto JSON cuyo único operador de primer nivel sea if. Cada rama
de resultado debe ser una cadena literal con un id de categoría o el literal null,
nunca una expresión calculada. Tu lógica se evalúa contra esta entrada para cada
transacción:
{
"transaction": {
"fin_id": "tx_01JB2M9QK4R7X3W8N5PDY6TF2H",
"ext_id": "plaid-tx-4821bd0e",
"account_id": "acc_01J9V5R9WQD3M8Y2KXN4T7PB6C",
"date": "2026-07-24",
"auth_date": "2026-07-23",
"amount": "-4.50",
"currency": "USD",
"description": "BLUE BOTTLE COFFEE",
"payee": "Blue Bottle Coffee",
"sub_account": null,
"acc_name": "Chase Total Checking",
"raw": {
"provider fields": "..."
}
}
}
Dos comodidades a tener en cuenta: description se pasa a mayúsculas por ti (así que la
coincidencia de subcadenas es en la práctica insensible a mayúsculas) y amount es la
habitual cadena decimal.
Un ejemplo resuelto: "las transacciones de tarjeta de más de 100 $ en Whole Foods son
Groceries" (recuerda que negativo = dinero que sale, así que más de 100 $ gastados
significa < -100):
{
"if": [
{
"and": [
{
"in": [
"WHOLE FOODS",
{
"var": "transaction.description"
}
]
},
{
"<": [
{
"var": "transaction.amount"
},
-100
]
}
]
},
"groceries_x7Pq2Rv9Zn",
null
]
}
Operadores permitidos: var, missing, missing_some, if, ==, ===, !=, !==,
!, !!, or, and, >, >=, <, <=, max, min, +, -, *, /, %,
map, reduce, filter, all, none, some, merge, in, cat, substr.
(log no está permitido.)
Límites por regla: 16 KB, profundidad de anidamiento 20 y un presupuesto de complejidad
de 100 nodos de operador; los operadores de array (map, filter, reduce, all,
none, some) cuentan 10× y no pueden anidarse entre sí. También hay un presupuesto
agregado para todas tus reglas; si lo alcanzas, simplifica o elimina reglas.
POST /categorizer/sync
Encola una pasada completa de reglas más una exportación a hojas de cálculo (202,
{"data": {"application": "queued"}}). Las ediciones de reglas encolan pasadas
automáticamente, así que rara vez lo necesitarás: existe para "vuelve a ejecutarlo todo
ahora".
GET /categorizer/status
La visión honesta de la tubería:
curl https://fintable.io/api/v2/categorizer/status \
-H "Authorization: Bearer YOUR_TOKEN"
{
"data": {
"requested_version": 12,
"completed_version": 12,
"active_version": null,
"settled": true,
"failed_at": null,
"destinations": [
{
"destination": "airtable",
"requested_version": 12,
"completed_version": 12,
"failed_at": null
},
{
"destination": "gsheet:184",
"requested_version": 12,
"completed_version": 12,
"failed_at": null
}
]
}
}
Cada cambio solicitado incrementa requested_version; las pasadas y exportaciones van
detrás. active_version es la pasada que se está ejecutando ahora mismo: null siempre
que no haya ninguna en marcha. settled: true significa que todo lo que has pedido ha aterrizado por completo:
en la base de datos y en cada destino de hoja de cálculo. Para esperar a que un cambio
de regla surta efecto, consulta hasta que settled sea true.
Integración
| Endpoint | Qué hace |
|---|---|
GET /integrations |
Estado y salud de tus integraciones de hojas de cálculo |
Una Integración es un destino al que Fintable sincroniza: tu base de Airtable o tus
hojas de Google Sheets. Es el puente entre esta API y el mundo de las hojas de cálculo:
coge aquí el base_id o el spreadsheet_id y luego trabaja con las APIs propias de
Airtable o de Google directamente sobre los datos sincronizados.
GET /integrations
curl https://fintable.io/api/v2/integrations \
-H "Authorization: Bearer YOUR_TOKEN"
{
"data": {
"airtable": {
"base_id": "appXk2fW9qLmN3vT8",
"url": "https://airtable.com/appXk2fW9qLmN3vT8",
"accounts_table_name": "Accounts",
"transactions_table_name": "Transactions",
"holdings_table_name": "Holdings",
"transactions_enabled": true,
"token_type": "OAUTH",
"healthy": true,
"error": null
},
"google_sheets": [
{
"spreadsheet_id": "1vQx8mP2kL9nR4tY7wZ3jB6cD5eF8gH0iJ2kL4mN6oP8",
"url": "https://docs.google.com/spreadsheets/d/1vQx8mP2kL9nR4tY7wZ3jB6cD5eF8gH0iJ2kL4mN6oP8",
"title": "Family finances",
"tabs": {
"accounts": {
"sheet": "Accounts",
"range": "A1:Z"
},
"transactions": {
"sheet": "Transactions",
"range": "A1:Z"
},
"holdings": {
"sheet": null,
"range": null
}
},
"healthy": true,
"error": null
}
]
}
}
El objeto airtable (null si no has conectado Airtable):
| Campo | Tipo | Significado |
|---|---|---|
base_id |
string | La base de Airtable a la que sincroniza Fintable: úsala con la propia API de Airtable |
url |
string | Enlace directo a la base |
accounts_table_name |
string | Tabla configurada para cuentas |
transactions_table_name |
string | Tabla configurada para transacciones |
holdings_table_name |
string | null | Tabla configurada para posiciones, cuando está activada |
transactions_enabled |
boolean | Si la sincronización de transacciones a esta base está activa |
token_type |
string | OAUTH, PERSONAL o DEPRECATED |
healthy |
boolean | Si la última validación fue correcta |
error |
string | null | Qué falla, cuando healthy es false |
Cada entrada de google_sheets[]:
| Campo | Tipo | Significado |
|---|---|---|
spreadsheet_id |
string | La hoja de cálculo a la que sincroniza Fintable: úsala con la propia API de Google |
url |
string | Enlace directo a la hoja de cálculo |
title |
string | El título de la hoja de cálculo |
tabs |
object | Pestañas accounts / transactions / holdings configuradas, cada una {sheet, range} (null cuando no está configurada) |
healthy |
boolean | Si la última validación fue correcta |
error |
string | null | Qué falla, cuando healthy es false |
La salud se sirve desde una caché de validación: la primera llamada tras un periodo de inactividad puede tardar unos segundos mientras valida en vivo. Configura las integraciones en Panel → Integraciones.
Institución
| Endpoint | Qué hace |
|---|---|
GET /institutions |
Buscar en el directorio (público, paginado por desplazamiento) |
Una Institución es un banco o bróker al que Fintable puede conectarse: una entrada en el
directorio buscable de unas 50.000. El directorio es público —sin autenticación—,
así que puedes usarlo en flujos de registro o en comprobaciones de disponibilidad. Sus
valores slug alimentan POST /connections/link.
{
"data": [
{
"slug": "12913262_chase",
"name": "Chase",
"domain": "chase.com",
"supported": true,
"countries": [
"US"
],
"coverage_url": "https://fintable.io/coverage/us/12913262_chase",
"updated_at": "2026-07-19T02:11:36Z"
}
],
"meta": {
"page": 1,
"has_more": true
}
}
| Campo | Tipo | Significado |
|---|---|---|
slug |
string | El id de la institución: pásalo a POST /connections/link |
name |
string | Nombre visible |
domain |
string | null | El dominio web de la institución |
supported |
boolean | Si Fintable puede conectarse a ella ahora mismo |
countries |
array of strings | Códigos ISO de país en los que opera |
coverage_url |
string | null | Página pública de cobertura; null cuando no existe |
updated_at |
string | null | Cuándo cambió por última vez la entrada del directorio |
GET /institutions
Parámetros de consulta:
| Parámetro | Significado |
|---|---|
q |
Búsqueda difusa por nombre, mínimo 3 caracteres |
domain |
Coincidencia por dominio web |
country |
Código ISO de país, p. ej. US |
provider |
PLAID, NORDIGEN, AKOYA, FINICITY, MERCURY, SNAPTRADE (GoCardless es NORDIGEN internamente) |
page |
Número de página: siempre 10 resultados por página |
curl "https://fintable.io/api/v2/institutions?q=chase&country=US"
Este es el único endpoint de la API paginado por desplazamiento: avanza con page hasta
que has_more sea false.
La documentación y la guía como datos
| Endpoint | Qué devuelve |
|---|---|
GET /guide |
La guía de usuario completa de Fintable en markdown |
GET /docs |
Este documento en markdown |
GET /openapi.json |
La descripción OpenAPI 3.1 de esta API |
La propia documentación está disponible como markdown plano: práctico para alimentar un LLM o para renderizarla en tus propias herramientas. Todo público, sin autenticación.
GET /guide
La guía de usuario completa de Fintable en markdown. ?locale=main|uk|es selecciona el
idioma.
GET /docs
Este documento en markdown. ?locale=main|uk|es selecciona el idioma.
GET /openapi.json
La descripción OpenAPI 3.1 de esta API: apunta a ella Swagger UI, Postman o un generador de código.
Convenciones de la API
Unas cuantas convenciones se aplican en todas partes, así que solo tienes que aprenderlas una vez.
El envoltorio de la respuesta
Las listas devuelven {"data": [...]} y los objetos individuales {"data": {...}}. Las
listas de transacciones llevan además un next_cursor (consulta
Paginación). La única excepción: el directorio público de instituciones
está paginado por desplazamiento y usa meta: {page, has_more}.
Las peticiones deberían enviar Accept: application/json.
El dinero es una cadena
Los importes son cadenas decimales exactas —"-4.50", "1234.56"—, nunca números
en coma flotante, así que no pierdes ni un céntimo por redondeos. Los importes negativos
son dinero que sale. Cada importe viaja con un campo currency aparte (la moneda
efectiva, respetando cualquier anulación de moneda que hayas configurado). Los saldos de
las cuentas siguen la misma convención.
Marcas de tiempo y fechas
Las marcas de tiempo son ISO-8601 en UTC, como 2026-07-26T15:04:05Z. Los campos date
y auth_date de las transacciones son cadenas YYYY-MM-DD sin más.
Los ids de objeto son opacos
Los ids son cadenas opacas de hasta 64 caracteres. Guárdalos tal cual y no extraigas significado de ellos: las formas de abajo son para reconocerlos, no para parsearlos.
| Objeto | Qué aspecto tiene |
|---|---|
| Transacción | tx_01J0AB... (las cuentas antiguas pueden tener cadenas numéricas heredadas como "48214321") |
| Cuenta | acc_01J0AB... (aquí también existen cadenas numéricas heredadas) |
| Posición | hol_01J0AB... o una cadena numérica heredada |
| Categoría | {name-slug}_{10 alfanuméricos}, p. ej. dining-out_aB3xY9k2Lm |
| Regla | UUID, p. ej. a25a374d-e4d7-4652-aca7-5dd3c3d02d15 |
| Conexión | conn_{provider}_{number}, p. ej. conn_plaid_1771845993762884095 |
Errores
Toda respuesta que no sea 2xx tiene exactamente una forma, así que un único manejador de errores cubre toda la API:
{
"error": {
"type": "not_found",
"message": "No transaction with that id."
}
}
Los fallos de validación (422) incluyen además mensajes por campo:
{
"error": {
"type": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"sync_start_date": [
"Trial accounts can sync at most 30 days of history."
]
}
}
}
| HTTP | type |
Cuándo lo verás |
|---|---|---|
| 400 | bad_request / invalid_cursor |
Petición mal formada; o un cursor reutilizado con un orden distinto |
| 401 | unauthenticated |
Token ausente, caducado o revocado |
| 403 | forbidden |
Token válido, pero la acción no está permitida (p. ej. sincronizar en una cuenta gratuita) |
| 404 | not_found |
No existe ese objeto, incluidos los objetos de otra cuenta |
| 405 | method_not_allowed |
Verbo HTTP incorrecto |
| 409 | conflict |
La acción entra en conflicto con el estado actual (p. ej. eliminar una categoría que aún usan reglas) |
| 413 | payload_too_large |
Cuerpo de la petición por encima del límite |
| 422 | validation_failed |
La petición se entendió, pero algún campo es inválido |
| 429 | rate_limited |
Baja el ritmo: viene con una cabecera Retry-After |
| 500 | server_error |
Culpa nuestra; reinténtalo o escríbenos |
| 503 | service_unavailable |
Caída temporal o mantenimiento |
Límites de tasa
Los límites son generosos para clientes educados y bien programados; solo los
encontrarás si machacas un endpoint. Cada ruta tiene exactamente un cubo, y las llamadas
a herramientas MCP consumen los mismos cubos que sus equivalentes REST. Cuando
alcanzas un límite recibes un 429 con una cabecera Retry-After: respétala.
| Cubo | Límite |
|---|---|
| Lecturas autenticadas | 300/min por token |
| Escrituras genéricas (PATCH/DELETE, categorías) | 60/min por cuenta |
| Crear/actualizar reglas | 12/hora por cuenta |
POST /sync (y por conexión) |
Personal/Trial: 2/día · Office/Enterprise: 1/hora |
POST /connections/link (y reconexión) |
1/min por cuenta |
PATCH /transactions/bulk |
10/hora por cuenta |
POST /categorizer/sync |
6/día por cuenta |
GET /institutions público |
60/min por IP |
/guide, /docs, openapi.json públicos |
60/min por IP |
| Endpoint MCP | 120/min por token |
POST /oauth/register |
5/hora por IP |
Caché
Las respuestas autenticadas siempre se sirven frescas (Cache-Control: no-store). Los
endpoints públicos (/institutions, /guide, /docs) se pueden cachear hasta una hora.
Paginación
Años de histórico de transacciones pueden ser decenas de miles de filas, así que
GET /transactions (y su variante por cuenta) nunca lo devuelve todo de golpe: pagina
con un cursor opaco. ¿Por qué un cursor y no números de página? Porque tus datos se
mueven: una sincronización puede insertar o actualizar transacciones mientras paginas, y
las páginas por desplazamiento se saltarían o duplicarían filas en silencio. Un cursor
fija tu posición exacta en la secuencia, de modo que un recorrido completo ve cada fila
exactamente una vez.
Pide una página y, si hay más, la respuesta te dice por dónde continuar:
curl "https://fintable.io/api/v2/transactions?limit=100" \
-H "Authorization: Bearer YOUR_TOKEN"
{
"data": [
"... 100 transactions ..."
],
"next_cursor": "eyJ2IjoxLCJvIjoiZGF0ZSIsazoi..."
}
Devuelve el cursor para obtener la página siguiente y sigue así hasta que next_cursor
sea null:
curl "https://fintable.io/api/v2/transactions?cursor=eyJ2IjoxLC..." \
-H "Authorization: Bearer YOUR_TOKEN"
Las reglas:
limites 100 por defecto y llega como máximo a 500.- Los cursores son opacos y están ligados a su orden. Un cursor generado en un listado
con
order=datese rechaza (400invalid_cursor) si se reutiliza conorder=updated, y viceversa. - El orden por defecto es de más reciente a más antiguo por fecha de transacción.
Sincronización incremental
Si estás replicando transacciones en tu propia base de datos o aplicación, volver a
descargar todo el histórico solo para recoger los cambios de ayer es lento y
derrochador, y los límites de tasa no están dimensionados para eso. La sincronización
incremental es la alternativa eficiente: cada transacción lleva una marca updated_at,
y el endpoint de listado puede ordenar por ella, así que puedes pedir exactamente "todo
lo que ha cambiado desde la última vez que miré".
Consultar los cambios
Consulta con ?order=updated&updated_since=<marca ISO>. Los resultados vuelven ordenados
por updated_at ascendente, paginados con el mismo mecanismo de cursor de antes. La
receta:
- Llama a
GET /transactions?order=updated&updated_since=2026-07-25T00:00:00Z. - Recorre las páginas con
next_cursor, procesando cada transacción. - Recuerda el
updated_atmás alto que hayas procesado; úsalo como el siguienteupdated_since.
La letra pequeña honesta: las eliminaciones
Las eliminaciones son invisibles para la consulta incremental: no hay lápida ni registro de borrados. Dos situaciones que debes contemplar en tu diseño:
Estamos trabajando en una solución mejor que haga visibles las eliminaciones. Mientras
tanto, nuestro mejor consejo es no descargar transacciones pendientes: usa
pending=false al copiar transacciones a tu propia base de datos o aplicación.
- Rotación de transacciones pendientes. Las transacciones pendientes pueden sustituirse al contabilizarse (nuevo id, importe o fecha ajustados). Si aun así las importas, vuelve a descargar periódicamente una ventana de los últimos 30 días para detectarlo.
- Eliminaciones de cuentas completas. Cuando una cuenta desaparece de
GET /accountso pasa aenabled: false, descarta todas las transacciones que tuvieras cacheadas de esa cuenta.
Si necesitas más certeza que eso, vuelve a descargar todo periódicamente.
¿Preguntas o comentarios?
Si algo de aquí no queda claro, falta o directamente está mal, nos gustaría saberlo: escríbenos o usa la burbuja de soporte de cualquier página. Si estás construyendo algo sobre la API, estaremos encantados de ayudarte a que funcione.