Todas las funcionalidades
API disponible

Integra el timbrado
a tu sistema

Conecta tu ERP, e-commerce o sistema propio a TimbraMX con una API de facturación simple (también conocida como web service de timbrado): mandas el CFDI 4.0 ya sellado y regresa timbrado.

Sin SDK propietario que instalar - peticiones HTTP con JSON, funciona desde Node.js, Python, PHP, Java, .NET o cualquier lenguaje que hable HTTP.

REST + JSON
API key por empresa
Respuesta en segundos

Información general

Lo básico que necesitas saber antes de mandar tu primera petición.

URL base (sandbox y producción)https://app.timbra-mx.com/api/v1 - la misma URL para ambos ambientes; el que cambia es el header X-Api-Key.
Versión actualv1
AutenticaciónHeader X-Api-Key
Formato de solicitudesapplication/json en el body (excepto donde se indique lo contrario)
Formato de respuestasapplication/json, salvo /xml (application/xml) y /pdf (application/pdf), que regresan el archivo crudo
CodificaciónUTF-8
Zona horariaFechas en formato ISO 8601. Cuando no se manda zona horaria explícita, se interpreta en la zona horaria de Ciudad de México.
HTTPSObligatorio - no hay endpoint sin TLS
Content-Type requeridoapplication/json en toda petición con body
Tamaño máximo de solicitud10 MB por petición
Timeout recomendado del cliente30 segundos - el timbrado real contra el PAC normalmente responde en 2-8 segundos, pero deja margen para reintentos de red. Si tu petición hace timeout, no sabes si sí se completó (ver Idempotencia más abajo).

¿Cómo funciona la integración?

La API de TimbraMX es de timbrado, no de armado del CFDI: tu sistema construye y sella el XML con tu propio CSD, y TimbraMX lo envía al PAC para obtener el folio fiscal.

1

Arma tu CFDI 4.0

Tu sistema genera el XML del comprobante y lo sella con el CSD de tu empresa.

2

Envía a la API

POST del XML sellado, codificado en base64, con tu API key en el header.

3

TimbraMX timbra

Envía tu CFDI al PAC y espera el UUID fiscal del SAT.

4

Recibes el resultado

Respuesta JSON con el estado y el UUID fiscal de tu factura ya timbrada.

Endpoints disponibles

MétodoRutaPara qué sirve
Facturas
POST/api/v1/facturas/validarValida un CFDI (modo relay) sin timbrarlo ni gastar un intento con el PAC.
POST/api/v1/facturasTimbra un CFDI ya sellado (XML en base64) o arma y timbra uno simple (modo sellar).
GET/api/v1/facturasLista las facturas de tu empresa, paginado.
GET/api/v1/facturas/:idConsulta el detalle de una factura por su id.
GET/api/v1/facturas/:id/xmlDescarga el XML timbrado crudo (archivo, no JSON).
GET/api/v1/facturas/:id/pdfDescarga el PDF de la factura (archivo binario).
GET/api/v1/facturas/:id/acuseAcuse de cancelación (solo si ya fue cancelada).
GET/api/v1/facturas/:id/estatus-satConsulta en vivo ante el SAT si sigue vigente.
POST/api/v1/facturas/:id/cancelarCancela un CFDI ya timbrado ante el SAT.
GET/api/v1/facturas/:id/estatus-cancelacionConfirma una cancelación pendiente ante el PAC, sin volver a solicitarla.
POST/api/v1/facturas/:id/nota-creditoGenera una nota de crédito que acredita, total o parcialmente, una factura ya timbrada.
GET/api/v1/facturas/saldoConsulta tus timbres disponibles y su próxima vigencia.
Complementos de pago (CFDI tipo P)
POST/api/v1/complementos-pagoTimbra un complemento de pago para una o varias facturas PPD.
GET/api/v1/complementos-pagoLista tus complementos de pago, paginado.
GET/api/v1/complementos-pago/:idConsulta el detalle, incluyendo los pagos y facturas cubiertas.
GET/api/v1/complementos-pago/:id/xml · /pdfDescarga el archivo XML o PDF del complemento.
POST/api/v1/complementos-pago/:id/cancelarCancela el CFDI tipo P ante el SAT.
GET/api/v1/complementos-pago/:id/estatus-cancelacionConfirma una cancelación pendiente ante el PAC, sin volver a solicitarla.
Nómina (CFDI de Nómina 1.2)
POST/api/v1/nominaArma, sella y timbra un recibo de nómina en un solo paso.
GET/api/v1/nominaLista tus recibos de nómina, paginado.
GET/api/v1/nomina/:idConsulta el detalle de un recibo de nómina.
GET/api/v1/nomina/:id/xml · /pdfDescarga el archivo XML o PDF del recibo.
POST/api/v1/nomina/:id/cancelarCancela el CFDI de nómina ante el SAT.
GET/api/v1/nomina/:id/estatus-cancelacionConfirma una cancelación pendiente ante el PAC, sin volver a solicitarla.
Retenciones e Información de Pagos
POST/api/v1/retencionesTimbra un CFDI de Retenciones (v1: dividendos, enajenación de acciones, pagos al extranjero).
GET/api/v1/retencionesLista tus retenciones, filtrable por tipo y paginado.
GET/api/v1/retenciones/:idConsulta el detalle de una retención.
GET/api/v1/retenciones/:id/xml · /pdfDescarga el archivo XML o PDF de la retención.
POST/api/v1/retenciones/:id/cancelarCancela el CFDI de retenciones ante el SAT.
GET/api/v1/retenciones/:id/estatus-cancelacionConsulta el estatus de una cancelación en curso ante el PAC.

Límite: 60 peticiones por minuto, por cada recurso (no una sola bolsa global). Cada RFC tiene una API key de sandbox (pruebas) y una de producción.

Configuración previa en el portal

La API nunca crea clientes, series ni sube tu CSD - todo eso se prepara una vez desde el portal web.

1

Crea tu cuenta

Regístrate en app.timbra-mx.com/portal-login.html.

2

Sube tu CSD

Mi empresa → CSD. Obligatorio para timbrar, tanto desde el portal como por API en modo sellar. En modo relay no hace falta (tú sellas con tu propio CSD).

