ipsofacturaHablar con Ventas →
← Blog
APIEmpresasCFDI

Cómo administrar múltiples empresas emisoras de CFDI por API

|9 min de lectura

Tu sistema tiene 40 empresas registradas. Alguien llama a POST /cfdi/timbrar con el empresa_id equivocado. El SAT lo timbra, el folio queda grabado y ahora tienes un CFDI válido con el RFC incorrecto. Cancelarlo requiere la aceptación del receptor — si la da.

Ese es el escenario que hace que los sistemas multi-empresa fallen, y no por lógica de timbrado sino por un problema más simple: no había forma de buscar la empresa correcta antes de usarla.

El endpoint GET /empresas ahora acepta un parámetro ?q= para filtrar. Esta guía explica cómo funciona y cómo construir un flujo de selección de emisor que no dependa de UUIDs memorizados.


Antes de empezar

Necesitas:

  • Una API key de Ipsofactura (sandbox o producción)
  • Al menos dos empresas registradas en tu cuenta — si no, puedes crearlas con POST /empresas
  • Python 3.8+ o curl si quieres correr los ejemplos directamente

Si estás empezando con la API, lee primero la guía de autenticación — los ejemplos aquí asumen que ya tienes el header x-api configurado.


El problema con los UUIDs

Cuando registras una empresa en Ipsofactura, la API devuelve un id en formato UUID:

0bacab57-4dc0-4f15-a026-53e7deaf1df8

Ese id es lo que necesitas para timbrar. El problema es que un UUID no le dice nada a nadie. Si tu sistema almacena los empresa_id en una base de datos y los asocia por RFC, funciona. Pero si necesitas buscar en runtime — porque el usuario eligió un cliente, porque vas a timbrar en nombre de un tercero o porque simplemente necesitas validar que el emisor existe y está activo — no había un endpoint para eso.

GET /empresas?q= resuelve exactamente eso.


Cómo funciona el filtro

El parámetro q combina cuatro criterios de búsqueda. Los cuatro criterios se evalúan en paralelo, y la respuesta puede incluir cualquier empresa que coincida con uno o más criterios:

ModoCampoTipo de match
RFCrfcParcial, sin importar mayúsculas
Nombre fiscalnombre_fiscalParcial, sin importar mayúsculas
Régimen fiscalregimen_fiscalExacto (clave de tres dígitos)
IDidExacto (UUID completo)

Una sola llamada con ?q=EKU9 devuelve todas las empresas cuyo RFC contenga esa cadena. Lo mismo con ?q=ESCUELA — busca por nombre fiscal. Si pasas ?q=601, devuelve todas las del régimen general de personas morales. Y si pasas un UUID completo, devuelve la empresa exacta o lista vacía.

El endpoint nunca devuelve empresas de otro partner aunque el RFC coincida. La búsqueda está aislada por API key.


Ejemplos de uso

Buscar por RFC parcial

curl https://api.ipsofactura.com/empresas?q=PAL20 \
  -H "x-api: tu_api_key"
import requests

resp = requests.get(
    "https://api.ipsofactura.com/empresas",
    params={"q": "PAL20"},
    headers={"x-api": "tu_api_key"}
)
empresas = resp.json()["empresas"]

Respuesta:

{
  "empresas": [
    {
      "id": "0bacab57-4dc0-4f15-a026-53e7deaf1df8",
      "rfc": "PAL200101AB1",
      "nombre_empresa": "Palenca Tecnología",
      "nombre_fiscal": "PALENCA TECNOLOGIA SA DE CV",
      "regimen_fiscal": "601",
      "codigo_postal": "06600",
      "es_activo": true
    }
  ]
}

Buscar por nombre fiscal

curl "https://api.ipsofactura.com/empresas?q=tecnologia" \
  -H "x-api: tu_api_key"

Funciona con cualquier fragmento del nombre fiscal. El match es case-insensitive — tecnologia, TECNOLOGIA y Tecnología devuelven el mismo resultado.

Buscar por régimen fiscal

curl "https://api.ipsofactura.com/empresas?q=612" \
  -H "x-api: tu_api_key"

Útil si tu sistema segmenta operaciones por tipo de persona. El régimen 612 agrupa a personas físicas con actividades empresariales y profesionales; 601 a personas morales en régimen general. El match de regimen_fiscal es exacto, no parcial. Aun así, ?q=6 puede devolver empresas por coincidencias parciales de rfc o nombre_fiscal.

