The repository or configuration that created this release differ from a previous release. View details.
python-cfdiclient
Cliente Python para consumir los servicios web del SAT relacionados con CFDI:
- Autenticación con e.firma/FIEL.
- Solicitud de descarga masiva de CFDI o metadata.
- Verificación del estado de una solicitud.
- Descarga de paquetes generados por el SAT.
- Consulta del estado de un CFDI.
El paquete está pensado para integrarse en aplicaciones que ya cuentan con los
archivos .cer, .key, la contraseña de la e.firma y el RFC del contribuyente.
No incluye credenciales reales ni debe usarse para almacenarlas.
Servicio SAT de referencia: https://www.sat.gob.mx/consultas/42968/consulta-y-recuperacion-de-comprobantes-(nuevo)
Requisitos
- Python 3.10 o superior. Las versiones corregidas de Requests, urllib3, pytest y setuptools requieren esta versión mínima de Python.
- Certificado
.cery llave privada.keyde la e.firma en formato DER. - Contraseña de la llave privada.
- Acceso de red a los servicios del SAT.
Instalación
Instala la versión publicada en PyPI:
python -m pip install cfdiclient
Para trabajar con el proyecto localmente:
python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
pytest
Flujo de descarga masiva
La descarga masiva no entrega los archivos inmediatamente. El flujo normal es:
- Cargar la e.firma con
Fiel. - Obtener un token con
Autenticacion. - Crear una solicitud con
SolicitaDescargaEmitidosoSolicitaDescargaRecibidos. - Consultar periódicamente la solicitud con
VerificaSolicitudDescarga. - Cuando
estado_solicitudsea3, descargar cada paquete conDescargaMasiva.
Estados de solicitud reportados por el SAT:
| Estado | Significado |
|---|---|
0 |
Token inválido |
1 |
Aceptada |
2 |
En proceso |
3 |
Terminada |
4 |
Error |
5 |
Rechazada |
6 |
Vencida |
Ejemplo completo
Este ejemplo solicita CFDI recibidos, espera a que el SAT termine la solicitud y
guarda cada paquete como archivo .zip.
import base64
import datetime
import time
from pathlib import Path
from cfdiclient import (
Autenticacion,
DescargaMasiva,
Fiel,
SolicitaDescargaRecibidos,
VerificaSolicitudDescarga,
)
RFC = "XAXX010101000"
FIEL_CER = Path("certificados/ejemploCer.cer")
FIEL_KEY = Path("certificados/ejemploKey.key")
FIEL_PASS = "contrasena"
FECHA_INICIAL = datetime.datetime(2025, 1, 1)
FECHA_FINAL = datetime.datetime(2025, 1, 31, 23, 59, 59)
cer_der = FIEL_CER.read_bytes()
key_der = FIEL_KEY.read_bytes()
fiel = Fiel(cer_der, key_der, FIEL_PASS)
auth = Autenticacion(fiel)
token = auth.obtener_token()
solicitador = SolicitaDescargaRecibidos(fiel)
solicitud = solicitador.solicitar_descarga(
token=token,
rfc_solicitante=RFC,
fecha_inicial=FECHA_INICIAL,
fecha_final=FECHA_FINAL,
rfc_receptor=RFC,
tipo_solicitud="CFDI",
)
if solicitud["cod_estatus"] != "5000":
raise RuntimeError(f"Solicitud no aceptada por el SAT: {solicitud}")
id_solicitud = solicitud["id_solicitud"]
verificador = VerificaSolicitudDescarga(fiel)
while True:
token = auth.obtener_token()
verificacion = verificador.verificar_descarga(token, RFC, id_solicitud)
estado = int(verificacion["estado_solicitud"])
if estado in (1, 2):
time.sleep(60)
continue
if estado != 3:
raise RuntimeError(f"La solicitud no terminó correctamente: {verificacion}")
descargador = DescargaMasiva(fiel)
for id_paquete in verificacion["paquetes"]:
respuesta = descargador.descargar_paquete(token, RFC, id_paquete)
paquete = base64.b64decode(respuesta["paquete_b64"])
Path(f"{id_paquete}.zip").write_bytes(paquete)
break
Preparar e.firma y token
Los ejemplos siguientes asumen que ya cargaste cer_der, key_der y token.
Puedes obtenerlos así:
from pathlib import Path
from cfdiclient import Autenticacion, Fiel
cer_der = Path("certificado.cer").read_bytes()
key_der = Path("llave.key").read_bytes()
fiel = Fiel(cer_der, key_der, "contrasena")
token = Autenticacion(fiel).obtener_token()
Solicitar CFDI emitidos
Usa SolicitaDescargaEmitidos cuando quieras comprobantes emitidos por el RFC
solicitante.
import datetime
from cfdiclient import Fiel, SolicitaDescargaEmitidos
fiel = Fiel(cer_der, key_der, "contrasena")
descarga = SolicitaDescargaEmitidos(fiel)
resultado = descarga.solicitar_descarga(
token=token,
rfc_solicitante="XAXX010101000",
fecha_inicial=datetime.datetime(2025, 1, 1),
fecha_final=datetime.datetime(2025, 1, 31, 23, 59, 59),
rfc_emisor="XAXX010101000",
tipo_solicitud="CFDI",
)
print(resultado)
# {
# "id_solicitud": "be2a3e76-684f-416a-afdf-0f9378c346be",
# "cod_estatus": "5000",
# "mensaje": "Solicitud Aceptada",
# }
Solicitar CFDI recibidos
Usa SolicitaDescargaRecibidos cuando quieras comprobantes recibidos por el RFC
solicitante.
import datetime
from cfdiclient import Fiel, SolicitaDescargaRecibidos
fiel = Fiel(cer_der, key_der, "contrasena")
descarga = SolicitaDescargaRecibidos(fiel)
resultado = descarga.solicitar_descarga(
token=token,
rfc_solicitante="XAXX010101000",
fecha_inicial=datetime.datetime(2025, 1, 1),
fecha_final=datetime.datetime(2025, 1, 31, 23, 59, 59),
rfc_receptor="XAXX010101000",
tipo_solicitud="Metadata",
estado_comprobante="Vigente",
)
print(resultado)
Verificar una solicitud
from cfdiclient import Fiel, VerificaSolicitudDescarga
fiel = Fiel(cer_der, key_der, "contrasena")
verificador = VerificaSolicitudDescarga(fiel)
resultado = verificador.verificar_descarga(
token="eyJhbGci...",
rfc_solicitante="XAXX010101000",
id_solicitud="6331caae-c253-406f-9332-126f89cc474a",
)
print(resultado)
# {
# "cod_estatus": "5000",
# "estado_solicitud": "3",
# "codigo_estado_solicitud": "5000",
# "numero_cfdis": "8",
# "mensaje": "Solicitud Aceptada",
# "paquetes": ["a4897f62-a279-4f52-bc35-03bde4081627_01"],
# }
Descargar un paquete
import base64
from pathlib import Path
from cfdiclient import DescargaMasiva, Fiel
fiel = Fiel(cer_der, key_der, "contrasena")
descarga = DescargaMasiva(fiel)
resultado = descarga.descargar_paquete(
token="eyJhbGci...",
rfc_solicitante="XAXX010101000",
id_paquete="2d8bbdf1-c36d-4b51-a57c-c1744acdd89c_01",
)
Path("paquete.zip").write_bytes(base64.b64decode(resultado["paquete_b64"]))
Consultar estado de un CFDI
from cfdiclient import Validacion
validacion = Validacion()
estado = validacion.obtener_estado(
rfc_emisor="XAXX010101000",
rfc_receptor="XAXX010101000",
total="1000.41",
uuid="0XXX0X00-000-0XX0-XX0X-000X0X0XXX00",
)
print(estado)
# {
# "codigo_estatus": "S - Comprobante obtenido satisfactoriamente.",
# "es_cancelable": "Cancelable con aceptación",
# "estado": "Vigente",
# }
Parámetros comunes de solicitud
SolicitaDescargaEmitidos.solicitar_descarga y
SolicitaDescargaRecibidos.solicitar_descarga aceptan estos parámetros:
| Parámetro | Descripción |
|---|---|
token |
Token obtenido con Autenticacion.obtener_token(). |
rfc_solicitante |
RFC del contribuyente que realiza la solicitud. |
fecha_inicial |
Inicio del rango como datetime.datetime o datetime.date. |
fecha_final |
Fin del rango como datetime.datetime o datetime.date. |
rfc_emisor |
RFC emisor. Úsalo normalmente en solicitudes de emitidos. |
rfc_receptor |
RFC receptor. Úsalo normalmente en solicitudes de recibidos. |
tipo_solicitud |
"CFDI" para XML o "Metadata" para metadata. |
tipo_comprobante |
Filtro opcional por tipo de comprobante. |
estado_comprobante |
Filtro opcional; por defecto "Vigente". |
rfc_a_cuenta_terceros |
Filtro opcional para comprobantes a cuenta de terceros. |
complemento |
Filtro opcional por complemento. |
uuid |
Filtro opcional por UUID. |
Desarrollo
Comandos útiles para colaboradores:
pytest
pylint --rcfile=pylint.rc cfdiclient tests
python -m build
Las pruebas deben ejecutarse sin credenciales reales y sin depender de servicios
vivos del SAT. Al agregar cambios sobre SOAP/XML, mantén sincronizados los
templates cfdiclient/*.xml con el código que los llena.
Seguridad
- No subas certificados, llaves privadas, contraseñas, tokens ni paquetes CFDI reales al repositorio.
- Los archivos en
certificados/son datos de ejemplo. - Prefiere variables de entorno o un secreto administrado por tu plataforma para cargar credenciales en aplicaciones productivas.
Metadata
Release files for cfdiclient 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Change of provenance
The repository or configuration that created these release files differ from that of a previous release.
We recommend inspecting the repository and configuration before proceeding. View details.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cfdiclient-2.0.0.tar.gz | 27.8 kB | With provenance | Details for cfdiclient-2.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cfdiclient-2.0.0-py3-none-any.whl | Python 3 | none | any | Details for cfdiclient-2.0.0-py3-none-any.whl |
Total release size: 56.5 kB
Release files / cfdiclient-2.0.0.tar.gz
Change of provenance
This file belongs to a release with changed provenance: the repository or configuration that created the release files differ from that of a previous release.
We recommend inspecting the repository and configuration before proceeding. View details.
| Download URL | cfdiclient-2.0.0.tar.gz |
|---|---|
| Size | 27.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee4ef0e836b854a043c2d75f64d13d352427a91f8efbe57eccdfea68c50d5dfb
|
|
BLAKE2b-256 checksum How to use checksums |
108cda4b151701fbba44908c2c013209366807ff0fbff633657494f8d345f3fe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / cfdiclient-2.0.0-py3-none-any.whl
Change of provenance
This file belongs to a release with changed provenance: the repository or configuration that created the release files differ from that of a previous release.
We recommend inspecting the repository and configuration before proceeding. View details.
| Download URL | cfdiclient-2.0.0-py3-none-any.whl |
|---|---|
| Size | 28.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9cac69014a4e2a4fb04ca8936f309e9f7b26bbb23f99694df9b905baad54f5ef
|
|
BLAKE2b-256 checksum How to use checksums |
914ea07dc9ca8b8e42b011a5f99e5303fec7aa3d271a0567ab884017cf14ec0b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for cfdiclient 2.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Change of provenance
The configuration that created this release differs from a previous release.
While this could reflect a routine change by the project maintainers, it may also indicate that the release files have been compromised.
Attestation for cfdiclient-2.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.