Ejemplo en PYTHON

Ejemplo de uso del API SAT en python.

Archivo fuente: python.py

#!/usr/bin/env python3

"""
Ejemplo Python — POST /api/v1/usuario/datos-fiscales
Usa requests (pip install requests).
"""

import os
import sys
import json
import requests
from typing import Optional


class SatApiClient:
    """Cliente para la API de SAT API."""

    def __init__(self, api_key: str, base_url: str = "https://api.cfdi4.com",
                 user_agent: str = "python-sat-api/1.0", timeout: int = 30):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.user_agent = user_agent
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {api_key}",
            "User-Agent": user_agent,
            "Content-Type": "application/json",
        })

    def _post(self, endpoint: str, body: dict) -> dict:
        """Hace POST y devuelve el body parseado."""
        url = f"{self.base_url}{endpoint}"
        try:
            r = self.session.post(url, json=body, timeout=self.timeout)
            r.raise_for_status()
            data = r.json()
            return data.get("body", {})
        except requests.exceptions.HTTPError as e:
            return {"cfdi_codigo": 4001, "cfdi_texto": str(e), "data": None}
        except requests.exceptions.Timeout:
            return {"cfdi_codigo": 3001, "cfdi_texto": "Timeout", "data": None}
        except requests.exceptions.RequestException as e:
            return {"cfdi_codigo": 5001, "cfdi_texto": str(e), "data": None}

    def datos_fiscales(self, rfc: str) -> dict:
        """POST /api/v1/usuario/datos-fiscales"""
        return self._post("/api/v1/usuario/datos-fiscales", {"rfc": rfc.upper().strip()})

    def opinion_cumplimiento(self, rfc: str) -> dict:
        """POST /api/v1/sat/opinion-cumplimiento"""
        return self._post("/api/v1/sat/opinion-cumplimiento", {"rfc": rfc.upper().strip()})

    def csf(self, rfc: str) -> dict:
        """POST /api/v1/sat/constancia-situacion-fiscal"""
        return self._post("/api/v1/sat/constancia-situacion-fiscal", {"rfc": rfc.upper().strip()})


def main():
    api_key = os.environ.get("SAT_API_KEY")
    if not api_key:
        print("ERROR: configura la variable de entorno SAT_API_KEY", file=sys.stderr)
        print("  export SAT_API_KEY='sat_xxxxxxxxxxxx'", file=sys.stderr)
        sys.exit(1)

    rfc = sys.argv[1] if len(sys.argv) > 1 else "XAXX010101000"

    client = SatApiClient(api_key)
    print(f"Consultando datos fiscales de RFC: {rfc}\n")

    result = client.datos_fiscales(rfc)

    codigo = result.get("cfdi_codigo", 0)
    texto = result.get("cfdi_texto", "")
    desc = result.get("cfdi_descripcion", "")
    saldo = result.get("saldo", 0)
    data = result.get("data")

    print(f"  Código:    {codigo}")
    print(f"  Texto:     {texto}")
    print(f"  Detalle:   {desc}")
    print(f"  Saldo:     {saldo} créditos")

    if codigo == 1 and data:
        print(f"\n  Datos fiscales:")
        print(f"  {json.dumps(data, indent=4, ensure_ascii=False)}")
    elif codigo == 1004:
        print("\n  ⚠️  Saldo insuficiente. Recarga en https://panel.cfdi4.com/saldos")
    elif codigo == 1001:
        print("\n  ⚠️  API key inválida. Verifica la variable SAT_API_KEY")

    sys.exit(0 if codigo == 1 else 1)


if __name__ == "__main__":
    main()