Resolver un UUID

Si ya tienes el empresa_id pero quieres confirmar que sigue activo antes de timbrar:

empresa_id = "0bacab57-4dc0-4f15-a026-53e7deaf1df8"

resp = requests.get(
    "https://api.ipsofactura.com/empresas",
    params={"q": empresa_id},
    headers={"x-api": "tu_api_key"}
)

empresas = resp.json()["empresas"]
if not empresas:
    raise ValueError(f"Empresa {empresa_id} no encontrada o no pertenece a este partner")

empresa = empresas[0]
if not empresa["es_activo"]:
    raise ValueError(f"Empresa {empresa['rfc']} está inactiva")

El flujo completo: buscar → validar → timbrar

Si estás construyendo una interfaz donde el operador elige el emisor antes de facturar, el flujo natural es este:

import requests

API_KEY = "tu_api_key"
BASE    = "https://api.ipsofactura.com"
HEADERS = {"x-api": API_KEY, "Content-Type": "application/json"}

def buscar_empresa(q: str) -> list[dict]:
    resp = requests.get(f"{BASE}/empresas", params={"q": q}, headers=HEADERS)
    resp.raise_for_status()
    return resp.json()["empresas"]

def timbrar(empresa_id: str, payload: dict) -> dict:
    payload["empresa_id"] = empresa_id
    resp = requests.post(f"{BASE}/cfdi/timbrar", json=payload, headers=HEADERS)
    resp.raise_for_status()
    return resp.json()

# El operador escribe "Palenca" en el buscador
resultados = buscar_empresa("Palenca")

if len(resultados) == 0:
    raise ValueError("No se encontraron empresas con ese criterio")

if len(resultados) > 1:
    # Mostrar lista al operador para que seleccione
    for r in resultados:
        print(f"{r['rfc']} — {r['nombre_fiscal']}")
    empresa_id = resultados[0]["id"]  # simplificado
else:
    empresa_id = resultados[0]["id"]

cfdi = timbrar(empresa_id, {
    "tipo_comprobante": "I",
    "metodo_pago": "PUE",
    "forma_pago": "03",
    "moneda": "MXN",
    "lugar_expedicion": "06600",
    "receptor": {
        "rfc": "XAXX010101000",
        "nombre": "PUBLICO EN GENERAL",
        "uso_cfdi": "S01",
        "domicilio_fiscal_receptor": "06600",
        "regimen_fiscal_receptor": "616"
    },
    "conceptos": [{
        "clave_prod_serv": "81111806",
        "cantidad": 1,
        "clave_unidad": "E48",
        "descripcion": "Servicio de consultoría",
        "valor_unitario": 1000.00,
        "impuestos": {
            "traslados": [{
                "base": 1000.00, "impuesto": "002",
                "tipo_factor": "Tasa", "tasa_o_cuota": 0.16, "importe": 160.00
            }]
        }
    }]
})

print(f"Timbrado: {cfdi['uuid']}")

La ventaja de pasar por el buscador antes de timbrar es que el empresa_id no sale de tu base de datos: sale del estado actual de la API. Si la empresa fue desactivada, el buscador devuelve es_activo: false antes de que el request de timbrado llegue al SAT.


Incluir los certificados en la búsqueda

Si necesitas verificar también que el CSD esté vigente, agrega include_certificados=true:

curl "https://api.ipsofactura.com/empresas?q=PAL20&include_certificados=true" \
  -H "x-api: tu_api_key"

La respuesta incluye un campo certificados por empresa con el estado del CSD, la fecha de vencimiento y el número de certificado. Útil para alertar al administrador antes de que el CSD expire en producción — no cuando ya falló un timbrado.


Límites a tener en cuenta

El parámetro q acepta hasta 100 caracteres. Queries más largas devuelven 400 VALIDATION_ERROR. No es un límite que vayas a alcanzar con búsquedas normales por RFC o nombre, pero sí si construyes un buscador que pasa input del usuario directamente al query param — valida en tu frontend antes de hacer el request.

El endpoint no pagina los resultados.

¿Listo para automatizar?

Empieza a timbrar en menos de 2 horas.

Primeros 100 timbres sin costo. Sandbox listo. Sin contrato anual.

Hablar con Ventas →

Más artículos

8 min · API
Cómo integrar una API de facturación SAT en tu sistema
15 min · Sandbox
Cómo empezar a probar el sandbox de Ipsofactura