Lumen Docs
Facturación

Crear crédito fiscal

Emite un comprobante de crédito fiscal electrónico en El Salvador.

Emite un comprobante de crédito fiscal electrónico (CCF) a un cliente contribuyente de IVA. Úsalo desde tu ERP, SaaS o POS.

En lugar de armar el JSON del DTE, separar el IVA de los precios, calcular la retención o percepción de IVA cuando aplique, firmar digitalmente y llamar a recepción del MH, solo envías cliente, ítems y pagos. Lumen abstrae el proceso, 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

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. A diferencia de la factura de consumidor final, el receptor (customer) es siempre obligatorio.

Endpoint

https://api-sandbox.lumendte.com/v1/tax-invoices

Headers

CampoTipoRequeridoDescripción
AuthorizationstringSíBearer <access_token>
Content-TypestringSíapplication/json
x-company-tax-numberstringSíNRC de la empresa emisora (con o sin guiones)
x-establishment-codestringSíCódigo dado por MH para el establecimiento (ej. M001)
x-point-of-sale-codestringSíCódigo dado por MH para el punto de venta (ej. P001)
Idempotency-KeystringSíIdentificador estable de esta venta en tu sistema (1 a 128 caracteres, sin espacios). Ver Idempotencia

Payload mínimo

El CCF exige receptor contribuyente. Este es el payload mínimo para emitir a contado:

{
  "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": "Servicio de consultoría",
      "quantity": 1,
      "unitPrice": 113.0
    }
  ],
  "payments": [
    {
      "paymentCode": "01",
      "amount": 113.0
    }
  ]
}

Respuesta exitosa

Status: 201 Created

Guarda generationCode y controlNumber. El generationCode también lo necesitas para emitir notas de crédito que hagan referencia a este CCF.

Las URLs pdfUrl y jsonUrl expiran en 15 minutos: descárgalas o reenvíalas al cliente de inmediato desde tu backend. 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, el crédito fiscal es válido igualmente, los archivos se generan en la primera llamada a los endpoints de descarga. No vuelvas a emitir el comprobante.

El campo status indica el resultado:

statusCuándo aplica
PROCESSEDHacienda aceptó el documento
DRAFTSolo en sandbox, sin credenciales de facturación y certificado: no se firma ni se transmite

Procesado (PROCESSED)

{
  "status": "PROCESSED",
  "generationCode": "FF84E5DB-79C5-42CE-B415-EC510C53EFB5",
  "controlNumber": "DTE-03-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-03-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 nombre comercial, contacto, descuentos, ítems exentos o pagos a crédito/mixtos, envía el objeto completo. No se aceptan campos adicionales a los documentados: si envías uno desconocido, la petición se rechaza con 400 INVALID_PARAMETERS.

{
  "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": "MEDIUM",
    "economicActivityCode": "46900",
    "address": {
      "complement": "Blvd. Los Próceres, San Salvador",
      "departmentCode": "06",
      "districtCode": "13"
    }
  },
  "items": [
    {
      "sku": "PROD-001",
      "description": "Servicio de consultoría",
      "quantity": 1,
      "unitPrice": 113.0,
      "taxType": "TAXABLE",
      "itemType": "SERVICE",
      "unitOfMeasure": "59",
      "discountAmount": 0
    }
  ],
  "payments": [
    {
      "paymentCode": "01",
      "amount": 113.0
    }
  ]
}

Customer

Receptor contribuyente. Siempre obligatorio en CCF, incluida su dirección.

CampoTipoRequeridoDescripción
namestringSíNombre o razón social del cliente.
taxIdentityNumberstringSíNIT (14 dígitos) o DUI (9 dígitos) del cliente, con o sin guiones.
taxRegistrationNumberstringSíNRC del cliente, entre 2 y 8 dígitos (con o sin guiones)
taxpayerTypestringSíSMALL, MEDIUM o LARGE.
economicActivityCodestringSíCódigo de actividad económica, entre 5 y 6 caracteres (ej. 46900). Ver Actividades económicas
addressobjectSíDirección del cliente. Hacienda la exige en el CCF
address.complementstringSíComplemento de dirección (calle, colonia, etc.).
address.departmentCodestringSíCódigo de departamento. Envíalo junto con districtCode. Ver Departamentos
address.districtCodestringSíCódigo de distrito. Debe existir para el departmentCode enviado; el municipio se deduce automáticamente. Ver Distritos
tradeNamestringNoNombre comercial del cliente.
emailstringNoCorreo del cliente.
phonestringNoTeléfono nacional o internacional (ej. 2255-8888, +503 2255-8888)

Items

