Skip to content

Ingresos ​

CRUD sobre los ingresos (ventas) de tu empresa. Cada ingreso pertenece a una cuenta y opcionalmente a un cliente, una categoría, un proyecto y un empleado/vendedor.

Efectos colaterales ​

Crear un Income por API dispara los mismos efectos que crearlo desde la app:

  • Suma amount_cents (o paid_amount_cents si lo envías) al balance de la cuenta — crea un Deposit interno.
  • Si los items referencian productos, incrementa los contadores de venta de cada producto.
  • Si los productos tienen inventario activado, reserva los materiales/unidades correspondientes.
  • Dispara automatizaciones con trigger sale_created que estén activas.

Period locking: si tu contabilidad cerró el periodo (mes) al que pertenece occurred_at, recibirás 422 period_locked. La API key no puede saltarse cierres — usa la app web si necesitas re-abrir.

El objeto Income ​

json
{
  "id":                       "65c2a1f1234567890abcdef0",
  "occurred_at":              "2026-05-20T14:23:11Z",
  "description":              "Servicio web cliente Laura",
  "amount_cents":             1500000,
  "amount":                   15000.0,
  "paid_amount_cents":        1500000,
  "paid_amount":              15000.0,
  "currency":                 "mxn",
  "paid":                     true,
  "refunded":                 false,
  "from_account_receivable":  false,
  "account_id":               "65b3a1f1234567890abcdef0",
  "account_name":             "BBVA MXN",
  "customer_id":              "65a1b2c3d4e5f6789012abcd",
  "customer_name":            "Laura Méndez",
  "category_id":              null,
  "category_name":            "Servicios",
  "project_id":               null,
  "project_name":             null,
  "employee_id":              null,
  "employee_name":            null,
  "sale_id":                  "65c2a1f1234567890abcdef0",
  "recurring_transaction_id": null,
  "metadata":                 { "external_ref": "cobro_2026_09", "orderId": "ord_123" },
  "created_at":               "2026-05-20T14:23:12Z",
  "updated_at":               "2026-05-20T14:23:12Z"
}

Sobre GET /incomes/:id el response también incluye items (line items embedded).

Campos ​

CampoTipoDescripción
idstringObjectId.
occurred_atstringFecha del ingreso (ISO 8601).
descriptionstringTexto libre.
amount_cents / amountint / floatTotal en centavos / decimal.
paid_amount_centsintMonto efectivamente cobrado (puede ser menor a amount_cents para crédito parcial).
currencystringISO 4217 lowercase.
paidbooltrue si está cobrado al 100% — false genera una cuenta por cobrar.
refundedboolMarca si fue reembolsado.
from_account_receivablebooltrue cuando el ingreso se creó al cerrar un AR (no lo envíes manualmente).
account_id / account_namestringCuenta destino.
customer_id / customer_namestringCliente (opcional).
category_id / category_namestringCategoría (opcional).
project_id / project_namestringProyecto (opcional).
employee_id / employee_namestringVendedor / responsable (opcional).
sale_idstringLa venta a la que pertenece este cobro, si la hay.
recurring_transaction_idstringLa recurrencia que lo generó, si la hay.
metadataobjectTu bolsa libre. Ver abajo.

metadata ​

Un objeto plano donde guardas tus identificadores. Vuelve tal cual en cada lectura y, lo importante, dentro de cada webhook sobre este ingreso.

Es lo que convierte un aviso de «se pagó el ingreso 65c2a1f...» —un id de Finova que no sabes a qué corresponde— en uno de «se pagó tu orden ord_123».

json
{
  "data": {
    "account_id":   "65b3a1f1234567890abcdef0",
    "amount_cents": 150000,
    "currency":     "mxn",
    "occurred_at":  "2026-09-17T12:00:00Z",
    "metadata": {
      "external_ref": "cobro_2026_09",
      "orderId":      "ord_123",
      "clientId":     "portal-clientes"
    }
  }
}

Límites ​

Claveshasta 50
Longitud de clave40 bytes
Longitud de valor500 bytes
Anidamientoninguno — sólo valores simples, ni objetos ni listas

Un valor null borra la clave. Fuera de esos límites, 422 invalid_metadata.

Son los mismos límites que Stripe, a propósito: si ya integraste con ellos, no tienes que aprender otra regla.

external_ref: idempotencia del alta ​

