Saltar al contenido

Guía

API de Holded: guía práctica para integrarla con tus herramientas

Cómo usar la API de Holded: obtener la clave, hacer la primera llamada, paginar resultados, recibir webhooks, respetar los límites y conectarla con n8n, tu web o tu CRM.

Por Óscar Gómez
7 min de lectura

Qué es la API de Holded y qué permite

Holded es un software de gestión muy extendido entre pymes y autónomos en España: facturación, contabilidad, inventario, CRM, proyectos y equipo en un mismo sitio. Su API permite que otras herramientas lean y escriban esos datos de forma automática, sin exportar ni copiar nada a mano.

Con la API v2 se puede trabajar, entre otras cosas, con:

  • Ventas: facturas, facturas rectificativas, proformas, presupuestos, pedidos de venta, albaranes y tickets, incluido el registro de pagos, el envío por email y la descarga del PDF.
  • Compras: facturas de compra, pedidos de compra, albaranes de entrada y reembolsos.
  • Catálogo y contactos: clientes, proveedores, productos, stock y almacenes.
  • Finanzas: asientos contables, plan de cuentas, cuentas bancarias y sus movimientos.
  • Equipo y proyectos: fichajes, ausencias y registros de horas por proyecto.
  • Bandeja de entrada: subida de documentos y conversión en documentos de compra.

Holded también publica un servidor MCP oficial, pensado para que asistentes de IA consulten y operen sobre tu cuenta.

Requisitos antes de empezar

  • Un plan de pago de Holded: la API no está disponible en el plan gratuito.
  • Acceso a la sección Desarrolladores de los ajustes (por defecto, usuarios propietarios o administradores, o roles con ese permiso).
  • Una herramienta para hacer peticiones HTTP: curl, Postman, tu lenguaje de programación o una plataforma de automatización como n8n.

Cómo obtener tu API key

  1. En Holded, abre Ajustes desde el menú de tu cuenta.
  2. En la sección Desarrolladores, entra en Credenciales.
  3. Pulsa Añadir API Token, elige los permisos que necesita la integración y crea el token.
  4. Copia el token y guárdalo en un lugar seguro: Holded solo lo muestra completo en el momento de crearlo.

Crea un token distinto para cada integración y dale solo los permisos que necesita. Si una integración solo lee facturas, no le des permiso para borrar contactos.

Tu primera llamada a la API

Todas las peticiones van a https://api.holded.com y se autentican con la cabecera Authorization: Bearer. Para comprobar que todo funciona, lista tus últimas facturas:

curl "https://api.holded.com/api/v2/invoices?limit=5" \
  -H "Authorization: Bearer TU_API_KEY" \
  -H "Accept: application/json"

Si la respuesta es un 401, la clave no es válida. Si es un 403, la clave existe pero no tiene el permiso que exige ese endpoint.

Paginación: cómo traer todos los resultados

Los listados de la API v2 usan paginación por cursor. Cada respuesta incluye los resultados en items, un cursor para pedir la página siguiente y un indicador has_more. El parámetro limit controla el tamaño de página: 25 por defecto y 100 como máximo.

Este ejemplo en JavaScript recorre todas las facturas y respeta los límites de la API si se alcanzan:

