API para plataformas
Para marketplaces y aplicaciones donde varias empresas venden a sus clientes. Con esta API, cada venta genera automáticamente la factura de la empresa que vende, se envía por correo al comprador y puedes mostrarla dentro de tu aplicación.
Cómo funciona
POST /v1/invoices. La factura sale a nombre de la empresa, numerada y lista.La empresa que vende es siempre el emisor legal de la factura: sus datos fiscales, su numeración y su cuenta. Las facturas que crea tu plataforma van en una serie propia (por ejemplo MK2026-0001), separada de las que la empresa haga a mano, y la empresa las ve en su FactuPulse como cualquier otra. Tu plataforma solo puede ver y tocar las facturas que ella misma ha creado.
Las devoluciones y cancelaciones las gestiona tu aplicación; cuando una venta se anula, llamas a anular y FactuPulse emite la factura rectificativa, que es lo que exige la ley (una factura emitida no se borra).
Autenticación
URL base: https://factupulse.com/api/v1. Todas las peticiones y respuestas son JSON en UTF-8.
Envía la clave en la cabecera Authorization:
Authorization: Bearer fpk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Si algún proxy de tu lado elimina esa cabecera, también vale X-Api-Key: fpk_live_….
Todas las operaciones usan GET o POST, porque algunos proxies bloquean PUT y DELETE.
Límite: 3.000 peticiones cada 10 minutos por plataforma. Si lo superas recibes 429 rate_limited; espera y reintenta.
GET/v1/ping
Comprueba que la clave funciona.
{ "ok": true, "platform": "MercaApp", "time": "2026-10-05T10:00:00+02:00" }
Alta automática de empresas
Cuando un vendedor activa la facturación en tu app (o al darlo de alta), crea su cuenta de FactuPulse con sus datos. Queda conectada a tu plataforma al momento, sin que tenga que hacer nada en FactuPulse.
POST/v1/companies
| Campo | |
|---|---|
reference | Obligatorio. Tu identificador del vendedor. Si repites la llamada con la misma reference, no se crea otra cuenta: te devolvemos la existente con "duplicate": true. |
termsAccepted | Obligatorio, true. Confirmas que el vendedor ha aceptado los términos de uso y la política de privacidad de FactuPulse. Muéstraselo en tu pantalla de alta, por ejemplo con una casilla: «Acepto que FactuPulse emita mis facturas y sus términos y privacidad». |
owner | Obligatorio. Persona titular: name y email. |
company | Datos fiscales del vendedor: legalName (obligatorio: razón social o nombre completo del autónomo), tradeName, nif, address, zip, city, province, country, phone, email. Para poder facturar hacen falta NIF y domicilio; si no los tienes ahora, el vendedor los puede completar en FactuPulse. |
successUrl, cancelUrl | Opcional, si paga cada empresa. Direcciones https:// de tu app a las que vuelve el vendedor después de pagar o si cancela el pago (ver cobro). |
returnUrl | Opcional. Solo se usa si el correo ya tenía cuenta (ver abajo). |
POST /v1/companies
{
"reference": "seller-77",
"termsAccepted": true,
"owner": { "name": "Joan Puig", "email": "joan@fornpuig.cat" },
"company": { "legalName": "Forn Puig SL", "tradeName": "Forn Puig", "nif": "B12345674",
"address": "C/ Major 1", "zip": "08001", "city": "Barcelona" }
}
201
{
"id": 42, "reference": "seller-77", "name": "Forn Puig", "legalName": "Forn Puig SL", "nif": "B12345674",
"ready": true, "plan": "pro", "paidBy": "platform",
"subscription": { "status": "active", "renewsAt": "2026-11-05T18:04:15+01:00", "cancelAtPeriodEnd": false },
"canInvoice": true, "price": 7, "connectedAt": "2026-10-05T18:04:15+02:00", "welcomeEmailSent": true
}
Si paga tu plataforma con tarjeta, el alta cobra el primer mes en el acto y solo responde 201 si el cobro se ha hecho (ver pago de las empresas). Si paga cada empresa, la respuesta trae checkoutUrl: lleva al vendedor a esa dirección justo después del alta.
Guarda id con tu vendedor: es el company de cada factura. Cuando la empresa queda pagada, la persona titular recibe un correo de bienvenida para elegir su contraseña; con ella puede entrar en FactuPulse cuando quiera a ver y descargar sus facturas, aunque no le hace falta para que tu plataforma facture.
Si el correo ya tiene cuenta en FactuPulse, no la tocamos (sería entrar en la cuenta de otra persona): recibes 409 account_exists con un connectUrl. Muéstraselo al vendedor; al abrirlo, entra con su cuenta y pulsa «Conectar» (ver empresas que ya tienen cuenta).
409
{ "error": { "code": "account_exists", "message": "Ese correo ya tiene cuenta en FactuPulse…",
"connectUrl": "https://factupulse.com/?connect=Qm3k…" } }
GET/v1/companies/{id}
Estado de una empresa. Los mismos campos salen, para todas, en GET /v1/companies.
| Campo | |
|---|---|
ready | Tiene completos sus datos fiscales (razón social, NIF y domicilio). |
plan | pro o basic. |
paidBy | platform (la pagas tú) o company (la paga ella). Lo acordamos contigo al darte de alta. |
subscription | El pago mensual de esa empresa: status (active, past_due si está reintentando un cobro, canceled…), renewsAt y cancelAtPeriodEnd, o null. |
canInvoice | Resumen: true si ahora mismo puedes emitir sus facturas. |
price | Lo que cuesta esa empresa al mes en euros, IVA incluido. |
Pago de las empresas
Cada empresa dada de alta se paga cada mes, sin periodo de prueba, al precio acordado con FactuPulse para tu plataforma. Según lo acordado, paga tu plataforma (lo habitual) o cada empresa. GET /v1/billing te dice cuál es tu caso.
Si paga tu plataforma con su tarjeta
- Una sola vez: guarda la tarjeta de tu empresa. Pide el enlace con
POST /v1/billing/setup-link(o te lo enviamos nosotros), ábrelo y añade la tarjeta en la página segura de Stripe. Sirve también para cambiarla cuando caduque. - Cada alta (
POST /v1/companies) se cobra al momento a esa tarjeta: el primer mes en el acto y después cada mes en la misma fecha. La respuesta solo llega con201cuando el cobro se ha hecho, y desde ese momento ya puedes emitir sus facturas (canInvoice: true). - Si no hay tarjeta guardada, no se crea la cuenta: recibes
402 payment_method_requiredcon unsetupUrlpara añadirla. - Si el cobro falla (tarjeta rechazada, sin fondos…), recibes
402 payment_failedcon elcompanycreado, pero no podrás emitir sus facturas hasta que se cobre. Resuélvelo y repite la misma petición de alta (mismareference) o llama aPOST /v1/companies/{id}/activate. - Si más adelante falla una renovación, Stripe reintenta el cobro durante unos días. Si al final no se cobra, la suscripción se cancela y sus facturas reciben
402 subscription_required; reactívala conPOST /v1/companies/{id}/activate. - Cuando un vendedor deja tu plataforma, llama a
POST /v1/companies/{id}/disconnect: deja de cobrarse al momento y se desconecta. Sus facturas se quedan en su cuenta de FactuPulse. Si es el vendedor quien desconecta tu plataforma desde FactuPulse, también deja de cobrarse.
Las facturas de estos cobros te llegan de FactuPulse por correo, una por empresa y mes, con el nombre y NIF de la empresa en la descripción.
POST /v1/billing/setup-link
{ "successUrl": "https://merca.app/admin/facturacion", "cancelUrl": "https://merca.app/admin/facturacion" }
200 { "url": "https://checkout.stripe.com/c/pay/cs_live_…" }
GET /v1/billing
200 { "paidBy": "platform", "chargedToCard": true, "card": "Visa ····4242 (caduca 12/2030)",
"pricePerCompany": 7, "paidCompanies": 12, "monthlyTotal": 84, "currency": "EUR" }
POST /v1/companies (sin tarjeta)
402 { "error": { "code": "payment_method_required", "message": "…", "setupUrl": "https://checkout.stripe.com/…" } }
POST /v1/companies (cobro rechazado)
402 { "error": { "code": "payment_failed", "message": "No se ha podido cobrar la empresa a tu tarjeta: …", "company": 42 } }
POST /v1/companies/42/disconnect
200 { "ok": true, "company": 42, "disconnected": true }
Si paga cada empresa
Cada vendedor paga su suscripción mensual con su tarjeta. Sus facturas solo se emiten mientras esté activa; si no, POST /v1/invoices devuelve 402 subscription_required. El alta (POST /v1/companies) ya te devuelve el enlace de pago en checkoutUrl: lleva al vendedor a él. Si no paga en ese momento, o más adelante falla un cobro, pide otro enlace:
POST/v1/companies/{id}/checkout
| Campo | |
|---|---|
successUrl, cancelUrl | Direcciones https:// de tu app a las que vuelve el vendedor después de pagar o si cancela. |
POST /v1/companies/42/checkout
{ "successUrl": "https://merca.app/vendedores/77/ok", "cancelUrl": "https://merca.app/vendedores/77" }
200
{ "url": "https://checkout.stripe.com/c/pay/cs_live_…", "price": 7, "interval": "month", "currency": "EUR" }
Abre url en el navegador (o en un navegador dentro de tu app). Es la página de pago segura de Stripe: el vendedor pone su tarjeta y sus datos de facturación, se le cobra el primer mes al momento y la suscripción se renueva sola cada mes. El enlace caduca en 24 horas, así que pídelo justo antes de usarlo. Sus facturas de la suscripción le llegan de FactuPulse por correo, y puede cambiar de tarjeta o darse de baja desde FactuPulse → Plan.
La activación tarda unos segundos en reflejarse después del pago. Al volver a tu successUrl, consulta GET /v1/companies/{id} hasta que canInvoice sea true. Si más adelante un cobro falla o se da de baja, canInvoice vuelve a false y tus facturas para ella reciben 402 subscription_required: avísale y vuelve a pedirle el pago.
402 o 422 company_incomplete, deja la factura en tu cola y reinténtala (con el mismo orderId) cuando canInvoice sea true.Empresas que ya tienen cuenta de FactuPulse
Si el vendedor ya usa FactuPulse, no hace falta crearle otra cuenta: genera un enlace de conexión y que la autorice él.
POST/v1/connect-links
Crea un enlace de conexión. Válido 30 días y de un solo uso.
| Campo | Tipo | |
|---|---|---|
reference | texto | Opcional. Tu identificador de esa empresa (por ejemplo, el id de vendedor). Te lo devolvemos al conectar y en /v1/companies. |
email | texto | Opcional. Correo de la empresa: se rellena en el formulario de acceso. |
returnUrl | texto | Opcional. Dirección https:// a la que volvemos tras conectar. |
POST /v1/connect-links
{ "reference": "seller-77", "email": "joan@fornpuig.cat", "returnUrl": "https://merca.app/vendedores/77/facturacion" }
201
{ "url": "https://factupulse.com/?connect=Qm3k…", "expiresAt": "2026-11-04T10:00:00+01:00" }
Muestra ese enlace a la empresa (un botón «Activar facturación con FactuPulse», por ejemplo). Al abrirlo:
- Entra con su cuenta (si no la tuviera, también puede registrarse desde ahí).
- Ve qué podrá hacer tu plataforma y pulsa Conectar. Solo la persona titular de la empresa puede hacerlo.
- Si indicaste
returnUrl, la devolvemos ahí con estos parámetros:
https://merca.app/vendedores/77/facturacion?company=42&reference=seller-77&status=connected
Guarda company (el id de la empresa en FactuPulse) junto a tu vendedor: lo usarás en cada factura. No te fíes solo de esa redirección; compruébalo con /v1/companies.
La empresa puede desconectar tu plataforma cuando quiera desde Ajustes → Conexiones. A partir de ese momento tus llamadas para ella devuelven 403 company_not_connected.
Consulta su estado con GET /v1/companies/{id} como cualquier otra empresa. Si pagas tú con tarjeta, se cobra al conectar; si no aparece canInvoice: true, llama a POST /v1/companies/{id}/activate. Si paga cada empresa, pídele el pago.
Crear una factura
POST/v1/invoices
Crea y emite la factura en el acto (con número definitivo y fecha de hoy). Llámalo cuando el pedido esté pagado o confirmado.
| Campo | Tipo | |
|---|---|---|
company | número | Obligatorio. Id de la empresa que vende. |
orderId | texto | Obligatorio. Tu número de pedido (hasta 120 caracteres). Si repites un orderId no se crea otra factura: te devolvemos la existente con "duplicate": true. Así puedes reintentar sin miedo. |
customer | objeto | El comprador: name, email, nif, address, zip, city, province, country (código de 2 letras, por defecto ES). |
lines | lista | Obligatorio. Cada línea: description, quantity (por defecto 1), unitPrice, vat (porcentaje: 21, 10, 4, 0; por defecto 21), discount (porcentaje, opcional). |
pricesIncludeTax | sí/no | Si es true, unitPrice lleva el IVA incluido (lo normal en venta a particulares). Por defecto false: precios sin IVA. Ver precios con IVA incluido. |
paid | sí/no | Si es true, la factura queda como cobrada. Opcionales: paidAt (AAAA-MM-DD) y paymentMethod (por defecto «Tarjeta»). |
sendEmail | sí/no | Si es true, enviamos la factura en PDF al correo del comprador, en nombre de la empresa. |
language | texto | Idioma de la factura y del correo: es (por defecto), en, ca, gl, eu. |
notes | texto | Opcional. Aparece en la factura. |
Particular o empresa: el tipo de factura sale solo
- Factura completa: si el comprador trae
name,nifyaddress. Es la que necesita una empresa o un autónomo para deducirse el IVA. El cliente se guarda en la ficha de la empresa (o se reutiliza si ya existía ese NIF). Si el NIF no es válido, recibes422 invalid_customer. - Factura simplificada: en cualquier otro caso (típico de particulares). La ley la permite hasta 400 € IVA incluido, y hasta 3.000 € en ventas al por menor y otros sectores concretos (art. 4 del Reglamento de facturación). FactuPulse rechaza las de más de 3.000 € con
422 invalid_invoice; entre 400 y 3.000 € es responsabilidad de cada empresa comprobar que su actividad lo permite. Si el comprador es una empresa o un autónomo, pide siempre sus datos fiscales.
Consejo: en tu checkout, ofrece una casilla «Necesito factura a nombre de empresa» que pida razón social, NIF y dirección.
Ejemplo: particular, precios con IVA, cobrada y enviada por correo
POST /v1/invoices
{
"company": 42,
"orderId": "A-10045",
"pricesIncludeTax": true,
"paid": true,
"sendEmail": true,
"customer": { "name": "Laura Gómez", "email": "laura@example.com" },
"lines": [
{ "description": "Camiseta algodón", "quantity": 2, "unitPrice": 12.10, "vat": 21 },
{ "description": "Envío", "quantity": 1, "unitPrice": 3.99, "vat": 21 }
]
}
201
{
"id": "a7a6e0f0-f048-41dc-ae3f-e19dc2ad6693",
"number": "MK2026-0001",
"orderId": "A-10045",
"company": 42,
"type": "simplified",
"status": "issued",
"issueDate": "2026-10-05",
"currency": "EUR",
"language": "es",
"customer": { "name": "Laura Gómez", "nif": "", "email": "laura@example.com" },
"lines": [
{ "description": "Camiseta algodón", "quantity": 2, "unitPrice": 10, "vat": 21, "discount": 0, "base": 20 },
{ "description": "Envío", "quantity": 1, "unitPrice": 3.3, "vat": 21, "discount": 0, "base": 3.3 }
],
"taxes": [ { "rate": 21, "base": 23.3, "amount": 4.89 } ],
"subtotal": 23.3, "tax": 4.89, "withholding": 0, "total": 28.19, "paid": 28.19,
"pdfUrl": "https://factupulse.com/api/public/VK9SJ_b1TDZf06WMxhhFeJzp08Ip462C/pdf",
"emailedAt": "2026-10-05T17:54:38+02:00",
"rectifies": null, "cancelledBy": null,
"chargedTotal": 28.19, "roundingDifference": 0
}
Guarda id, number y pdfUrl con tu pedido. Si el envío del correo falla, la factura se crea igualmente y la respuesta incluye emailError; puedes reintentarlo con reenviar.
Ejemplo: empresa (factura completa)
{
"company": 42,
"orderId": "A-10046",
"sendEmail": true,
"customer": { "name": "Talleres Ruiz SL", "nif": "B87654323", "address": "Av. Diagonal 100",
"zip": "08019", "city": "Barcelona", "email": "compras@ruiz.es" },
"lines": [ { "description": "Taladro percutor", "quantity": 1, "unitPrice": 100, "vat": 21 } ]
}
Precios con IVA incluido
Con pricesIncludeTax: true, FactuPulse calcula la base imponible de cada línea para que la factura sume lo que pagó el cliente. Si una línea no cuadra al céntimo con su cantidad (por ejemplo, 3 unidades a 9,99 €), se factura como una sola línea por el importe total, con la cantidad en la descripción («3 × Taza»).
La ley exige calcular el IVA sobre la suma de bases de cada tipo y redondearlo al céntimo. Por eso hay importes con IVA incluido que ninguna base puede dar exactamente: por ejemplo, 16,05 € al 21 % (13,26 € + 2,78 € = 16,04 €; 13,27 € + 2,79 € = 16,06 €). En esos casos la factura difiere en un céntimo de lo cobrado. La respuesta te lo indica siempre:
chargedTotal: la suma de lo que enviaste (lo que pagó el cliente).roundingDifference:total − chargedTotal; normalmente 0, y como mucho ±0,01 por tipo de IVA.
Si necesitas que coincida siempre al céntimo, envía los precios sin IVA (pricesIncludeTax: false) y cobra al cliente el total que devuelve la factura.
Mostrar la factura en tu app
pdfUrl es un enlace privado y permanente al PDF que no necesita clave: puedes abrirlo directamente en la app del comprador (un botón «Ver factura») o en un visor web. Trátalo como un dato privado del comprador: no lo publiques.
Si prefieres servir el PDF desde tu servidor, usa la clave:
GET/v1/invoices/{id}/pdf?company=42
Devuelve el PDF (application/pdf). Añade &download=1 para que se descargue con su nombre de archivo.
Consultar y reenviar
GET/v1/invoices?company=42
Facturas creadas por tu plataforma para esa empresa, de la más reciente a la más antigua. Filtros: orderId, from y to (AAAA-MM-DD), limit (1–100, por defecto 50), offset.
GET /v1/invoices?company=42&orderId=A-10045
{ "data": [ { "id": "a7a6e0f0-…", "number": "MK2026-0001", … } ] }
GET/v1/invoices/{id}?company=42
Una factura (ver objeto factura).
POST/v1/invoices/{id}/send
{ "company": 42, "email": "otra@direccion.com" }
Envía (o reenvía) la factura por correo. Sin email, se usa el del comprador.
Anular una factura (devoluciones y cancelaciones)
POST/v1/invoices/{id}/cancel
{ "company": 42, "reason": "Devolución del pedido A-10045", "sendEmail": true }
Emite una factura rectificativa por el importe total, en la serie de rectificativas de la empresa (por ejemplo R2026-0003), y devuelve la factura original con "status": "cancelled" y los datos de la rectificativa en cancelledBy. Con sendEmail, la rectificativa se envía al comprador. Si llamas dos veces, no se duplica ("duplicate": true).
Para una devolución parcial, de momento anula la factura y crea una nueva con lo que se queda el cliente y un orderId distinto (por ejemplo A-10045-2).
Objeto factura
| Campo | |
|---|---|
id | Identificador de la factura en FactuPulse. |
number | Número legal (serie + año + correlativo). |
orderId | Tu número de pedido. |
company | Id de la empresa emisora. |
type | invoice (completa), simplified o rectifying. |
status | issued o cancelled. |
issueDate, currency, language | Fecha de emisión, moneda e idioma. |
customer | name, nif, email. |
lines | Líneas tal como figuran en la factura (precios sin IVA) con su base. |
taxes | Desglose por tipo: rate, base, amount. |
subtotal, tax, withholding, total | Base imponible, impuestos, retención y total, en euros. |
paid | Importe cobrado. |
pdfUrl | Enlace privado al PDF. |
emailedAt | Cuándo se envió por correo por última vez, o null. |
rectifies, cancelledBy | Factura a la que rectifica / rectificativa que la anula. |
Los importes son números con dos decimales como máximo, en euros.
Errores
Los errores devuelven un código HTTP y este cuerpo; message está en español y se puede mostrar al vendedor:
{ "error": { "code": "company_incomplete", "message": "Antes de emitir facturas completa los datos de tu empresa: NIF, domicilio fiscal." } }
| HTTP | code | Qué hacer |
|---|---|---|
| 400 | company_required, order_id_required, lines_required, invalid_line, email_required, invalid_return_url | Falta un dato o está mal formado. Corrige la petición. |
| 401 | unauthorized | Clave ausente, incorrecta o desactivada. |
| 400 | reference_required, terms_required, owner_name_required, owner_email_invalid, legal_name_required | Faltan datos para dar de alta la empresa. |
| 402 | payment_method_required | No hay tarjeta guardada: añádela en setupUrl y repite. |
| 402 | payment_failed | No se pudo cobrar el alta. Repite la misma petición o usa /activate cuando esté resuelto. |
| 402 | subscription_required | Esa empresa no está pagada ahora mismo. Ver pago de las empresas. |
| 402 | plan_limit | La empresa ha llegado al límite de su plan. |
| 409 | account_exists | Ese correo ya tiene cuenta: muestra connectUrl al vendedor. |
| 409 | already_subscribed, paid_by_platform, not_card_billing | Esa operación no aplica a tu forma de pago. |
| 403 | company_not_connected | La empresa no te ha autorizado o lo ha retirado. Pídele que vuelva a conectar. |
| 404 | company_not_found, invoice_not_found | No existe o no la creó tu plataforma. |
| 422 | company_incomplete | La empresa debe completar sus datos fiscales en FactuPulse. Reintenta después con el mismo orderId. |
| 422 | invalid_nif, invalid_customer, invalid_invoice, cannot_cancel | Datos que la ley no admite (NIF incorrecto, simplificada de más de 3.000 €…). Lee message. |
| 429 | rate_limited | Demasiadas peticiones. Espera y reintenta. |
| 502 | email_failed | No se pudo enviar el correo. La factura existe; reintenta el envío. |
| 502 / 503 | billing_error, billing_unavailable | No se pudo crear el enlace de pago. Reintenta más tarde. |
| 5xx | — | Error temporal. Reintenta con el mismo orderId (no se duplicará). |
Buenas prácticas
- Factura después de cobrar. Llama a
POST /v1/invoicescuando el pago esté confirmado (en el webhook de tu pasarela, por ejemplo). - Una cola con reintentos. Si la llamada falla por red o con 5xx, reintenta más tarde con el mismo
orderId. Nunca generes unorderIdnuevo para reintentar. - Un pedido con varios vendedores son varias facturas: una por empresa, con un
orderIddistinto para cada una (por ejemploA-10045-42). - La clave, solo en tu servidor. Nunca en la app ni en el código del navegador.
- Muestra el
messagede los errores 402, 403 y 422 al vendedor: le dice exactamente qué tiene que hacer.
Prueba rápida
export FP_KEY="fpk_live_…"
curl https://factupulse.com/api/v1/ping -H "Authorization: Bearer $FP_KEY"
curl https://factupulse.com/api/v1/companies -H "Authorization: Bearer $FP_KEY" \
-H "Content-Type: application/json" \
-d '{"reference":"prueba-1","termsAccepted":true,
"owner":{"name":"Tu nombre","email":"tu@correo.com"},
"company":{"legalName":"Empresa de prueba SL","nif":"B12345674","address":"C/ Prueba 1","zip":"28001","city":"Madrid"}}'
curl https://factupulse.com/api/v1/invoices -H "Authorization: Bearer $FP_KEY" \
-H "Content-Type: application/json" \
-d '{"company":42,"orderId":"PRUEBA-1","pricesIncludeTax":true,
"customer":{"name":"Cliente de prueba","email":"tu@correo.com"},
"lines":[{"description":"Producto de prueba","unitPrice":12.10,"vat":21}],
"sendEmail":true}'
Para hacer pruebas, da de alta una empresa de prueba con un correo vuestro, como en el ejemplo. Ten en cuenta que el alta se cobra como cualquier otra: cuando termines, desconéctala con POST /v1/companies/{id}/disconnect para que no se renueve. Las facturas emitidas no se pueden borrar (solo anular), como exige la ley.
¿Dudas o necesitas otra función? Escríbenos desde la página de contacto.