Suscripciones
Alta, cambios, pausa, baja y facturas de las cuotas recurrentes de los usuarios de una aplicación externa, en /external/v1/subscriptions.
Actualizado el . 7 min de lectura
Incluido en los planes Profesional y Avanzado. Ver precios
Una suscripción define una cuota que se repite: qué se factura, cada cuánto y cómo se cobra. UTILIA OS genera cada periodo la factura con su numeración, su fiscalidad y su firma, la envía por correo si tu aplicación lo pide y, con cobro automático, la cobra con la tarjeta autorizada.
Todos los endpoints están bajo /external/v1/subscriptions, exigen la cabecera X-Api-Key y comparten un límite de 60 peticiones por minuto. Cada aplicación solo ve las suscripciones de sus propios usuarios.
Plan y permisos
| Concepto | Valor |
|---|---|
| Aplicativo del plan | Finanzas |
| Capacidad del plan | Facturación recurrente (finance.recurring) |
Sin el aplicativo, la respuesta es 403 con errorCode: 'MODULE_NOT_ENABLED' y su moduleKey. Sin la capacidad, 403 con errorCode: 'CAPABILITY_NOT_ENABLED' y su capabilityKey.
Las suscripciones no exigen la pasarela: una suscripción en modo manual genera y envía facturas sin cobrar nada. La pasarela solo hace falta para el cobro automático.
| Endpoint | Permiso de la API (scope) |
|---|---|
Lecturas (GET) | billing:subscriptions:read |
| Alta, cambios, pausa, reanudación, reactivación, generación y reintento | billing:subscriptions:write |
POST /subscriptions/:id/cancel | billing:subscriptions:cancel |
Dar de baja tiene permiso propio porque corta un ingreso recurrente y no se deshace: para volver a cobrar hay que crear una suscripción nueva. billing:subscriptions:cancel solo lo concede un Super Administrador, con un motivo. Por eso pausar y dar de baja son operaciones distintas: pausar conserva el historial y la autorización de cobro.
Crear una suscripción
/api/external/v1/subscriptionsDa de alta una cuota recurrente para un usuario de la aplicación. La ficha de cliente del CRM se obtiene a partir del userId.
Cuerpo
- userIdstringobligatorio
- Identificador del usuario en tu aplicación (externalId). De él sale el cliente del CRM al que se factura.
- namestringobligatorio
- Nombre visible de la suscripción, entre 1 y 200 caracteres.
- descriptionstring
- Descripción interna.
- frequencyenum
- MONTHLY_FIXED_DAY (día fijo del mes) o INTERVAL_DAYS (cada N días). Por defecto mensual.
- dayOfMonthinteger
- Día del mes, entre 1 y 28, o 99 para el último día. Solo con frecuencia mensual. Si lo omites, se toma el día de startDate, rebajado a 28 cuando es posterior.
- intervalDaysinteger
- Días entre facturas, entre 1 y 365. Con frecuencia por intervalo.
- startDatestringobligatorio
- Fecha de la primera factura, en formato ISO 8601.
- endDatestring
- Fecha de fin. Al alcanzarla la suscripción pasa a COMPLETED.
- automationenum
- AUTO emite la factura de cada periodo; DRAFT la deja en borrador para que tu equipo la revise. Por defecto, AUTO.
- chargeModeenum
- MANUAL (la paga el usuario) o AUTO_STRIPE (se cobra sola con la tarjeta autorizada). Por defecto, MANUAL.
- paymentAuthorizationIdstring
- Autorización de cobro que respalda el cobro automático. En la práctica, obligatoria con AUTO_STRIPE.
- taxSystemenum
- IVA, IGIC, IPSI, EXEMPT o INTERNATIONAL. Sin este campo, el de tu organización.
- discountPercentnumber
- Descuento global entre 0 y 100.
- daysUntilDueinteger
- Días hasta el vencimiento de cada factura, entre 0 y 365.
- notesstring
- Notas que se imprimen en el PDF de cada factura.
- sendEmailboolean
- Envío por correo de cada factura generada. Sin este campo manda la preferencia de la aplicación.
- linesarrayobligatorio
- Líneas de la cuota. Mínimo una.
- metadataobjeto
- Datos libres. Las claves que empiezan por external están reservadas.
- idempotencyKeystring
- Clave de idempotencia, válida 24 horas.
Cada línea acepta order, name (obligatorio), description, quantity, unitPrice (obligatorio), discount, taxType (obligatorio), igicOperationType, exemptionReason y exemptionNote.
Los campos fiscales de la línea no son decorativos
Las facturas se emiten ya selladas, mes tras mes, sin que intervenga nadie. Una plantilla fiscalmente incorrecta produce facturas incorrectas sin fin. Si operas en Canarias, revisa igicOperationType; si la operación está exenta, exemptionReason y exemptionNote. Los valores admitidos son los mismos que en Facturas.
Ejemplo
curl -X POST https://os.utilia.ai/api/external/v1/subscriptions \
-H "X-Api-Key: utl_live_..." \
-H "Content-Type: application/json" \
-d '{
"userId": "user_01HXYZ",
"name": "Alinea Profesional mensual",
"frequency": "MONTHLY_FIXED_DAY",
"dayOfMonth": 1,
"startDate": "2026-10-01",
"automation": "AUTO",
"chargeMode": "AUTO_STRIPE",
"paymentAuthorizationId": "ckauth1234567890",
"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"
}'Respuesta (201)
{
"id": "cksub1234567890",
"name": "Alinea Profesional mensual",
"status": "ACTIVE",
"frequency": "MONTHLY_FIXED_DAY",
"automation": "AUTO",
"chargeMode": "AUTO_STRIPE",
"dayOfMonth": 1,
"intervalDays": null,
"startDate": "2026-10-01",
"endDate": null,
"nextInvoiceDate": "2026-11-01",
"periodAmount": 53.45,
"currency": "eur",
"externalUserId": "user_01HXYZ",
"paymentAuthorizationId": "ckauth1234567890",
"createdAt": "2026-10-01T09:12:44.000Z",
"updatedAt": "2026-10-01T09:12:44.000Z",
"taxSystem": "IGIC",
"discountPercent": 0,
"daysUntilDue": 7,
"notes": null,
"sendEmail": true,
"lines": [
{
"id": "ckline1234567890",
"order": 0,
"name": "Plan Profesional",
"description": null,
"quantity": 1,
"unitPrice": 49.95,
"discount": 0,
"taxType": "IGIC_7",
"subtotal": 49.95
}
],
"paymentFailureCount": 0,
"invoiceCount": 0,
"metadata": { "planId": "pro_monthly" }
}El cobro automático se rechaza por dos motivos distintos
Pedir chargeMode: "AUTO_STRIPE" falla, en lugar de pasar en silencio a cobro manual, en dos casos que se arreglan en sitios diferentes:
PAYMENT_AUTHORIZATION_REQUIRED: el usuario no tiene una autorización de cobro vigente. Lo resuelves tú: lleva a la persona a firmarla siguiendo elnextStepque trae el error.AUTO_CHARGE_DISABLED_FOR_ORGANIZATION: tu organización tiene apagado el cobro automático con tarjeta. Este error no traenextStep, porque tu aplicación no puede resolverlo: el interruptor está en Finanzas › Ajustes › Cobro automático con tarjeta. Recoger consentimientos no desbloquea nada mientras siga apagado.
Hasta el 5 de septiembre de 2026, el segundo caso llegaba como el primero. Si tu código comprueba errorCode === 'PAYMENT_AUTHORIZATION_REQUIRED' para pedir la firma al usuario, añade la rama del código nuevo.
Errores
| Situación | HTTP | errorCode |
|---|---|---|
| El usuario indicado no existe en la aplicación | 404 | EXTERNAL_USER_NOT_FOUND |
| Cobro automático sin autorización vigente | 409 | PAYMENT_AUTHORIZATION_REQUIRED, con nextStep |
| La organización tiene apagado el cobro automático | 409 | AUTO_CHARGE_DISABLED_FOR_ORGANIZATION, sin nextStep |
| El importe por periodo supera el tope de la aplicación | 422 | BILLING_AMOUNT_EXCEEDS_LIMIT |
Listar suscripciones
/api/external/v1/subscriptionsLista paginada de las suscripciones de la aplicación.
- userIdstring
- Filtra por usuario. Sin él se listan las de todos tus usuarios.
- statusenum
- ACTIVE, PAUSED, CANCELLED o COMPLETED.
- frequencyenum
- MONTHLY_FIXED_DAY o INTERVAL_DAYS.
- automationenum
- AUTO o DRAFT.
- chargeModeenum
- MANUAL o AUTO_STRIPE.
- pageinteger
- Página, por defecto 1.
- limitinteger
- Elementos por página, por defecto 20.
La respuesta tiene la forma { data, total, page, limit, totalPages }, y cada fila es el resumen de la suscripción, sin líneas ni metadatos. El detalle completo se pide con GET /subscriptions/:id.
Detalle, cambios y ciclo de vida
| Método y ruta | Qué hace |
|---|---|
GET /subscriptions/:id | Detalle completo, con líneas y metadatos. Exige userId en la consulta. |
PATCH /subscriptions/:id | Cambia condiciones: importe, líneas, frecuencia, fecha del próximo cobro, modo de cobro, envío por correo. |
POST /subscriptions/:id/pause | Pausa temporal. Deja de generar facturas, conserva historial y autorización. |
POST /subscriptions/:id/resume | Reanuda una suscripción pausada. |
POST /subscriptions/:id/cancel | Baja definitiva. |
POST /subscriptions/:id/reactivate | Reactiva una suscripción cancelada, cuando su estado lo permite. |
POST /subscriptions/:id/generate-now | Adelanta la generación de la factura del periodo. |
Todas las acciones toman en el cuerpo userId (obligatorio) y reason (opcional, para la auditoría). PATCH acepta además los mismos campos del alta que sí se pueden cambiar.
No existe DELETE: dar de baja es cancel, que conserva el historial de facturas y de intentos de cobro. Un borrado destruiría datos que la contabilidad necesita.
Estados de una suscripción
Activa
ACTIVE
Genera la factura de cada periodo.Se llega desde Activa cuando: pause, o acumula cobros fallidos hasta el umbral de tu organización
Pausada
PAUSED
No genera facturas; conserva el historial y la autorización.Se llega desde Activa cuando: cancel
Cancelada
CANCELLED
Baja definitiva; conserva el historial.
Se llega desde Activa cuando: Llega la fecha de fin (endDate)
Completada
COMPLETED
Estado final
Aviso SUBSCRIPTION_COMPLETED.
Otras transiciones
- Pausada pasa a Activa: resume la reanuda.
- Pausada pasa a Cancelada: cancel también la da de baja.
- Cancelada pasa a Activa: reactivate la recupera.
Errores del ciclo de vida
| Situación | HTTP | errorCode |
|---|---|---|
| La suscripción es de otra aplicación o de otro usuario | 404 | SUBSCRIPTION_NOT_ACCESSIBLE |
| Pausar una pausada, reanudar una cancelada, cambiar una completada | 409 | SUBSCRIPTION_STATE_INVALID |
Respuesta de generate-now
{
"generated": true,
"invoiceId": "ckinv1234567890",
"invoiceNumber": "202600042",
"invoiceStatus": "SENT",
"reason": null
}Con generated: false, el campo reason explica por qué no tocaba generar todavía.
Próximo cobro
/api/external/v1/subscriptions/:id/upcoming-chargeQué se va a facturar y cobrar la próxima vez, y si el cobro automático está listo.
{
"subscriptionId": "cksub1234567890",
"nextInvoiceDate": "2026-11-01",
"nextChargeDate": "2026-11-08",
"amount": 53.45,
"currency": "eur",
"autoChargeReady": true,
"autoChargeBlockedReason": null
}Es la consulta que conviene mostrar en el perfil del usuario dentro de tu aplicación: autoChargeReady: false, con su motivo, te avisa antes de que la renovación falle.
Facturas de la suscripción
/api/external/v1/subscriptions/:id/invoicesFacturas ya generadas por esta suscripción, con el periodo que cubre cada una.
{
"data": [
{
"id": "ckinv1234567890",
"invoiceNumber": "202600042",
"status": "PAID",
"totalAmount": 53.45,
"paidAmount": 53.45,
"currency": "eur",
"issueDate": "2026-10-01T00:00:00.000Z",
"dueDate": "2026-10-08T00:00:00.000Z",
"periodStart": "2026-10-01T00:00:00.000Z",
"periodEnd": "2026-10-31T23:59:59.000Z"
}
],
"total": 3,
"page": 1,
"limit": 20,
"totalPages": 1
}Cada fila es reducida: trae el periodo que cubre, pero no pdfUrl, externalUserId ni createdAt. Para el detalle completo de una factura, usa GET /external/v1/invoices/:id.
Historial de ejecuciones
/api/external/v1/subscriptions/:id/logsQué pasó en cada ciclo: si se generó la factura, si se saltó o si falló.
{
"data": [
{
"id": "cklog1234567890",
"executedAt": "2026-10-01T06:00:00.000Z",
"result": "GENERATED",
"invoiceId": "ckinv1234567890",
"invoiceNumber": "202600042",
"detail": "Factura generada y enviada por correo."
}
],
"total": 1,
"page": 1,
"limit": 20,
"totalPages": 1
}Reintentar el cobro de una factura de la suscripción
/api/external/v1/subscriptions/:id/invoices/:invoiceId/retry-chargeAdelanta el reintento del cobro de una factura generada por esta suscripción.
Devuelve un acuse de recibo: el cobro no se ejecuta en el momento de la llamada.
{
"accepted": true,
"scheduledChargeId": "cksch1234567890",
"status": "PENDING",
"scheduledAt": "2026-10-05T10:30:00.000Z"
}El resultado llega por los avisos de cobro (CHARGE_*).
Avisos de suscripciones
Todos dirigidos a la aplicación dueña del usuario.
| Aviso | Cuándo |
|---|---|
SUBSCRIPTION_CREATED | Alta. |
SUBSCRIPTION_UPDATED | Cambio de condiciones, con la lista de campos cambiados. |
SUBSCRIPTION_PAUSED | Pausa temporal. |
SUBSCRIPTION_RESUMED | Reanudación o reactivación. |
SUBSCRIPTION_CANCELLED | Baja definitiva. |
SUBSCRIPTION_COMPLETED | Se alcanzó la fecha de fin. |
SUBSCRIPTION_INVOICE_GENERATED | Factura del periodo, con emailSent. |
SUBSCRIPTION_PAYMENT_AUTHORIZATION_PENDING | Está configurada para cobrar sola pero no tiene autorización vigente. |
Con el SDK
import { UtiliaSDK } from '@utilia-os/sdk-js';
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
apiKey: process.env.UTILIA_API_KEY!,
});
const suscripcion = await sdk.subscriptions.create({
userId: 'user_01HXYZ',
name: 'Alinea Profesional mensual',
frequency: 'MONTHLY_FIXED_DAY',
dayOfMonth: 1,
startDate: '2026-10-01',
automation: 'AUTO',
chargeMode: 'AUTO_STRIPE',
paymentAuthorizationId: 'ckauth1234567890',
taxSystem: 'IGIC',
daysUntilDue: 7,
sendEmail: true,
lines: [
{ name: 'Plan Profesional', quantity: 1, unitPrice: 49.95, taxType: 'IGIC_7' },
],
idempotencyKey: 'sub_user_01HXYZ_pro',
});
const proximo = await sdk.subscriptions.getUpcomingCharge(suscripcion.id, {
userId: 'user_01HXYZ',
});
await sdk.subscriptions.pause(suscripcion.id, {
userId: 'user_01HXYZ',
reason: 'El cliente pidió parar dos meses.',
});Siguiente paso
- Autorizaciones de cobroRecoger el consentimiento que habilita el cobro automático.
- Acciones sobre una facturaEnviar, cobrar, marcar pagada o devolver una factura generada.
- Contenido de los avisosLos avisos de suscripciones y de cobros, campo a campo.
- Guía de facturaciónEl recorrido completo, del alta del usuario al cobro recurrente.