metadata.external_ref es especial. Lleva un índice único por empresa, así que si repites el POST con la misma referencia, Finova devuelve 200 con el ingreso que ya existía en lugar de crear un segundo.

Sirve para el caso que la cabecera Idempotency-Key no cubre: aquel en que se pierde la respuesta y reintentas horas después, o desde otro proceso. Reintentar a ciegas sin esto duplica un movimiento contable —dinero contado dos veces— y desde la contabilidad no hay forma de distinguir el duplicado del original.

Ver idempotencia.


Listar ingresos ​

GET/api/v1/incomesread-incomes

Paginado estándar. Ver guía de paginación.

Acepta search (alias q): búsqueda full-text/autocomplete sobre description, category_name, customer_name, employee_name y account_name. Devuelve hasta los 100 ingresos más relevantes, luego paginados.


Obtener un ingreso ​

GET/api/v1/incomes/:idread-incomes

Devuelve el ingreso completo incluyendo items (line items embedded).


Crear un ingreso ​

POST/api/v1/incomescreate-incomes

Campos aceptados en data: account_id (requerido), customer_id, category_id, amount_cents (requerido), paid_amount_cents, currency, occurred_at, description, paid, project_id, employee_id, items, metadata.

¿Vas a facturar esto?

Entonces registra una venta, no un ingreso. Un CFDI se emite sobre la venta: doce cobros de una suscripción anual son una sola factura, y desde un ingreso suelto no se pueden agrupar.

items es un array de line items embedded. Cada item:

CampoTipoDescripción
product_idstringRequerido. El producto del catálogo. Ver la nota de abajo.
quantityintCantidad. Por defecto 1.
unit_price_centsintPrecio unitario en centavos. Si lo omites, se toma el del producto.
namestringEtiqueta del concepto. Alias de description.
variant_idstringPara productos variables.
option_value_idstringValor de opción elegido.
tax_percentfloatTasa de IVA del concepto. Alias de tax_rate_percent.
price_includes_taxboolSi unit_price_cents ya trae el IVA.
tax_exempt / tax_zero_ratedboolExento o tasa 0% ante el SAT. No es lo mismo que "sin IVA".
sat_key / sat_unit_key / sat_unit_namestringClaves del SAT. Si las omites, se heredan del producto.
componentsarrayPara productos compuestos: { component_id, quantity }.

product_id es obligatorio en cada concepto

En Finova un concepto apunta siempre a algo del catálogo: es lo que permite descontar inventario, mover los contadores de unidades vendidas y heredar las claves del SAT al facturar. Un concepto suelto sin producto se rechaza con 422.

Si lo que vendes no está en el catálogo, créalo primero con POST /products — un producto de tipo service sirve para conceptos que no llevan inventario.

discount_cents ya no se documenta

Nunca llegó a guardarse: no existe descuento por concepto en el modelo. Si lo mandas, se ignora en silencio. Para aplicar un descuento, baja unit_price_cents.

Al leer (GET /incomes/:id), cada item incluye además su id y unit_price (decimal, espejo de unit_price_cents).

bash
curl -X POST https://developers.fi-nova.com/api/v1/incomes \
  -H 'Authorization: Bearer finova_sk_TU_SECRETO' \
  -H 'Content-Type: application/json' \
  -d '{
    "data": {
      "account_id":   "65b3a1f1234567890abcdef0",
      "customer_id":  "65a1b2c3d4e5f6789012abcd",
      "amount_cents": 1500000,
      "currency":     "mxn",
      "occurred_at":  "2026-05-20T14:23:11Z",
      "description":  "Servicio web cliente Laura",
      "paid":         true,
      "items": [
        {
          "product_id":       "65b0a1f1234567890abcdef0",
          "name":             "Diseño",
          "quantity":         1,
          "unit_price_cents": 1500000
        }
      ]
    }
  }'

Actualizar un ingreso ​

PUT/api/v1/incomes/:idupdate-incomes

Mismos campos que create. Si cambias occurred_at a otro periodo, ambos periodos deben estar abiertos — si alguno está bloqueado, 422 period_locked.


Eliminar un ingreso ​

DELETE/api/v1/incomes/:iddelete-incomes

Eliminar revierte los efectos colaterales (depósito, balance de cuenta, contadores de producto, reserva de inventario). Si el ingreso está en un periodo cerrado, recibes 422 period_locked.

Hecho con cuidado por Finova.