3

Da de alta al menos un cliente

En Mis clientes, por cada receptorRfc que vayas a usar - la API rechaza con 400 un RFC que no exista ya en tu catálogo.

4

Crea una serie activa

Por cada tipo de documento que vayas a timbrar (factura, nota de crédito, complemento de pago, nómina, retenciones) - la API rechaza con 400 si no hay ninguna serie activa de ese tipo.

5

Activa el módulo de API

Contrátalo si tu cuenta no lo tiene - sin esto, cualquier llamada regresa 403.

6

Genera tu API key de sandbox

Desde la vista "API" del portal - no requiere aprobación, disponible de inmediato.

7

Certifica tu cuenta

Contacta a soporte cuando tu integración funcione bien en sandbox - solo así se puede generar la API key de producción.

Autenticación y ambientes

Header X-Api-Key en cada petición. Cada cuenta tiene dos keys, generadas desde el portal (vista "API"): una de sandbox y una de producción. El ambiente lo decide el servidor según contra cuál de las dos keys hizo match el header - la URL es siempre la misma.

CaracterísticaSandboxProducción
Timbra de verdad contra el PACSíSí
UUIDReal (generado por el PAC de pruebas)Real, con valor fiscal
Valor fiscal ante el SATNoSí
Consume timbres de tu saldoNo, nuncaSí, 1 por timbrado exitoso
Ambiente del PACPruebasProducción
Requiere certificación previaNoSí (contacta a soporte)
API keySe genera libre desde el portalSolo se genera si la cuenta está certificada
Vigencia de la key90 días de inactividad, se renueva sola al regenerarlaNo expira por inactividad

El sandbox sí timbra de verdad contra el ambiente de pruebas del PAC (no es una simulación local): mismas validaciones XSD y reglas de negocio, UUID real (sin valor fiscal), y puedes probar cancelación de punta a punta e incluso provocar errores reales del PAC para probar tu manejo de errores antes de ir a producción. El saldo que ves en GET /facturas/saldo es siempre el real de producción, sin importar con cuál key consultes.

Ejemplos de integración

Asumiendo que ya armaste y sellaste el XML de tu CFDI 4.0 con tu CSD, así lo envías a timbrar.

Node.js

const xmlBase64 = Buffer.from(xmlSellado, "utf8").toString("base64");

const res = await fetch("https://app.timbra-mx.com/api/v1/facturas", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.TIMBRAMX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ xml: xmlBase64 }),
});

const data = await res.json();
if (!data.ok) throw new Error(data.error);
console.log("UUID fiscal:", data.data.uuidFiscal);

Python

import base64, os, requests

xml_base64 = base64.b64encode(xml_sellado.encode("utf-8")).decode()

resp = requests.post(
    "https://app.timbra-mx.com/api/v1/facturas",
    headers={
        "X-Api-Key": os.environ["TIMBRAMX_API_KEY"],
        "Content-Type": "application/json",
    },
    json={"xml": xml_base64},
)

data = resp.json()
if not data["ok"]:
    raise Exception(data["error"])
print("UUID fiscal:", data["data"]["uuidFiscal"])

PHP

$xmlBase64 = base64_encode($xmlSellado);

$ch = curl_init("https://app.timbra-mx.com/api/v1/facturas");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "X-Api-Key: " . getenv("TIMBRAMX_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode(["xml" => $xmlBase64]),
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);

if (!$data["ok"]) throw new Exception($data["error"]);
echo "UUID fiscal: " . $data["data"]["uuidFiscal"];

curl

curl -X POST https://app.timbra-mx.com/api/v1/facturas \
  -H "X-Api-Key: $TIMBRAMX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"xml":"PHNmZGk6Q29tcHJvYmFudGU..."}'

C# / .NET

var xmlBase64 = Convert.ToBase64String(
    Encoding.UTF8.GetBytes(xmlSellado));

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key",
    Environment.GetEnvironmentVariable("TIMBRAMX_API_KEY"));

var contenido = new StringContent(
    JsonSerializer.Serialize(new { xml = xmlBase64 }),
    Encoding.UTF8, "application/json");

var resp = await client.PostAsync(
    "https://app.timbra-mx.com/api/v1/facturas", contenido);
var json = await resp.Content.ReadAsStringAsync();
Console.WriteLine(json);

Java

String xmlBase64 = Base64.getEncoder()
    .encodeToString(xmlSellado.getBytes());

String body = "{\"xml\":\"" + xmlBase64 + "\"}";

HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://app.timbra-mx.com/api/v1/facturas"))
    .header("X-Api-Key", System.getenv("TIMBRAMX_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();

HttpResponse resp = client.send(req,
    HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());

¿Prefieres que nosotros armemos el XML? El Modo sellar acepta los datos del comprobante directamente en JSON, sin que tengas que construir el XML tú mismo - ver ejemplos abajo.

Modo sellar: sin armar el XML tú mismo

Los ejemplos de arriba son modo relay (tú ya armaste y sellaste el XML). En modo sellar, subes tu CSD al portal y mandas los datos del comprobante directamente en JSON - TimbraMX arma el XML, lo sella y lo timbra. Solo CFDI de Ingreso simple en v1 (sin complementos como Carta Porte o Comercio Exterior).

PHP (Laravel)

$response = Http::withHeaders([
    'X-Api-Key' => config('services.timbramx.api_key'),
    'Content-Type' => 'application/json',
])->post('https://app.timbra-mx.com/api/v1/facturas', [
    'receptorRfc' => 'XAXX010101000',
    'serie' => 'F',
    'metodoPago' => 'PUE',
    'moneda' => 'MXN',
    'conceptos' => [[
        'claveProdServ' => '81112101',
        'claveUnidad' => 'E48',
        'descripcion' => 'Servicio de consultoría',
        'cantidad' => 1,
        'valorUnitario' => 1000,
        'impuestos' => [[
            'impuesto' => '002', 'categoria' => 'traslado',
            'tipoFactor' => 'Tasa', 'tasaIva' => 0.16,
        ]],
    ]],
]);

$factura = $response->json('data');

C# / .NET

var body = new {
    receptorRfc = "XAXX010101000",
    serie = "F",
    metodoPago = "PUE",
    moneda = "MXN",
    conceptos = new[] {
        new {
            claveProdServ = "81112101",
            claveUnidad = "E48",
            descripcion = "Servicio de consultoría",
            cantidad = 1,
            valorUnitario = 1000,
            impuestos = new[] { new { impuesto = "002", categoria = "traslado", tipoFactor = "Tasa", tasaIva = 0.16 } },
        },
    },
};

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", apiKey);
var response = await client.PostAsJsonAsync("https://app.timbra-mx.com/api/v1/facturas", body);
var resultado = await response.Content.ReadFromJsonAsync<JsonElement>();

Node.js

const respuesta = await fetch("https://app.timbra-mx.com/api/v1/facturas", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.TIMBRAMX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    receptorRfc: "XAXX010101000",
    serie: "F",
    metodoPago: "PUE",
    moneda: "MXN",
    conceptos: [{
      claveProdServ: "81112101",
      claveUnidad: "E48",
      descripcion: "Servicio de consultoría",
      cantidad: 1,
      valorUnitario: 1000,
      impuestos: [{ impuesto: "002", categoria: "traslado", tipoFactor: "Tasa", tasaIva: 0.16 }],
    }],
  }),
});
const resultado = await respuesta.json();

