API para desarrolladores

Conecta Puli con tu software, Zapier o Make.

Una API REST sencilla para crear clientes, emitir facturas con VeriFactu, registrar cobros y gastos, y webhooks firmados que te avisan cuando pasa algo. Disponible en los planes Premium y Empresarial.

Autenticación

Crea una clave en Ajustes → API y webhooks. La clave se muestra una sola vez; guárdala como un secreto. Cada petición lleva la cabecera Authorization. Una clave de «solo lectura» no puede crear ni modificar nada.

curl https://puli.es/api/v1/me \
  -H "Authorization: Bearer puli_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  • Base: https://puli.es/api/v1
  • Formato: JSON en UTF-8; fechas ISO 8601 (2026-10-13); importes en euros; IVA en %.
  • Límite: 120 peticiones por minuto y clave. Pasado, 429 con Retry-After.
  • Paginación: limite (máx. 100) y cursor; la respuesta trae siguienteCursor o null.

Errores

Siempre con la misma forma: { "error": { "codigo": "…", "mensaje": "…" } }. Códigos: 400 datos_invalidos, 401 no_autenticado / clave_invalida, 402 plan_insuficiente, 403 sin_permiso, 404 no_encontrado, 409 conflicto (p. ej. límite del plan o factura ya emitida), 429 limite_peticiones.

Endpoints

MétodoRutaQué hacePermiso
GET/meEmpresa, plan y permisos de la clave. Úsalo para probar la conexión.lectura
GET/clientes?q=&nif=&limite=&cursor=Lista clientes (búsqueda por texto o NIF).lectura
POST/clientesCrea un cliente.escritura
GET/clientes/{id}Un cliente.lectura
PATCH/clientes/{id}Cambia solo los campos que envíes.escritura
DELETE/clientes/{id}Elimina un cliente.escritura
GET/productos?q=&activos=1Lista productos y servicios.lectura
POST/productosCrea un producto (precio base sin IVA).escritura
GET/productos/{id}Un producto.lectura
PATCH/productos/{id}Actualiza un producto.escritura
DELETE/productos/{id}Elimina un producto.escritura
GET/productos/{id}/movimientosMovimientos de stock del producto (ventas, compras, ajustes…).lectura
POST/productos/{id}/movimientosMueve stock: `entrada`, `salida`, `ajuste` (con signo), `recuento` o `traspaso`.escritura
GET/almacenesTus almacenes (el principal siempre existe).lectura
GET/facturas?estado=&desde=&hasta=&clienteId=&limite=&cursor=Lista facturas, de la más reciente a la más antigua.lectura
POST/facturasCrea un borrador. Con `"emitir": true` se numera y registra en VeriFactu en el acto.escritura
GET/facturas/{id}Una factura con líneas, totales, pagos y estado VeriFactu.lectura
POST/facturas/{id}/emitirEmite un borrador (número de serie y registro VeriFactu).escritura
POST/facturas/{id}/pagosRegistra un cobro. Si cubre el saldo, la factura pasa a pagada.escritura
GET/facturas/{id}/pdfEl PDF con el diseño de tu empresa.lectura
GET/presupuestos?estado=&desde=&hasta=Lista presupuestos.lectura
POST/presupuestosCrea un presupuesto numerado.escritura
GET/presupuestos/{id}Un presupuesto.lectura
GET/gastos?categoria=&desde=&hasta=Lista gastos.lectura
POST/gastosCrea un gasto (importe + IVA o líneas).escritura
GET/gastos/{id}Un gasto.lectura
GET/proyectos?estado=Lista proyectos con su resumen (horas, sin facturar, facturado, rentabilidad).lectura
POST/proyectosCrea un proyecto (nombre, clienteId, tarifaHora, presupuesto…).escritura
GET/proyectos/{id}Un proyecto con sus horas.lectura
PATCH/proyectos/{id}Cambia datos o estado del proyecto.escritura
POST/proyectos/{id}/horasRegistra horas (minutos, fecha, descripción, facturable).escritura
POST/proyectos/{id}/facturarCrea la factura con las horas pendientes (y gastos si `incluirGastos`); con `emitir` la numera.escritura
GET/webhooksTus webhooks.lectura
POST/webhooksCrea un webhook; devuelve el secreto una sola vez.escritura
PATCH/webhooks/{id}Cambia URL, eventos o activo.escritura
DELETE/webhooks/{id}Elimina un webhook.escritura
POST/webhooks/{id}/pruebaEnvía un evento `webhook.prueba` firmado.escritura
GET/webhooks/{id}/entregasÚltimas entregas con código HTTP y reintentos.lectura

Ejemplo: crear y emitir una factura

Necesitas el id de un cliente (créalo con POST /clientes o búscalo por NIF). El servidor calcula los totales, asigna el número de serie y registra la factura en VeriFactu.

