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, Codex) |
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.
Workspaces
Un Workspace es un conjunto aislado de conexiones, cuentas, transacciones, reglas del categorizador e integraciones de Fintable. Cada cuenta tiene un Workspace predeterminado — el propio Workspace del dueño de la credencial, usado cuando no se selecciona ninguno. Los planes Office y Enterprise pueden añadir Workspaces gestionados para clientes, empresas u hogares bajo un Workspace principal que posee la facturación. Los Workspaces son la forma de compartir datos financieros de manera segura con un equipo, un familiar o un cliente: cada Workspace tiene su propio acceso y sus propios permisos de API concedidos explícitamente, limitados solo a ese Workspace.
Los scopes y los permisos de Workspace responden preguntas distintas: los scopes son qué puede hacer una credencial (leer/escribir); los permisos de Workspace son dónde puede hacerlo. Se combinan: un token de solo lectura con tres Workspaces concedidos puede leer los tres y no escribir en ninguno.
Conceder acceso a Workspaces
El acceso a un Workspace gestionado es explícito y por credencial:
- Tokens de acceso personal — marca los Workspaces que el token puede alcanzar al crearlo en Panel → API, o edita después el acceso de un token existente (sin rotar el secreto).
- Aplicaciones OAuth / MCP — la pantalla de consentimiento lista tus Workspaces elegibles; marca los que la aplicación puede usar. El permiso sigue a la aplicación, así que sobrevive a la renovación de tokens. Edítalo o revócalo en cualquier momento desde Aplicaciones conectadas.
Nada se concede en silencio: las credenciales existentes siguen limitadas al Workspace predeterminado hasta que edites su acceso o vuelvas a conectarlas, y los Workspaces recién creados nunca se añaden automáticamente a una credencial existente. Los permisos se comprueban en cada petición: una revocación, una bajada de plan o la eliminación del Workspace surten efecto de inmediato.
GET /workspaces
Lista los Workspaces que la credencial puede seleccionar: primero su Workspace predeterminado y luego los Workspaces
gestionados concedidos, por nombre. Este endpoint no acepta workspace_id.
curl https://fintable.io/api/v2/workspaces \
-H "Authorization: Bearer YOUR_TOKEN"
{
"data": [
{
"id": "ws_01K2F3A6QH2D5J7M9V4X8N1BRC",
"name": "Nate",
"kind": "primary",
"is_default": true
},
{
"id": "ws_01K2F3C41W8B6Z0H7R9T5Y2QPM",
"name": "Client A",
"kind": "managed",
"is_default": false
}
]
}
| Campo | Tipo | Significado |
|---|---|---|
id |
string | ID público opaco del Workspace: el valor que pasas como workspace_id |
name |
string | Nombre visible del Workspace |
kind |
string | Relación de facturación: primary posee el plan; managed está cubierto por su principal |
is_default |
boolean | true para el Workspace usado cuando se omite workspace_id |
Seleccionar un Workspace
Todos los endpoints vinculados a Workspaces aceptan un parámetro de consulta workspace_id opcional — en todos
los métodos HTTP. Siempre es un parámetro de consulta: los cuerpos de las peticiones contienen solo datos del
recurso, y un campo workspace_id en un cuerpo JSON nunca selecciona un Workspace.
# Leer las transacciones de un cliente
curl "https://fintable.io/api/v2/transactions?workspace_id=ws_01K2F3C41W8B6Z0H7R9T5Y2QPM&limit=100" \
-H "Authorization: Bearer YOUR_TOKEN"
# Renombrar una cuenta dentro de ese Workspace (selector en la consulta, el cuerpo son datos del recurso)
curl -X PATCH "https://fintable.io/api/v2/accounts/1234?workspace_id=ws_01K2F3C41W8B6Z0H7R9T5Y2QPM" \
-H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \
-d '{"display_name": "Client A Checking"}'
# Iniciar una sincronización allí
curl -X POST "https://fintable.io/api/v2/sync?workspace_id=ws_01K2F3C41W8B6Z0H7R9T5Y2QPM" \
-H "Authorization: Bearer YOUR_TOKEN"
# Crear un enlace de conexión que cree el banco dentro de ese Workspace
curl -X POST "https://fintable.io/api/v2/connections/link?workspace_id=ws_01K2F3C41W8B6Z0H7R9T5Y2QPM" \
-H "Authorization: Bearer YOUR_TOKEN"
El selector se aplica a /connections, /accounts, /transactions, /sync, /integrations y a todos los
endpoints /categorizer/*, incluidas sus rutas de miembro y de enlaces. No se aplica a:
/me— siempre describe al dueño autenticado de la credencial y su estado de facturación;workspace_idnunca lo cambia/workspaces— define los valores válidos del selector- los endpoints públicos (
/guide,/docs,/openapi.json,/institutions,/rates,/prices) /feedback— contacta con soporte en lugar de acceder a datos del Workspace
Detalles de comportamiento:
- Omitir
workspace_idconserva exactamente el comportamiento actual: la credencial opera sobre su propio Workspace predeterminado. Pasar el ID de tu propio Workspace equivale a omitirlo. - Las respuestas correctas de endpoints vinculados añaden un
workspace_idde nivel superior junto adata/next_cursor:{"data": [...], "workspace_id": "ws_...", "next_cursor": null}. - Un
workspace_iddesconocido, mal formado, eliminado o no concedido devuelve el sobre de errornot_foundestándar: desconocido y no autorizado son deliberadamente indistinguibles. - Los cursores de paginación de transacciones quedan ligados al Workspace para el que se emitieron; reutilizar un
cursor contra otro Workspace falla con
invalid_cursor. Pide cada página con el mismoworkspace_id. - Los límites de peticiones pertenecen a la credencial/dueño, no al Workspace seleccionado: seleccionar varios Workspaces nunca multiplica una cuota.
- Los enlaces de conexión creados con
workspace_idcrean o reconectan bancos dentro de ese Workspace. El flujo del navegador pide tu contraseña (la del dueño de la credencial) y continúa en el Workspace de destino.
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; el volumen es max(abs(total de entradas), abs(total de salidas)) en todas las cuentas bancarias y subcuentas habilitadas; 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 proveedor detrás de esta conexión, p. ej. PLAID, NORDIGEN, AKOYA, FINICITY, MERCURY, PRIVATBANK-BIZ, MONOBANK, 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 | false cuando el proveedor informa de un problema o falla la última sincronización |
status_text |
string | Estado legible: OK, Last sync failed., 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",
"ext_id": "Qg39dj7M9vIqwQrybDRni1oMKAAvx8s4vO4yO",
"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_... |
ext_id |
string | El id propio del proveedor: la columna **Plaid Account ID de tus hojas. Ver cómo casar filas de la hoja |
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",
"ext_id": "3k3OMr7qdPtD8oXPPBNZtarZeDDPwwfoPX0ED",
"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_... |
ext_id |
string | El id propio del proveedor: la columna **Plaid TX ID de tus hojas. Ver cómo casar filas de la hoja |
account_id |
string | La Cuenta a la que pertenece esta transacción: su id, no la columna **Plaid Account ID (detalles) |
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.
- Tus reglas son lo único que categoriza. No se categoriza nada en el momento de la
sincronización, y Fintable nunca asigna una categoría a partir del enriquecimiento de
comercio del propio proveedor, así que esto funciona igual en Plaid, GoCardless, Akoya
y el resto. Una transacción con
category: nullsignifica que ninguna regla la ha hecho coincidir; esa es toda la explicación.POST /categorizer/syncvuelve a pasar tus reglas actuales por todo el historial, pero no puede inventarse una categoría que tus reglas no asignan. Para ver hasta dónde llegan tus reglas ahora mismo, consultaGET /categorizer/status?include=coverage.
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": "3k3OMr7qdPtD8oXPPBNZtarZeDDPwwfoPX0ED",
"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. fin_id y ext_id son el id y el
ext_id de la transacción en la API.
Una advertencia que conviene tener en cuenta al diseñar reglas: payee es tan bueno como
los datos que envía el banco. Muchas conexiones —sobre todo las de banca abierta— no
envían ningún nombre de comercio, y en esas filas payee es null. Una regla que deba
funcionar en todas partes debería basarse en description, y recurrir a raw para los
campos propios del proveedor cuando necesite más.
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.
Añade ?include=coverage para ver qué parte de tu historial alcanzan realmente tus
reglas:
{
"data": {
"settled": true,
"coverage": {
"total": 4527,
"categorized": 2627,
"uncategorized": 1900,
"manual_override": 12
},
"...": "..."
}
}
total cuenta todas las transacciones que considera el categorizador, categorized las
que llevan categoría y manual_override las que fijaste a mano: las reglas nunca tocan
ese último grupo. Un uncategorized grande no es un fallo ni una limitación del
proveedor: es el número de transacciones que todavía no coinciden con ninguna regla tuya
(ver cómo se comportan las reglas). La cobertura es opcional porque calcularla
recorre todo tu conjunto de transacciones: no la pidas dentro de un bucle de consulta de
settled.
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, TABS, SNAPTRADE (GoCardless es NORDIGEN; las API bancarias directas son TABS) |
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.
Tipo de cambio
| Endpoint | Qué hace |
|---|---|
GET /rates |
Tipos de cambio de un día (y conversión) |
GET /rates/timeseries |
Una serie diaria de tipos entre dos fechas |
GET /rates/currencies |
Los códigos de divisa disponibles |
Tipos de cambio de divisas directamente del Banco Central Europeo. Igual que el directorio de instituciones, son públicos —sin autenticación—, porque son datos de referencia de dominio público. Útiles para presentar en una sola divisa un conjunto de cuentas multidivisa, o para valorar una transacción antigua al tipo que realmente se aplicaba en su fecha.
Dos particularidades de los datos del BCE que conviene conocer antes de construir sobre ellos:
- El BCE publica una vez por día hábil, sobre las 16:00 CET. No hay movimiento intradía, así que consultar más de una vez por hora no aporta nada.
- No hay ningún dato para fines de semana ni festivos. Si pides un sábado, obtendrás
el día hábil anterior: el campo
datede la respuesta siempre indica qué día se usó de verdad, así que fíate de él y no de lo que enviaste.
{
"data": {
"base": "USD",
"date": "2026-07-27",
"amount": "1",
"rates": {
"EUR": "0.878",
"GBP": "0.7509"
}
}
}
| Campo | Tipo | Significado |
|---|---|---|
base |
cadena | La divisa contra la que se cotizan los tipos |
date |
cadena | El día hábil en que se publicaron — puede ser anterior al que pediste |
amount |
cadena | Cuánto de base se convirtió; "1" (por defecto) da tipos puros |
rates |
objeto | Código de divisa → valor, como cadenas decimales exactas |
GET /rates
Parámetros de consulta:
| Parámetro | Significado |
|---|---|
base |
Código de tres letras de la divisa base. Por defecto EUR |
symbols |
Códigos a devolver — USD,GBP, o symbols[] repetido. Omítelo para todas |
date |
Una fecha histórica (YYYY-MM-DD), desde 1999-01-04. Por defecto, la última publicación |
amount |
Cantidad de base a convertir. Por defecto 1 |
# Los tipos de hoy
curl "https://fintable.io/api/v2/rates?base=USD&symbols=EUR,GBP"
# Convertir 250 USD a euros
curl "https://fintable.io/api/v2/rates?base=USD&symbols=EUR&amount=250"
# El tipo que se aplicaba un día concreto
curl "https://fintable.io/api/v2/rates?base=USD&symbols=EUR&date=2024-01-15"
Con amount, rates contiene esa cantidad ya convertida en lugar del tipo en bruto
—por eso amount se devuelve también en la respuesta.
GET /rates/timeseries
Un tipo por día hábil a lo largo de un rango. Los días que el BCE se saltó simplemente no
aparecen en rates: espera huecos y no los trates como errores.
| Parámetro | Significado |
|---|---|
start |
Primera fecha (YYYY-MM-DD) — obligatorio |
end |
Última fecha. Por defecto, la última publicación |
base |
Código de la divisa base. Por defecto EUR |
symbols |
Códigos a devolver |
amount |
Cantidad de base a convertir. Por defecto 1 |
curl "https://fintable.io/api/v2/rates/timeseries?start=2024-01-15&end=2024-01-18&base=USD&symbols=EUR"
{
"data": {
"base": "USD",
"start_date": "2024-01-15",
"end_date": "2024-01-18",
"amount": "1",
"rates": {
"2024-01-15": { "EUR": "0.91366" },
"2024-01-16": { "EUR": "0.91895" },
"2024-01-17": { "EUR": "0.91937" },
"2024-01-18": { "EUR": "0.91954" }
}
}
}
Como mucho 366 días por llamada; los rangos más largos devuelven un 422 pidiéndote que lo acotes.
GET /rates/currencies
Todos los códigos de divisa que publica el BCE, con su nombre — unos 30.
curl "https://fintable.io/api/v2/rates/currencies"
{
"data": [
{ "code": "AUD", "name": "Australian Dollar" },
{ "code": "BRL", "name": "Brazilian Real" }
]
}
Un código que no esté en esta lista (o que el BCE ya no publicara en la fecha que pediste
—HRK tras la entrada de Croacia en el euro, por ejemplo) devuelve un 422, no un 404.
Cotización de acciones
| Endpoint | Qué hace |
|---|---|
GET /prices |
Últimas cotizaciones de hasta 50 tickers |
GET /prices/{symbol} |
Última cotización de un ticker |
GET /prices/{symbol}/history |
Barras OHLCV históricas |
Cotizaciones de acciones cotizadas en EE. UU., también públicas —sin autenticación—.
Combínalas con tus posiciones de inversión para valorar una cartera, o con
GET /rates para expresar esos valores en tu propia divisa.
{
"data": [
{
"symbol": "AAPL",
"price": "183.63",
"currency": "USD",
"open": "182.16",
"high": "184.26",
"low": "180.93",
"previous_close": "185.92",
"change": "-2.29",
"change_percent": "-1.2317",
"volume": 65603010,
"trading_day": "2024-01-16",
"as_of": "2024-01-16T20:59:59Z",
"feed": "iex"
}
]
}
| Campo | Tipo | Significado |
|---|---|---|
symbol |
cadena | El ticker, siempre en mayúsculas |
price |
cadena | Precio de la última operación; si no la hay, el cierre del día |
currency |
cadena | La divisa del mercado — USD para acciones estadounidenses |
open high low |
cadena | null | El rango de la sesión en curso |
previous_close |
cadena | null | Cierre de la sesión anterior |
change |
cadena | null | price − previous_close |
change_percent |
cadena | null | El mismo movimiento en porcentaje |
volume |
entero | null | Acciones negociadas hoy hasta el momento |
trading_day |
cadena | null | La sesión a la que corresponden las cifras del día |
as_of |
cadena | null | Cuándo se observó el precio |
feed |
cadena | La fuente de datos de mercado detrás de la cotización |
Las cotizaciones son cadenas decimales exactas, como todos los números de esta API.
Merece la pena fijarse en el campo feed. Con iex —el valor por defecto— las cifras
proceden solo del mercado IEX y no de la cinta consolidada, así que volume es el volumen
de IEX, una fracción pequeña del total realmente negociado, y price puede diferir algo de
la última operación consolidada. Sirve para valorar una posición, pero no es una cotización
oficial. Los valores poco líquidos pueden no negociarse en IEX un día dado: en ese caso
price recurre al cierre de la sesión.
GET /prices
| Parámetro | Significado |
|---|---|
symbols |
Tickers separados por comas — obligatorio, como mucho 50 por llamada |
curl "https://fintable.io/api/v2/prices?symbols=AAPL,MSFT,NVDA"
Los resultados llegan en el mismo orden en que los pediste. Los tickers sin datos se omiten de la respuesta en lugar de hacer fallar toda la petición, así que comprueba lo que llega en vez de dar por hecho que la lista coincide con la que enviaste. La ruta de un solo ticker hace justo lo contrario: devuelve 404.
GET /prices/{symbol}
Un ticker, el mismo objeto pero sin el array. Devuelve 404 cuando no hay datos de cotización.
curl "https://fintable.io/api/v2/prices/AAPL"
GET /prices/{symbol}/history
Barras históricas, ajustadas por splits y dividendos, de modo que una ventana de varios años es directamente comparable de principio a fin.
| Parámetro | Significado |
|---|---|
timeframe |
1min, 5min, 15min, 1hour, 1day, 1week, 1month. Por defecto 1day |
start |
Primera fecha (YYYY-MM-DD) |
end |
Última fecha (YYYY-MM-DD) |
limit |
Máximo de barras, 1–1000. Por defecto 1000 |
curl "https://fintable.io/api/v2/prices/AAPL/history?timeframe=1day&start=2024-01-02&end=2024-01-31"
{
"data": {
"symbol": "AAPL",
"timeframe": "1day",
"currency": "USD",
"feed": "iex",
"bars": [
{
"timestamp": "2024-01-02T05:00:00Z",
"date": "2024-01-02",
"open": "187.15",
"high": "188.44",
"low": "183.89",
"close": "185.64",
"volume": 1795262,
"trade_count": 18557,
"vwap": "185.9"
}
]
}
}
Un ticker o un rango sin barras devuelve 404.
Comentarios
| Endpoint | Qué hace |
|---|---|
POST /feedback |
Envía un correo al equipo de Fintable y recibe una respuesta humana |
¿Algo roto, incompleto o confuso? Cuéntanoslo desde donde estés — tu código, tu terminal
o tu asistente de IA — sin salir a abrir una página de soporte. El mensaje llega al mismo
buzón que un equipo de soporte humano atiende todo el día, con tu dirección en
Reply-To, y recibes respuesta en un plazo de 2 días hábiles, normalmente mucho
antes.
POST /feedback
| Campo | Tipo | Significado |
|---|---|---|
message |
string | Obligatorio. El mensaje en sí, de 10 a 10000 caracteres. El detalle es lo que hace útil la respuesta (ver abajo) |
subject |
string | Un resumen de una línea para el buzón |
email |
string | Dónde responder. Por defecto, el correo de tu cuenta |
screenshot |
string | Una imagen en base64 (vale una URL data:): PNG, JPEG, WebP o GIF, hasta 2 MB decodificados |
screenshot_filename |
string | Un nombre para el adjunto, p. ej. sync-error.png |
Incluye todo lo que puedas: qué estabas haciendo, qué ocurrió, qué esperabas en su lugar, el texto exacto del error, los ids de conexión o cuenta implicados y cuándo pasó. Una captura de lo que estás viendo vale por varios párrafos. Un informe vago te cuesta una ida y vuelta de más.
curl -X POST https://fintable.io/api/v2/feedback \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": "Faltan holdings en mi cuenta de brokerage",
"message": "acct_1182 se sincroniza bien desde mayo, pero GET /accounts/acct_1182/holdings devuelve una lista vacía desde el 2026-07-28. La conexión indica estado ok y una sincronización el 2026-08-03T06:12:00Z.",
"screenshot": "iVBORw0KGgoAAAANSUhEUg..."
}'
{
"data": {
"sent": true,
"reply_to": "[email protected]",
"screenshot_attached": true,
"message": "Thanks — your message is with the Fintable team..."
}
}
Un 2xx significa que el correo se envió de verdad, no solo que quedó en cola. Sirve
cualquier token, incluso uno de solo lectura. El límite es de 2 por hora y 5 por día,
y solo cuentan los mensajes que enviamos de verdad: una petición rechazada no te cuesta
nada.
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 |
Casar objetos de la API con las filas de tu hoja
Si Fintable ya sincroniza en una base de Airtable o en una Google Sheet, tarde o temprano querrás alinear esas filas con la API: una sola vez, para adoptar la API sobre una base existente, o de forma continua.
No cases por fecha + importe + descripción. Funciona para la mayoría de las filas y falla en silencio justo en las que importan: dos transferencias de nómina idénticas en la misma semana son indistinguibles así.
Casa por ext_id. Es el valor que Fintable escribe en la columna clave de cada destino
sincronizado:
| Columna de la hoja | Campo de la API | Notas |
|---|---|---|
**Plaid TX ID |
ext_id en Transacción |
Único dentro de su cuenta |
**Plaid Account ID |
ext_id en Cuenta |
El identificador de la cuenta en el proveedor |
El Plaid de esos nombres de columna es heredado. Se nombraron cuando Plaid era el
único proveedor y conservan el mismo nombre en GoCardless, Akoya, SnapTrade y todo lo
demás. El valor no es un id de Plaid: es como llame a ese objeto el proveedor que hay
detrás de la conexión.
ext_id no es id. id (tx_..., acc_...) es el identificador propio de Fintable
y es lo que aceptan y devuelven todos los endpoints; ext_id existe solo para este cruce.
Trátalo también como opaco: su forma depende del proveedor (un id de Plaid como
3k3OMr7qdPtD8oXPPBNZtarZeDDPwwfoPX0ED, uno compuesto como
7863464615930617517--a7a655b9c42ea4f4ad246543c9e9f044 en una conexión de GoCardless) y
esas formas no son un contrato. Compáralo, no lo parsees.
En las cuentas es donde esto duele. **Plaid Account ID no es el account_id de la API. El account_id de una
transacción es el id de la cuenta en Fintable —el mismo valor que id en Cuenta—, mientras que la columna
de la hoja lleva el id del proveedor, es decir el ext_id de esa misma cuenta. Son dos valores distintos para la misma
cuenta, así que cruzar tu tabla de Cuentas por account_id no encontrará nada, y en silencio. Cruza por ext_id:
GET /accounts devuelve ambos, así que una sola pasada te da la tabla de equivalencias.
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 |
POST /feedback |
2/hora y 5/día por cuenta (solo mensajes enviados) |
GET /institutions público |
60/min por IP |
GET /rates (y /rates/*) público |
60/min por IP |
GET /prices (y /prices/*) 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, /rates, /prices) se pueden
cachear hasta una hora. Eso incluye las cotizaciones, así que un precio puede ir hasta una
hora por detrás del mercado: cada uno lleva un as_of que indica cuándo se observó
realmente. De sobra para valorar una cartera; no está pensado para operar.
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:
envíalo directamente desde tu código con POST /feedback (o pide a tu
asistente de IA que use la herramienta MCP feedback),
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.