Python

import requests

resultado = requests.post(
    "https://app.timbra-mx.com/api/v1/facturas",
    headers={"X-Api-Key": api_key, "Content-Type": "application/json"},
    json={
        "receptorRfc": "XAXX010101000",
        "serie": "F",
        "metodoPago": "PUE",
        "moneda": "MXN",
        "conceptos": [{
            "claveProdServ": "81112101",
            "claveUnidad": "E48",
            "descripcion": "Servicio de consultoría",
            "cantidad": 1,
            "valorUnitario": 1000,
            "impuestos": [{"impuesto": "002", "categoria": "traslado", "tipoFactor": "Tasa", "tasaIva": 0.16}],
        }],
    },
).json()

En cualquiera de los lenguajes, usa tu API key de sandbox para probar y la de producción cuando ya esté certificada tu cuenta - la URL nunca cambia.

Diccionario de campos de la respuesta

Aplica al mismo data que regresan Facturas, Notas de crédito, Complementos de pago y Nómina (Retenciones es análogo, sin serie/folio/total propios de venta).

CampoTipoSiempre presenteDescripción
idstring (UUID)SíId interno de TimbraMX - úsalo para GET /:id, /xml, /pdf, /cancelar. No es el UUID fiscal.
estadostringSíVer la tabla de estados más abajo.
uuidFiscalstring (UUID)Solo si estado es timbrada o posteriorEl UUID real del SAT - este es el que identifica el CFDI ante el mundo fiscal.
pacstringSolo si ya se intentó timbrarIdentificador interno del PAC que timbró (no lo uses para lógica de negocio, puede cambiar).
serie / foliostringSolo en Facturas/Notas de crédito/Complementos de pago/NóminaLa serie y folio asignados al timbrar (el folio no existe antes de timbrar).
totalnumberSolo en Facturas/Notas de créditoTotal del comprobante.
timbradaEnstring (fecha-hora ISO)Solo si estado es timbrada o posteriorCuándo se completó el timbrado.
canceladaEnstring (fecha-hora ISO)Solo si estado es canceladaCuándo se confirmó la cancelación.
xmlstring (base64)Solo si estado es timbrada o posteriorEl CFDI completo timbrado, en base64 - decodifícalo para obtener el XML real.
errorCodigo / errorMensajestringSolo si estado es errorEl código y mensaje que dio el PAC en el intento fallido.

Notas de crédito, pagos, nómina y retenciones

Además de timbrar facturas, la API cubre los otros tres tipos de CFDI que usa un negocio en operación diaria. La estructura oficial de cada nodo del comprobante está definida en el Anexo 20 del SAT; esta sección solo cubre cómo enviarlo a través de la API de TimbraMX.

POST

/api/v1/facturas/:id/nota-credito

Acredita, total o parcialmente, una factura de Ingreso ya timbrada por esta misma API key. El :id es el id interno que regresó el endpoint de timbrado (no el UUID fiscal). La forma de pago se copia de la factura original -regla del Anexo 20- y el método de pago siempre es PUE.

Request

{
  "importe": 1160.00,
  "descripcion": "Devolución de mercancía",
  "serie": "NC"
}

Errores propios

[NC-002] importe inválido (mayor al total de la factura original).
[NC-003] falta la descripción.

curl

curl -X POST https://app.timbra-mx.com/api/v1/facturas/$FACTURA_ID/nota-credito \
  -H "X-Api-Key: $TIMBRAMX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"importe":1160.00,"descripcion":"Devolución de mercancía","serie":"NC"}'

Node.js

const res = await fetch(
  `https://app.timbra-mx.com/api/v1/facturas/${facturaId}/nota-credito`,
  {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.TIMBRAMX_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      importe: 1160.00,
      descripcion: "Devolución de mercancía",
      serie: "NC",
    }),
  }
);
const data = await res.json();
console.log("Nota de crédito:", data.data.uuidFiscal);

Python