Líneas del documento. Mínimo 1, máximo 2000 (si te pasas, la API responde 400 INVOICE_ITEMS_LIMIT_EXCEEDED).

CampoTipoRequeridoDescripción
descriptionstringSíDescripción del ítem
quantitynumberSíCantidad (> 0)
unitPricenumberSíPrecio unitario con IVA incluido (> 0). En ítems gravados Lumen separa el IVA; en exentos o no sujetos el precio se toma tal cual
skustring | nullNoCódigo interno del producto
taxTypestringNoTAXABLE (default), EXEMPT, NOT_SUBJECT
itemTypestringNoGOODS, SERVICE (default), BOTH
unitOfMeasurestringNoUnidad de medida del producto. Default: "59" (Unidad). Ver Unidades de medida
discountAmountnumberNoDescuento monetario de la línea, con IVA incluido igual que unitPrice (≥ 0 y ≤ quantity × unitPrice). Default: 0

Ejemplo: 2 unidades a $565.00 con $56.50 de descuento.

{
  "description": "Servicio de consultoría",
  "quantity": 2,
  "unitPrice": 565.0,
  "discountAmount": 56.5
}

La línea queda en $950.00 sin IVA (1000.00 − 50.00) más $123.50 de IVA: total $1,073.50, que es 1130.00 − 56.50. En ítems gravados Lumen separa el IVA del descuento y calcula el IVA sobre el monto ya descontado; tú no tienes que separarlo. En ítems exentos o no sujetos el descuento se toma tal cual.

Significados de taxType:

ValorSignificado
TAXABLEVenta gravada
EXEMPTVenta exenta
NOT_SUBJECTVenta no sujeta

Significados de itemType:

ValorSignificado
GOODSProductos o bienes
SERVICEServicio
BOTHImplica tanto servicio como producto a la vez

Payments

Formas de pago. Mínimo 1. Puedes enviar más de una si la transacción se pagó con varios medios.

Lumen establece la condición de la operación (CAT-016) automáticamente según los pagos enviados:

Pagos enviadosCondición
Solo contado (cualquier código ≠ 13)Contado
Solo crédito (13)Crédito
Crédito (13) + contadoMixto
CampoTipoRequeridoDescripción
paymentCodestringSíCódigo de forma de pago. Ver Formas de pago
amountnumberSíMonto del pago. Envíalo siempre con el valor real de esa forma de pago.
referencestringNoReferencia o código de autorización. Se ignora en efectivo (01) y en crédito puro
creditTermstring | nullCondicionalTipo de plazo CAT-018. Requerido si paymentCode es 13 (ej. 01 = Días)
creditPeriodintegerCondicionalCantidad de días/meses/años según creditTerm (1–999). Requerido si paymentCode es 13
methodDetailstring | nullCondicionalRequerido si paymentCode es 99 (Otros)

Cuando paymentCode es 13 (cuentas por pagar), debes indicar plazo y periodo. Ejemplo para un pago a crédito de 30 días:

{
  "payments": [
    {
      "paymentCode": "13",
      "amount": 150.0,
      "creditTerm": "01",
      "creditPeriod": 30
    }
  ]
}

creditTerm (01) = días; creditPeriod (30) = 30 días de plazo. En ventas a crédito envía siempre el monto real de la venta.

Retención y percepción de IVA

No envías estos montos: Lumen los calcula con el taxpayerType de tu empresa y el del cliente. Solo aplican cuando el monto gravado neto (después de descuentos y sin IVA) es de $100.00 o más:

Tu empresaClienteResultado
SMALL o MEDIUMLARGERetención del 1%: el total a pagar disminuye
LARGESMALL o MEDIUMPercepción del 1%: se registra la percepción en el comprobante
Otra combinación—Sin retención ni percepción

Ejemplo de solicitud

curl -X POST https://api-sandbox.lumendte.com/v1/tax-invoices \
  -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 '{
    "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": "Servicio de consultoría",
        "quantity": 1,
        "unitPrice": 113.0
      }
    ],
    "payments": [
      {
        "paymentCode": "01",
        "amount": 113.0
      }
    ]
  }'

Idempotencia

Idempotency-Key es obligatorio para todas las integraciones. Identifica una venta 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 del pedido, id interno, 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 un segundo DTE.

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.

