Skip to content

Notas de versión ​

Historial de cambios de la API pública de Finova for Developers. Cada release deja también su huella en las respuestas: todos los endpoints regresan los headers X-Finova-API-Version y X-Finova-API-Released-At, así sabes exactamente contra qué versión está corriendo la API cuando depuras una integración.

Si te interesa saber qué cambios pueden ocurrir sin avisar y cuáles requieren una versión nueva, lee la guía de Versionado.


1.0.06 — 2026-09-17 ​

Webhooks salientes, ventas, facturas (CFDI), recurrentes y metadata. Cambios aditivos — no afecta integraciones existentes.

Webhooks ​

Hasta ahora, la única forma de enterarte de algo en Finova era preguntar: un GET /incomes cada pocos minutos que casi siempre devolvía lo mismo, y que de todas formas llegaba tarde. Ya no.

  • Nuevo recurso /webhook-endpoints: registras una URL, eliges a qué eventos se suscribe y Finova te avisa. Scopes read- / create- / update- / delete-webhook_endpoints.
  • Firma HMAC-SHA256 con marca de tiempo, formato Stripe: X-Finova-Signature: t=<segundos>,v1=<hex> sobre "{t}.{cuerpo crudo}". La marca va dentro de lo firmado, así que una petición capturada no se puede reenviar mañana.
  • Id de evento estable en cada aviso, el mismo en todos los intentos: es lo que te permite descartar un reenvío.
  • Seis reintentos con espera creciente (inmediato, 1 min, 5 min, 30 min, 2 h, 6 h). Tras 20 fallos seguidos el endpoint se desactiva solo.
  • Comodín de suscripción: invoice.* cubre los cuatro de factura, * cubre todos. Solo al final y sobre un segmento entero.
  • Historial de entregas: GET /webhook-endpoints/:id/deliveries contesta «no me llegó el aviso» — si el evento se emitió, qué contestó tu servidor y cuándo toca el siguiente intento. Se guardan 30 días.

16 eventos: sale.created, sale.paid, sale.refunded, income.created, income.paid, income.deleted, recurring.created, recurring.occurrence_created, recurring.ended, recurring.deactivated, invoice.emitted, invoice.cancelled, invoice.substituted, invoice.payment_complement_added, customer.created, lead.created.

Ver la guía de webhooks.

metadata ​

  • Ingresos, ventas y recurrentes aceptan ahora un objeto metadata: una bolsa libre de primer nivel donde guardas tus identificadores. Hasta 50 claves, sin anidamiento — los mismos límites que Stripe.

  • Vuelve tal cual en cada lectura y, lo importante, dentro de cada webhook sobre ese recurso. Es lo que convierte un aviso sobre 65c2a1f... —un id de Finova que no sabes a qué corresponde— en uno sobre tu propia orden ord_123.

  • metadata.external_ref hace el alta idempotente para siempre. Lleva índice único por empresa: si repites el POST con la misma referencia, recibes 200 con el recurso que ya existía en vez de un segundo.

    La cabecera Idempotency-Key cubre el reintento inmediato (24 h de caché). Esto cubre el otro caso, el que duele: el POST llegó, la respuesta se perdió, y tres horas después un barrido de reconciliación lo vuelve a registrar. Sin external_ref eso duplica un movimiento contable —dinero contado dos veces— y desde la contabilidad no hay forma de distinguir la copia del original.

Ver idempotencia.

Ventas ​

  • Nuevo recurso /sales (read-sales, create-sales), con su esquema de cobro: contado, meses, anticipo + saldo, o esquema propio.
  • Es sobre lo que se factura. Una factura no se corresponde uno a uno con un cobro: una orden pagada en tres parcialidades se factura una vez, y sin agrupar, doce meses de una suscripción serían doce facturas donde el cliente espera una.
  • Registrar una venta dispara los mismos efectos que hacerlo desde la aplicación: ingreso o cuenta por cobrar, depósito, inventario, contadores de unidades vendidas y automatizaciones de sale_created.
  • Sin DELETE: deshacer una venta no es borrarla, es una devolución, que deja rastro contable.

Facturas (CFDI 4.0) ​

  • Nuevo recurso /sales/:id/invoice y el libro de facturas en /invoices. Requiere el módulo Facturación.
  • Emitir, cancelar, sustituir, timbrar complementos de pago y descargar el PDF y el XML.
  • El timbrado responde 202: llama al proveedor ante el SAT, que tarda segundos y a veces más. Espera el webhook invoice.emitted en lugar de sondear.
  • Cancelar pide delete-invoices, no create-invoices: es irreversible ante el SAT y no debería caber dentro de la misma llave que emite.