async function listarFacturas(apiKey) {
  const facturas = [];
  let cursor;

  do {
    const url = new URL('https://api.holded.com/api/v2/invoices');
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}`, Accept: 'application/json' },
    });

    if (res.status === 429) {
      const espera = Number(res.headers.get('Retry-After') ?? 60);
      await new Promise((resolve) => setTimeout(resolve, espera * 1000));
      continue;
    }
    if (!res.ok) throw new Error(`Holded respondió ${res.status}`);

    const data = await res.json();
    facturas.push(...data.items);
    cursor = data.has_more ? data.cursor : undefined;
  } while (cursor);

  return facturas;
}

Webhooks: enterarte de los cambios al momento

En lugar de preguntar a Holded cada pocos minutos si hay algo nuevo, lo eficiente es usar webhooks: Holded envía una petición POST a tu URL cada vez que ocurre un evento. Se configuran en Holded indicando una URL HTTPS pública y los eventos que te interesan.

Hay eventos para casi todos los documentos y entidades, por ejemplo:

  • invoice.create, invoice.update, invoice.approve e invoice.delete para facturas.
  • payment.create cuando se registra un pago.
  • contact.create y contact.update para clientes y proveedores.
  • product.update y stock.update para catálogo e inventario.

Cada petición incluye cabeceras como x-holded-webhook-event (el nombre del evento), x-holded-webhook-id (un identificador único, útil para no procesar dos veces el mismo evento) y x-holded-webhook-signature, una firma HMAC-SHA256 del cuerpo de la petición con el prefijo sha256=.

Verifica siempre esa firma antes de confiar en el contenido. En Node.js:

import crypto from 'node:crypto';

// rawBody: el cuerpo tal cual llega, sin parsear como JSON.
function firmaValida(rawBody, cabeceraFirma, secreto) {
  const esperada =
    'sha256=' + crypto.createHmac('sha256', secreto).update(rawBody).digest('hex');
  const a = Buffer.from(esperada);
  const b = Buffer.from(cabeceraFirma ?? '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Límites de uso de la API

La API aplica dos límites a la vez sobre toda la cuenta: uno por minuto, para frenar picos, y una cuota mensual según tu plan. Todas las claves de la misma cuenta comparten esos contadores, así que crear más tokens no da más margen.

Según la documentación de Holded en el momento de escribir esta guía:

Plan Peticiones por minuto Llamadas al mes
Plus 60 500
Basic 60 2.000
Standard 120 7.500
Advanced 120 30.000
Premium 600 100.000

Holded actualiza estas cifras, así que comprueba las de tu cuenta en el apartado de uso de la API de Desarrolladores. Al superar un límite, la API responde 429 Too Many Requests con una cabecera Retry-After que indica cuántos segundos esperar.

Con cuotas mensuales tan ajustadas en los planes pequeños, el diseño importa: usa webhooks en lugar de consultar periódicamente, guarda en caché los datos que cambian poco (productos, contactos, tipos de IVA) y evita sincronizaciones completas innecesarias.

API v1 y API v2: cuál usar

Muchos tutoriales y conectores antiguos usan la API v1, que se autentica con la clave en una cabecera llamada key y usa rutas como /api/invoicing/v1/.... Holded la mantiene disponible para propietarios y administradores por ahora, pero la v2 es la base actual: autenticación Bearer, permisos por token, paginación por cursor y webhooks firmados.

Para una integración nueva, usa la v2. Si mantienes una integración con la v1, conviene planificar la migración antes de que Holded la retire.

Ideas de integración con Holded

  • Tienda online: cada pedido pagado genera su factura en Holded con el cliente y los productos correctos, y el stock se actualiza solo.
  • Formulario web o CRM: un lead que se convierte en cliente se crea como contacto en Holded con sus datos fiscales, sin volver a teclearlos.
  • Presupuestos: cuando un presupuesto se acepta, se convierte en pedido o factura y se avisa al equipo.
  • Cobros: un informe semanal de facturas vencidas y recordatorios automáticos a los clientes.
  • Documentos de proveedores: las facturas recibidas por email se suben a la bandeja de entrada de Holded para su registro.
  • Proyectos: las horas registradas en otra herramienta se vuelcan en los proyectos de Holded para facturarlas.

Cómo conectar Holded con n8n

n8n es una forma práctica de integrar Holded sin desarrollar una aplicación completa:

  1. Crea en n8n una credencial de tipo Bearer (o Header Auth con la cabecera Authorization: Bearer TU_API_KEY).
  2. Usa el nodo HTTP Request para llamar a los endpoints de la API v2: listar facturas, crear contactos, registrar pagos.
  3. Para reaccionar a eventos, crea un flujo con el nodo Webhook, copia su URL de producción y regístrala en los webhooks de Holded.
  4. Valida la firma del webhook en un nodo Code antes de seguir procesando el evento.
  5. Añade un flujo de errores que te avise si una llamada falla o se alcanza el límite de la API.

Errores habituales

  • Poner la API key en el frontend de una web: cualquiera podría verla. Las llamadas a Holded siempre deben hacerse desde un servidor, una función serverless o una plataforma como n8n.
  • Consultar cada minuto si hay cambios: consume la cuota mensual en pocos días. Usa webhooks.
  • No controlar duplicados: un webhook puede llegar más de una vez. Usa x-holded-webhook-id para no crear dos veces la misma factura o contacto.
  • Dar todos los permisos a un token: si se filtra, el daño es mayor. Limita cada token a lo que necesita.
  • Ignorar los errores 429: reintentar inmediatamente solo empeora el bloqueo. Respeta Retry-After.

Si quieres conectar Holded con tu tienda online, tu CRM u otras herramientas, en MROS Studio diseñamos y mantenemos integraciones a medida, documentadas y a tu nombre.

Preguntas frecuentes

¿La API de Holded es gratis?

La API está incluida en los planes de pago de Holded, no en el plan gratuito. Cada plan tiene una cuota mensual de llamadas y un límite por minuto, así que el coste real depende de cómo diseñes la integración: usar webhooks y cachear datos reduce mucho el consumo.

¿Dónde se obtiene la API key de Holded?

En Holded, abre Ajustes y, en la sección Desarrolladores, entra en Credenciales. Ahí puedes crear un token, elegir sus permisos y copiarlo. Solo se muestra completo una vez, así que guárdalo en un gestor de secretos.

¿Qué diferencia hay entre la API v1 y la v2 de Holded?

La v2 usa autenticación Bearer, permisos por token, paginación por cursor y webhooks firmados. La v1 es la API clásica, con la clave en una cabecera llamada key. Holded mantiene la v1 disponible por ahora, pero para integraciones nuevas conviene usar la v2.

¿Se puede conectar Holded con n8n?

Sí. Desde n8n se llama a la API con el nodo HTTP Request y una credencial de tipo Bearer, y los eventos de Holded se reciben con el nodo Webhook. Así se pueden automatizar facturas, contactos o pedidos sin desarrollar una aplicación completa.