resp = requests.post(
    f"https://app.timbra-mx.com/api/v1/facturas/{factura_id}/nota-credito",
    headers={
        "X-Api-Key": os.environ["TIMBRAMX_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "importe": 1160.00,
        "descripcion": "Devolución de mercancía",
        "serie": "NC",
    },
)
data = resp.json()
print("Nota de crédito:", data["data"]["uuidFiscal"])
POST

/api/v1/complementos-pago

Timbra un CFDI tipo P para registrar uno o varios pagos de facturas PPD ya timbradas. Las facturas a cubrir se identifican por su UUID fiscal (facturaUuid), no por id interno, y deben ser PPD, estar timbradas y ser del mismo receptor. Un pago puede cubrir varios documentos, y el body puede traer varios pagos en un solo complemento. Soporta Idempotency-Key.

Lo que tú mandas: RFC del receptor, serie, y por cada pago sus documentos cubiertos con el monto aplicado. Lo que calcula TimbraMX: el saldo insoluto/anterior de cada factura cubierta (los lee directo de la factura original, no se los mandas tú), el total del complemento, el folio, el sello y el UUID fiscal del CFDI tipo P.

Request

{
  "receptorRfc": "COE850101AB1",
  "serie": "PAGOS",
  "pagos": [{
    "fechaPago": "2026-09-15T12:00:00",
    "formaPago": "03",
    "moneda": "MXN",
    "documentos": [{
      "facturaUuid": "3F9A1C2E-...",
      "impPagado": 12500.00
    }]
  }]
}

Errores propios

[PAG-001] monto pagado inválido · [PAG-002] forma de pago inválida · [PAG-004] pago sin documentos · [PAG-005] falta tipo de cambio (moneda distinta de MXN) · [PAG-008] sin pagos · [PAG-009] falta el UUID · [PAG-010] falta el RFC del receptor.

curl

curl -X POST https://app.timbra-mx.com/api/v1/complementos-pago \
  -H "X-Api-Key: $TIMBRAMX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "receptorRfc": "COE850101AB1",
    "serie": "PAGOS",
    "pagos": [{
      "fechaPago": "2026-09-15T12:00:00",
      "formaPago": "03",
      "moneda": "MXN",
      "documentos": [
        {"facturaUuid": "3F9A1C2E-...", "impPagado": 12500.00}
      ]
    }]
  }'

Node.js

const res = await fetch(
  "https://app.timbra-mx.com/api/v1/complementos-pago",
  {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.TIMBRAMX_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      receptorRfc: "COE850101AB1",
      serie: "PAGOS",
      pagos: [{
        fechaPago: "2026-09-15T12:00:00",
        formaPago: "03",
        moneda: "MXN",
        documentos: [
          { facturaUuid: "3F9A1C2E-...", impPagado: 12500.00 },
        ],
      }],
    }),
  }
);
const data = await res.json();
console.log("Complemento de pago:", data.data.id);

Python

resp = requests.post(
    "https://app.timbra-mx.com/api/v1/complementos-pago",
    headers={
        "X-Api-Key": os.environ["TIMBRAMX_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "receptorRfc": "COE850101AB1",
        "serie": "PAGOS",
        "pagos": [{
            "fechaPago": "2026-09-15T12:00:00",
            "formaPago": "03",
            "moneda": "MXN",
            "documentos": [
                {"facturaUuid": "3F9A1C2E-...", "impPagado": 12500.00}
            ],
        }],
    },
)
data = resp.json()
print("Complemento de pago:", data["data"]["id"])
POST

/api/v1/nomina

Arma, sella y timbra el CFDI de Nómina 1.2 en un solo paso -el body es la misma estructura que el diálogo de Nómina del portal. Siempre timbra (no guarda borrador). El receptorRfc debe existir ya en tu catálogo de "Mis clientes". Todos los campos numéricos dentro de nomina viajan como string, no number, igual que exige el XSD del SAT. Soporta Idempotency-Key.

Request (mínimo)

{
  "receptorRfc": "MARL900101AB2",
  "serie": "NOM",
  "nomina": {
    "tipoNomina": "O",
    "fechaPago": "2026-09-15",
    "fechaInicialPago": "2026-09-01",
    "fechaFinalPago": "2026-09-15",
    "numDiasPagados": "15",
    "totalPercepciones": "7500.00",
    "receptor": {
      "curp": "MARL900101HDFRZS02",
      "tipoContrato": "01",
      "tipoRegimen": "02",
      "numEmpleado": "1",
      "periodicidadPago": "04",
      "claveEntFed": "JAL"
    },
    "percepciones": {
      "totalGravado": "7500.00",
      "totalExento": "0.00",
      "percepciones": [{
        "tipoPercepcion": "001",
        "clave": "001",
        "concepto": "Sueldos",
        "importeGravado": "7500.00",
        "importeExento": "0.00"
      }]
    }
  }
}

Errores propios más comunes

[FC-219] falta TotalPercepciones/TotalOtrosPagos · [FC-220] fecha inicial posterior a la final · [FC-173] falta número de empleado · [FC-176] salario diario integrado inválido.

Debe existir totalPercepciones o totalOtrosPagos (o ambos). tipoNomina es "O" (Ordinaria) o "E" (Extraordinaria).

curl

curl -X POST https://app.timbra-mx.com/api/v1/nomina \
  -H "X-Api-Key: $TIMBRAMX_API_KEY" \
  -H "Content-Type: application/json" \
  -d @nomina.json

Node.js

const res = await fetch("https://app.timbra-mx.com/api/v1/nomina", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.TIMBRAMX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    receptorRfc: "MARL900101AB2",
    serie: "NOM",
    nomina: {
      tipoNomina: "O",
      fechaPago: "2026-09-15",
      fechaInicialPago: "2026-09-01",
      fechaFinalPago: "2026-09-15",
      numDiasPagados: "15",
      totalPercepciones: "7500.00",
      receptor: {
        curp: "MARL900101HDFRZS02",
        tipoContrato: "01",
        tipoRegimen: "02",
        numEmpleado: "1",
        periodicidadPago: "04",
        claveEntFed: "JAL",
      },
      percepciones: {
        totalGravado: "7500.00",
        totalExento: "0.00",
        percepciones: [{
          tipoPercepcion: "001",
          clave: "001",
          concepto: "Sueldos",
          importeGravado: "7500.00",
          importeExento: "0.00",
        }],
      },
    },
  }),
});
const data = await res.json();
console.log("Recibo de nómina:", data.data.uuidFiscal);

Python

with open("nomina.json") as f:
    payload = json.load(f)

resp = requests.post(
    "https://app.timbra-mx.com/api/v1/nomina",
    headers={
        "X-Api-Key": os.environ["TIMBRAMX_API_KEY"],
        "Content-Type": "application/json",
    },
    json=payload,
)
data = resp.json()
print("Recibo de nómina:", data["data"]["uuidFiscal"])
POST

/api/v1/retenciones

Timbra un CFDI de Retenciones e Información de Pagos en un solo paso. Soporta Idempotency-Key.

