Crear sujeto excluido
Emite una factura de sujeto excluido electrónica con la API de Lumen.
Emite una factura de sujeto excluido electrónica en El Salvador cuando tu empresa compra un bien o servicio a un proveedor que no es contribuyente de IVA. Úsalo desde tu ERP, SaaS o módulo de cuentas por pagar al registrar esa compra.
En lugar de entender la normativa para emitir una factura de sujeto excluido, manejar reglas de holgura o redondeo para retención de renta, firmar y transmitir a Hacienda, solo envías proveedor, ítems, si retienes renta y los pagos. Lumen reserva el correlativo, firma, 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. Tu empresa autenticada es la compradora; el supplier es el sujeto excluido (proveedor) y es siempre obligatorio.
Endpoint
https://api-sandbox.lumendte.com/v1/excluded-taxpayersHeaders
| 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 compradora (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 compra en tu sistema (1 a 128 caracteres, sin espacios). Ver Idempotencia |
Payload mínimo
El proveedor es obligatorio. Este es el payload mínimo para emitir a contado sin retención de renta:
{
"supplier": {
"name": "Juan Perez",
"documentType": "DUI",
"documentNumber": "01010101-1",
"address": {
"complement": "Col. Escalón, San Salvador",
"departmentCode": "06",
"districtCode": "13"
}
},
"items": [
{
"description": "Servicio de mantenimiento",
"quantity": 1,
"unitPrice": 100.0
}
],
"withholdIncomeTax": false,
"payments": [
{
"paymentCode": "01",
"amount": 100.0
}
]
}Respuesta exitosa
Status: 201 Created
Guarda generationCode y controlNumber. Las URLs pdfUrl y jsonUrl expiran en 15 minutos: descárgalas 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 documento es válido igualmente: los archivos se generarán en la primera llamada a los endpoints de descarga. No vuelvas a emitir la factura.
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-14-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-14-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 contacto del proveedor, actividad económica, descuentos, tipo de ítem o pagos a crédito, 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.
Con withholdIncomeTax: true, Lumen aplica retención de renta del 10% sobre el subtotal y la resta del total a pagar: el monto de payments debe ser lo que efectivamente pagas al proveedor.
{
"supplier": {
"name": "Juan Perez",
"email": "juan.perez@example.com",
"phone": "7777-7777",
"documentType": "DUI",
"documentNumber": "01010101-1",
"economicActivityCode": "62010",
"address": {
"complement": "Col. Escalón, San Salvador",
"departmentCode": "06",
"districtCode": "13"
}
},
"items": [
{
"sku": "SERV-001",
"description": "Servicio de consultoría",
"quantity": 1,
"unitPrice": 100.0,
"discountAmount": 0,
"itemType": "SERVICE",
"unitOfMeasure": "59"
}
],
"withholdIncomeTax": true,
"payments": [
{
"paymentCode": "05",
"amount": 90.0,
"reference": "TRX-98765"
}
]
}En este ejemplo: subtotal 100, retención 10, total a pagar 90.
Proveedor (sujeto excluido)
Siempre obligatorio.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Sí | Nombre completo del proveedor |
documentType | string | Sí | DUI, NIT, PASSPORT, RESIDENT_CARD, OTHER |
documentNumber | string | Sí | Número de documento. Los guiones son opcionales; la API aplica el formato que exige Hacienda (DUI sin guiones, NIT con guiones) |
address.complement | string | Sí | Complemento de dirección (calle, colonia, etc.) |
address.departmentCode | string | Sí | Código de departamento. Ver Departamentos |
address.districtCode | string | Sí | Código de distrito; el municipio se deduce automáticamente. Ver Distritos |
email | string | No | Correo del proveedor |
phone | string | No | Teléfono nacional o internacional (ej. 7777-7777, +14155552671) |
economicActivityCode | string | No | Código de actividad económica (máx. 5). Ver Actividades económicas |
Ítems
Líneas del documento. Mínimo 1, máximo 2000 (si te pasas, la API responde 400 INVOICE_ITEMS_LIMIT_EXCEEDED). Este DTE no lleva IVA: unitPrice es el precio del servicio o bien adquirido.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
description | string | Sí | Descripción del bien o servicio adquirido |
quantity | number | Sí | Cantidad (> 0) |
unitPrice | number | Sí | Precio unitario de compra sin IVA (> 0) |
sku | string | null | No | Código interno del producto. Default: null |
discountAmount | number | No | Descuento monetario de la línea (≥ 0). Default: 0. |
itemType | string | No | GOODS, SERVICE (default), BOTH |
unitOfMeasure | string | No | Unidad de medida. Default: "59" (Unidad). Ver Unidades de medida |
Significados de itemType:
| Valor | Significado |
|---|---|
GOODS | Productos o bienes |
SERVICE | Servicio |
BOTH | Implica tanto servicio como producto a la vez |
Retención de renta
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
withholdIncomeTax | boolean | Sí | Si true, Lumen aplica retención de renta del 10% y la resta del total a pagar |
Formas de pago
Mínimo 1. Puedes enviar más de una si la compra se pagó con varios medios. El monto debe corresponder al total a pagar (después de la retención, si aplica).
Lumen establece la condición de la operación (CAT-016) automáticamente según los pagos enviados:
| Pagos enviados | Condición |
|---|---|
Solo contado (cualquier código ≠ 13) | Contado |
Solo crédito (13) | Crédito |
Crédito (13) + contado | Mixto |
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
paymentCode | string | Sí | Código de forma de pago. Ver Formas de pago |
amount | number | Sí | Monto del pago (≥ 0). Envíalo siempre con el valor real de esa forma de pago |
reference | string | No | Referencia o código de autorización. Se omite en efectivo (01) y en crédito puro |
creditTerm | string | null | Condicional | Tipo de plazo CAT-018. Requerido si paymentCode es 13 (ej. 01 = Días) |
creditPeriod | number | null | Condicional | Cantidad de días/meses/años según creditTerm (1–999). Requerido si paymentCode es 13 |
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": 250.0,
"creditTerm": "01",
"creditPeriod": 30
}
]
}creditTerm (01) = días; creditPeriod (30) = 30 días de plazo. Envía el monto real; en crédito puro Lumen envía montoPago: 0.00 al MH.
Ejemplo de solicitud
curl -X POST https://api-sandbox.lumendte.com/v1/excluded-taxpayers \
-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 '{
"supplier": {
"name": "Juan Perez",
"documentType": "DUI",
"documentNumber": "01010101-1",
"address": {
"complement": "Col. Escalón, San Salvador",
"departmentCode": "06",
"districtCode": "13"
}
},
"items": [
{
"description": "Servicio de mantenimiento",
"quantity": 1,
"unitPrice": 100.0
}
],
"withholdIncomeTax": false,
"payments": [
{
"paymentCode": "01",
"amount": 100.0
}
]
}'Idempotencia
Idempotency-Key es obligatorio para todas las integraciones. Identifica una compra 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 compra, id interno, 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 factura.
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 DUI o el NIT del proveedor 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-14-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 cuentas por pagar) 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 | INVALID_IDEMPOTENCY_KEY | Idempotency-Key tiene más de 128 caracteres, espacios o caracteres de control. No se transmite a Hacienda | — |
| 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 supplier.economicActivityCode no existe. Usa un código del catálogo de Actividades económicas | — |
| 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 por normativa, correlativo o documento relacionado | REJECTED |
| 422 | MH_RECEPTION_TAXPAYER_ERROR | Problema con el emisor o el sujeto excluido: NIT inexistente o inactivo, o emisor no autorizado para emitir este tipo de documento | 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 |