Facturas rectificativas
API externa /external/v1/invoices/:id/rectifications: emitir la rectificativa de una factura de tu aplicación cuando devuelves el dinero o un contracargo deja la venta sin efecto.
Actualizado el . 11 min de lectura
Incluido en todos los planes. Ver precios
Cuando una venta queda sin efecto, total o parcialmente, la factura no
se anula: se rectifica. Con /external/v1/invoices/:id/rectifications
tu aplicación emite la rectificativa de una factura que ella misma
emitió, en dos casos:
- Has devuelto el dinero con tu propia pasarela de pago (por ejemplo, tu cuenta de Stripe), también por desistimiento o por una baja con devolución.
- Un contracargo deja la venta sin efecto: lo has aceptado o perdido y el cliente no conserva lo que compró.
Si cobraste con la pasarela de UTILIA OS, no uses esta ruta para devolver: usa el reembolso, que mueve el dinero y puede emitir la rectificativa en el mismo acto.
De la devolución en tu pasarela a la rectificativa
Tu pasarela
Devuelves el dinero
El reembolso se hace en tu cuenta (re_...).POST …/rectifications
Pides la rectificativa
Con la causa, el importe si es parcial, y el bloque refund con la referencia de tu pasarela.Finanzas
UTILIA OS la emite
Código legal, importes negativos, mención y registro de la devolución sobre la factura original.
Lo que decide UTILIA OS, no tu aplicación
| Qué | Cómo se decide |
|---|---|
| Código legal | R5 si la original es una simplificada sin destinatario identificado; R1 si es ordinaria o simplificada cualificada (la operación queda sin efecto total o parcialmente: art. 80.Dos de la Ley del IVA y art. 22.4 de la Ley 20/1991 del IGIC). R2, R3 y R4 no se emiten por esta ruta. |
| Modalidad | Siempre por diferencias, con importes negativos. |
| Líneas | Si rectificas todo y no hay rectificativas previas, copia las líneas de la original con la cantidad en negativo y la original queda en estado RECTIFIED. Si no, una línea por tramo fiscal de la original, con el importe repartido al céntimo. |
| Tratamiento fiscal | Conserva el tipo y la causa de exención de la original, así que repite su mención legal (por ejemplo, «Exención franquicia fiscal») aunque el régimen haya cambiado después. |
| Fecha de expedición | Siempre la de hoy, en la zona horaria de tu organización. No se puede adelantar ni atrasar. La fecha en que devolviste el dinero va en refund.refundedAt. |
| Numeración | La serie de rectificativas de tu organización (R-202600003). |
Si mandas un campo que no está en el contrato, como rectificationCode,
issueRefund o reissue, la petición se rechaza con
400 VALIDATION_ERROR: así nadie cree que ha elegido algo que no eligió.
Un contracargo no siempre se rectifica
Rectifica un contracargo solo si la operación queda sin efecto: lo aceptas o lo pierdes y das de baja el servicio o reconoces el derecho al reembolso. Un contracargo por fraude o por impago en el que la venta sigue en pie es un impago, no una alteración del precio, y un contracargo que ganas no deja nada que rectificar. No hay un criterio oficial publicado: decidirlo es responsabilidad de tu aplicación, y UTILIA OS no lo comprueba.
Plan y permisos
La ruta comprueba tres puertas en cada llamada:
- El plan. Tu organización necesita el aplicativo Finanzas. No hace falta Pagos con tarjeta (Stripe): UTILIA OS no mueve dinero. Sin Finanzas,
403 MODULE_NOT_ENABLED. La facturación de la aplicación tiene que estar encendida (403 BILLING_NOT_ENABLED). - Los permisos de la aplicación (scopes), sin periodo de gracia:
crm:invoices:rectifypara emitir. Es crítico financiero: solo lo concede un Super Administrador, con un motivo.- Además
crm:invoices:writesi mandas el bloquerefund. - Además
crm:invoices:sendsi pides el envío consendEmail: true. Si el envío viene de la preferencia de la aplicación y falta el permiso, no se envía y la respuesta lo dice enoperationMessage. crm:invoices:readpara la lista.
- La persona responsable de la aplicación. La rectificativa se emite en su nombre, así que tiene que poder hacerlo en el panel:
INVOICES_VIEWeINVOICES_RECTIFYcon alcance sobre la factura original,INVOICES_ISSUE,INVOICES_REFUNDsi mandasrefund, eINVOICES_SENDsi pides el envío. Si le falta alguno,403 APP_RESPONSIBLE_PERMISSION_DENIED, sin decir cuál. Si no hay una persona responsable activa,403 APP_RESPONSIBLE_INACTIVE.
Ojo con un caso: si cambias de persona responsable y la nueva solo ve sus propias facturas (alcance «propias»), no podrá rectificar las que se emitieron con la anterior.
El alta se admite solo de servidor a servidor: con la clave en la
cabecera X-Api-Key (si llega en la dirección, 401 API_KEY_IN_URL_NOT_ALLOWED)
y sin cabecera Origin de navegador (403 ORIGIN_NOT_ALLOWED). Tiene
un cupo propio de 10 peticiones por minuto y aplicación, además del
general de 60.
Emitir una rectificativa
/api/external/v1/invoices/:id/rectificationsEmite la rectificativa de la factura :id, que tiene que ser de tu aplicación. Responde 201 al crear y 200 al repetir una operación ya registrada con la misma clave. Permiso: crm:invoices:rectify.
:id es la factura original, la que se rectifica. Se pueden
rectificar facturas ordinarias o simplificadas emitidas (estados
ISSUED, SENT, OVERDUE, PAID, PARTIALLY_PAID, REFUNDED o
PARTIALLY_REFUNDED); no los borradores, las anuladas, las ya
rectificadas del todo ni las propias rectificativas.
Cuerpo
- userIdstringobligatorio
- externalId del usuario propietario de la factura original (1 a 255 caracteres). Si no coincide, 404 INVOICE_NOT_ACCESSIBLE.
- idempotencyKeystringobligatorio
- Clave de idempotencia (1 a 128 caracteres), OBLIGATORIA: un documento fiscal duplicado resta ingresos dos veces. Ver «Repetir y reanudar».
- causeenumobligatorio
- REFUND (devolución del precio, total o parcial, incluidos el desistimiento y la baja con devolución) o CHARGEBACK (contracargo aceptado o perdido CON la operación sin efecto).
- amountnumber
- Importe a restar, CON impuestos, en la divisa de la factura y con 2 decimales como máximo (mínimo 0,01). Si lo omites, se rectifica todo lo que quede por rectificar.
- reasonstring
- Causa que se imprime (hasta 500 caracteres). Si la omites: «Devolución del precio.» con REFUND o «Contracargo: la operación queda sin efecto.» con CHARGEBACK.
- refundobjeto
- La devolución que ya hiciste en tu pasarela. UTILIA OS la registra sobre la factura original como «Pasarela de pago externa», sin mover dinero. Exige además crm:invoices:write. Sin este bloque solo se emite el documento.
- refund.referencestringobligatorio
- Obligatorio si mandas el bloque. Identificador de la devolución en tu pasarela (por ejemplo, re_... o dp_...): hasta 120 caracteres, empieza por una letra o un número y solo admite letras, números, guion bajo, punto, dos puntos y guion, sin espacios. No puede repetirse en otro reembolso de pasarela externa de tu organización.
- refund.refundedAtstring
- Fecha de la devolución (ISO 8601). Puede ser anterior a hoy, pero no futura, ni anterior a la factura original, ni de un periodo cerrado o con una declaración ya presentada. Si la omites, hoy.
- refund.methodenum
- Medio real con el que se devolvió: CARD (a la tarjeta, por defecto), TRANSFER o CASH.
- issueDatestring
- Solo se admite la fecha de hoy en la zona horaria de tu organización. Cualquier otra responde 400 VALIDATION_ERROR. Puedes omitirlo.
- sendEmailboolean
- Enviar la rectificativa por correo al emitirla. Si lo omites, manda la preferencia de la aplicación. Con true exige crm:invoices:send; si falta, 403 antes de hacer nada.
- notesstring
- Notas visibles para el cliente, impresas en el PDF de la rectificativa.
- metadataobjeto
- Datos libres de la rectificativa. Las mismas claves reservadas que el alta de facturas.
Sin el bloque refund se puede rectificar incluso una factura que no
consta cobrada: la rectificativa negativa compensa la deuda y, si es
total, la original queda RECTIFIED.
Con refund, la factura tiene que constar cobrada fuera de la
pasarela de UTILIA OS al menos por el importe de la rectificativa (por
ejemplo, con el bloque payment del alta o con mark-paid). Si no, la
respuesta es 409 REFUND_EXCEEDS_COLLECTED con refundableAmount, y no
se crea nada. Lo que se cobró con la pasarela de UTILIA OS se devuelve
con el reembolso de Acciones sobre una factura emitida.
Ejemplo: devolución total
curl -X POST https://os.utilia.ai/api/external/v1/invoices/cksimp1234567890/rectifications \
-H "X-Api-Key: utl_live_..." \
-H "Content-Type: application/json" \
-d '{
"userId": "user_01HXYZ",
"idempotencyKey": "devolucion-pedido-4471",
"cause": "REFUND",
"refund": { "reference": "re_3QabcDEF", "refundedAt": "2026-10-13" }
}'Respuesta (201)
{
"rectification": {
"id": "ckrect1234567890",
"invoiceNumber": "R-202600003",
"type": "RECTIFYING",
"status": "ISSUED",
"rectificationCode": "R5",
"rectificationType": "DIFFERENCE",
"originalInvoiceId": "cksimp1234567890",
"legalMentions": ["Exención franquicia fiscal"],
"totalAmount": -39.99,
"paidAmount": 0,
"currency": "eur",
"issueDate": "2026-10-13T00:00:00.000Z",
"pdfUrl": "/external/v1/invoices/ckrect1234567890/pdf",
"externalUserId": "user_01HXYZ",
"lines": [
{
"name": "Plan Básico mensual",
"quantity": -1,
"unitPrice": 39.99,
"taxType": "EXEMPT",
"taxAmount": 0,
"total": -39.99,
"exemptionReason": "EXEMPT_IGIC_SMALL_BUSINESS",
"exempt": true
}
],
"rectifications": []
},
"originalInvoice": {
"id": "cksimp1234567890",
"invoiceNumber": "T-202600001",
"type": "SIMPLIFIED",
"status": "RECTIFIED",
"totalAmount": 39.99,
"paidAmount": 0,
"currency": "eur"
},
"refund": {
"id": "ckrefund1234567890",
"amount": 39.99,
"currency": "eur",
"status": "SUCCEEDED",
"source": "EXTERNAL_GATEWAY",
"reference": "re_3QabcDEF",
"refundedAt": "2026-10-13T00:00:00.000Z"
},
"remainingRectifiableAmount": 0,
"operationStatus": "complete",
"operationMessage": "La rectificativa está emitida y la devolución registrada.",
"idempotent": false
}El ejemplo recorta campos de los dos documentos; llegan completos, con la forma del detalle de factura.
- rectificationobjetoobligatorio
- La rectificativa emitida, con la forma del detalle de factura: type RECTIFYING, rectificationCode (R5 o R1), rectificationType DIFFERENCE, originalInvoiceId, total negativo, menciones y pdfUrl.
- originalInvoiceobjetoobligatorio
- La factura original tal como queda: su estado e importe cobrado. Si ya no queda nada por rectificar, RECTIFIED.
- refundobjeto | nullobligatorio
- La devolución registrada, o nulo si no mandaste refund. amount va en la divisa (no en céntimos) y es el valor absoluto del total de la rectificativa. source es siempre EXTERNAL_GATEWAY.
- remainingRectifiableAmountnumberobligatorio
- Lo que aún se puede rectificar de la original, con impuestos.
- operationStatusenumobligatorio
- complete, processing o review_required. Ver «Repetir y reanudar».
- operationMessagestringobligatorio
- Explicación en castellano del estado y del siguiente paso, si lo hay (por ejemplo, por qué no se envió el correo).
- emailSentboolean
- Solo viaja si el envío procedía. false: la rectificativa está emitida y no se envió; reenvíala con POST /external/v1/invoices/:id/send.
- idempotentbooleanobligatorio
- true si la respuesta corresponde a una operación ya registrada con la misma clave.
Ejemplo: devolución parcial sin registrar el dinero
Un cliente de una factura ordinaria de 107,00 € (100 € más IGIC al 7 %) recibe una devolución de 53,50 € por un canal que no quieres registrar en UTILIA OS. Solo emites el documento:
{
"userId": "user_01HXYZ",
"idempotencyKey": "devolucion-parcial-ord-8812",
"cause": "REFUND",
"amount": 53.50,
"reason": "Devolución de la mitad del importe por servicio prestado parcialmente."
}La rectificativa sale con código R1 (la original es ordinaria), una
línea de base −50,00 € y cuota −3,50 €, total −53,50 €.
remainingRectifiableAmount queda en 53,50 y la original conserva su
estado. Si el importe no se pudiera repartir al céntimo entre los tramos
fiscales de la original, la respuesta sería
422 RECTIFICATION_AMOUNT_NOT_REPRESENTABLE, con closestSmallerGross y
closestLargerGross cuando los hay.
Repetir y reanudar
La rectificativa tiene varios pasos (documento, registro de la devolución, emisión y correo). Si algo falla a medias, no se repite lo que ya está hecho:
¿Qué hago con operationStatus?
¿Qué operationStatus trae la respuesta?
complete:
Terminada
Guarda el id de la rectificativa. Lee operationMessage por si el correo no salió.processing:
En curso
Otra petición con la misma clave está trabajando ahora. Consulta de nuevo con la misma clave; no generes otra.review_required:
A medias
Un paso falló y operationMessage dice cuál. Repite la petición con la misma clave y el mismo cuerpo: se reanuda desde el paso pendiente sin duplicar nada.
- La misma clave con el mismo cuerpo y la misma factura devuelve la misma operación, con
200eidempotent: true, y reanuda lo pendiente. - La misma clave con otro cuerpo u otra factura responde
409 BILLING_IDEMPOTENCY_CONFLICT. - Mientras una rectificación de la factura esté sin terminar, pedir otra con una clave distinta responde
409 RECTIFICATION_NOT_ALLOWED: termina antes la primera.
Consultar las rectificativas de una factura
/api/external/v1/invoices/:id/rectificationsRectificativas emitidas de la factura :id, de la más antigua a la más reciente, con lo que aún se puede rectificar. Incluye las que haya emitido tu equipo desde UTILIA OS, nunca sus borradores. Permiso: crm:invoices:read.
userId va como parámetro de consulta.
{
"originalInvoiceId": "ckinv1234567890",
"rectifications": [
{
"id": "ckrect5550000001",
"invoiceNumber": "R-202600004",
"type": "RECTIFYING",
"status": "ISSUED",
"rectificationCode": "R1",
"rectificationType": "DIFFERENCE",
"originalInvoiceId": "ckinv1234567890",
"totalAmount": -53.5
}
],
"remainingRectifiableAmount": 53.5,
"rectifiableWithRefundAmount": 20
}rectifiableWithRefundAmount es lo máximo que puedes rectificar ahora
registrando a la vez la devolución: lo menor entre lo que queda por
rectificar y lo cobrado fuera de la pasarela de UTILIA OS que aún no se
ha devuelto. Solo viaja cuando es menor que remainingRectifiableAmount.
La rectificativa es una factura más: se lee con GET /external/v1/invoices/:id,
se descarga con /pdf y se envía con POST /:id/send, usando su
propio id. En el detalle de la original, rectifications la enumera y
effectiveTotal da el total vigente.
Errores
| Situación | HTTP | errorCode |
|---|---|---|
Cuerpo no válido, campo fuera del contrato o issueDate distinta de hoy | 400 | VALIDATION_ERROR |
| La clave llegó en la dirección | 401 | API_KEY_IN_URL_NOT_ALLOWED |
| Falta un permiso de la aplicación | 403 | INSUFFICIENT_SCOPE |
| El plan no incluye Finanzas | 403 | MODULE_NOT_ENABLED |
| La facturación de la aplicación está apagada | 403 | BILLING_NOT_ENABLED |
| No hay persona responsable activa | 403 | APP_RESPONSIBLE_INACTIVE |
| La persona responsable no puede rectificar esa factura | 403 | APP_RESPONSIBLE_PERMISSION_DENIED |
| La petición viene de un navegador | 403 | ORIGIN_NOT_ALLOWED |
| La factura no existe, es de otra aplicación o de otro usuario | 404 | INVOICE_NOT_ACCESSIBLE |
| Borrador, anulada, ya rectificada, rectificativa, emisión o anulación sin terminar, otra rectificación en curso o periodo cerrado (el mensaje dice el motivo) | 409 | RECTIFICATION_NOT_ALLOWED |
amount supera lo que queda (llega remainingRectifiableAmount) | 409 | RECTIFICATION_AMOUNT_EXCEEDS_REMAINING |
| Las rectificaciones anteriores no cuadran y tu equipo tiene que revisarlas | 409 | RECTIFICATION_HISTORY_REVIEW_REQUIRED |
Con refund, la factura no consta cobrada fuera de la pasarela de UTILIA OS por ese importe (llega refundableAmount) | 409 | REFUND_EXCEEDS_COLLECTED |
refund.reference ya figura en otro reembolso de tu organización | 409 | REFUND_REFERENCE_ALREADY_USED |
| La misma clave con otro cuerpo u otra factura | 409 | BILLING_IDEMPOTENCY_CONFLICT |
| El importe no se puede repartir al céntimo, o es parcial en una factura con retención | 422 | RECTIFICATION_AMOUNT_NOT_REPRESENTABLE |
| Más de 10 peticiones por minuto | 429 | — |
Los datos de negocio (remainingRectifiableAmount, refundableAmount,
closestSmallerGross, closestLargerGross) llegan en la raíz del cuerpo
del error. Con los SDK 4.3.0 se leen en error.details.
Avisos que produce
| Momento | Aviso |
|---|---|
| Se crea la rectificativa | INVOICE_RECTIFICATION_CREATED |
| Se emite y surte efectos | INVOICE_RECTIFIED, con la original en invoice y la rectificativa en rectifyingInvoice |
Se registra la devolución (con refund) | INVOICE_REFUNDED, con refund.source: "EXTERNAL_GATEWAY" |
Los tres van dirigidos solo a tu aplicación. El contenido está en Avisos: contenido y catálogo de eventos.
Con el SDK
const resultado = await sdk.invoices.rectifications.create('cksimp1234567890', {
userId: 'user_01HXYZ',
idempotencyKey: 'devolucion-pedido-4471',
cause: 'REFUND',
refund: { reference: 're_3QabcDEF' },
});
if (resultado.operationStatus === 'review_required') {
// Repite la misma llamada, con la misma clave, para terminarla.
}
const lista = await sdk.invoices.rectifications.list('cksimp1234567890', 'user_01HXYZ');
console.log(lista.remainingRectifiableAmount);Siguiente paso
- Facturar y cobrar con tu propia pasarelaEl recorrido completo: factura cobrada en el acto, devolución y rectificativa.
- FacturasEl alta de facturas ordinarias y simplificadas.
- Rectificativas y abonos en FinanzasCómo ve y gestiona tu equipo las rectificativas.
- Contenido de los avisosLos avisos de facturas, rectificativas y devoluciones.