Alcance v1: solo 3 de los 12 tipos del catálogo del SAT -dividendos, enajenacion_acciones y pagos_extranjero. Los otros 9 tipos solo están disponibles desde el portal por ahora.
Importante: si la contraparte usa el RFC genérico XAXX010101000, el campo nombreContraparte debe ser exactamente "PÚBLICO EN GENERAL" (con acento) - el PAC lo rechaza sin el acento.

Request (ejemplo: dividendos)

{
  "tipoOperacion": "dividendos",
  "serie": "RET",
  "rfcContraparte": "MARL900101AB2",
  "nombreContraparte": "Luis Martinez Rodriguez",
  "codigoPostalContraparte": "44100",
  "fechaOperacion": "2026-09-15",
  "montoOperacion": 50000,
  "cveRetenc": "14",
  "mesPeriodo": 9,
  "ejercicio": 2026,
  "impRetenidos": [{
    "impuesto": "001",
    "montoRet": 5000,
    "tipoPagoRet": "02"
  }]
}

Errores propios

[RET-001] monto de operación inválido · [RET-003] clave de retención inválida · [RET-010]/[RET-011] nombre incorrecto para RFC genérico · [RET-015] país de residencia inexistente en el catálogo del SAT.

impuesto usa c_Impuesto (001 ISR, 002 IVA, 003 IEPS) y tipoPagoRet usa c_TipoPagoRet (01 provisional, 02 definitivo). Si la contraparte es extranjera, manda numRegIdTribContraparte en vez de codigoPostalContraparte.

curl

curl -X POST https://app.timbra-mx.com/api/v1/retenciones \
  -H "X-Api-Key: $TIMBRAMX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tipoOperacion": "dividendos",
    "serie": "RET",
    "rfcContraparte": "MARL900101AB2",
    "nombreContraparte": "Luis Martinez Rodriguez",
    "codigoPostalContraparte": "44100",
    "fechaOperacion": "2026-09-15",
    "montoOperacion": 50000,
    "cveRetenc": "14",
    "mesPeriodo": 9,
    "ejercicio": 2026,
    "impRetenidos": [
      {"impuesto": "001", "montoRet": 5000, "tipoPagoRet": "02"}
    ]
  }'

Node.js

const res = await fetch("https://app.timbra-mx.com/api/v1/retenciones", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.TIMBRAMX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    tipoOperacion: "dividendos",
    serie: "RET",
    rfcContraparte: "MARL900101AB2",
    nombreContraparte: "Luis Martinez Rodriguez",
    codigoPostalContraparte: "44100",
    fechaOperacion: "2026-09-15",
    montoOperacion: 50000,
    cveRetenc: "14",
    mesPeriodo: 9,
    ejercicio: 2026,
    impRetenidos: [
      { impuesto: "001", montoRet: 5000, tipoPagoRet: "02" },
    ],
  }),
});
const data = await res.json();
console.log("Retención timbrada:", data.data.uuidFiscal);

Python

