Emitir una Factura de Ingreso
Aprende cómo crear y timbrar una Factura de Ingreso (CFDI tipo "I") usando el endpoint POST /invoices. Este endpoint valida los datos, calcula impuestos, sella el CFDI con tu CSD y lo timbra automáticamente con el PAC.
Un CFDI 4.0 timbrado con UUID fiscal, archivos XML y PDF, listo para entregar a tu cliente.
Endpoint
POST https://sandbox-api.lummy.com/invoices
- Sandbox:
https://sandbox-api.lummy.com - Producción:
https://api.lummy.com
Autenticación y Headers
| Header | Tipo | Obligatorio | Descripción |
|---|---|---|---|
Content-Type | application/json | ✅ | Formato del body |
x-organization-id | string (UUID) | ✅ | ID de tu organización |
x-api-key | string | ✅ | Tu API Key |
x-idempotency | string (UUID) | ✅ | UUID único para prevenir duplicados |
El header x-idempotency garantiza que no se dupliquen facturas. Si reenvías el mismo request con el mismo UUID, recibirás la factura original sin crear una nueva. Genera un UUID único por cada factura nueva.
Estructura del Request
El cuerpo de la petición es un objeto JSON que representa el CrearComprobanteDto.
Objeto Principal: CrearComprobanteDto
| Campo | Tipo | Requerido | Descripción | Catálogo SAT |
|---|---|---|---|---|
sucursalId | string (UUID) | Opcional | ID de la sucursal emisora. Si se omite, se usa la matriz. | |
clienteId | string (UUID) | Opcional | ID de un cliente existente. Si se provee, se ignora el objeto receptor. | |
receptor | ReceptorDto | Condicional | Objeto con los datos del receptor. Requerido si no se provee clienteId. | |
tipoDeComprobante | string | Obligatorio | Tipo de Comprobante. Para facturas de ingreso, usar I. | c_TipoDeComprobante |
usoCFDI | string | Condicional | Uso que el receptor le dará a la factura. Requerido si tipoDeComprobante es I o E. | c_UsoCFDI |
metodoPago | string | Condicional | Método de pago. Requerido si tipoDeComprobante es I o E. | c_MetodoPago |
formaPago | string | Condicional | Forma de pago. Requerido si tipoDeComprobante es I o E. | c_FormaPago |
condicionesDePago | string | Opcional | Condiciones comerciales aplicables para el pago del comprobante. | |
serie | string | Opcional | Serie para el folio del comprobante. | |
folio | string | Opcional | Folio del comprobante. | |
fecha | string (ISO 8601) | Obligatorio | Fecha y hora de expedición del comprobante. | |
moneda | string | Obligatorio | Moneda del comprobante. | c_Moneda |
tipoCambio | number | Condicional | Tipo de cambio. Requerido si moneda es diferente de MXN y XXX. | |
exportacion | string | Obligatorio | Clave que indica si el comprobante ampara una operación de exportación. | c_Exportacion |
lugarExpedicion | string | Obligatorio | Código postal del lugar de expedición del comprobante. | c_CodigoPostal |
conceptos | ConceptoDto[] | Condicional | Arreglo de conceptos. Requerido si tipoDeComprobante no es P. | |
cfdiRelacionados | CfdiRelacionadosDto | Opcional | Nodo para especificar información de CFDI relacionados. | |
informacionGlobal | InformacionGlobalDto | Condicional | Nodo para especificar información de la factura global. Requerido si el RFC del receptor es XAXX010101000. | |
complementoPago | ComplementoPagoDto | Condicional | Nodo para el Complemento de Recepción de Pagos. Requerido si tipoDeComprobante es P. | |
complementoCartaPorte | ComplementoCartaPorteDto | Condicional | Nodo para el Complemento Carta Porte. Requerido si tipoDeComprobante es T. | |
addenda | string | Opcional | Nodo XML de la addenda requerida por el cliente. | |
espaciosDeNombres | EspacioDeNombreDto[] | Condicional | Arreglo de namespaces (xmlns) requeridos por la addenda. Requerido si se incluye addenda. | |
complementos | ComplementoGenericoDto[] | Opcional | Arreglo para otros complementos de factura. |
Ejemplos de Código
A continuación, ejemplos funcionales en diferentes lenguajes para integrar el endpoint en tu sistema:
- cURL
- Node.js (TypeScript)
- Python
- PHP (Guzzle)
curl -X POST https://sandbox-api.lummy.com/invoices \
-H "Content-Type: application/json" \
-H "x-organization-id: ${LUMMY_ORG_ID}" \
-H "x-api-key: ${LUMMY_API_KEY}" \
-H "x-idempotency: $(uuidgen)" \
-d '{
"tipoDeComprobante": "I",
"receptor": {
"rfc": "XAXX010101000",
"nombre": "PUBLICO EN GENERAL",
"regimenFiscal": "616",
"domicilioFiscal": "06500"
},
"usoCFDI": "G03",
"metodoPago": "PUE",
"formaPago": "03",
"fecha": "2025-11-19T10:00:00",
"moneda": "MXN",
"exportacion": "01",
"lugarExpedicion": "06500",
"conceptos": [
{
"claveProdServ": "81111500",
"claveUnidad": "E48",
"descripcion": "Desarrollo de Software - Sistema Web a Medida",
"cantidad": 1,
"valorUnitario": 15000.00,
"objetoImp": "02",
"impuestos": {
"traslados": [
{
"base": 15000.00,
"impuesto": "002",
"tipoFactor": "Tasa",
"tasaOCuota": 0.16
}
]
}
}
]
}'
Define LUMMY_ORG_ID y LUMMY_API_KEY en tu shell:
export LUMMY_ORG_ID="your-organization-uuid"
export LUMMY_API_KEY="your-api-key"
import axios, {AxiosError} from 'axios';
import {v4 as uuidv4} from 'uuid';
// Interfaces para type-safety
interface Receptor {
rfc: string;
nombre: string;
regimenFiscal: string;
domicilioFiscal: string;
}
interface ConceptoTraslado {
base: number;
impuesto: string;
tipoFactor: string;
tasaOCuota: number;
}
interface ConceptoImpuestos {
traslados?: ConceptoTraslado[];
}
interface Concepto {
claveProdServ: string;
claveUnidad: string;
descripcion: string;
cantidad: number;
valorUnitario: number;
objetoImp: string;
impuestos?: ConceptoImpuestos;
}
interface InvoicePayload {
tipoDeComprobante: string;
receptor: Receptor;
usoCFDI: string;
metodoPago: string;
formaPago: string;
fecha: string;
moneda: string;
exportacion: string; // '01' = No aplica exportación
lugarExpedicion: string;
conceptos: Concepto[];
}
interface InvoiceResponse {
invoiceId: string;
cfdiUuid: string;
verificationUrl: string;
xmlS3Url: string;
pdfS3Url: string;
}
async function crearFactura(): Promise<InvoiceResponse> {
const API_URL = 'https://sandbox-api.lummy.com/invoices';
const ORG_ID = process.env.LUMMY_ORG_ID!;
const API_KEY = process.env.LUMMY_API_KEY!;
const payload: InvoicePayload = {
tipoDeComprobante: 'I', // I = Ingreso
receptor: {
rfc: 'XAXX010101000',
nombre: 'PUBLICO EN GENERAL',
regimenFiscal: '616', // Sin obligaciones fiscales
domicilioFiscal: '06500',
},
usoCFDI: 'G03', // Gastos en general
metodoPago: 'PUE', // Pago en Una Exhibición
formaPago: '03', // Transferencia electrónica
fecha: new Date().toISOString().slice(0, 19), // Formato: 2025-11-19T10:00:00
moneda: 'MXN',
exportacion: '01', // No aplica
lugarExpedicion: '06500',
conceptos: [
{
claveProdServ: '81111500', // Servicios de ingeniería de software
claveUnidad: 'E48', // Unidad de servicio
descripcion: 'Desarrollo de Software - Sistema Web a Medida',
cantidad: 1,
valorUnitario: 15000.0,
objetoImp: '02', // Sí objeto de impuesto
impuestos: {
traslados: [
{
base: 15000.0,
impuesto: '002', // IVA
tipoFactor: 'Tasa',
tasaOCuota: 0.16, // 16%
},
],
},
},
],
};
try {
const response = await axios.post<InvoiceResponse>(API_URL, payload, {
headers: {
'Content-Type': 'application/json',
'x-organization-id': ORG_ID,
'x-api-key': API_KEY,
'x-idempotency': uuidv4(), // UUID único por factura
},
});
console.log('✅ Factura timbrada exitosamente');
console.log('UUID Fiscal:', response.data.cfdiUuid);
console.log('Descargar XML:', response.data.xmlS3Url);
console.log('Descargar PDF:', response.data.pdfS3Url);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
console.error('❌ Error al timbrar:', axiosError.response?.data);
throw axiosError;
}
throw error;
}
}
// Ejecutar
crearFactura();
npm install axios uuid
npm install -D @types/uuid
import os
import requests
from uuid import uuid4
from datetime import datetime
def crear_factura():
"""
Crea y timbra una factura de ingreso usando la API de Lummy.
Returns:
dict: Respuesta con invoiceId, cfdiUuid, URLs de descarga
"""
api_url = "https://sandbox-api.lummy.com/invoices"
org_id = os.getenv("LUMMY_ORG_ID")
api_key = os.getenv("LUMMY_API_KEY")
if not org_id or not api_key:
raise ValueError("Debes definir LUMMY_ORG_ID y LUMMY_API_KEY")
headers = {
"Content-Type": "application/json",
"x-organization-id": org_id,
"x-api-key": api_key,
"x-idempotency": str(uuid4()), # UUID único por factura
}
payload = {
"tipoDeComprobante": "I", # I = Ingreso
"receptor": {
"rfc": "XAXX010101000",
"nombre": "PUBLICO EN GENERAL",
"regimenFiscal": "616", # Sin obligaciones fiscales
"domicilioFiscal": "06500",
},
"usoCFDI": "G03", # Gastos en general
"metodoPago": "PUE", # Pago en Una Exhibición
"formaPago": "03", # Transferencia electrónica
"fecha": datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
"moneda": "MXN",
"exportacion": "01", # No aplica
"lugarExpedicion": "06500",
"conceptos": [
{
"claveProdServ": "81111500", # Servicios de ingeniería de software
"claveUnidad": "E48", # Unidad de servicio
"descripcion": "Desarrollo de Software - Sistema Web a Medida",
"cantidad": 1,
"valorUnitario": 15000.00,
"objetoImp": "02", # Sí objeto de impuesto
"impuestos": {
"traslados": [
{
"base": 15000.00,
"impuesto": "002", # IVA
"tipoFactor": "Tasa",
"tasaOCuota": 0.16, # 16%
}
]
},
}
],
}
try {
response = requests.post(api_url, json=payload, headers=headers, timeout=30)
response.raise_for_status() # Lanza excepción si status >= 400
data = response.json()
print("✅ Factura timbrada exitosamente")
print(f"UUID Fiscal: {data['cfdiUuid']}")
print(f"Descargar XML: {data['xmlS3Url']}")
print(f"Descargar PDF: {data['pdfS3Url']}")
return data
except requests.exceptions.HTTPError as e:
print(f"❌ Error HTTP: {e.response.status_code}")
print(f"Respuesta: {e.response.json()}")
raise
except requests.exceptions.RequestException as e:
print(f"❌ Error de conexión: {str(e)}")
raise
if __name__ == "__main__":
crear_factura()
pip install requests
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Ramsey\Uuid\Uuid;
/**
* Crea y timbra una factura de ingreso usando la API de Lummy
*
* @return array Respuesta con invoiceId, cfdiUuid, URLs de descarga
* @throws Exception Si falla la autenticación o timbrado
*/
function crearFactura(): array
{
$apiUrl = 'https://sandbox-api.lummy.com/invoices';
$orgId = getenv('LUMMY_ORG_ID');
$apiKey = getenv('LUMMY_API_KEY');
if (!$orgId || !$apiKey) {
throw new Exception('Debes definir LUMMY_ORG_ID y LUMMY_API_KEY');
}
$client = new Client([
'timeout' => 30,
'headers' => [
'Content-Type' => 'application/json',
'x-organization-id' => $orgId,
'x-api-key' => $apiKey,
'x-idempotency' => Uuid::uuid4()->toString(), // UUID único por factura
],
]);
$payload = [
'tipoDeComprobante' => 'I', // I = Ingreso
'receptor' => [
'rfc' => 'XAXX010101000',
'nombre' => 'PUBLICO EN GENERAL',
'regimenFiscal' => '616', // Sin obligaciones fiscales
'domicilioFiscal' => '06500',
],
'usoCFDI' => 'G03', // Gastos en general
'metodoPago' => 'PUE', // Pago en Una Exhibición
'formaPago' => '03', // Transferencia electrónica
'fecha' => date('Y-m-d\TH:i:s'), // Formato: 2025-11-19T10:00:00
'moneda' => 'MXN',
'exportacion' => '01', // No aplica
'lugarExpedicion' => '06500',
'conceptos' => [
[
'claveProdServ' => '81111500', // Servicios de ingeniería de software
'claveUnidad' => 'E48', // Unidad de servicio
'descripcion' => 'Desarrollo de Software - Sistema Web a Medida',
'cantidad' => 1,
'valorUnitario' => 15000.00,
'objetoImp' => '02', // Sí objeto de impuesto
'impuestos' => [
'traslados' => [
[
'base' => 15000.00,
'impuesto' => '002', // IVA
'tipoFactor' => 'Tasa',
'tasaOCuota' => 0.16, // 16%
],
],
],
],
],
];
try {
$response = $client->post($apiUrl, [
'json' => $payload,
]);
$data = json_decode($response->getBody()->getContents(), true);
echo "✅ Factura timbrada exitosamente\n";
echo "UUID Fiscal: {$data['cfdiUuid']}\n";
echo "Descargar XML: {$data['xmlS3Url']}\n";
echo "Descargar PDF: {$data['pdfS3Url']}\n";
return $data;
} catch (RequestException $e) {
$statusCode = $e->getResponse() ? $e->getResponse()->getStatusCode() : 'N/A';
$errorBody = $e->getResponse() ? $e->getResponse()->getBody()->getContents() : 'Sin detalles';
echo "❌ Error HTTP {$statusCode}\n";
echo "Respuesta: {$errorBody}\n";
throw $e;
}
}
// Ejecutar
try {
crearFactura();
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}
composer require guzzlehttp/guzzle ramsey/uuid
Response
Respuesta Exitosa (HTTP 201 Created)
{
"invoiceId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"cfdiUuid": "cfa52b8b-93f2-4e6b-8c73-64ad88deb17c",
"verificationUrl": "https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=cfa52b8b-93f2-4e6b-8c73-64ad88deb17c&re=AAA010101AAA&rr=XAXX010101000&tt=17400.00&fe=/OAgdg==",
"xmlS3Url": "https://lummy-invoices.s3.amazonaws.com/cfdi/cfa52b8b-93f2-4e6b-8c73-64ad88deb17c.xml",
"pdfS3Url": "https://lummy-invoices.s3.amazonaws.com/cfdi/cfa52b8b-93f2-4e6b-8c73-64ad88deb17c.pdf"
}
| Campo | Tipo | Descripción |
|---|---|---|
invoiceId | string (UUID) | ID interno de la factura en Lummy |
cfdiUuid | string (UUID) | UUID fiscal del SAT - Folio fiscal del CFDI timbrado |
verificationUrl | string (URL) | URL para verificar el CFDI en el portal del SAT |
xmlS3Url | string (URL) | URL pública para descargar el XML timbrado |
pdfS3Url | string (URL) | URL pública para descargar el PDF de la factura |
El cfdiUuid es el Folio Fiscal oficial. Este UUID debe aparecer en todas las representaciones impresas del CFDI y es el identificador único que tu cliente usará para verificar la factura en el SAT.
Respuestas de Error
400 Bad Request - Datos Inválidos
{
"statusCode": 400,
"message": "RFC inválido: debe tener 12 o 13 caracteres",
"error": "Bad Request"
}
Causas comunes:
- RFC con formato incorrecto
- Claves de catálogo SAT inválidas (ej.
claveProdServ,usoCFDI) - Fechas fuera del rango permitido por el SAT
- Campos requeridos faltantes
404 Not Found - CSD No Encontrado
{
"statusCode": 404,
"message": "No se encontró CSD activo para la sucursal",
"error": "Not Found"
}
Solución: Verifica que tu organización tenga un Certificado de Sello Digital (CSD) vigente cargado en el sistema.
409 Conflict - Idempotencia Duplicada
{
"statusCode": 409,
"message": "La clave de idempotencia ya ha sido usada",
"error": "Conflict"
}
Solución: Si recibes este error, significa que ya existe una factura con ese x-idempotency. Genera un nuevo UUID si quieres crear una factura diferente.
Campos Obligatorios Explicados
exportacion
- Valores:
01(No aplica),02(Definitiva),03(Temporal) - Para ventas nacionales, siempre usa
'01'
objetoImp
01: No objeto de impuesto02: Sí objeto de impuesto (requiere nodoimpuestos)03: Sí objeto de impuesto y NO obligado al desglose
claveProdServ
Clave del catálogo SAT de productos y servicios:
81111500: Servicios de ingeniería de software84111506: Servicios de consultoría en tecnología- Buscar más claves aquí
claveUnidad
Unidad de medida del SAT:
E48: Unidad de servicioH87: PiezaKGM: Kilogramo
Notas Importantes
Cálculo Automático de Totales
No necesitas enviar los campos Subtotal, Total, ni Importe de conceptos. Nuestro sistema los calcula automáticamente según las reglas del SAT para garantizar precisión.
Zona Horaria
El campo fecha debe enviarse en hora local del lugar de expedición. El sistema ajustará automáticamente la zona horaria según el código postal de lugarExpedicion.
Transaccionalidad
Si el timbrado falla después de validar los datos, NO se consume un timbre de tu saldo. El sistema usa transacciones atómicas para garantizar consistencia.
Siguientes Pasos
- Consultar una Factura (próximamente)
- Cancelar una Factura (próximamente)
- Emitir Nota de Crédito (próximamente)
- Referencia completa de la API (próximamente)
Soporte
¿Necesitas ayuda? Contáctanos:
- Email: soporte@lummy.io
- Documentación: docs.lummy.io
Objeto: ReceptorDto
| Campo | Tipo | Requerido | Descripción | Catálogo SAT |
|---|---|---|---|---|
rfc | string | Obligatorio | RFC del receptor. | |
nombre | string | Obligatorio | Nombre o Razón Social del receptor. | |
regimenFiscal | string | Obligatorio | Régimen Fiscal del receptor. | c_RegimenFiscal |
domicilioFiscal | string | Obligatorio | Código Postal del domicilio fiscal del receptor. | c_CodigoPostal |
Objeto: ConceptoDto
| Campo | Tipo | Requerido | Descripción | Catálogo SAT |
|---|---|---|---|---|
descripcion | string | Obligatorio | Descripción del concepto. | |
valorUnitario | number | Obligatorio | Precio unitario del concepto. | |
cantidad | number | Obligatorio | Cantidad de bienes o servicios del concepto. | |
claveProdServ | string | Obligatorio | Clave de producto o servicio. | c_ClaveProdServ |
claveUnidad | string | Obligatorio | Clave de unidad de medida. | c_ClaveUnidad |
objetoImp | string | Obligatorio | Clave que indica si el concepto es objeto de impuesto. | c_ObjetoImp |
descuento | number | Opcional | Monto del descuento aplicable al concepto. | |
impuestos | ConceptoImpuestosDto | Condicional | Nodo de impuestos del concepto. Requerido si objetoImp es 02. | |
aCuentaTerceros | ACuentaTercerosDto | Opcional | Nodo para especificar información de cuenta de terceros. | |
cuentaPredial | string | Opcional | Número de cuenta predial. | |
informacionAduanera | InformacionAduaneraDto[] | Opcional | Nodo para especificar información aduanera. | |
partes | ParteDto[] | Opcional | Nodo para especificar las partes del concepto. | |
complementoConcepto | string | Opcional | Nodo XML del complemento de concepto. |
Objeto: ConceptoImpuestosDto
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
traslados | ConceptoTrasladoDto[] | Opcional | Arreglo de traslados de impuestos. |
retenciones | ConceptoRetencionDto[] | Opcional | Arreglo de retenciones de impuestos. |
Objeto: ConceptoTrasladoDto
| Campo | Tipo | Requerido | Descripción | Catálogo SAT |
|---|---|---|---|---|
base | number | Obligatorio | Base imponible para el cálculo del impuesto. | |
impuesto | string | Obligatorio | Clave del impuesto. | c_Impuesto |
tipoFactor | string | Obligatorio | Tipo de factor. | c_TipoFactor |
tasaOCuota | number | Obligatorio | Tasa o cuota del impuesto. |
Objeto: ConceptoRetencionDto
| Campo | Tipo | Requerido | Descripción | Catálogo SAT |
|---|---|---|---|---|
base | number | Obligatorio | Base imponible para el cálculo del impuesto. | |
impuesto | string | Obligatorio | Clave del impuesto. | c_Impuesto |
tipoFactor | string | Obligatorio | Tipo de factor. | c_TipoFactor |
tasaOCuota | number | Obligatorio | Tasa o cuota del impuesto. |