Crear nota de crédito
Emite una nota de crédito electrónica en El Salvador.
Emite una nota de crédito electrónica que disminuye el valor de un comprobante de crédito fiscal ya emitido. Úsalo desde tu ERP, SaaS o backend cuando hay una devolución, un descuento o bonificación posterior a la venta, o una corrección de montos a favor del cliente.
En lugar de armar y entender las especificaciones de la normativa para emitir una nota de crédito, resolver el documento relacionado, separar el IVA de los precios, replicar la retención o percepción de IVA del CCF original y mucho más, solo envías el documento a ajustar, el receptor y los ítems. Lumen valida el vínculo, calcula impuestos, reserva el correlativo, transmite y te devuelve PDF/JSON con URLs prefirmadas.
La emisión es síncrona: en una sola petición obtienes el resultado final de Hacienda.
Método: POST
Nota importante
Es necesario el envío del token de acceso <access_token> para hacer uso de este endpoint. Para ver cómo generar el token, vea Cómo generar un token de acceso.
Recuerde: El token de acceso caduca cada hora.
Antes de emitir, la empresa, el establecimiento y el punto de venta deben existir. Cada nota de crédito ajusta un solo documento: un crédito fiscal electrónico emitido con Lumen de la misma empresa, o un CCF físico.
Endpoint
https://api-sandbox.lumendte.com/v1/credit-notesHeaders
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
Authorization | string | Sí | Bearer <access_token> |
Content-Type | string | Sí | application/json |
x-company-tax-number | string | Sí | NRC de la empresa emisora (con o sin guiones) |
x-establishment-code | string | Sí | Código dado por MH para el establecimiento (ej. M001) |
x-point-of-sale-code | string | Sí | Código dado por MH para el punto de venta (ej. P001) |
Idempotency-Key | string | Sí | Identificador estable de esta nota de crédito en tu sistema (1 a 128 caracteres, sin espacios). Ver Idempotencia |
Payload mínimo
El caso más común es ajustar un CCF electrónico. Este es el payload mínimo:
{
"relatedDocument": {
"documentType": "ELECTRONIC",
"documentNumber": "FF54E9DB-79C3-42CE-B432-EC552C97EFB9"
},
"customer": {
"name": "Distribuidora Morazán S.A. de C.V.",
"taxIdentityNumber": "0614-280310-102-8",
"taxRegistrationNumber": "298145-4",
"taxpayerType": "MEDIUM",
"economicActivityCode": "46900",
"address": {
"complement": "Blvd. Los Próceres, San Salvador",
"departmentCode": "06",
"districtCode": "13"
}
},
"items": [
{
"description": "Devolución de servicio de consultoría",
"quantity": 1,
"unitPrice": 113.0
}
]
}documentNumber es el generationCode que recibiste al emitir el CCF. Para un CCF físico usa documentType: "PHYSICAL", el correlativo impreso como documentNumber y su fecha de emisión en issueDate (YYYY-MM-DD).
Respuesta exitosa
Status: 201 Created
Guarda generationCode y controlNumber: el generationCode lo necesitarás más adelante para descargar el PDF/JSON y para cualquier consulta posterior.
Las URLs pdfUrl y jsonUrl expiran en 15 minutos. No las persistas en tu base de datos para uso posterior; cuando expiren, pide unas nuevas con Descargar PDF y Descargar JSON usando el generationCode.
Si pdfUrl o jsonUrl llegan en null, la nota de crédito es válida igualmente: los archivos se generarán en la primera llamada en los endpoints de descarga. No vuelvas a emitir la nota de crédito.
El campo status indica el resultado:
status | Cuándo aplica |
|---|---|
PROCESSED | Hacienda aceptó el documento |
DRAFT | Solo en sandbox, sin credenciales de facturación y certificado: no se firma ni se transmite |
Procesada (PROCESSED)
{
"status": "PROCESSED",
"generationCode": "FF84E5DB-79C5-42CE-B415-EC510C53EFB5",
"controlNumber": "DTE-05-M001P001-000000000000001",
"receivedStamp": "20219E9D4DC0292F4681AD759B0B0F5CA99DC23G",
"environment": "SANDBOX",
"pdfUrl": "https://files.example.com/invoices/1/FF84E5DB-79C5-42CE-B415-EC510C53EFB5.pdf?X-Amz-Expires=900",
"jsonUrl": "https://files.example.com/invoices/1/FF84E5DB-79C5-42CE-B415-EC510C53EFB5.json?X-Amz-Expires=900"
}Borrador sandbox (DRAFT)
En sandbox sin credenciales listas, Lumen crea el documento como borrador: no llama a firma ni a Hacienda y no tiene validez fiscal. receivedStamp es null. El PDF y el JSON sí quedan disponibles.
{
"status": "DRAFT",
"generationCode": "FF84E5DB-79C5-42CE-B415-EC510C53EFB5",
"controlNumber": "DTE-05-M001P001-000000000000001",
"receivedStamp": null,
"environment": "SANDBOX",
"pdfUrl": "https://files.example.com/invoices/1/FF84E5DB-79C5-42CE-B415-EC510C53EFB5.pdf?X-Amz-Expires=900",
"jsonUrl": "https://files.example.com/invoices/1/FF84E5DB-79C5-42CE-B415-EC510C53EFB5.json?X-Amz-Expires=900"
}Sandbox vs producción
DRAFT solo ocurre en sandbox. En producción, si faltan las credenciales de
facturación y certificado, la API responde 404 con
COMPANY_CREDENTIAL_NOT_FOUND en lugar de crear un borrador.
environment: SANDBOX o PRODUCTION.
Estructura completa del payload
Si necesitas informar nombre comercial, contacto, descuentos o una condición de operación distinta a contado, envía el objeto completo. A diferencia de la factura y el CCF, la nota de crédito no lleva payments. No se aceptan campos adicionales a los documentados: si envías uno desconocido, la petición se rechaza con 400 INVALID_PARAMETERS.
{
"relatedDocument": {
"documentType": "PHYSICAL",
"documentNumber": "0001234",
"issueDate": "2026-06-13"
},
"customer": {
"name": "Distribuidora Morazán S.A. de C.V.",
"tradeName": "Comercial Morazán",
"taxIdentityNumber": "0614-280310-102-8",
"taxRegistrationNumber": "298145-4",
"email": "facturacion@morazan.com",
"phone": "+503 2255-8888",
"taxpayerType": "LARGE",
"economicActivityCode": "46900",
"address": {
"complement": "Blvd. Los Próceres, San Salvador",
"departmentCode": "06",
"districtCode": "13"
}
},
"items": [
{
"sku": "PROD-001",
"description": "Bonificación por pronto pago",
"quantity": 1,
"unitPrice": 56.5,
"taxType": "TAXABLE",
"itemType": "SERVICE",
"unitOfMeasure": "59",
"discountAmount": 5.65
}
],
"paymentTerms": "CREDIT"
}Documento relacionado
Documento único que se ajusta. Siempre obligatorio.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
documentType | string | Sí | ELECTRONIC (CCF emitido con Lumen) o PHYSICAL (CCF en papel) |
documentNumber | string | Sí | generationCode (UUID) si es electrónico; correlativo impreso si es físico. Máx. 36 caracteres |
issueDate | string | Condicional | YYYY-MM-DD. Obligatorio si documentType es PHYSICAL. En electrónicos se ignora: Lumen la toma del CCF emitido |
Para ELECTRONIC, el documento debe:
- Existir en tu cuenta y pertenecer a la empresa de
x-company-tax-number. - Estar en estado
PROCESSED(aceptado por Hacienda). En sandbox sin credenciales de Hacienda también se acepta un CCF enDRAFT.
Cliente
Receptor contribuyente. Siempre obligatorio en NC, incluida su dirección. Debe ser el mismo cliente del CCF.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Sí | Nombre o razón social del cliente |
taxIdentityNumber | string | Sí | NIT del cliente (con o sin guiones) |
taxRegistrationNumber | string | Sí | NRC del cliente (con o sin guiones) |
taxpayerType | string | Sí | SMALL, MEDIUM o LARGE |
economicActivityCode | string | Sí | Código de actividad económica, (máx. 5 caracteres). Ver Actividades económicas |
address | object | Sí | Dirección del cliente. Hacienda la exige en la nota de crédito |
address.complement | string | Sí | Complemento de dirección (calle, colonia, etc.) |
address.departmentCode | string | Sí | Código de departamento. Envíalo junto con districtCode. Ver Departamentos |
address.districtCode | string | Sí | Código de distrito. Envíalo junto con departmentCode; el municipio se deduce automáticamente. Ver Distritos |
tradeName | string | No | Nombre comercial del cliente |
email | string | No | Correo del cliente |
phone | string | No | Teléfono nacional o internacional (ej. 2255-8888, +503 2255-8888) |
Items
Lo que se acredita al cliente. Mínimo 1, máximo 2000 (si te pasas, la API responde 400 INVOICE_ITEMS_LIMIT_EXCEEDED).
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
description | string | Sí | Descripción del producto o servicio que se acredita |
quantity | number | Sí | Cantidad (> 0) |
unitPrice | number | Sí | Precio unitario con IVA incluido (> 0). En ítems gravados Lumen separa el IVA; en exentos o no sujetos el precio se toma tal cual |
sku | string | null | No | Código interno del producto |
taxType | string | No | TAXABLE (default), EXEMPT, NOT_SUBJECT |
itemType | string | No | GOODS, SERVICE (default), BOTH |
unitOfMeasure | string | No | Unidad de medida del producto. Default: "59" (Unidad). Ver Unidades de medida |
discountAmount | number | No | Descuento monetario de la línea, con IVA incluido. Default: 0 |
Significados de taxType:
| Valor | Significado |
|---|---|
TAXABLE | Venta gravada |
EXEMPT | Venta exenta |
NOT_SUBJECT | Venta no sujeta |
Significados de itemType:
| Valor | Significado |
|---|---|
GOODS | Productos o bienes |
SERVICE | Servicio |
BOTH | Implica tanto servicio como producto a la vez |
Forma de pago
Condición de la operación (CAT-016). Opcional; si no la envías se usa CASH.
| Valor | Significado |
|---|---|
CASH | Contado |
CREDIT | Crédito |
OTHER | Otro |
Retención y percepción de IVA
Lumen los calcula según el documento relacionado.
relatedDocument.documentType | Resultado |
|---|---|
ELECTRONIC | Si el CCF original tuvo retención o percepción de IVA del 1%, se aplica lo mismo sobre el monto gravado neto (sin IVA y después de descuentos) de la nota |
PHYSICAL | No se aplica retención ni percepción |
Ejemplo de solicitud
curl -X POST https://api-sandbox.lumendte.com/v1/credit-notes \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer tu_access_token" \
-H "x-company-tax-number: 123456-7" \
-H "x-establishment-code: M001" \
-H "x-point-of-sale-code: P001" \
-H "Idempotency-Key: order-9981" \
-d '{
"relatedDocument": {
"documentType": "ELECTRONIC",
"documentNumber": "FF54E9DB-79C3-42CE-B432-EC552C97EFB9"
},
"customer": {
"name": "Distribuidora Morazán S.A. de C.V.",
"taxIdentityNumber": "0614-280310-102-8",
"taxRegistrationNumber": "298145-4",
"taxpayerType": "MEDIUM",
"economicActivityCode": "46900",
"address": {
"complement": "Blvd. Los Próceres, San Salvador",
"departmentCode": "06",
"districtCode": "13"
}
},
"items": [
{
"description": "Devolución de servicio de consultoría",
"quantity": 1,
"unitPrice": 113.0
}
]
}'Idempotencia
Idempotency-Key es obligatorio para todas las integraciones. Identifica una nota de crédito de tu empresa en el ambiente actual (SANDBOX o PRODUCTION) y para este tipo de DTE.
Usa un identificador estable de tu sistema, por ejemplo el id de la devolución, id interno, un UUID, etc. Si la petición hace timeout o falla la red, reenvía el mismo POST con la misma clave, internamente se conserva el correlativo y no se emite una segunda nota de crédito.
No omitas el header ni envíes un valor vacío o solo espacios. En esos casos, la petición queda sin protección contra duplicados y cada POST genera y transmite un DTE nuevo.
| Regla | Comportamiento |
|---|---|
| Formato | De 1 a 128 caracteres, sin espacios al inicio, al final o en medio |
| Vacío o solo espacios | No permitido. Se procesa sin protección contra duplicados |
| Valor inválido | 400 INVALID_IDEMPOTENCY_KEY. No se transmite a Hacienda |
| Mismo establecimiento y punto de venta | Obligatorio en cada reintento. |
| Otro establecimiento o punto de venta | 409 IDEMPOTENCY_KEY_CONTEXT_MISMATCH. No se emite nada y no se mueve el correlativo |
Para corregir un rechazo del DTE ante Hacienda, reenvía el POST con la misma Idempotency-Key, el mismo x-establishment-code, el mismo x-point-of-sale-code y el body corregido. Esto es útil en casos cuando por ejemplo el NIT o el NRC del cliente es incorrecto.
Errores
Todas las respuestas de error comparten esta estructura:
{
"errorCode": "MH_RECEPTION_DOCUMENT_ERROR",
"message": "The amount exceeds the amount of the related document at the tax authority.",
"statusCode": 422,
"status": "REJECTED",
"generationCode": "FF84E5DB-79C5-42CE-B415-EC510C53EFB5",
"controlNumber": "DTE-05-M001P001-000000000000001",
"observations": ["..."]
}| Campo | Cuándo aparece |
|---|---|
errorCode | Siempre disponible. Código estable para manejar el error en tu código |
message | Siempre disponible. En errores de validación (INVALID_PARAMETERS) es un arreglo con cada problema |
statusCode | Siempre disponible. Código HTTP |
status, generationCode, controlNumber | Solo serán reportados en el error si se intentó emitir el DTE. |
observations | Solo si Hacienda rechazó el documento por alguna razón y nos informan el motivo. |
Cómo actuar según status
El campo status indica hasta qué etapa avanzó la emisión del documento. Úsalo para decidir si debes corregir y reintentar la petición o verificar primero el documento para evitar una emisión duplicada.
Al reintentar, envía la misma Idempotency-Key, el mismo x-establishment-code y el mismo x-point-of-sale-code. Consulta Idempotencia para conocer todas las reglas de reutilización de la clave.
| Status en el error | Qué significa | Qué hacer |
|---|---|---|
| (ausente) | El documento no se creó y no se transmitió a Hacienda | Revisa la respuesta, corrige la causa y reintenta la misma petición |
FAILED | Ocurrió un fallo interno y el DTE no fue transmitido a Hacienda | Intenta nuevamente la petición con el mismo Idempotency-Key y el mismo body. Si persiste, contacta a soporte |
REJECTED | Hacienda recibió el DTE y lo rechazó | Corrige los datos según message y observations. Reenvía el mismo POST con la misma Idempotency-Key y el body corregido. |
Códigos de error
Cada error incluye un errorCode estable que no cambia entre versiones. Úsalo para decidir qué hacer en tu integración (ERP, SaaS o backend B2B) y para mostrar a tus usuarios mensajes claros en su idioma. No dependas del texto de message, que está en inglés y puede cambiar, ni solo del código HTTP, porque varios errores comparten el mismo.
| HTTP | errorCode | Causa y solución | status |
|---|---|---|---|
| 400 | INVALID_PARAMETERS | El body no cumple las validaciones (campo faltante, valor inválido o campo no permitido). Revisa message | — |
| 400 | MISSING_COMPANY_TAX_NUMBER | Falta el header x-company-tax-number | — |
| 400 | MISSING_ESTABLISHMENT_CODE | Falta el header x-establishment-code | — |
| 400 | MISSING_POINT_OF_SALE_CODE | Falta el header x-point-of-sale-code | — |
| 400 | INVOICE_ITEMS_LIMIT_EXCEEDED | Enviaste más de 2000 ítems | — |
| 400 | INVOICE_ITEM_DISCOUNT_EXCEEDS_LINE_TOTAL | El descuento de un ítem es mayor que quantity × unitPrice. El message indica qué ítem corregir | — |
| 400 | INVALID_IDEMPOTENCY_KEY | Idempotency-Key tiene más de 128 caracteres, espacios o caracteres de control. No se transmite a Hacienda | — |
| 400 | RELATED_DOCUMENT_ISSUE_DATE_REQUIRED | documentType es PHYSICAL y falta relatedDocument.issueDate | — |
| 400 | RELATED_DOCUMENT_INVALID_TYPE | El documento electrónico relacionado no es un CCF. Usa el generationCode de un crédito fiscal | — |
| 400 | ELECTRONIC_INVOICE_INVALID_STATUS | El CCF relacionado no está en PROCESSED (o DRAFT en sandbox sin credenciales); por ejemplo, fue rechazado o quedó sin confirmar | — |
| 400 | MH_RECEPTION_VALIDATION_ERROR | Hacienda rechazó un campo, formato, fecha o cálculo | REJECTED |
| 401 | UNAUTHORIZED | Falta Authorization, o el token es inválido o expiró. | — |
| 401 | TENANT_CREDENTIAL_NOT_FOUND | El clientId de tu client credentials no existe o está inactivo | — |
| 404 | COMPANY_NOT_FOUND | No existe una empresa con ese NRC en tu cuenta | — |
| 404 | ESTABLISHMENT_NOT_FOUND | El establecimiento no existe para esa empresa | — |
| 404 | POINT_OF_SALE_NOT_FOUND | El punto de venta no existe o está inactivo en ese establecimiento | — |
| 404 | ECONOMIC_ACTIVITY_NOT_FOUND | El customer.economicActivityCode no existe. Usa un código del catálogo de Actividades económicas | — |
| 404 | DISTRICT_NOT_FOUND | El customer.address.districtCode no existe para el departmentCode enviado. Revisa el catálogo de Distritos | — |
| 404 | ELECTRONIC_INVOICE_NOT_FOUND | No existe un documento con ese relatedDocument.documentNumber (generationCode) para la empresa indicada | — |
| 404 | COMPANY_CREDENTIAL_NOT_FOUND | Faltan las credenciales y el certificado de Hacienda (obligatorio en producción) | — |
| 409 | IDEMPOTENCY_KEY_IN_PROGRESS | Ya hay una emisión en curso para esta Idempotency-Key. Espera y reintenta la misma clave | — |
| 409 | IDEMPOTENCY_KEY_NOT_RETRYABLE | La clave pertenece a un documento en CONTINGENCY y no admite reintento | — |
| 409 | IDEMPOTENCY_KEY_CONTEXT_MISMATCH | La clave ya existe, pero el establecimiento o el punto de venta no coinciden con el documento original | — |
| 409 | MH_RECEPTION_DOCUMENT_ERROR | Hacienda reporta un registro duplicado | REJECTED |
| 422 | MH_RECEPTION_DOCUMENT_ERROR | Hacienda rechazó el documento relacionado: el monto supera al del CCF, el CCF ya fue invalidado o los datos no coinciden con el CCF | REJECTED |
| 422 | MH_RECEPTION_TAXPAYER_ERROR | Problema con el emisor o el receptor: NIT/NRC inexistente o inactivo, o emisor no autorizado para emitir notas de crédito | REJECTED |
| 502 | MH_RECEPTION_SERVICE_ERROR | Error de servicio de Hacienda. Usa el status del body para distinguir el caso | REJECTED |
| 502 | MH_RECEPTION_INVALID_RESPONSE | Hacienda devolvió una respuesta inválida o sin sello de recepción | |
| 504 | MH_RECEPTION_TIMEOUT | Hacienda no respondió a tiempo | |
| 5xx | Otros | Fallas de firma electrónica, de autenticación ante Hacienda o internas. Reintenta con la misma Idempotency-Key; si persiste, contacta a soporte | Ver status del body |