Facturar y cobrar desde tu aplicación
Recorrido completo para facturar y cobrar desde tu aplicación con UTILIA OS: identificar al usuario, guardar la tarjeta, emitir la factura y cobrar la cuota.
Actualizado el . 18 min de lectura
Incluido en los planes Profesional y Avanzado. Ver precios
Esta guía recorre el ciclo entero de facturar y cobrar desde tu aplicación: identificar al usuario, guardar su tarjeta, recoger su autorización, emitir la factura y enviársela, montar la cuota mensual, cobrar sin nadie delante y enterarte de todo por avisos.
Los ejemplos usan una aplicación inventada llamada Nubeta. Su base de datos de usuarios y sus pantallas siguen siendo suyas; UTILIA OS aporta la numeración fiscal, el PDF, el correo y la pasarela. Todas las facturas de Nubeta son facturas de UTILIA OS con origen «aplicación externa», en la misma serie correlativa que las que emite tu equipo desde el panel, salvo que tu organización reserve a Nubeta una serie propia (ver Series disponibles).
Antes de empezar
- Una aplicación externa creada en UTILIA OS, con su clave de API activa.
- El plan de tu organización con los aplicativos que use cada parte: Integraciones para la pantalla y los usuarios, Finanzas para facturar, Finanzas y Pagos con tarjeta (Stripe) para cobrar con tarjeta, y las capacidades de facturación recurrente y de cobro automático para las cuotas y las autorizaciones.
- Facturar no exige pasarela. Con Integraciones y Finanzas, tu aplicación ya puede activar la facturación, emitir facturas, enviarlas por correo y registrar el cobro por transferencia. Stripe hace falta para cobrar con tarjeta, no para facturar.
- Si cobras con tu propia pasarela y solo quieres que UTILIA OS facture, sigue la guía Facturar cuando cobras con tu propia pasarela.
- Un servidor propio. La clave de API nunca sale de él.
Prueba sin emitir facturas reales
Cada factura emitida lleva un número correlativo que no se puede borrar. Antes de la primera real, comprueba tu cuerpo con POST /external/v1/invoices/validate, descarga su PDF de muestra y recibe avisos de ejemplo en tu servidor. Todo está en Probar la facturación sin emitir facturas. El diagnóstico de tu aplicación, que dice qué le falta antes de facturar, está en Probar tu integración.
El recorrido de un extremo a otro
De registrar al usuario a cobrar su cuota
Usuarios externos
identify crea el usuario externo y su ficha de cliente en el CRM.Autorizaciones de cobro
El usuario acepta el texto de consent-text y lo registras como prueba.Finanzas
Facturar y cobrar
Cada periodo, UTILIA OS emite la factura, la cobra y, si lo tienes activado, la envía por correo.Tu servidor
SUBSCRIPTION_INVOICE_GENERATED, CHARGE_SUCCEEDED y el resto actualizan tu estado.
Paso 1: activar la facturación de la aplicación
Encender el interruptor
En la ficha de tu aplicación, en Dinero, Facturación, la opción Activar la facturación permite a la aplicación emitir facturas y mover dinero.
Solo puede encenderla un Super Administrador, y al guardar se le pide un motivo de al menos veinte caracteres, que queda en el registro de auditoría con su nombre y la hora. Apagarla no exige nada: es la forma rápida de cortar.
Mientras esté apagada, cada factura que la aplicación intente emitir responde
403 BILLING_NOT_ENABLED. Si la aplicación tiene permisos de facturas y tu plan incluye Finanzas, el diagnóstico lo marca como paso pendiente: el bloque Estado del Resumen lo dice en una línea y la pestaña Pruebas ofrece Activar la facturación. La validación en seco también lo avisa.Decidir si UTILIA OS envía las facturas
En la misma pantalla, Enviar las facturas por correo al emitirlas hace que cada factura emitida por la aplicación salga hacia el usuario final con la plantilla, el remitente y el PDF de tu organización, y pase a estado «Enviada».
Es una preferencia por defecto, no una obligación:
sendEmailen la llamada manda sobre ella en los dos sentidos. Contruela factura sale aunque el interruptor esté apagado; confalseno sale aunque esté encendido; sinsendEmail, decide el interruptor.Comprobar la cuenta de cobro (solo si va a cobrar con tarjeta)
Los cobros con tarjeta se procesan con la cuenta Stripe de tu organización: no hay cuenta por aplicación. Su estado se ve en la misma pantalla de Facturación, con enlace a Finanzas › Ajustes › Pagos con tarjeta (Stripe). Sin una cuenta lista para operar, los cobros responden
409 STRIPE_ACCOUNT_NOT_READY.Este paso es opcional. Una organización sin la pasarela contratada llega hasta aquí igual: activa la facturación, emite, envía y anota el cobro por transferencia con
mark-paid. Los bloques de cobro con tarjeta aparecen con candado.Conceder los permisos de la aplicación
En la ficha de la aplicación, en Configuración, Ajustes, pestaña Permisos, concede solo lo que vaya a usar. La pantalla distingue dos motivos por los que algo no está disponible, no incluido en el plan y no concedido a la aplicación, porque se arreglan en sitios distintos.
Suscribirse a los avisos
En Avisos a tu servidor, indica la dirección de tu servidor y marca los eventos. Guarda la clave de firma que te devuelve UTILIA OS: con ella comprobarás que cada aviso viene de verdad de UTILIA OS.
Paso 2: los permisos que necesita cada parte
Los permisos de la API (scopes) se conceden por familia. Estos son los del ciclo de facturación, con el aplicativo y la capacidad del plan que además hacen falta.
| Permiso | Para qué | Plan que exige |
|---|---|---|
crm:users:write | Dar de alta y actualizar usuarios finales | Integraciones |
crm:invoices:write | Crear facturas y registrar cobros fuera de la pasarela | Finanzas |
crm:invoices:issue | Emitir la factura (se exige junto al anterior al crear) | Finanzas |
crm:invoices:read | Listar, ver el detalle y descargar el PDF | Finanzas |
crm:invoices:send | Enviar la factura por correo | Finanzas |
crm:invoices:cancel | Anular una factura emitida | Finanzas |
crm:invoices:rectify | Emitir la rectificativa de una factura cuando devuelves el dinero con tu propia pasarela | Finanzas |
billing:payment-methods:read y :write | Guardar y gestionar tarjetas | Finanzas y Pagos con tarjeta (Stripe) |
billing:charge | Cobrar una factura | Finanzas y Pagos con tarjeta (Stripe) |
billing:refund | Devolver dinero cobrado | Finanzas y Pagos con tarjeta (Stripe) |
billing:payment-authorizations:read, :write, :revoke | Autorizaciones de cobro | Finanzas y Pagos con tarjeta (Stripe), con cobro automático |
billing:subscriptions:read, :write | Cuotas recurrentes | Finanzas, con facturación recurrente |
billing:subscriptions:cancel | Dar de baja una cuota | Finanzas, con facturación recurrente |
billing:scheduled-charges:read, :write | Cobros programados, y reintentar ya el cobro de una cuota de suscripción | Finanzas y Pagos con tarjeta (Stripe), con cobro automático |
La pantalla de Facturación de la aplicación y su interruptor solo exigen Integraciones y Finanzas. La pasarela solo gobierna los cobros con tarjeta: guardar tarjetas, autorizaciones, charge, refund y los cobros programados.
Siete permisos son críticos financieros porque mueven dinero, cortan un ingreso o restan ingresos con un documento fiscal: billing:charge, billing:refund, billing:subscriptions:cancel, billing:payment-authorizations:write, billing:scheduled-charges:write, crm:invoices:cancel y crm:invoices:rectify. Los concede un Super Administrador con un motivo que queda en el registro de auditoría. billing:scheduled-charges:write entró en la lista el 21 de octubre de 2026, porque adelantar un reintento cobra en el momento: las aplicaciones que ya lo tenían lo conservan.
No hay periodo de gracia: un permiso que la aplicación no tenga produce 403 con errorCode: 'INSUFFICIENT_SCOPE' desde la primera llamada, con el que falta en requiredScopes. Concédelos antes de integrar. Las reglas completas están en Permisos de la API.
Paso 3: instalar el SDK
npm install @utilia-os/sdk-jsimport { UtiliaSDK } from '@utilia-os/sdk-js';
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
apiKey: process.env.UTILIA_API_KEY!,
});La clave de API nunca llega al navegador
La clave tiene todos los permisos de tu aplicación, incluidos los que mueven dinero. Úsala siempre desde tu servidor: tu interfaz habla con tu servidor, y tu servidor con UTILIA OS. La clave empieza por utl_.
Paso 4: identificar al usuario
const user = await sdk.users.identify({
externalId: 'user_01HXYZ',
email: 'ana@estudiomarea.example',
name: 'Estudio Marea SL',
taxId: 'B76543210',
billingAddress: {
street: 'Avenida de Canarias 12, planta 3',
city: 'Las Palmas de Gran Canaria',
postalCode: '35001',
country: 'España',
province: 'Las Palmas',
},
metadata: { plan: 'pro' },
});
console.log(user.clientId);externalId es el identificador estable del usuario en Nubeta, y es el que se usa en todas las llamadas posteriores como userId. La operación crea o actualiza: llámala sin comprobar antes si el usuario existe.
taxId y billingAddress son opcionales, pero conviene mandarlos aquí en cuanto Nubeta los tenga: se copian a la ficha de cliente del CRM y a la de la pasarela, y son los datos que salen impresos en el PDF. Una factura emitida a un cliente sin identificador fiscal obliga a rectificarla después. Si no los mandas, el alta de la factura rellena los huecos con los datos del receptor, y lo que declare identify manda siempre. Omitir un campo no borra el que ya estaba guardado.
El puente con el CRM se monta solo
La primera vez que una aplicación factura, guarda una tarjeta o crea una suscripción para un usuario, UTILIA OS le crea su ficha de cliente en el CRM con su nombre, su NIF y su dirección, y un contacto de facturación con su correo. Desde ese momento, ese usuario existe para Finanzas y para el CRM como cualquier otro cliente, y la ficha muestra de qué aplicación viene. Si tu aplicación ya había creado la ficha con POST /external/v1/clients, pasa su identificador en clientId al identificar y se vinculan en lugar de duplicarse.
El puente solo trabaja con fichas creadas por la propia aplicación. Una ficha nativa del CRM, dada de alta por tu equipo desde el panel, nunca se toca desde la API externa: clientId la rechaza con 404 CLIENT_NOT_ACCESSIBLE, y la deduplicación automática por NIF o por correo tampoco la reutiliza. Si la coincidencia cae en una ficha nativa, se crea una ficha nueva y el hallazgo queda en el registro de auditoría para que tu equipo lo revise. Unir un usuario de Nubeta con una ficha nativa sigue siendo posible, pero lo decide tu equipo desde el panel. Más detalle en Usuarios externos.
Paso 5: guardar la tarjeta
Guardar la tarjeta y autorizar el cobro son dos cosas distintas. Esta es la primera: la tarjeta queda registrada en la pasarela.
const setup = await sdk.paymentMethods.createSetupIntent({
externalId: 'user_01HXYZ',
email: 'ana@estudiomarea.example',
name: 'Estudio Marea SL',
});
// setup.clientSecret, setup.publishableKey, setup.stripeAccountIdTu interfaz monta el formulario de la pasarela con ese clientSecret y la cuenta que viene en stripeAccountId. El detalle está en Métodos de pago.
Paso 6: recoger la autorización de cobro
Con la tarjeta guardada, tu usuario todavía no ha autorizado nada. Para cobrarle sin que esté delante hace falta su consentimiento expreso, que es la prueba legal que respalda cada cargo futuro.
Pedir el texto vigente
TypeScriptconst consent = await sdk.paymentAuthorizations.getConsentText({ scope: 'RECURRING' }); // consent.text, consent.version, consent.companyNameMuéstraselo tal cual. Lleva el nombre fiscal de la organización: no lo reescribas ni lo reutilices entre organizaciones.
Registrar la aceptación
TypeScriptconst authorization = await sdk.paymentAuthorizations.create({ userId: 'user_01HXYZ', setupIntentId: 'seti_1OabcDEF', consentText: consent.text, consentVersion: consent.version, acceptedAt: new Date().toISOString(), acceptedIp: peticion.ip, acceptedUserAgent: peticion.headers['user-agent'], scope: 'RECURRING', });La IP y el navegador los manda tu servidor, porque la llamada sale de él. Junto con el texto, la versión y el momento, prueban quién aceptó y desde dónde. Si el texto o la versión no son los vigentes, la llamada falla con
422 PAYMENT_AUTHORIZATION_CONSENT_MISMATCHantes de consumir el SetupIntent.
Hay dos alcances y cada uno necesita su autorización: RECURRING cubre las cuotas de una suscripción y SCHEDULED_INVOICE, el cobro programado de una factura suelta. Una no vale para lo otro, así que una aplicación que haga las dos cosas recoge dos autorizaciones del mismo usuario.
Paso 7: emitir la factura y enviarla
Antes de emitir la primera de verdad, manda el mismo cuerpo a
POST /external/v1/invoices/validate: te dice si se emitiría, qué lo
impediría y cómo quedaría, sin numerar nada (ver
Validar una factura sin emitirla).
Después, la emisión real:
const invoice = await sdk.invoices.create({
user: {
externalId: 'user_01HXYZ',
email: 'ana@estudiomarea.example',
name: 'Estudio Marea SL',
taxId: 'B76543210',
},
recipient: {
name: 'Estudio Marea SL',
taxId: 'B76543210',
address: 'Calle Mayor 1',
city: 'Las Palmas de Gran Canaria',
postalCode: '35001',
country: 'España',
email: 'administracion@estudiomarea.example',
},
taxSystem: 'IGIC',
lines: [
{ name: 'Plan Profesional', quantity: 1, unitPrice: 49.95, taxType: 'IGIC_7' },
],
sendEmail: true,
idempotencyKey: 'nubeta_pedido_4821',
});
console.log(invoice.invoiceNumber, invoice.emailSent);La factura se numera con el contador correlativo de tu organización y genera su PDF. sendEmail manda sobre la preferencia de la aplicación: true la envía aunque el interruptor esté apagado, y false la retiene aunque esté encendido. Guarda invoiceNumber (202600042, el número registrado) junto con el id para buscarla y conciliarla; para enseñarla, usa displayNumber (2026-00042), el número tal como va impreso, con guion entre el año y el número de orden.
La llamada puede tardar varios segundos: dale a tu cliente HTTP un tiempo de espera de al menos 30 y, si se corta, repítela con la misma idempotencyKey. Si la primera sigue en marcha, recibes la misma factura con issuanceDeliveryStatus: processing, nunca una segunda.
emailSent en falso no es una factura fallida
La factura está emitida y numerada; solo falta el correo. El envío ocurre después del cierre fiscal a propósito: un servidor de correo caído no puede tumbar una factura ya numerada. Mira emailDelivery.status: con retrying, UTILIA OS la está reenviando sola y no tienes que hacer nada; con failed o withheld, resuelve el motivo y envíala con sdk.invoices.send(...). No la vuelvas a crear nunca: sería una segunda factura. Los motivos están en El correo de la factura.
Para reenviar, o para enviar en un momento distinto del de la emisión:
await sdk.invoices.send(invoice.id, {
userId: 'user_01HXYZ',
message: 'Adjuntamos la factura del mes de octubre.',
});Sin to, el destinatario es el contacto de facturación principal del cliente y, en su defecto, el correo del usuario externo.
Entregar la factura al usuario
El PDF de la API externa exige la clave de API, así que no sirve como enlace para tu usuario. Lo que se le entrega es el enlace público:
const link = await sdk.invoices.regeneratePublicLink(invoice.id, {
userId: 'user_01HXYZ',
});
// link.publicUrlLa dirección se entrega una sola vez
De un enlace emitido, UTILIA OS guarda solo su huella. Por eso getPublicLink es una lectura que devuelve publicUrl: null y solo informa de la vigencia y de las acciones permitidas, y por eso emitir uno nuevo revoca el anterior de esa factura. Guarda la dirección en tu base de datos la primera vez. La excepción es el enlace que devuelve un charge con REQUIRES_ACTION: ese es adicional y no revoca el que tu usuario ya tuviera. Todos permiten solo ver y pagar.
Si prefieres servir el PDF desde tu propia interfaz, descárgalo en tu servidor y entrégalo por una ruta tuya que compruebe el acceso de la persona:
import { writeFile } from 'node:fs/promises';
const blob = await sdk.invoices.downloadPdf(invoice.id, 'user_01HXYZ');
await writeFile('factura.pdf', new Uint8Array(await blob.arrayBuffer()));Paso 8: la cuota mensual
const subscription = await sdk.subscriptions.create({
userId: 'user_01HXYZ',
name: 'Nubeta Profesional mensual',
frequency: 'MONTHLY_FIXED_DAY',
dayOfMonth: 1,
startDate: '2026-10-01',
automation: 'AUTO',
chargeMode: 'AUTO_STRIPE',
paymentAuthorizationId: authorization.id,
taxSystem: 'IGIC',
daysUntilDue: 7,
sendEmail: true,
lines: [
{ name: 'Plan Profesional', quantity: 1, unitPrice: 49.95, taxType: 'IGIC_7' },
],
metadata: { planId: 'pro_monthly' },
idempotencyKey: 'sub_user_01HXYZ_pro',
});A partir de aquí, cada periodo UTILIA OS emite la factura, la envía y la cobra, y te avisa de cada paso. Puedes consultar en cualquier momento qué toca a continuación:
const proximo = await sdk.subscriptions.getUpcomingCharge(subscription.id, {
userId: 'user_01HXYZ',
});
// proximo.nextInvoiceDate, proximo.amount, proximo.autoChargeReadyPedir chargeMode: "AUTO_STRIPE" sin una autorización vigente falla con 409 PAYMENT_AUTHORIZATION_REQUIRED. No pasa en silencio a cobro manual, porque te dejaría creyendo que tienes montado un cobro que nunca se produciría. El error trae un nextStep con las dos operaciones que faltan.
Pausar, reanudar y dar de baja son tres operaciones distintas. Pausar conserva el historial y la autorización; dar de baja corta el ingreso y no se deshace. El detalle está en Suscripciones.
Paso 9: cobrar sin nadie delante
Para cobrar ahora mismo una factura suelta contra la autorización del usuario:
const cobro = await sdk.invoices.charge(invoice.id, {
userId: 'user_01HXYZ',
idempotencyKey: `cobro_${invoice.id}`,
});
if (cobro.status === 'REQUIRES_ACTION') {
// El banco pide autenticación reforzada. El cobro NO se ha producido.
enviarAlUsuario(cobro.publicUrl);
}| Estado | Qué significa |
|---|---|
SUCCEEDED | Dinero cobrado. |
REQUIRES_ACTION | El banco pide autenticación reforzada. Lleva al usuario al publicUrl. |
PROCESSING | En curso. El resultado llega por aviso. |
FAILED | Rechazado, con failureCode y failureMessage. |
Cobrar con el usuario delante es otra operación: sdk.payments.payInvoice(...) devuelve un clientSecret para que tu interfaz monte el formulario de la pasarela y la persona confirme. sdk.invoices.charge(...) es el cobro sin nadie delante y exige la autorización previa. No son intercambiables.
Para cobrar en una fecha futura en lugar de ahora, usa los cobros programados, con una autorización de alcance SCHEDULED_INVOICE.
Paso 10: reintentos y avisos de morosidad
Cuando un cobro se rechaza, UTILIA OS lo reintenta solo hasta agotar los intentos configurados. No tienes que programar nada: te llegan los avisos.
| Aviso | Qué hacer en Nubeta |
|---|---|
CHARGE_FAILED | Anotar el rechazo. willRetry dice si queda algún intento. |
CHARGE_RETRY_SCHEDULED | Nada; informar al usuario si procede. |
CHARGE_REQUIRES_ACTION | Pedir el enlace público y llevar al usuario a completarlo. |
CHARGE_RETRIES_EXHAUSTED | Cortar el servicio o abrir una gestión de cobro. Indica si la suscripción ha quedado pausada. |
PAYMENT_METHOD_EXPIRING | Pedir al usuario una tarjeta nueva antes de que falle la renovación. |
Paso 11: cobro fuera de la pasarela, reembolso y anulación
// El usuario pagó por transferencia
await sdk.invoices.markPaid(invoice.id, {
userId: 'user_01HXYZ',
method: 'TRANSFER',
reference: 'TRF-2026-10-0042',
});
// Devolver lo cobrado, entero o en parte
await sdk.invoices.refunds.create(invoice.id, {
userId: 'user_01HXYZ',
reason: 'requested_by_customer',
idempotencyKey: `devolucion_${invoice.id}`,
});
// Anular una factura emitida por error (no devuelve dinero)
await sdk.invoices.cancel(invoice.id, 'user_01HXYZ', 'Facturación duplicada');Anular y devolver son cosas distintas: anular corrige el documento, devolver mueve dinero.
Paso 12: recibir los avisos
Tu servidor expone una dirección HTTPS que acepta POST con JSON y comprueba la firma de cada petición. El SDK trae el verificador:
import express from 'express';
import { UtiliaSDK } from '@utilia-os/sdk-js';
const sdk = new UtiliaSDK({ baseURL: 'https://os.utilia.ai/api', apiKey: process.env.UTILIA_API_KEY! });
const app = express();
app.post('/webhooks/utilia', express.raw({ type: 'application/json' }), (req, res) => {
let evento;
try {
evento = sdk.webhooks.parse(req.body.toString('utf8'), req.headers, process.env.UTILIA_WEBHOOK_SECRET!);
} catch {
return res.status(401).send('Firma no válida');
}
const entrega = req.header('X-Utilia-Delivery-Id')!;
if (yaProcesado(entrega)) return res.status(200).send('OK');
switch (evento.event) {
case 'SUBSCRIPTION_INVOICE_GENERATED':
registrarFactura(evento.data);
break;
case 'CHARGE_SUCCEEDED':
renovarServicio(evento.data);
break;
case 'CHARGE_REQUIRES_ACTION':
pedirAutenticacion(evento.data);
break;
case 'CHARGE_RETRIES_EXHAUSTED':
suspenderServicio(evento.data);
break;
}
marcarProcesado(entrega);
res.status(200).send('OK');
});El verificador comprueba la firma sobre la cadena timestamp.rawBody y la ventana contra reenvíos. Usa el cuerpo crudo, sin volver a interpretarlo. El formato está en Firma HMAC, y el catálogo completo de avisos en Contenido de los avisos.
Cobras con tu propia pasarela
Si tu aplicación cobra con su propia cuenta de Stripe (o con otra pasarela) y usa UTILIA OS solo para facturar, sigue la guía Facturar cuando cobras con tu propia pasarela: factura simplificada ya cobrada, devolución en tu pasarela y rectificativa. Solo necesitas Finanzas.
Cuando algo va mal
| Respuesta | Qué significa | Qué hacer |
|---|---|---|
403 con errorCode: 'MODULE_NOT_ENABLED' | El plan de tu organización no incluye el aplicativo. La respuesta trae su moduleKey. | Es una decisión comercial, no un fallo técnico. Contrata el aplicativo o retira esa parte de la integración. No reintentes. |
403 con errorCode: 'CAPABILITY_NOT_ENABLED' | El aplicativo está contratado, pero no la capacidad concreta. La respuesta trae su capabilityKey. | Igual que el anterior. El resto del aplicativo sigue funcionando. |
403 con errorCode: 'INSUFFICIENT_SCOPE' | La aplicación no tiene el permiso concedido. requiredScopes dice cuál falta. | Pide que lo concedan en los permisos de la API de la aplicación. No reintentes. |
403 con errorCode: 'BILLING_NOT_ENABLED' | La facturación de la aplicación está apagada. | Un Super Administrador la enciende con Activar la facturación en la pantalla Facturación de la aplicación. Antes de la primera factura real, la validación en seco lo detecta. |
409 con retryable: true | La operación tiene que esperar a que termine otra (por ejemplo, una rectificativa mientras la factura se sigue emitiendo). | Repite la misma petición, con la misma clave, pasados retryAfterSeconds. |
409 STRIPE_ACCOUNT_NOT_READY | La cuenta de cobro de tu organización no está lista. | Revisa Finanzas › Ajustes › Pagos con tarjeta (Stripe). |
429 | Has superado las 60 peticiones por minuto de los endpoints de facturas. | Espera lo que indique Retry-After. Los SDK la respetan solos a partir de la 4.6.0; las versiones anteriores no la leen. |
Lista de comprobación antes de producción
- La clave de API vive solo en tu servidor, en una variable de entorno.
- La aplicación tiene concedidos exactamente los permisos que usa, ni uno más.
- Envías
idempotencyKeyen toda creación de factura, suscripción, cobro y reembolso, y das aPOST /external/v1/invoicesun tiempo de espera de al menos 30 segundos. - Has probado el cuerpo con
POST /external/v1/invoices/validatey tu receptor con un aviso de ejemplo de la pestaña Pruebas. - Tu receptor de avisos comprueba la firma, valida la ventana de tiempo y descarta repetidos por
X-Utilia-Delivery-Id. - Lees
emailDeliverypara saber si el correo salió: conretryingesperas, confailedowithheldenvías consend, y nunca rehaces la factura. - Guardas el enlace público la primera vez que lo emites, en lugar de volver a emitirlo.
- Gestionas
CHARGE_REQUIRES_ACTIONyCHARGE_RETRIES_EXHAUSTED, no solo el cobro correcto. - No guardas nunca un
clientSecret: caduca enseguida.
Preguntas habituales
¿Qué correos envía UTILIA OS a mis usuarios y cuándo?
Siempre con la plantilla y el remitente de tu organización:
- La factura al emitirse sale si la llamada lo pide con
sendEmail: true, aunque el interruptor Enviar las facturas por correo al emitirlas esté apagado. ConsendEmail: falseno sale aunque esté encendido, y sinsendEmaildecide el interruptor. - El justificante al cobrarse y los avisos de vencimiento y recordatorio solo salen con el interruptor encendido, y si tu organización los tiene activados;
sendEmailno los cambia.
Con el interruptor apagado y sin sendEmail: true, UTILIA OS no escribe a tus usuarios: el envío es cosa tuya.
¿Puedo personalizar la numeración de mis facturas?
Sí: tu organización puede crear una serie para tu aplicación (por ejemplo, APP-202600001), con su propia numeración correlativa, en Ajustes › Organización, necesitas entrar con tu cuenta, pestaña Facturas. Tu aplicación la elige con seriesCode al crear la factura, o queda como serie por defecto de la aplicación y se usa sola. Las consultas con GET /external/v1/invoices/series. La clave de API no crea ni cambia series, ni elige libremente el prefijo, y la numeración sigue siendo correlativa dentro de cada serie, como exige la normativa.
Sin serie propia, todo sigue como siempre: las ordinarias en la serie general, compartida con las que emite tu equipo desde el panel, y las simplificadas en la suya (T-). Las rectificativas van siempre a la suya (R-), también las de una factura numerada en la serie de tu aplicación.
¿Puedo facturar en varias divisas?
No. Se factura en la divisa configurada en la organización. Si la cuenta de cobro declara otra divisa por defecto, el cobro se rechaza con 409 BILLING_CURRENCY_NOT_SUPPORTED en lugar de cobrar el mismo número de céntimos en otra moneda.
¿Puedo emitir facturas rectificativas desde la API?
Sí, cuando devuelves el dinero o un contracargo deja la venta sin efecto. Si cobraste con tu propia pasarela, usa POST /external/v1/invoices/:id/rectifications; si cobraste con la de UTILIA OS, el reembolso con createRectifyingInvoice: true. Tú das la causa y, si es parcial, el importe; el código legal (R1 o R5) lo decide UTILIA OS, siempre por diferencias. Las rectificativas por otros motivos las emite tu equipo desde Finanzas.
¿Puede mi aplicación tener su propia cuenta de cobro?
No. Los cobros de todas las aplicaciones se procesan con la cuenta Stripe de la organización.
¿Puedo autenticarme sin clave de API, con OAuth de servidor a servidor?
No. La API externa se autentica con X-Api-Key. El flujo OAuth de UTILIA OS es para sesiones de personas, y no existe un permiso de cliente para aplicaciones sin usuario.
¿Dónde veo en UTILIA OS las facturas de mi aplicación?
En Ajustes › Creadas por tu empresa › tu aplicación › Dinero › Facturas, y también en Finanzas › Facturas, donde cada factura muestra de qué aplicación viene. El usuario final aparece como cliente en el CRM con la insignia de tu aplicación.
¿Qué pasa si mi aplicación pierde la conexión con UTILIA OS?
- Crear facturas o suscripciones: la clave de idempotencia te protege. Reintenta con la misma clave.
- Cobrar: si la pasarela ya confirmó el cargo, el aviso te llegará al volver.
- Avisos: hay cinco intentos con esperas de 5, 10, 20 y 40 segundos. La ventana total son minutos, no horas: ante una caída larga, cuadra los datos leyendo los listados en vez de confiar en los reintentos. Detalle en Entregas y reintentos.