ReglaComportamiento
FormatoDe 1 a 128 caracteres, sin espacios al inicio, al final o en medio
Vacío o solo espaciosNo permitido. Se procesa sin protección contra duplicados
Valor inválido400 INVALID_IDEMPOTENCY_KEY. No se transmite a Hacienda
Mismo establecimiento y punto de ventaObligatorio en cada reintento.
Otro establecimiento o punto de venta409 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_TAXPAYER_ERROR",
  "message": "The taxpayer is not active at the tax authority.",
  "statusCode": 422,
  "status": "REJECTED",
  "generationCode": "FF84E5DB-79C5-42CE-B415-EC510C53EFB5",
  "controlNumber": "DTE-03-M001P001-000000000000001",
  "observations": ["..."]
}
CampoCuándo aparece
errorCodeSiempre disponible. Código estable para manejar el error en tu código
messageSiempre disponible. En errores de validación (INVALID_PARAMETERS) es un arreglo con cada problema
statusCodeSiempre disponible. Código HTTP
status, generationCode, controlNumberSolo serán reportados en el error si se intentó emitir el DTE.
observationsSolo 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 errorQué significaQué hacer
(ausente)El documento no se creó y no se transmitió a HaciendaRevisa la respuesta, corrige la causa y reintenta la misma petición
FAILEDOcurrió un fallo interno y el DTE no fue transmitido a HaciendaIntenta nuevamente la petición con el mismo Idempotency-Key y el mismo body. Si persiste, contacta a soporte
REJECTEDHacienda 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.

HTTPerrorCodeCausa y soluciónstatus
400INVALID_PARAMETERSEl body no cumple las validaciones (campo faltante, valor inválido o campo no permitido). Revisa message—
400MISSING_COMPANY_TAX_NUMBERFalta el header x-company-tax-number—
400MISSING_ESTABLISHMENT_CODEFalta el header x-establishment-code—
400MISSING_POINT_OF_SALE_CODEFalta el header x-point-of-sale-code—
400INVOICE_ITEMS_LIMIT_EXCEEDEDEnviaste más de 2000 ítems—
400INVOICE_ITEM_DISCOUNT_EXCEEDS_LINE_TOTALEl discountAmount de un ítem es mayor que quantity × unitPrice. El message indica qué ítem corregir—
400INVALID_IDEMPOTENCY_KEYIdempotency-Key tiene más de 128 caracteres, espacios o caracteres de control. No se transmite a Hacienda—
400MH_RECEPTION_VALIDATION_ERRORHacienda rechazó un campo, formato, fecha o cálculoREJECTED
401UNAUTHORIZEDFalta Authorization, o el token es inválido o expiró.—
401TENANT_CREDENTIAL_NOT_FOUNDEl clientId de tu client credentials no existe o está inactivo—
404COMPANY_NOT_FOUNDNo existe una empresa con ese NRC en tu cuenta—
404ESTABLISHMENT_NOT_FOUNDEl establecimiento no existe para esa empresa—
404POINT_OF_SALE_NOT_FOUNDEl punto de venta no existe o está inactivo en ese establecimiento—
404ECONOMIC_ACTIVITY_NOT_FOUNDEl customer.economicActivityCode no existe. Usa un código del catálogo de Actividades económicas—
404DISTRICT_NOT_FOUNDEl customer.address.districtCode no existe para el departmentCode enviado. Revisa el catálogo de Distritos—
404COMPANY_CREDENTIAL_NOT_FOUNDFaltan las credenciales y el certificado de Hacienda (obligatorio en producción)—
409IDEMPOTENCY_KEY_IN_PROGRESSYa hay una emisión en curso para esta Idempotency-Key. Espera y reintenta la misma clave—
409IDEMPOTENCY_KEY_NOT_RETRYABLELa clave pertenece a un documento en CONTINGENCY y no admite reintento—
409IDEMPOTENCY_KEY_CONTEXT_MISMATCHLa clave ya existe, pero el establecimiento o el punto de venta no coinciden con el documento original—
409MH_RECEPTION_DOCUMENT_ERRORHacienda reporta un registro duplicadoREJECTED
422MH_RECEPTION_DOCUMENT_ERRORHacienda rechazó el documento por normativa, correlativo o documento relacionadoREJECTED
422MH_RECEPTION_TAXPAYER_ERRORProblema con el emisor o el receptor: NIT/NRC inexistente o inactivo, o emisor no autorizado para emitir CCFREJECTED
502MH_RECEPTION_SERVICE_ERRORError de servicio de Hacienda. Usa el status del body para distinguir el casoREJECTED
502MH_RECEPTION_INVALID_RESPONSEHacienda devolvió una respuesta inválida o sin sello de recepción
504MH_RECEPTION_TIMEOUTHacienda no respondió a tiempo
5xxOtrosFallas de firma electrónica, de autenticación ante Hacienda o internas. Reintenta con la misma Idempotency-Key; si persiste, contacta a soporteVer status del body

On this page