resp = requests.post(
    "https://app.timbra-mx.com/api/v1/retenciones",
    headers={
        "X-Api-Key": os.environ["TIMBRAMX_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "tipoOperacion": "dividendos",
        "serie": "RET",
        "rfcContraparte": "MARL900101AB2",
        "nombreContraparte": "Luis Martinez Rodriguez",
        "codigoPostalContraparte": "44100",
        "fechaOperacion": "2026-09-15",
        "montoOperacion": 50000,
        "cveRetenc": "14",
        "mesPeriodo": 9,
        "ejercicio": 2026,
        "impRetenidos": [
            {"impuesto": "001", "montoRet": 5000, "tipoPagoRet": "02"}
        ],
    },
)
data = resp.json()
print("Retención timbrada:", data["data"]["uuidFiscal"])

datosEspecificos por tipo de operación

El campo datosEspecificos es un objeto cuyo contenido depende del tipoOperacion. Los nombres de los campos van en PascalCase exacto, igual que en el XML del SAT (no camelCase).

Para tipoOperacion: "dividendos"

CampoTipoObligatorioDescripción
CveTipDivOUtilstringSíClave del tipo de dividendo/utilidad (catálogo del SAT).
TipoSocDistrDivstringNo (default "Sociedad Nacional")"Sociedad Nacional" o "Sociedad Extranjera".
MontISRAcredRetMexicostringNo (default "0")Monto de ISR acreditable retenido en México.
MontISRAcredRetExtranjerostringNo (default "0")Monto de ISR acreditable retenido en el extranjero.
MontRetExtDivExtstringNoMonto retenido en el extranjero por dividendos de fuente extranjera.
MontISRAcredNalstringNoISR acreditable nacional.
MontDivAcumNalstringNoDividendo acumulable nacional.
MontDivAcumExtstringNoDividendo acumulable extranjero.
ProporcionRemstringNoProporción de remanente distribuible.
{ "CveTipDivOUtil": "01", "TipoSocDistrDiv": "Sociedad Nacional", "MontISRAcredRetMexico": "0", "MontISRAcredRetExtranjero": "0" }

Para tipoOperacion: "enajenacion_acciones"

CampoTipoObligatorioDescripción
ContratoIntermediacionstringSíNúmero de contrato de intermediación bursátil.
GananciastringNo (default "0")Monto de la ganancia en la operación.
PerdidastringNo (default "0")Monto de la pérdida en la operación.
{ "ContratoIntermediacion": "CI-2026-001", "Ganancia": "15000.00", "Perdida": "0" }

Para tipoOperacion: "pagos_extranjero"

CampoTipoObligatorioDescripción
EsBenefEfectDelCobrostringNo (default "SI")"SI" o "NO" - si el beneficiario extranjero es el efectivo receptor del cobro.
NoBeneficiarioobjectSolo si EsBenefEfectDelCobro es "NO"Datos de quien NO es el beneficiario efectivo.
NoBeneficiario.PaisDeResidParaEfecFiscstring-País de residencia fiscal (catálogo c_Pais).
NoBeneficiario.ConceptoPagostring-Clave del concepto del pago.
NoBeneficiario.DescripcionConceptostring-Descripción del concepto.
BeneficiarioobjectNoDatos del beneficiario efectivo, si se conocen.
Beneficiario.RFCstring-RFC del beneficiario, si es mexicano.
Beneficiario.CURPstring-CURP del beneficiario, si aplica.
Beneficiario.NomDenRazSocBstring-Nombre o razón social del beneficiario.
Beneficiario.ConceptoPagostring-Clave del concepto del pago.
Beneficiario.DescripcionConceptostring-Descripción del concepto.
{ "EsBenefEfectDelCobro": "SI", "Beneficiario": { "RFC": "XEXX010101000", "NomDenRazSocB": "Acme Corp", "ConceptoPago": "15", "DescripcionConcepto": "Servicios profesionales" } }

Manejo de errores

Todo error trae { "ok": false, "error": "..." }, y los que vienen del PAC (502) además codigo/detalle con la razón real. Así debes reaccionar a cada código.

CódigoSignificado¿Reintentar?Acción recomendada
400Datos inválidosNoLee error (dice exactamente qué campo falló) y corrige el body antes de reintentar.
401API key inválida o expiradaNoRevisa el header X-Api-Key; si era de sandbox y expiró, regenérala desde el portal.
402Sin timbres disponiblesNoCompra/recarga timbres desde el portal (Mis compras) antes de reintentar.
403Sin acceso a la APINoContrata el módulo de API, o certifica tu cuenta si intentabas usar la key de producción.
404Recurso inexistenteNoRevisa el id y que corresponda al mismo ambiente (sandbox/producción) de tu key.
409Operación concurrente sobre el MISMO documentoSí, tras unos segundosEs un candado por registro (mismo id) - por ejemplo, dos llamadas casi simultáneas a /cancelar sobre la misma factura. No hace falta cambiar el Idempotency-Key: espera y usa GET /:id para ver en qué quedó. Dos creaciones con Idempotency-Key diferentes NUNCA chocan entre sí con un 409 - cada una crea su propio documento.
429Límite de peticiones excedidoSí, con backoffNo hay header Retry-After: la respuesta trae RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset (este último en segundos restantes de la ventana) - úsalo para calcular cuánto esperar.
502El PAC rechazó el CFDI/cancelaciónSí, con el MISMO Idempotency-KeyLee codigo/detalle (la razón real del PAC) para saber qué corregir; si el dato estaba bien, reintenta con la misma clave de idempotencia.

Para reintentos seguros (ej. timeout de red), manda el header Idempotency-Key (o el campo idempotency_key en el body) con cualquier string único de hasta 255 caracteres. Si ya existe una factura tuya con esa misma clave, el servidor regresa el comprobante original en vez de timbrar y cobrar otra vez. Aplica a los endpoints de creación: /facturas, /facturas/:id/nota-credito, /complementos-pago, /nomina y /retenciones.

Lo que no es obvio de la idempotencia (verificado en el código, no supuesto)

  • No expira. La clave se guarda como índice único permanente ligado al documento, no como un caché con TTL - mientras el documento exista, la clave sigue siendo válida para siempre.
  • No compara el body. Si reusas la misma Idempotency-Key con un body distinto al de la petición original, TimbraMX no valida que coincidan: simplemente ignora el body nuevo y regresa el documento original tal cual quedó la primera vez. Es tu responsabilidad no reusar una clave para dos operaciones distintas.
  • Es solo un candado de creación, no una caché de lectura. Si el primer intento con esa clave falló antes de llegar a crear el registro (ej. un 400 por datos inválidos), no queda nada guardado con esa clave - puedes corregir el body y reenviar la misma clave sin problema.
  • Ventana de carrera en peticiones verdaderamente simultáneas. Con la misma clave, en el mismo milisegundo (antes de que la primera termine de escribir), es una ventana muy angosta y no probada exhaustivamente contra el motor actual - en el peor caso, la segunda petición puede toparse con un error de base de datos en vez de la respuesta idempotente limpia. Si tu integración dispara reintentos en paralelo (no solo secuenciales), un pequeño backoff antes del segundo intento elimina este riesgo en la práctica.

Estados de un documento (ciclo de vida)

Aplica igual a Facturas, Notas de crédito, Complementos de pago y Nómina. Retenciones usa un modelo separado con el mismo patrón, salvo donde se indica.

borrador ──(timbrar)──> timbrando ──(el PAC responde bien)──> timbrada
   ↑error│                  │
   └─────┘(el PAC rechaza)──> error ──(reintentar)──> timbrando

timbrada ──(cancelar)──> cancelando ──> cancelada
                             │
                             └──(el SAT exige aprobación del receptor)──> cancelacion_pendiente ──(el receptor aprueba, o pasan 72h sin objeción)──> cancelada
                                                                              │
                                                                              └──(el receptor rechaza)──> se queda timbrada (la cancelación no procede)
EstadoSignificadoAcción del integrador
borradorExiste en TimbraMX pero nunca se envió al PAC (la API pública nunca crea este estado directamente - solo aplica a documentos capturados desde el portal).Ninguna vía API - este estado no lo produce tu integración.
timbrandoPetición en curso hacia el PAC - estado transitorio, no deberías verlo en una respuesta.Si lo ves en un GET, espera unos segundos y vuelve a consultar.
timbradaCFDI válido, con uuidFiscal y xml.Guarda id y uuidFiscal en tu propio sistema; ya puedes descargar /xml y /pdf.
errorEl PAC rechazó el timbrado - revisa errorCodigo/errorMensaje en GET /:id.Muestra el error al usuario; corrige el dato y vuelve a intentar (nuevo POST, no hay "reintentar" in-place en modo sellar).
cancelandoSolicitud de cancelación en curso - transitorio.Espera unos segundos y vuelve a consultar.
cancelacion_pendienteEl SAT exige aprobación del receptor antes de cancelar de verdad (según monto/antigüedad, regla del SAT).Llama a GET /:id/estatus-cancelacion (disponible en los 4 recursos) para confirmar el estatus real ante el PAC sin volver a solicitar la cancelación.
canceladaCancelación confirmada por el SAT.Actualiza tu sistema - este documento ya no es válido fiscalmente.

Nota sobre cancelacion_pendiente: GET /{id}/estatus-cancelacion ya está unificado en los 4 recursos (Facturas, Complementos de pago, Nómina y Retenciones) - refresca el estatus real ante el PAC sin volver a solicitar la cancelación, y si ya se confirmó, actualiza el campo estado a cancelada en la misma llamada. GET /api/v1/facturas/:id/estatus-sat es distinto: te dice si el SAT ya reconoce el CFDI como cancelado en tiempo real, pero no actualiza ese campo - solo informa.

Límites y seguridad

Lo que necesitas saber para no toparte con un bloqueo inesperado, sobre todo si tu integración procesa lotes.

60 peticiones/min, pero por recurso

No es una sola bolsa global por cuenta: Facturas, Complementos de pago, Nómina y Retenciones tienen cada uno su propio contador independiente de 60 peticiones/minuto - una cuenta activa en los 4 a la vez puede llegar hasta 240 peticiones/min combinadas. El límite es por cuenta autenticada, no por IP.

Ventana fija de 60 segundos

La respuesta trae los headers RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset (segundos restantes de la ventana actual) - no hay header Retry-After.

Body máximo 10 MB

Por petición. Cada timbrado exitoso en producción consume 1 timbre de tu saldo - usar la API no tiene costo adicional sobre timbrar desde el portal.

Vigencia de la API key

La de sandbox vence a los 90 días de generada (se renueva regenerándola desde el portal); la de producción no tiene vencimiento automático.

⚠️ Ráfaga anti-bot: 4 peticiones en 5 segundos por IP, bloqueo permanente

Corre antes de validar tu API key. Si la disparas, tu IP queda bloqueada de forma permanente (no temporal) hasta que se libere a mano por soporte - aplica incluso con una API key válida, porque este candado corre antes de siquiera revisar la key. Es distinto del límite normal de 60/min de arriba y bastante más estricto.

Si tu integración procesa lotes (por ejemplo, timbrar varias facturas pendientes de una cola), agrega al menos ~1.5 segundos entre peticiones consecutivas desde la misma IP/servidor para evitar quedar bloqueado por tráfico enteramente legítimo.

Seguridad de tu API key

Tu API key es literalmente la contraseña de tu cuenta - quien la tenga puede timbrar, cancelar y consultar todo lo que tú puedes.

  • Solo desde tu backend. Nunca la incluyas en código que corra en el navegador, en una app móvil, ni en ningún cliente que el usuario final pueda inspeccionar - siempre desde tu servidor.
  • Nunca la subas a un repositorio. Guárdala en variables de entorno o en el manejo de secretos de tu infraestructura, no en el código fuente ni en archivos de configuración versionados.
  • Nunca la registres en logs. Si tu sistema loguea headers de request completos por default, excluye explícitamente X-Api-Key.
  • Úsala solo sobre HTTPS. La API no acepta HTTP plano.
  • Si se compromete, regénerala de inmediato desde el portal (vista API) - la key anterior deja de funcionar en el momento en que generas una nueva.

Qué NO soporta la API hoy

Para que no descubras estas limitaciones después de haber integrado, aquí está la lista completa y honesta.

Complementos en modo sellar

El modo sellar de /facturas solo arma CFDI de Ingreso simple (tipo "I") sin complementos - Carta Porte, Comercio Exterior, IEDU, etc. requieren que tú armes el XML completo y uses modo relay, o que uses el portal.

Retenciones

Solo 3 de los 12 tipos del catálogo del SAT (dividendos, enajenacion_acciones, pagos_extranjero) - los otros 9 solo están en el portal.

Addendas

Se pueden agregar a una factura ya timbrada, pero solo desde el portal - no hay endpoint de API para addendas todavía.

Webhooks

No existen. No hay forma de que TimbraMX le avise a tu sistema cuando algo cambia - usa polling (GET /:id).

Filtros en los listados

Solo pagina/limite en Facturas, Complementos de pago y Nómina - sin filtro por RFC, estado ni rango de fechas. Retenciones es la única excepción, con filtro tipo.

Búsqueda por UUID fiscal

No existe GET /facturas?uuidFiscal=... ni similar - guarda tú el id interno que regresó el POST original (viaja junto con uuidFiscal en esa respuesta).

Crear clientes/series/CSD por API

No existe - todo se prepara una vez desde el portal (ver "Configuración previa en el portal" arriba).

Funcionalidad por complemento: modo relay vs. modo sellar

FuncionalidadModo relayModo sellar
CFDI de Ingreso simpleSíSí
Carta PorteSí (tú armas el XML)No
Comercio ExteriorSí (tú armas el XML)No
IEDU / otros complementos especialesSí (tú armas el XML)No
AddendaSí (tú la incluyes en el XML)No (agrégala desde el portal después de timbrar)

Catálogos del SAT usados por esta API

Esta API no reinventa catálogos propios: usa directamente los catálogos oficiales del SAT (Anexo 20). No los reproducimos completos aquí porque el SAT los actualiza sin previo aviso - usa siempre la versión vigente publicada por el SAT como fuente.

CatálogoCampos donde se usaConsulta en TimbraMX
c_FormaPagoformaPago (Facturas, Complementos de pago)GET /catalogos/forma-pago
c_MonedamonedaGET /catalogos/moneda
c_ClaveProdServconceptos[].claveProdServGET /catalogos/clave-prod-serv?buscar=...
c_ClaveUnidadconceptos[].claveUnidadGET /catalogos/clave-unidad?buscar=...
c_RegimenFiscalRégimen fiscal del receptor (ya dado de alta en "Mis clientes", no se manda por API)GET /catalogos/regimen-fiscal
c_UsoCFDIUso CFDI del receptor (idem, se resuelve desde el cliente ya dado de alta)GET /catalogos/uso-cfdi
c_Impuestoconceptos[].impuestos[].impuesto, impRetenidos[].impuesto001 ISR / 002 IVA / 003 IEPS (los 3 únicos valores, no cambian)
c_TipoFactorconceptos[].impuestos[].tipoFactorTasa / Exento (más Cuota, no expuesto aún en modo sellar)
c_TipoPagoRetimpRetenidos[].tipoPagoRet01-04 (ver la tabla de Retenciones para el mapeo por impuesto)
c_CveRetenccveRetencFijo por tipoOperacion - ver la tabla de Retenciones
c_PaispaisResidencia (Retenciones)ISO 3166-1 alfa-3 (MEX, USA, etc.) - validado contra la tabla real del SAT al timbrar
Bono verificado: los endpoints GET /catalogos/... de arriba son de lectura pública, sin necesidad de X-Api-Key - por ejemplo, GET https://app.timbra-mx.com/catalogos/forma-pago responde 200 sin ningún header de autenticación. Úsalos libremente si tu integración necesita armar un selector propio con los catálogos vigentes, sin copiar y mantener tu propia versión del catálogo del SAT. No cuentan contra tu límite de 60/min (viven fuera de los routers de /api/v1/*).

Casos de prueba en sandbox

Antes de pedir tu certificación para producción, verifica que tu integración maneja bien estos casos - todos se pueden probar en sandbox sin costo ni riesgo.

CasoCómo provocarloResultado esperado
Timbrado exitosoBody válido, key de sandbox201, estado: "timbrada"
API key inválidaHeader X-Api-Key con un valor cualquiera401
Sin header X-Api-KeyOmite el header401
RFC de receptor inexistentereceptorRfc que no está en tus clientes400
Serie inexistente o inactivaserie con un código que no existe400
Sin timbresSolo se puede en producción - sandbox nunca cobra ni bloquea por saldo402 (solo producción)
Módulo no contratadoUsa un endpoint de un recurso sin ese módulo activo403
Id inexistenteGET /facturas/:id con un UUID que no existe404
Id de otro ambienteConsulta con la key de sandbox un id creado en producción (o viceversa)404
Petición duplicada (mismo Idempotency-Key)Repite la misma petición con el mismo header200, idempotente: true
Operación concurrenteDos llamadas casi simultáneas a /cancelar sobre el mismo idLa segunda: 409
Rate limitMás de 60 peticiones/min al mismo recurso con la misma key429
Cancelación exitosaPOST /facturas/:id/cancelar con motivo válido sobre una factura timbradaestado: "cancelada" o "cancelacion_pendiente"
Validación sin timbrarPOST /facturas/validar con un XML mal formado200, data.valido: false con la lista de errores
Descarga de XML/PDF sin timbrarGET /facturas/:id/xml sobre un id en estado: "borrador"400 (el id sí existe - 200 en GET /:id - solo el archivo no; 404 es únicamente si el id no existe o es de otro ambiente)
PARA AGENTES DE IA

Integra con IA

¿Usas una IA (Claude, ChatGPT, Cursor, Copilot...) para construir tu integración? Dale estos recursos para que genere código correcto contra el contrato real de la API. Empieza siempre en sandbox.

Pega cualquiera de estas URLs directo en tu herramienta de IA para que tenga el contrato real de la API - endpoints, campos, límites y ejemplos - antes de generar código.

Preguntas frecuentes sobre la API

¿Cómo me autentico con la API? +

Con el header X-Api-Key. Cada cuenta tiene dos: una de sandbox (pruebas, sin costo) y una de producción (solo se genera cuando tu cuenta está certificada). Genera tu API key de sandbox desde el portal y empieza a integrar hoy mismo.

¿La API arma el CFDI por mí o solo lo timbra? +

Las dos formas existen. Modo relay: tu sistema arma y sella el XML con tu CSD, TimbraMX solo lo timbra. Modo sellar: subes tu CSD al portal y nos mandas solo los datos del comprobante (receptor y conceptos); nosotros armamos, sellamos y timbramos.

¿Puedo cancelar un CFDI por la API? +

Sí, con POST /api/v1/facturas/:id/cancelar, usando el mismo catálogo de motivos de cancelación del SAT (01-04) que el portal.

¿Hay límite de peticiones a la API? +

Sí, pero no es una sola bolsa global: cada recurso (Facturas, Complementos de pago, Nómina, Retenciones) tiene su propio límite de 60 peticiones/minuto, hasta 240/min combinadas. Si lo excedes, la API responde 429 con los headers RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset (no hay Retry-After). Aparte, hay un límite anti-bot de 4 peticiones en 5 segundos por IP que bloquea permanentemente si se dispara - ver la sección de Límites y seguridad.

¿En qué lenguaje puedo integrar la API? +

En cualquiera que pueda hacer peticiones HTTP - es una API REST estándar con JSON. Node.js y Python son solo ejemplos; funciona igual desde PHP, Java, .NET, Ruby, etc.

¿Puedo generar notas de crédito, pagos y nómina por la API? +

Sí. POST /api/v1/facturas/:id/nota-credito para notas de crédito, POST /api/v1/complementos-pago para CFDI tipo P, y POST /api/v1/nomina para recibos de nómina 1.2 - cada uno con sus propios endpoints de consulta, XML/PDF y cancelación.

¿Qué tipos de retenciones cubre la API? +

En v1, solo 3 de los 12 tipos del catálogo del SAT: dividendos, enajenacion_acciones y pagos_extranjero, vía POST /api/v1/retenciones. Los otros 9 tipos solo están disponibles desde el portal por ahora.

¿Qué debo configurar en el portal antes de usar la API? +

Antes de tu primera llamada: crea tu cuenta, sube tu CSD, da de alta al menos un cliente y una serie activa por tipo de documento, activa el módulo de API y genera tu API key de sandbox - todo desde el portal, la API no crea clientes, series ni CSD.

¿Cuál es la diferencia entre sandbox y producción? +

La URL es la misma; el ambiente lo decide la API key que uses. Sandbox timbra de verdad contra el ambiente de pruebas del PAC, con UUID real pero sin valor fiscal y sin consumir tu saldo; producción sí tiene valor fiscal y consume 1 timbre por timbrado exitoso. La key de producción solo se genera cuando tu cuenta está certificada.

Empieza a integrar hoy

Genera tu API key de sandbox desde el portal y timbra tu primer CFDI de prueba en minutos, sin costo.

CFDI 4.0 Timbrado inmediato Soporte especializado