Validaciones de Catálogos SAT
Endpoints para validar la compatibilidad entre diferentes campos de un CFDI antes de enviarlo. Esto permite que tu frontend valide los datos en tiempo real y evite errores de timbrado.
Valida los datos antes de crear la factura para ofrecer una mejor experiencia de usuario y reducir rechazos del SAT.
Validar UsoCFDI con Régimen Fiscal
Verifica que el Uso de CFDI sea compatible con el Régimen Fiscal del receptor.
Endpoint
POST https://api.lummy.io/v1/catalogs/validate/uso-cfdi
| Entorno | URL |
|---|---|
| Producción | https://api.lummy.io/v1/catalogs/validate/uso-cfdi |
| Sandbox | https://sandbox.lummy.io/v1/catalogs/validate/uso-cfdi |
Request Body
{
"usoCfdi": "G03",
"regimenFiscal": "612"
}
Response
- Valido
- Invalido
{
"valid": true,
"message": "El UsoCFDI 'G03' es válido para el Régimen Fiscal '612'"
}
{
"valid": false,
"message": "El UsoCFDI 'Gastos en general' no es compatible con el Régimen Fiscal 'General de Ley Personas Morales'",
"errorCode": "CFDI40161"
}
Ejemplos de Código
- cURL
- Node.js (TypeScript)
- Python
- PHP
- C# (.NET)
- Java
- Go
- Ruby
curl -X POST "https://api.lummy.com/catalogs/validate/uso-cfdi" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "x-organization-id: ${ORG_ID}" \
-H "Content-Type: application/json" \
-d '{"usoCfdi": "G03", "regimenFiscal": "612"}'
import axios from 'axios';
interface ValidationResult {
valid: boolean;
message: string;
errorCode?: string;
}
async function validarUsoCfdi(
usoCfdi: string,
regimenFiscal: string
): Promise<ValidationResult> {
const response = await axios.post<ValidationResult>(
'https://api.lummy.com/catalogs/validate/uso-cfdi',
{ usoCfdi, regimenFiscal },
{
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
'x-organization-id': process.env.ORG_ID,
'Content-Type': 'application/json',
},
}
);
if (!response.data.valid) {
console.error(`${response.data.message}`);
console.error(` Código: ${response.data.errorCode}`);
} else {
console.log(`${response.data.message}`);
}
return response.data;
}
// Uso
validarUsoCfdi('G03', '612');
import os
import requests
from typing import TypedDict, Optional
class ValidationResult(TypedDict):
valid: bool
message: str
errorCode: Optional[str]
def validar_uso_cfdi(uso_cfdi: str, regimen_fiscal: str) -> ValidationResult:
"""Valida compatibilidad de UsoCFDI con Régimen Fiscal."""
response = requests.post(
"https://api.lummy.com/catalogs/validate/uso-cfdi",
json={"usoCfdi": uso_cfdi, "regimenFiscal": regimen_fiscal},
headers={
"Authorization": f"Bearer {os.getenv('ACCESS_TOKEN')}",
"x-organization-id": os.getenv("ORG_ID"),
"Content-Type": "application/json",
},
timeout=30
)
response.raise_for_status()
result = response.json()
if not result["valid"]:
print(f"{result['message']}")
print(f" Código: {result.get('errorCode', 'N/A')}")
else:
print(f"{result['message']}")
return result
# Uso
validar_uso_cfdi("G03", "612")
<?php
use GuzzleHttp\Client;
function validarUsoCfdi(string $usoCfdi, string $regimenFiscal): array
{
$client = new Client([
'headers' => [
'Authorization' => 'Bearer ' . getenv('ACCESS_TOKEN'),
'x-organization-id' => getenv('ORG_ID'),
'Content-Type' => 'application/json',
],
]);
$response = $client->post('https://api.lummy.com/catalogs/validate/uso-cfdi', [
'json' => [
'usoCfdi' => $usoCfdi,
'regimenFiscal' => $regimenFiscal,
],
]);
$result = json_decode($response->getBody()->getContents(), true);
if (!$result['valid']) {
echo "{$result['message']}\n";
echo " Código: " . ($result['errorCode'] ?? 'N/A') . "\n";
} else {
echo "{$result['message']}\n";
}
return $result;
}
validarUsoCfdi('G03', '612');
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
public class ValidationResult
{
public bool Valid { get; set; }
public string Message { get; set; } = "";
public string? ErrorCode { get; set; }
}
public async Task<ValidationResult> ValidarUsoCfdiAsync(string usoCfdi, string regimenFiscal)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
var request = new HttpRequestMessage(HttpMethod.Post,
"https://api.lummy.com/catalogs/validate/uso-cfdi");
request.Headers.Add("x-organization-id", orgId);
var payload = JsonSerializer.Serialize(new { usoCfdi, regimenFiscal });
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<ValidationResult>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;
Console.WriteLine(result.Valid ? $"{result.Message}" : $"{result.Message}");
return result;
}
import java.net.URI;
import java.net.http.*;
import com.google.gson.Gson;
public class ValidationResult {
public boolean valid;
public String message;
public String errorCode;
}
public ValidationResult validarUsoCfdi(String usoCfdi, String regimenFiscal) throws Exception {
HttpClient client = HttpClient.newHttpClient();
Gson gson = new Gson();
String payload = gson.toJson(Map.of(
"usoCfdi", usoCfdi,
"regimenFiscal", regimenFiscal
));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.lummy.com/catalogs/validate/uso-cfdi"))
.header("Authorization", "Bearer " + accessToken)
.header("x-organization-id", orgId)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
ValidationResult result = gson.fromJson(response.body(), ValidationResult.class);
System.out.println(result.valid ? result.message : result.message);
return result;
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type ValidationResult struct {
Valid bool `json:"valid"`
Message string `json:"message"`
ErrorCode string `json:"errorCode,omitempty"`
}
func validarUsoCfdi(accessToken, orgID, usoCfdi, regimenFiscal string) (*ValidationResult, error) {
payload, _ := json.Marshal(map[string]string{
"usoCfdi": usoCfdi,
"regimenFiscal": regimenFiscal,
})
req, _ := http.NewRequest("POST",
"https://api.lummy.com/catalogs/validate/uso-cfdi",
bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-organization-id", orgID)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result ValidationResult
json.NewDecoder(resp.Body).Decode(&result)
if result.Valid {
fmt.Println(result.Message)
} else {
fmt.Println(result.Message)
}
return &result, nil
}
require 'net/http'
require 'json'
require 'uri'
def validar_uso_cfdi(uso_cfdi, regimen_fiscal)
uri = URI('https://api.lummy.com/catalogs/validate/uso-cfdi')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV['ACCESS_TOKEN']}"
request['x-organization-id'] = ENV['ORG_ID']
request['Content-Type'] = 'application/json'
request.body = { usoCfdi: uso_cfdi, regimenFiscal: regimen_fiscal }.to_json
response = http.request(request)
result = JSON.parse(response.body)
puts result['valid'] ? "#{result['message']}" : "#{result['message']}"
result
end
validar_uso_cfdi('G03', '612')
Validar Código Postal
Verifica que un código postal exista y si tiene estímulo de franja fronteriza (para IVA del 8%).
Endpoint
POST https://api.lummy.io/v1/catalogs/validate/zip-code
| Entorno | URL |
|---|---|
| Producción | https://api.lummy.io/v1/catalogs/validate/zip-code |
| Sandbox | https://sandbox.lummy.io/v1/catalogs/validate/zip-code |
Request Body
{
"zipCode": "22000"
}
Response
- Valido
- Sin Estimulo
- Invalido
{
"valid": true,
"message": "El código postal '22000' es válido y tiene estímulo de franja fronteriza (IVA 8%)",
"hasBorderStimulus": true,
"state": "Baja California"
}
{
"valid": true,
"message": "El código postal '06600' es válido",
"hasBorderStimulus": false,
"state": "Ciudad de México"
}
{
"valid": false,
"message": "El código postal '00000' no existe en el catálogo del SAT",
"errorCode": "CFDI40126"
}
Ejemplos de Código
- cURL
- Node.js (TypeScript)
- Python
- PHP
curl -X POST "https://api.lummy.com/catalogs/validate/zip-code" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "x-organization-id: ${ORG_ID}" \
-H "Content-Type: application/json" \
-d '{"zipCode": "22000"}'
interface ZipCodeValidation {
valid: boolean;
message: string;
errorCode?: string;
hasBorderStimulus?: boolean;
state?: string;
}
async function validarCodigoPostal(zipCode: string): Promise<ZipCodeValidation> {
const response = await axios.post<ZipCodeValidation>(
'https://api.lummy.com/catalogs/validate/zip-code',
{ zipCode },
{
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
'x-organization-id': process.env.ORG_ID,
'Content-Type': 'application/json',
},
}
);
const result = response.data;
if (result.valid && result.hasBorderStimulus) {
console.log(`${result.message}`);
console.log(` Estado: ${result.state}`);
console.log(` Aplica IVA 8% por franja fronteriza`);
}
return result;
}
validarCodigoPostal('22000');
def validar_codigo_postal(zip_code: str) -> dict:
"""Valida código postal y verifica estímulo de franja fronteriza."""
response = requests.post(
"https://api.lummy.com/catalogs/validate/zip-code",
json={"zipCode": zip_code},
headers={
"Authorization": f"Bearer {os.getenv('ACCESS_TOKEN')}",
"x-organization-id": os.getenv("ORG_ID"),
"Content-Type": "application/json",
},
timeout=30
)
result = response.json()
if result["valid"] and result.get("hasBorderStimulus"):
print(f"{result['message']}")
print(f" Estado: {result.get('state')}")
print(" Aplica IVA 8% por franja fronteriza")
return result
validar_codigo_postal("22000")
function validarCodigoPostal(string $zipCode): array
{
$client = new Client();
$response = $client->post('https://api.lummy.com/catalogs/validate/zip-code', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('ACCESS_TOKEN'),
'x-organization-id' => getenv('ORG_ID'),
],
'json' => ['zipCode' => $zipCode],
]);
$result = json_decode($response->getBody()->getContents(), true);
if ($result['valid'] && ($result['hasBorderStimulus'] ?? false)) {
echo "{$result['message']}\n";
echo " Aplica IVA 8% por franja fronteriza\n";
}
return $result;
}
Los códigos postales con estímulo de franja fronteriza permiten aplicar IVA del 8% en lugar del 16%. Esto aplica para zonas fronterizas del norte de México.
Validar Moneda
Verifica que una moneda exista y retorna el número de decimales permitidos.
Endpoint
POST https://api.lummy.io/v1/catalogs/validate/currency
| Entorno | URL |
|---|---|
| Producción | https://api.lummy.io/v1/catalogs/validate/currency |
| Sandbox | https://sandbox.lummy.io/v1/catalogs/validate/currency |
Request Body
{
"currency": "USD"
}
Response
{
"valid": true,
"message": "La moneda 'USD' es válida con 2 decimales",
"decimals": 2,
"description": "Dólar Americano"
}
Ejemplos de Código
- cURL
- Node.js (TypeScript)
- Python
- PHP
curl -X POST "https://api.lummy.com/catalogs/validate/currency" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "x-organization-id: ${ORG_ID}" \
-H "Content-Type: application/json" \
-d '{"currency": "USD"}'
interface CurrencyValidation {
valid: boolean;
message: string;
decimals?: number;
description?: string;
}
async function validarMoneda(currency: string): Promise<CurrencyValidation> {
const response = await axios.post<CurrencyValidation>(
'https://api.lummy.com/catalogs/validate/currency',
{ currency },
{
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
'x-organization-id': process.env.ORG_ID,
'Content-Type': 'application/json',
},
}
);
console.log(`Moneda: ${response.data.description}`);
console.log(`Decimales permitidos: ${response.data.decimals}`);
return response.data;
}
validarMoneda('USD');
def validar_moneda(currency: str) -> dict:
"""Valida moneda y retorna decimales permitidos."""
response = requests.post(
"https://api.lummy.com/catalogs/validate/currency",
json={"currency": currency},
headers={
"Authorization": f"Bearer {os.getenv('ACCESS_TOKEN')}",
"x-organization-id": os.getenv("ORG_ID"),
},
timeout=30
)
result = response.json()
if result["valid"]:
print(f"Moneda: {result.get('description')}")
print(f"Decimales permitidos: {result.get('decimals')}")
return result
validar_moneda("USD")
function validarMoneda(string $currency): array
{
$client = new Client();
$response = $client->post('https://api.lummy.com/catalogs/validate/currency', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('ACCESS_TOKEN'),
'x-organization-id' => getenv('ORG_ID'),
],
'json' => ['currency' => $currency],
]);
return json_decode($response->getBody()->getContents(), true);
}
Los montos de la factura deben respetar el número de decimales de la moneda. Por ejemplo:
- MXN: 2 decimales
- USD: 2 decimales
- JPY: 0 decimales
Validar Régimen Fiscal
Verifica que un régimen fiscal exista y opcionalmente valida si aplica para Persona Física o Moral.
Endpoint
POST https://api.lummy.io/v1/catalogs/validate/regimen-fiscal
| Entorno | URL |
|---|---|
| Producción | https://api.lummy.io/v1/catalogs/validate/regimen-fiscal |
| Sandbox | https://sandbox.lummy.io/v1/catalogs/validate/regimen-fiscal |
Request Body
{
"regimenFiscal": "612",
"tipoPersona": "PF"
}
Response
{
"valid": true,
"message": "El Régimen Fiscal '612' es válido",
"appliesToPersonaFisica": true,
"appliesToPersonaMoral": false,
"description": "Personas Físicas con Actividades Empresariales y Profesionales"
}
Ejemplos de Código
- cURL
- Node.js (TypeScript)
- Python
curl -X POST "https://api.lummy.com/catalogs/validate/regimen-fiscal" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "x-organization-id: ${ORG_ID}" \
-H "Content-Type: application/json" \
-d '{"regimenFiscal": "612", "tipoPersona": "PF"}'
interface RegimenValidation {
valid: boolean;
message: string;
appliesToPersonaFisica?: boolean;
appliesToPersonaMoral?: boolean;
description?: string;
}
async function validarRegimenFiscal(
regimenFiscal: string,
tipoPersona?: 'PF' | 'PM'
): Promise<RegimenValidation> {
const response = await axios.post<RegimenValidation>(
'https://api.lummy.com/catalogs/validate/regimen-fiscal',
{ regimenFiscal, tipoPersona },
{
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
'x-organization-id': process.env.ORG_ID,
'Content-Type': 'application/json',
},
}
);
const result = response.data;
console.log(`Régimen: ${result.description}`);
console.log(`Aplica a PF: ${result.appliesToPersonaFisica ? 'Sí' : 'No'}`);
console.log(`Aplica a PM: ${result.appliesToPersonaMoral ? 'Sí' : 'No'}`);
return result;
}
validarRegimenFiscal('612', 'PF');
def validar_regimen_fiscal(regimen_fiscal: str, tipo_persona: str = None) -> dict:
"""Valida régimen fiscal y compatibilidad con tipo de persona."""
payload = {"regimenFiscal": regimen_fiscal}
if tipo_persona:
payload["tipoPersona"] = tipo_persona
response = requests.post(
"https://api.lummy.com/catalogs/validate/regimen-fiscal",
json=payload,
headers={
"Authorization": f"Bearer {os.getenv('ACCESS_TOKEN')}",
"x-organization-id": os.getenv("ORG_ID"),
},
timeout=30
)
result = response.json()
print(f"Régimen: {result.get('description')}")
print(f"Aplica a PF: {'Sí' if result.get('appliesToPersonaFisica') else 'No'}")
print(f"Aplica a PM: {'Sí' if result.get('appliesToPersonaMoral') else 'No'}")
return result
validar_regimen_fiscal("612", "PF")
Validar Objeto de Impuesto
Verifica la congruencia entre el código de ObjetoImp y si el concepto incluye impuestos.
Endpoint
POST https://api.lummy.io/v1/catalogs/validate/tax-object
| Entorno | URL |
|---|---|
| Producción | https://api.lummy.io/v1/catalogs/validate/tax-object |
| Sandbox | https://sandbox.lummy.io/v1/catalogs/validate/tax-object |
Request Body
{
"taxObject": "02",
"hasTaxes": true
}
Reglas de ObjetoImp
| Código | Descripción | ¿Requiere Impuestos? |
|---|---|---|
01 | No objeto de impuesto | No debe tener |
02 | Si objeto de impuesto | Debe tener |
03 | Si objeto del impuesto y no obligado al desglose | No debe tener |
04 | Sí objeto del impuesto y no causa impuesto | Opcional |
Response
- Valido
- Error 02 sin impuestos
{
"valid": true,
"message": "La combinación de ObjetoImp e impuestos es válida"
}
{
"valid": false,
"message": "Cuando ObjetoImp es \"02\" (Sí objeto de impuesto), el concepto debe incluir impuestos",
"errorCode": "CFDI40171"
}
Ejemplos de Código
- cURL
- Node.js (TypeScript)
- Python
curl -X POST "https://api.lummy.com/catalogs/validate/tax-object" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "x-organization-id: ${ORG_ID}" \
-H "Content-Type: application/json" \
-d '{"taxObject": "02", "hasTaxes": true}'
async function validarObjetoImpuesto(
taxObject: string,
hasTaxes: boolean
): Promise<ValidationResult> {
const response = await axios.post<ValidationResult>(
'https://api.lummy.com/catalogs/validate/tax-object',
{ taxObject, hasTaxes },
{
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
'x-organization-id': process.env.ORG_ID,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
// Validar que ObjetoImp 02 tenga impuestos
validarObjetoImpuesto('02', true);
def validar_objeto_impuesto(tax_object: str, has_taxes: bool) -> dict:
"""Valida congruencia entre ObjetoImp e impuestos."""
response = requests.post(
"https://api.lummy.com/catalogs/validate/tax-object",
json={"taxObject": tax_object, "hasTaxes": has_taxes},
headers={
"Authorization": f"Bearer {os.getenv('ACCESS_TOKEN')}",
"x-organization-id": os.getenv("ORG_ID"),
},
timeout=30
)
return response.json()
# Validar que ObjetoImp 02 tenga impuestos
validar_objeto_impuesto("02", True)
Cliente Completo de Validación
Aquí un ejemplo de clase cliente que encapsula todas las validaciones:
- Node.js (TypeScript)
- Python
import axios, { AxiosInstance } from 'axios';
export class LummyValidationClient {
private client: AxiosInstance;
constructor(accessToken: string, orgId: string) {
this.client = axios.create({
baseURL: 'https://api.lummy.com/catalogs/validate',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-organization-id': orgId,
'Content-Type': 'application/json',
},
});
}
async validateUsoCfdi(usoCfdi: string, regimenFiscal: string) {
const { data } = await this.client.post('/uso-cfdi', { usoCfdi, regimenFiscal });
return data;
}
async validateZipCode(zipCode: string) {
const { data } = await this.client.post('/zip-code', { zipCode });
return data;
}
async validateCurrency(currency: string) {
const { data } = await this.client.post('/currency', { currency });
return data;
}
async validateRegimenFiscal(regimenFiscal: string, tipoPersona?: 'PF' | 'PM') {
const { data } = await this.client.post('/regimen-fiscal', { regimenFiscal, tipoPersona });
return data;
}
async validateTaxObject(taxObject: string, hasTaxes: boolean) {
const { data } = await this.client.post('/tax-object', { taxObject, hasTaxes });
return data;
}
// Validación completa para un formulario de factura
async validateInvoiceForm(form: {
usoCfdi: string;
regimenFiscal: string;
tipoPersona: 'PF' | 'PM';
zipCode: string;
currency: string;
}) {
const results = await Promise.all([
this.validateUsoCfdi(form.usoCfdi, form.regimenFiscal),
this.validateRegimenFiscal(form.regimenFiscal, form.tipoPersona),
this.validateZipCode(form.zipCode),
this.validateCurrency(form.currency),
]);
const errors = results.filter(r => !r.valid);
return {
valid: errors.length === 0,
errors: errors.map(e => ({ message: e.message, code: e.errorCode })),
};
}
}
// Uso
const client = new LummyValidationClient(accessToken, orgId);
// Validar formulario completo
const validation = await client.validateInvoiceForm({
usoCfdi: 'G03',
regimenFiscal: '612',
tipoPersona: 'PF',
zipCode: '06600',
currency: 'MXN',
});
if (!validation.valid) {
console.log('Errores de validación:', validation.errors);
}
import os
import requests
from typing import List, Dict, Optional
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor
@dataclass
class ValidationError:
message: str
code: Optional[str] = None
class LummyValidationClient:
def __init__(self, access_token: str, org_id: str):
self.base_url = "https://api.lummy.com/catalogs/validate"
self.headers = {
"Authorization": f"Bearer {access_token}",
"x-organization-id": org_id,
"Content-Type": "application/json",
}
def _post(self, endpoint: str, payload: dict) -> dict:
response = requests.post(
f"{self.base_url}/{endpoint}",
json=payload,
headers=self.headers,
timeout=30
)
response.raise_for_status()
return response.json()
def validate_uso_cfdi(self, uso_cfdi: str, regimen_fiscal: str) -> dict:
return self._post("uso-cfdi", {"usoCfdi": uso_cfdi, "regimenFiscal": regimen_fiscal})
def validate_zip_code(self, zip_code: str) -> dict:
return self._post("zip-code", {"zipCode": zip_code})
def validate_currency(self, currency: str) -> dict:
return self._post("currency", {"currency": currency})
def validate_regimen_fiscal(self, regimen_fiscal: str, tipo_persona: str = None) -> dict:
payload = {"regimenFiscal": regimen_fiscal}
if tipo_persona:
payload["tipoPersona"] = tipo_persona
return self._post("regimen-fiscal", payload)
def validate_tax_object(self, tax_object: str, has_taxes: bool) -> dict:
return self._post("tax-object", {"taxObject": tax_object, "hasTaxes": has_taxes})
def validate_invoice_form(
self,
uso_cfdi: str,
regimen_fiscal: str,
tipo_persona: str,
zip_code: str,
currency: str
) -> Dict[str, any]:
"""Valida un formulario completo de factura."""
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [
executor.submit(self.validate_uso_cfdi, uso_cfdi, regimen_fiscal),
executor.submit(self.validate_regimen_fiscal, regimen_fiscal, tipo_persona),
executor.submit(self.validate_zip_code, zip_code),
executor.submit(self.validate_currency, currency),
]
results = [f.result() for f in futures]
errors = [
ValidationError(r["message"], r.get("errorCode"))
for r in results if not r["valid"]
]
return {
"valid": len(errors) == 0,
"errors": errors,
}
# Uso
client = LummyValidationClient(
os.getenv("ACCESS_TOKEN"),
os.getenv("ORG_ID")
)
validation = client.validate_invoice_form(
uso_cfdi="G03",
regimen_fiscal="612",
tipo_persona="PF",
zip_code="06600",
currency="MXN"
)
if not validation["valid"]:
for error in validation["errors"]:
print(f"{error.message} ({error.code})")