API y webhooks
Conecta ERPiA con tu web, tu tienda online, Zapier, Make, n8n o tu propio software. Una API REST para leer y escribir, y webhooks firmados para que te avisemos cuando pasa algo.
API REST
Facturas, presupuestos, contactos, artículos, gastos, cobros y proyectos. Lectura en todos los planes; escritura en el plan de pago.
Webhooks firmados
Te avisamos en tu URL cuando se emite una factura, se registra un cobro o un cliente firma un presupuesto. Firma HMAC y reintentos automáticos.
OpenAPI 3.1
La especificación completa, para importarla en Postman, Insomnia o generar un cliente: openapi.yaml.
Autenticación
Crea una clave en Mi ERPIA → API y webhooks. Se muestra una sola vez; guárdala. Al crearla eliges si es de solo lectura o de lectura y escritura. Va en la cabecera X-API-Key de cada petición.
URL base: https://erpia.es/api/v1
curl https://erpia.es/api/v1/me \
-H "X-API-Key: erpia_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta: los datos de tu cuenta, el plan y los permisos (scopes) de la clave.
Recursos
| Recurso | Leer | Escribir | Filtros |
|---|---|---|---|
/facturas | GET, GET /:id | POST, PATCH /:id | desde, hasta, estado, tipo, contacto_id, q, actualizado_desde |
/presupuestos | GET, GET /:id | POST | desde, hasta, estado, contacto_id, q |
/contactos | GET, GET /:id | POST, PATCH /:id | tipo, q |
/articulos | GET, GET /:id | POST | q, familia, activo |
/gastos | GET, GET /:id | POST | desde, hasta, categoria, proyecto_id |
/cobros | GET | POST | desde, hasta, factura_id |
/proyectos | GET, GET /:id | — | estado |
/me, /eventos | GET | — | — |
Los listados devuelven data, count, limit y offset. Paginación con ?limit= (máximo 500) y ?offset=, con orden estable. Para sincronizar solo lo que ha cambiado, /facturas?actualizado_desde=2026-09-01T00:00:00Z.
Crear una factura
Las facturas creadas por API entran por el mismo camino que las de la pantalla: nacen como borrador con su número previsto y, al emitirlas, ERPiA asigna el correlativo definitivo de la serie y sella el registro Verifactu (huella encadenada y QR). No hay ninguna puerta trasera al encadenado: una factura emitida es inalterable y se corrige con una rectificativa.
POST /api/v1/facturas
X-API-Key: erpia_live_…
Idempotency-Key: pedido-8841
Content-Type: application/json
{
"contacto_id": "0b0e3a4e-2f5d-4a2b-9f1e-1c2d3e4f5a6b",
"lineas": [
{ "descripcion": "Reforma baño", "detalle": "Alicatado y fontanería", "cantidad": 1, "precio": 1450, "iva": 21 },
{ "descripcion": "Mano de obra", "cantidad": 8, "precio": 35, "unidad": "H" }
],
"fecha_vencimiento": "2026-10-15",
"emitir": true
}
Si indicas contacto_id, los datos del cliente se toman de su ficha; si no, envía al menos nombre_cliente. Cada línea puede llevar su propio iva (21, 10, 4 o 0). Con "emitir": true la factura sale numerada y sellada; sin él queda en borrador y puedes emitirla después con PATCH /facturas/:id {"emitir": true}. Un presupuesto (POST /presupuestos) devuelve además url_publica: el enlace donde tu cliente lo ve, lo acepta y lo firma desde el móvil.
Idempotencia
Envía la cabecera Idempotency-Key con un valor único por operación (el id de tu pedido, por ejemplo). Si la misma clave llega dos veces —porque un reintento de Zapier o un corte de red repitió la petición— ERPiA devuelve la misma respuesta con la cabecera Idempotent-Replayed: true y no crea nada dos veces. Las claves se recuerdan 7 días.
Webhooks salientes
Sin un disparador, una API de solo lectura no automatiza nada. En Mi ERPIA → API y webhooks añade la URL de tu servidor (o la que te da el «Webhook» de Zapier, Make o n8n), elige los eventos y ERPiA te avisará en segundos con un POST en JSON.
| Evento | Cuándo |
|---|---|
factura.emitida | Una factura, ticket o rectificativa sale del borrador (se numera y se sella) |
factura.cobrada | Una factura pasa a cobrada |
presupuesto.creado | Se crea un presupuesto |
presupuesto.aceptado | El cliente acepta el presupuesto |
presupuesto.firmado | El cliente firma el presupuesto desde el enlace público |
contacto.creado | Se da de alta un cliente o proveedor |
gasto.creado | Se registra un gasto (a mano, por foto o por API) |
cobro.registrado | Se registra un cobro |
stock.bajo_minimo | Un artículo cruza por debajo de su stock mínimo |
POST https://tu-servidor.com/erpia
Content-Type: application/json
X-ERPIA-Event: factura.emitida
X-ERPIA-Delivery: 6f1c… (id único de esta entrega)
X-ERPIA-Timestamp: 1789123456
X-ERPIA-Signature: t=1789123456,v1=3f8a…
{
"id": "6f1c…",
"evento": "factura.emitida",
"creado_en": "2026-09-09T10:12:03Z",
"intento": 1,
"data": {
"id": "…", "numero": "F2026-0042", "tipo": "factura", "estado": "confirmada",
"fecha_emision": "2026-09-09", "total": 2093.30,
"cliente": { "contacto_id": "…", "nombre": "Rita Gálvez", "nif": "12345678Z", "email": "rita@ejemplo.es" }
}
}
Verificar la firma
Cada webhook tiene un secreto (whsec_…) que ves en el panel. La firma es HMAC-SHA256(secreto, timestamp + "." + cuerpo) sobre el cuerpo tal cual llega, sin reformatear. Compara en tiempo constante y rechaza timestamps de hace más de 5 minutos.
// Node.js (Express con body en crudo)
import { createHmac, timingSafeEqual } from 'node:crypto'
function verificar(secreto, cabecera, cuerpoCrudo) {
const { t, v1 } = Object.fromEntries(cabecera.split(',').map(p => p.split('=')))
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const esperada = createHmac('sha256', secreto).update(`${t}.${cuerpoCrudo}`).digest('hex')
return esperada.length === v1.length && timingSafeEqual(Buffer.from(esperada), Buffer.from(v1))
}
# Python
import hmac, hashlib, time
def verificar(secreto: str, cabecera: str, cuerpo_crudo: bytes) -> bool:
partes = dict(p.split("=") for p in cabecera.split(","))
if abs(time.time() - int(partes["t"])) > 300:
return False
esperada = hmac.new(secreto.encode(), f"{partes['t']}.".encode() + cuerpo_crudo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperada, partes["v1"])
Reintentos. Responde 2xx en menos de 10 segundos. Si no, ERPiA reintenta al minuto, a los 5, a los 30, a las 2 horas y a las 12; después la entrega queda como abandonada y lo ves en el panel. Un webhook con 25 fallos seguidos se apaga solo (lo enciendes de nuevo con un clic). Las entregas pueden llegar más de una vez: usa X-ERPIA-Delivery para descartar duplicados.
Zapier, Make y n8n
- Disparador («cuando pasa algo en ERPiA»): crea un paso «Webhook» (Zapier: Webhooks by Zapier → Catch Hook; Make: Webhooks → Custom webhook; n8n: nodo Webhook), copia la URL que te da y pégala en Mi ERPIA → API y webhooks. Pulsa «Probar» para recibir un evento de muestra y mapear los campos.
- Acción («haz algo en ERPiA»): usa el módulo HTTP de cualquiera de los tres contra
https://erpia.es/api/v1/…con la cabeceraX-API-Key. Por ejemplo, lead de formulario → POST /contactos, pedido de la tienda → POST /facturas, factura cobrada → fila en Google Sheets.
Límites y buenas prácticas
- 120 peticiones por minuto por clave; 500 registros por página.
- La escritura por API y los webhooks son del plan de pago. La lectura funciona en todos los planes.
- Los importes van en euros con dos decimales; las fechas en
YYYY-MM-DD; los ids son UUID. - Los errores devuelven
{"error": "…"}con el código HTTP que toca: 400 petición inválida, 401 clave, 403 permiso o plan, 404 no existe, 409 conflicto (factura ya emitida, cobro que supera el total), 429 límite. - Si tu agente de IA quiere operar el ERP, existe además el servidor MCP: /para-agentes.
¿Necesitas un endpoint que no está?
Escríbenos a hola@erpia.es con lo que quieres conectar. La API crece con lo que la gente integra.