Transacciones recurrentes ​

  • Nuevo recurso /recurring-transactions (read-, create-, update-), con el calendario completo: frecuencia, intervalo, días, inicio, fin y próxima corrida.
  • El index no es un extra. Al migrar una cartera es muy probable que varias recurrencias ya estén dadas de alta en Finova, capturadas a mano; crear una segunda sobre el mismo cliente genera el mismo mes dos veces. Buscar antes de crear es la diferencia entre migrar de una vez y migrar rezando.
  • No escribas tú el periodo de cada mes. El generador de Finova corre a diario, se pone al día si falta un día y su idempotencia es un índice único en la base. Suscríbete a recurring.occurrence_created y anótalo.
  • Sin DELETE: para cancelar una suscripción se manda active: false, que deja el histórico en pie y emite recurring.deactivated.

Ingresos ​

  • Ahora exponen metadata, sale_id y recurring_transaction_id.

1.0.05 — 2026-07-15 ​

Filtro has_products y conteo de productos por categoría (con subárbol). Cambios aditivos — no afecta integraciones existentes.

Categorías de producto ​

  • Ahora exponen dos conteos:
    • product_count: productos asignados directamente a la categoría (no cuenta los de sus hijas).
    • product_count_including_descendants: productos del subárbol — los directos más los de todas sus categorías descendientes. Para una categoría hoja coincide con product_count; para un padre como "Lentes" incluye lo que cuelga de "De sol", "Graduados", etc.
  • Ambos conteos cuentan solo productos activos (is_active: true) — misma visibilidad que el default de GET /products, no incluyen borradores/desactivados.
  • Nuevo filtro ?has_products=true en GET /product-categories: devuelve solo las categorías con al menos un producto activo en su subárbol. Así una categoría padre sin productos directos pero con productos en sus hijas sí aparece.

Marcas ​

  • Nuevo filtro ?has_products=true en GET /brands: devuelve solo las marcas con al menos un producto activo asignado (product_count > 0).

General ​

  • El product_count de marcas y categorías cuenta solo productos activos y ahora es un valor cacheado: listar ya no dispara una consulta de conteo por página.

1.0.04 — 2026-06-22 ​

Filtros de productos (marca/categoría), totales filtrados y conteo de productos por marca. Cambios aditivos — no afecta integraciones existentes.

Productos ​

  • Filtro por marca y categoría en GET /products:
    • brand (alias brand_id) y category (alias product_category_id).
    • Cada uno acepta un solo valor o una lista separada por comas que mezcla ObjectIds y slugs, ej. ?category=comida,bebidas o ?brand=acme,5f....
    • Trae los productos que pertenezcan a cualquiera de los valores listados. Un id/slug desconocido no aporta resultados — el filtro nunca se ignora silenciosamente.
  • ?include_filtered_total=true: agrega filtered_total y filtered_total_pages a meta con el conteo del resultado ya filtrado (por q/search, brand, category). Útil para "Mostrando X de Y" cuando filtras.
    • Distinto de include_total, que cuenta todo el catálogo e ignora los filtros.
    • El conteo está acotado a 10000 (si lo supera, se satura ahí y meta.filtered_total_capped viene en true); con q/search activo el tope es 100.

Marcas ​

  • Las marcas ahora exponen product_count: número de productos no eliminados que apuntan a esa marca en tu empresa. En GET /brands se calcula con una sola agregación por página (no una consulta por marca).

1.0.03 — 2026-06-09 ​

Formularios (Forms) multi-idioma. Cambios aditivos — no afecta integraciones existentes.

Formularios ​

  • Nuevo recurso Formularios: lee la definición de un formulario de Marketing para renderizarlo en tu sitio y envía las respuestas de vuelta.
    • GET /forms y GET /forms/:id (scope read-forms) — :id acepta ObjectId o slug.
    • GET /forms/:form_id/submissions y GET /forms/:form_id/submissions/:id (scope read-forms).
    • POST /forms/:form_id/submissions (scope create-form_submissions) — registra una respuesta; valida contra los campos del formulario y dispara las post-acciones (lead/cliente, email, notificación) en background.
  • Multi-idioma: el formulario declara locales + default_locale. Pasa ?locale= en los GET para recibir title/description y label/placeholder/options de cada campo ya resueltos en ese idioma; además siempre vienen los mapas *_translations con todos los idiomas. En POST de respuestas, data.locale (o ?locale=) guarda el idioma usado y determina, entre otras cosas, qué plantilla de email se envía.

1.0.02 — 2026-06-07 ​

Productos multi-moneda. ⚠️ Cambio incompatible (breaking change). Un producto ya no está atado a una sola moneda: ahora tiene un precio por divisa.

Productos ​

  • Eliminados los campos escalares currency y price_cents del objeto product, de cada variant, y extra_price_cents de cada component.
  • Nuevos campos de precio como mapa multi-moneda { <iso4217_lowercase>: <centavos> }:
    • product.prices_cents — ej. { "mxn": 450000, "usd": 25000 }.
    • variant.prices_cents — vacío cuando la variante hereda el precio del producto.
    • component.extra_prices_cents — ajuste de precio del componente por divisa.
  • En la escritura (POST/PATCH) se aceptan prices_cents / extra_prices_cents (objeto divisa→centavos) y ya no se aceptan los currency / price_cents / extra_price_cents escalares.
  • Cada producto debe tener al menos una divisa con precio.
  • extra_price_cents de los valores de opción (option_types[].values[]) se mantiene escalar (sin cambio).