curl -X POST https://puli.es/api/v1/facturas \
  -H "Authorization: Bearer puli_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "clienteId": "9D1F…",
    "fechaExpedicion": "2026-10-13",
    "descripcion": "Servicios de octubre",
    "lineas": [
      { "concepto": "Diseño web", "cantidad": 10, "precioUnitario": 60, "iva": 21 },
      { "productoId": "3F1B…", "concepto": "Taza (del catálogo: descuenta stock al emitir)", "cantidad": 2, "precioUnitario": 4, "iva": 21 },
      { "concepto": "Hosting", "precioUnitario": 20, "iva": 21, "descuento": 10 }
    ],
    "tipoIRPF": 15,
    "emitir": true
  }'

Respuesta (resumida):

{
  "factura": {
    "id": "7C2A…", "serie": "A", "numero": "2026-0042", "numeroCompleto": "A-2026-0042",
    "estado": "emitida", "fechaExpedicion": "2026-10-13T10:00:00.000Z",
    "cliente": { "id": "9D1F…", "nombre": "Acme SL", "nif": "B12345678", "email": "admin@acme.es" },
    "baseImponible": 618, "cuotaIVA": 129.78, "tipoIRPF": 15, "cuotaIRPF": 92.7, "total": 655.08,
    "pagada": false, "saldoPendiente": 655.08,
    "verifactu": { "estadoAeat": "pendiente_envio", "csv": null, "uuid": "…" },
    "pdf": "https://puli.es/api/v1/facturas/7C2A…/pdf"
  },
  "verifactu": { "estadoAeat": "pendiente_envio", "csv": null }
}

Para cobrarla: POST /facturas/{id}/pagos con { "importe": 655.08, "fecha": "2026-10-20", "metodoPago": "Transferencia" }.

Webhooks

Añade una URL https en Ajustes → API y webhooks (o con POST /webhooks) y elige los eventos. Puli hace un POST con JSON y reintenta hasta 6 veces si tu servidor no responde 2xx (al minuto, a los 5, a los 30, a las 2 h y a las 12 h). Tras 25 fallos seguidos el webhook se desactiva solo.

EventoCuándo
factura.emitidaUna factura pasa de borrador a emitida, desde la web, el iPhone, Android o la API.
factura.pagadaUna factura queda cobrada: pago manual, enlace de pago, Tap to Pay o la API.
presupuesto.aceptadoEl cliente acepta el presupuesto desde el enlace público o tú lo marcas aceptado.
gasto.creadoSe registra un gasto (a mano, por OCR, por email o por la API).
stock.bajo_minimoUn producto con control de stock cae por debajo de su mínimo.

Cuerpo de un evento:

POST https://tu-servidor.com/puli
Content-Type: application/json
X-Puli-Event: factura.pagada
X-Puli-Delivery: evt_8f3c…
X-Puli-Signature: t=1760349600,v1=3b2a…

{
  "id": "evt_8f3c…",
  "tipo": "factura.pagada",
  "creado": "2026-10-13T10:00:00.000Z",
  "intento": 1,
  "datos": {
    "factura": {
      "id": "7C2A…", "numeroCompleto": "A-2026-0042", "estado": "pagada",
      "cliente": { "nombre": "Acme SL", "nif": "B12345678", "email": "admin@acme.es" },
      "total": 655.08, "totalPagado": 655.08, "saldoPendiente": 0, "pagada": true,
      "fechaCobro": "2026-10-20T12:00:00.000Z",
      "pdf": "https://puli.es/api/v1/facturas/7C2A…/pdf"
    }
  }
}

Verificar la firma

La cabecera X-Puli-Signature lleva la marca de tiempo t y v1 = HMAC-SHA256(secreto, t + "." + cuerpo) en hexadecimal, sobre el cuerpo crudo tal como llega. Rechaza firmas de más de 5 minutos.

import { createHmac, timingSafeEqual } from "node:crypto";

export function firmaValida(secreto, cabecera, cuerpoCrudo) {
  const partes = Object.fromEntries(cabecera.split(",").map((p) => p.split("=")));
  const t = Number(partes.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const esperado = createHmac("sha256", secreto).update(`${t}.${cuerpoCrudo}`).digest("hex");
  const recibido = String(partes.v1 || "");
  return recibido.length === esperado.length && timingSafeEqual(Buffer.from(recibido), Buffer.from(esperado));
}

Zapier y Make

Recibir eventos de Puli

  1. En Zapier crea un Zap con el disparador Webhooks by Zapier → Catch Hook (en Make, el módulo Webhooks → Custom webhook).
  2. Copia la URL que te dan y añádela en Puli, en Ajustes → API y webhooks, con los eventos que quieras.
  3. Pulsa Probar en Puli: Zapier/Make recibirán un webhook.prueba con la estructura de ejemplo.
  4. Usa datos.factura.* en los pasos siguientes (una fila en Google Sheets, un correo, un apunte en tu CRM…).

Crear cosas en Puli

  1. Acción Webhooks by Zapier → Custom Request (Make: HTTP → Make a request).
  2. Método POST, URL https://puli.es/api/v1/clientes o /facturas, cabeceras Authorization: Bearer puli_live_… y Content-Type: application/json.
  3. Cuerpo JSON como en los ejemplos de arriba. La respuesta trae el id para encadenar pasos.

¿Necesitas un endpoint que no está?

Cuéntanos qué quieres automatizar desde soporte. La API crece con lo que piden los clientes.