curl --request POST \
--url https://api.ipsofactura.com/cfdi/exports \
--header 'Content-Type: application/json' \
--header 'x-api: <api-key>' \
--data '
{
"desde": "2026-08-01",
"hasta": "2026-08-31",
"rfcEmisor": "EKU9003173C9"
}
'import requests
url = "https://api.ipsofactura.com/cfdi/exports"
payload = {
"desde": "2026-08-01",
"hasta": "2026-08-31",
"rfcEmisor": "EKU9003173C9"
}
headers = {
"x-api": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({desde: '2026-08-01', hasta: '2026-08-31', rfcEmisor: 'EKU9003173C9'})
};
fetch('https://api.ipsofactura.com/cfdi/exports', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ipsofactura.com/cfdi/exports",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'desde' => '2026-08-01',
'hasta' => '2026-08-31',
'rfcEmisor' => 'EKU9003173C9'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.ipsofactura.com/cfdi/exports"
payload := strings.NewReader("{\n \"desde\": \"2026-08-01\",\n \"hasta\": \"2026-08-31\",\n \"rfcEmisor\": \"EKU9003173C9\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.ipsofactura.com/cfdi/exports")
.header("x-api", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"desde\": \"2026-08-01\",\n \"hasta\": \"2026-08-31\",\n \"rfcEmisor\": \"EKU9003173C9\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ipsofactura.com/cfdi/exports")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"desde\": \"2026-08-01\",\n \"hasta\": \"2026-08-31\",\n \"rfcEmisor\": \"EKU9003173C9\"\n}"
response = http.request(request)
puts response.read_body{
"created_at": "2026-09-14T10:00:00-06:00",
"desde": "2026-08-01",
"estatus": "pendiente",
"hasta": "2026-08-31",
"id": "9a4f07ff-3abc-435f-b7a6-88c2bc65b4c1"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}Solicitar export masivo de XML
Encola la generación de un ZIP con todos los XML timbrados de la cuenta en un rango de fechas, para cerrar el mes o entregar un periodo completo a contabilidad sin hacer una llamada por CFDI. Responde 202 de inmediato con el id del export: armar el archivo tarda minutos, no ocurre dentro de esta petición.
desde y hasta son fechas, ambas inclusive, en zona America/Mexico_City, y se aplican sobre la fecha de timbrado del SAT. El rango no puede abarcar más de 366 días. rfc_emisor es opcional y limita el export a una sola empresa.
El ZIP trae un XML por CFDI, nombrado por folio fiscal, más un manifiesto.csv que lo relaciona con serie, folio, receptor, total y estatus. Incluye los CFDIs cancelados —siguen siendo documentos fiscales del emisor— y excluye los que timbró otro proveedor, de los que no conservamos el documento.
Para saber cuándo está listo: suscribe el evento export.listo o consulta GET /cfdi/exports/{id}. El archivo se conserva 7 días desde que se genera; después hay que volver a pedirlo.
curl --request POST \
--url https://api.ipsofactura.com/cfdi/exports \
--header 'Content-Type: application/json' \
--header 'x-api: <api-key>' \
--data '
{
"desde": "2026-08-01",
"hasta": "2026-08-31",
"rfcEmisor": "EKU9003173C9"
}
'import requests
url = "https://api.ipsofactura.com/cfdi/exports"
payload = {
"desde": "2026-08-01",
"hasta": "2026-08-31",
"rfcEmisor": "EKU9003173C9"
}
headers = {
"x-api": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({desde: '2026-08-01', hasta: '2026-08-31', rfcEmisor: 'EKU9003173C9'})
};
fetch('https://api.ipsofactura.com/cfdi/exports', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ipsofactura.com/cfdi/exports",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'desde' => '2026-08-01',
'hasta' => '2026-08-31',
'rfcEmisor' => 'EKU9003173C9'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.ipsofactura.com/cfdi/exports"
payload := strings.NewReader("{\n \"desde\": \"2026-08-01\",\n \"hasta\": \"2026-08-31\",\n \"rfcEmisor\": \"EKU9003173C9\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.ipsofactura.com/cfdi/exports")
.header("x-api", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"desde\": \"2026-08-01\",\n \"hasta\": \"2026-08-31\",\n \"rfcEmisor\": \"EKU9003173C9\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ipsofactura.com/cfdi/exports")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"desde\": \"2026-08-01\",\n \"hasta\": \"2026-08-31\",\n \"rfcEmisor\": \"EKU9003173C9\"\n}"
response = http.request(request)
puts response.read_body{
"created_at": "2026-09-14T10:00:00-06:00",
"desde": "2026-08-01",
"estatus": "pendiente",
"hasta": "2026-08-31",
"id": "9a4f07ff-3abc-435f-b7a6-88c2bc65b4c1"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}{
"code": "VALIDATION_ERROR",
"id": "<string>",
"idempotencyKey": "<string>",
"message": "CFDI40147: El RFC del receptor no se encuentra en la lista de contribuyentes inscritos no cancelados del SAT",
"retryAfter": 123,
"sat_code": "CFDI40147"
}Authorizations
API key emitida por Ipsofactura, enviada en el header x-api.
Body
Primer día del rango, inclusive (zona horaria America/Mexico_City).
"2026-08-01"
Último día del rango, inclusive. El rango no puede abarcar más de 366 días: para un periodo mayor hay que pedir varios exports.
"2026-08-31"
Opcional. Limita el export a un solo emisor. Si se omite, el export cubre todas las empresas registradas de la cuenta.
13^$|^[A-Za-z&Ññ]{3,4}[0-9]{6}[A-Za-z0-9]{3}$"EKU9003173C9"
Response
Export encolado
Estado de un export masivo de XML
Cuándo terminó de generarse o de fallar.
Cuándo se pidió el export.
Primer día del rango, inclusive.
"2026-08-01"
Por qué falló, cuando el estatus es fallido. Código estable; el texto de error_message es informativo y no debe parsearse.
"EXPORT_TOO_LARGE"
Descripción del fallo, para quien lo lea.
pendiente (en cola), en_proceso (armando el archivo), listo (descargable) o fallido.
"pendiente"
true cuando el export se generó correctamente pero su archivo ya se borró por la política de retención. No es un error: hay que volver a pedir el export.
false
Último día del rango, inclusive.
"2026-08-31"
Id del export. Es lo que se debe almacenar: la URL de descarga se vuelve a pedir con él.
"9a4f07ff-3abc-435f-b7a6-88c2bc65b4c1"
Emisor al que se limitó el export, si se pidió así.
"EKU9003173C9"
Tamaño del ZIP en bytes. Sólo cuando el export está listo.
5242880
CFDIs incluidos en el archivo. Sólo cuando el export está listo. Puede ser 0: un rango sin CFDIs produce un ZIP válido y vacío.
1043
URL firmada, de solo lectura, con vigencia de 15 minutos desde que se genera. No la almacenes: guarda el id y vuelve a llamar a GET /cfdi/exports/{id}. El ZIP contiene un XML por CFDI, nombrado por folio fiscal, más un manifiesto.csv que los relaciona con serie, folio, receptor, total y estatus.
"https://storage.ipsofactura.com/exports/9a4f07ff.zip?sig=..."