Migración: si tu integración leía product.price_cents / product.currency, cámbiala a product.prices_cents[<divisa>]. Para escribir, manda prices_cents: { "mxn": 450000 } en lugar de price_cents + currency.


1.0.01 — 2026-06-06 ​

Imágenes de producto, campos personalizados, categorías de producto y búsqueda por slug/sku. Cambios aditivos — tus integraciones existentes siguen funcionando sin cambios.

Productos ​

  • Nuevos campos en el producto: image_url, thumbnail_url y una galería images ([{ url, alt, position }]).
  • El detalle (GET /products/:id) ahora incluye variants, option_types, components e inventory — disponibilidad total y desglose por almacén/rack (solo cantidades, nunca costos).
  • Nuevos campos personalizados en productos y marcas: especificaciones/atributos estructurados definidos por el usuario (texto, número, booleano, listas, objetos, listas de objetos y archivos) para la tabla de specs de la ficha. En la lectura se devuelven aplanados como claves de primer nivel del recurso (ej. "voltaje": 110), no como un array custom_properties; en la escritura se pasan como el array custom_properties dentro de data. Las claves que coincidan con un campo reservado del recurso se descartan. Ver Campos personalizados.
  • Nuevos endpoints para gestionar imágenes (multipart): subir/eliminar la imagen principal del producto y la imagen de cada variante.
  • El CRUD acepta variants, option_types y components anidados (con semántica de crear/actualizar/eliminar por id + _destroy).
  • El objeto product ahora incluye product_category ({ id, name, slug }), la referencia estructurada a la categoría de catálogo. El campo category_name se mantiene (deprecado pero presente). La categoría de catálogo (en productos) es distinta de la categoría financiera (en ingresos/egresos), aunque compartan el nombre category_name.

Categorías de producto ​

  • Nuevo recurso /product-categories con CRUD completo: la taxonomía de catálogo con la que organizas tus productos (jerarquía padre/hijas, icon, imagen y product_count).
  • Cada categoría expone product_count (productos activos que apuntan a ella), icon (Material Symbol) e image_url.
  • Nuevo scope por credencial: read-product_categories (y create / update / delete).

Marcas ​

  • Nuevo logo_url (y el media item logo) en el objeto brand, editable desde la app o vía API (logo / logo_url). Útil para chips de marca y la cabecera de la página de marca.

Búsqueda por identificador ​

  • Los endpoints de detalle (GET /{recurso}/:id) aceptan ahora slug o sku además del id, resueltos dentro de tu empresa. Aplica a los recursos que tienen esos campos: productos (slug y sku), marcas, categorías de producto y ligas de agenda (slug). Ver Identificar un recurso.

1.0.0 — 2026-05-18 ​

Lanzamiento inicial de Finova for Developers.

Primera versión pública estable. La API y la forma de cada response es ahora un contrato — los cambios que rompan compatibilidad pasarán por una versión nueva (v2) con aviso por correo y al menos 12 meses corriendo en paralelo.

Lo que incluye ​

  • Endpoints disponibles: Productos, Clientes, Leads, Automatizaciones, Eventos del calendario, Ligas de agenda, Reservas, Usuarios y Empresa.
  • Autenticación con credenciales (finova_sk_...) y permisos a la medida por credencial. Lista blanca opcional de dominios desde donde se permite usar cada credencial.
  • Paginación tipo cursor (starting_after, ending_before) con tope automático para evitar páginas muy profundas. Detalles en la guía de Paginación.
  • Cache de respuestas opt-in (por cuenta) para endpoints de lectura, con invalidación automática cuando hay un cambio. Más en la guía de Cache.
  • Idempotencia: las llamadas que crean datos aceptan Idempotency-Key para que un reintento no duplique registros. Ver guía de Idempotencia.
  • Cobro por consulta real: cada llamada se factura por las consultas a la base de datos que ejecutó (incluyendo la validación de tu credencial, la verificación de que pertenezca a tu empresa y los permisos). Verás el conteo por llamada en el panel de uso, útil para optimizar tu integración.
  • Headers de versión: todas las respuestas incluyen X-Finova-API-Version y X-Finova-API-Released-At. Si más adelante necesitas reportar un problema, incluir estos dos valores ayuda a saber contra qué versión sucedió.

Cómo enterarte de cambios ​

  • Esta página se actualiza con cada release nuevo. Se ordena con la versión más reciente arriba.
  • Para cambios que rompen compatibilidad (siempre en una versión nueva, nunca dentro de v1), te llegará un correo al titular de la cuenta y verás un header Sunset en las respuestas de la versión que va a deprecarse.
  • Si exponemos algo en beta, lo verás con X-Finova-Beta: <feature> en la respuesta y un badge (beta) en la documentación. Sin esos marcadores, lo que estás usando es estable.

Hecho con cuidado por Finova.