Download Document
curl --request GET \
--url https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/downloadimport requests
url = "https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download', 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.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"error": {
"code": "DOCUMENT_NOT_FOUND",
"message": "El documento solicitado no existe o no pertenece a esta extracción",
"details": {
"extractionId": "JOB20250104123456789A",
"documentId": "doc_invalid"
}
}
}
{
"error": {
"code": "DOCUMENT_EXPIRED",
"message": "El documento ha expirado y ya no está disponible para descarga",
"details": {
"expirationDate": "2025-02-03T12:34:56.789Z",
"retentionDays": 30
}
}
}
{
"error": {
"code": "UNSUPPORTED_FORMAT",
"message": "El formato solicitado no está disponible para este documento",
"details": {
"requestedFormat": "PDF",
"availableFormats": ["XML"]
}
}
}
SAT API Reference
Download Document
Downloads a specific document from a completed job in XML or PDF format
GET
/
v1
/
extractions
/
{publicId}
/
documents
/
{documentId}
/
download
Download Document
curl --request GET \
--url https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/downloadimport requests
url = "https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download', 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.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.taxo.co/v1/extractions/{publicId}/documents/{documentId}/download")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"error": {
"code": "DOCUMENT_NOT_FOUND",
"message": "El documento solicitado no existe o no pertenece a esta extracción",
"details": {
"extractionId": "JOB20250104123456789A",
"documentId": "doc_invalid"
}
}
}
{
"error": {
"code": "DOCUMENT_EXPIRED",
"message": "El documento ha expirado y ya no está disponible para descarga",
"details": {
"expirationDate": "2025-02-03T12:34:56.789Z",
"retentionDays": 30
}
}
}
{
"error": {
"code": "UNSUPPORTED_FORMAT",
"message": "El formato solicitado no está disponible para este documento",
"details": {
"requestedFormat": "PDF",
"availableFormats": ["XML"]
}
}
}
Description
This endpoint allows you to download individual documents from a completed extraction job. Use the job’spublicId and the specific document ID to perform the download.
Los documentos solo están disponibles para descargas durante 30 días después de completado el job.
Parameters
string
required
ID público único del job completado (ej. “JOB20250104123456789A”)
string
required
ID único del documento a descargar
string
Formato del archivo a descargar:
XML: Archivo XML original del SAT (por defecto)PDF: Representación visual del documento
boolean
Si es
true, incluye el header Content-Disposition: attachment para forzar la descarga en navegadores.
Por defecto: falseResponse
La respuesta es el contenido binario del archivo solicitado.Headers de respuesta
string
Tipo MIME del archivo:
application/xmlpara archivos XMLapplication/pdfpara archivos PDF
number
Tamaño del archivo en bytes
string
Header de descarga (solo si se especifica
download=true)Ejemplo: attachment; filename="factura_12345678-1234-1234-1234-123456789012.xml"string
UUID fiscal del documento (solo para facturas CFDI)
string
Tipo de documento descargado
string
Formato del archivo:
XML o PDFEjemplos
curl -X GET "https://api.taxo.co/v1/extractions/JOB20250104123456789A/documents/doc_550e8400/download?type=XML" \
-H "Authorization: Bearer YOUR_API_KEY" \
-o "factura.xml"
curl -X GET "https://api.taxo.co/v1/extractions/JOB20250104123456789A/documents/doc_550e8400/download?type=PDF&download=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-o "factura.pdf"
const fs = require('fs');
async function downloadDocument(extractionId, documentId, format = 'XML') {
const response = await fetch(
`https://api.taxo.co/v1/extractions/${extractionId}/documents/${documentId}/download?type=${format}`,
{
headers: {
'Authorization': `Bearer ${process.env.TAXO_API_KEY}`
}
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Obtener metadatos del documento
const contentType = response.headers.get('content-type');
const documentUuid = response.headers.get('x-document-uuid');
const fileFormat = response.headers.get('x-file-format');
// Obtener contenido como ArrayBuffer
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// Generar nombre de archivo
const extension = format.toLowerCase();
const filename = `documento_${documentUuid || documentId}.${extension}`;
// Guardar archivo
fs.writeFileSync(filename, buffer);
console.log(`Documento descargado: ${filename} (${buffer.length} bytes)`);
return {
filename,
size: buffer.length,
contentType,
documentUuid,
fileFormat,
buffer
};
}
// Uso
try {
const result = await downloadDocument(
'JOB20250104123456789A',
'doc_550e8400-e29b-41d4-a716-446655440000',
'XML'
);
console.log('Descarga exitosa:', result.filename);
} catch (error) {
console.error('Error al descargar:', error.message);
}
import requests
import os
def download_document(extraction_id: str, document_id: str, format: str = 'XML') -> dict:
"""
Descarga un documento específico de una extracción
Args:
extraction_id: ID de la extracción
document_id: ID del documento
format: Formato del archivo ('XML' o 'PDF')
Returns:
dict: Información del archivo descargado
"""
url = f'https://api.taxo.co/v1/extractions/{extraction_id}/documents/{document_id}/download'
params = {'type': format, 'download': 'true'}
headers = {
'Authorization': f'Bearer {os.environ["TAXO_API_KEY"]}'
}
response = requests.get(url, params=params, headers=headers, stream=True)
response.raise_for_status()
# Obtener metadatos del documento
content_type = response.headers.get('content-type')
document_uuid = response.headers.get('x-document-uuid')
file_format = response.headers.get('x-file-format')
content_length = int(response.headers.get('content-length', 0))
# Generar nombre de archivo
extension = format.lower()
filename = f'documento_{document_uuid or document_id}.{extension}'
# Descargar y guardar archivo
with open(filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f'Documento descargado: {filename} ({content_length} bytes)')
return {
'filename': filename,
'size': content_length,
'content_type': content_type,
'document_uuid': document_uuid,
'file_format': file_format
}
# Uso
try:
result = download_document(
'JOB20250104123456789A',
'doc_550e8400-e29b-41d4-a716-446655440000',
'XML'
)
print(f'Descarga exitosa: {result["filename"]}')
except requests.RequestException as e:
print(f'Error al descargar: {e}')
import java.io.*;
import java.nio.file.*;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
public class DocumentDownloader {
private final RestTemplate restTemplate;
private final String apiKey;
public DocumentDownloader(String apiKey) {
this.restTemplate = new RestTemplate();
this.apiKey = apiKey;
}
public DownloadResult downloadDocument(String extractionId, String documentId, String format) {
String url = String.format(
"https://api.taxo.co/v1/extractions/%s/documents/%s/download?type=%s&download=true",
extractionId, documentId, format
);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apiKey);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<byte[]> response = restTemplate.exchange(
url, HttpMethod.GET, entity, byte[].class
);
// Get metadata
String contentType = response.getHeaders().getFirst("Content-Type");
String documentUuid = response.getHeaders().getFirst("X-Document-UUID");
String fileFormat = response.getHeaders().getFirst("X-File-Format");
// Generate filename
String extension = format.toLowerCase();
String filename = String.format("documento_%s.%s",
documentUuid != null ? documentUuid : documentId, extension);
try {
// Save file
byte[] content = response.getBody();
Path path = Paths.get(filename);
Files.write(path, content);
System.out.printf("Document downloaded: %s (%d bytes)%n", filename, content.length);
return new DownloadResult(filename, content.length, contentType, documentUuid, fileFormat);
} catch (IOException e) {
throw new RuntimeException("Error saving file", e);
}
}
// Clase para el resultado
public static class DownloadResult {
public final String filename;
public final long size;
public final String contentType;
public final String documentUuid;
public final String fileFormat;
public DownloadResult(String filename, long size, String contentType,
String documentUuid, String fileFormat) {
this.filename = filename;
this.size = size;
this.contentType = contentType;
this.documentUuid = documentUuid;
this.fileFormat = fileFormat;
}
}
}
// Uso
DocumentDownloader downloader = new DocumentDownloader(System.getenv("TAXO_API_KEY"));
try {
DownloadResult result = downloader.downloadDocument(
"JOB20250104123456789A",
"doc_550e8400-e29b-41d4-a716-446655440000",
"XML"
);
System.out.println("Descarga exitosa: " + result.filename);
} catch (Exception e) {
System.err.println("Error al descargar: " + e.getMessage());
}
Descarga masiva de documentos
Para descargar múltiples documentos eficientemente:async function downloadAllDocuments(extractionId, documents, maxConcurrency = 5) {
const results = [];
const errors = [];
// Función para procesar documentos en lotes
async function processBatch(batch) {
const promises = batch.map(async (doc) => {
try {
// Descargar ambos formatos si están disponibles
const downloads = [];
for (const format of doc.availableFormats) {
const result = await downloadDocument(extractionId, doc.id, format);
downloads.push(result);
}
return { documentId: doc.id, downloads, success: true };
} catch (error) {
console.error(`Error descargando ${doc.id}:`, error.message);
errors.push({ documentId: doc.id, error: error.message });
return { documentId: doc.id, success: false, error: error.message };
}
});
return await Promise.all(promises);
}
// Procesar documentos en lotes para controlar concurrencia
for (let i = 0; i < documents.length; i += maxConcurrency) {
const batch = documents.slice(i, i + maxConcurrency);
console.log(`Procesando lote ${Math.floor(i / maxConcurrency) + 1}/${Math.ceil(documents.length / maxConcurrency)}`);
const batchResults = await processBatch(batch);
results.push(...batchResults);
// Pequeña pausa entre lotes para no sobrecargar el servidor
if (i + maxConcurrency < documents.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
console.log(`\n✅ Descarga completada:`);
console.log(` Exitosos: ${results.filter(r => r.success).length}`);
console.log(` Fallidos: ${errors.length}`);
return { results, errors };
}
// Uso con documentos de una extracción completada
const extractionStatus = await getExtractionStatus('JOB20250104123456789A');
if (extractionStatus.status === 'COMPLETED') {
const downloadResults = await downloadAllDocuments(
extractionStatus.publicId,
extractionStatus.documents,
3 // máximo 3 descargas simultáneas
);
console.log(`Documentos descargados: ${downloadResults.results.length}`);
}
import asyncio
import aiohttp
import aiofiles
from tqdm import tqdm
async def download_document_async(session, extraction_id, document_id, format):
"""Descarga asíncrona de un documento"""
url = f'https://api.taxo.co/v1/extractions/{extraction_id}/documents/{document_id}/download'
params = {'type': format}
headers = {'Authorization': f'Bearer {os.environ["TAXO_API_KEY"]}'}
async with session.get(url, params=params, headers=headers) as response:
response.raise_for_status()
document_uuid = response.headers.get('X-Document-UUID')
extension = format.lower()
filename = f'documento_{document_uuid or document_id}.{extension}'
async with aiofiles.open(filename, 'wb') as f:
async for chunk in response.content.iter_chunked(8192):
await f.write(chunk)
return {
'document_id': document_id,
'filename': filename,
'size': int(response.headers.get('Content-Length', 0)),
'format': format
}
async def download_all_documents_async(extraction_id, documents, max_concurrent=5):
"""Descarga todos los documentos con límite de concurrencia"""
# Crear lista de tareas de descarga
tasks = []
for doc in documents:
for format in doc['availableFormats']:
tasks.append((doc['id'], format))
results = []
errors = []
# Crear semáforo para limitar concurrencia
semaphore = asyncio.Semaphore(max_concurrent)
async def download_with_semaphore(session, doc_id, format):
async with semaphore:
try:
result = await download_document_async(session, extraction_id, doc_id, format)
return result
except Exception as e:
errors.append({'document_id': doc_id, 'format': format, 'error': str(e)})
return None
# Ejecutar descargas con barra de progreso
async with aiohttp.ClientSession() as session:
with tqdm(total=len(tasks), desc="Descargando documentos") as pbar:
download_tasks = [
download_with_semaphore(session, doc_id, format)
for doc_id, format in tasks
]
for coro in asyncio.as_completed(download_tasks):
result = await coro
if result:
results.append(result)
pbar.update(1)
print(f"\n✅ Descarga completada:")
print(f" Exitosos: {len(results)}")
print(f" Fallidos: {len(errors)}")
return results, errors
# Uso
async def main():
extraction_status = get_extraction_status('JOB20250104123456789A')
if extraction_status['status'] == 'COMPLETED':
results, errors = await download_all_documents_async(
extraction_status['publicId'],
extraction_status['documents'],
max_concurrent=3
)
print(f"Documentos descargados: {len(results)}")
# Ejecutar
asyncio.run(main())
Formatos disponibles por tipo de documento
Facturas (INVOICE)
Facturas (INVOICE)
- XML: Archivo CFDI original del SAT con todos los datos fiscales
- PDF: Representación visual de la factura (cuando está disponible)
Constancia de Situación Fiscal (TAX_STATUS)
Constancia de Situación Fiscal (TAX_STATUS)
- PDF: Único formato disponible para constancias fiscales
Retenciones (TAX_RETENTION)
Retenciones (TAX_RETENTION)
- XML: Archivo XML de la retención
- PDF: Representación visual (cuando está disponible)
Códigos de error
{
"error": {
"code": "DOCUMENT_NOT_FOUND",
"message": "El documento solicitado no existe o no pertenece a esta extracción",
"details": {
"extractionId": "JOB20250104123456789A",
"documentId": "doc_invalid"
}
}
}
{
"error": {
"code": "DOCUMENT_EXPIRED",
"message": "El documento ha expirado y ya no está disponible para descarga",
"details": {
"expirationDate": "2025-02-03T12:34:56.789Z",
"retentionDays": 30
}
}
}
{
"error": {
"code": "UNSUPPORTED_FORMAT",
"message": "El formato solicitado no está disponible para este documento",
"details": {
"requestedFormat": "PDF",
"availableFormats": ["XML"]
}
}
}
Mejores prácticas
Verificación de integridad: Calcula hashes MD5 o SHA256 de los archivos descargados para verificar su integridad.
Límites de descarga: Respeta los rate limits para evitar ser bloqueado. Máximo 1000 descargas por hora.
Almacenamiento local: Los documentos descargados son válidos legalmente. Asegúrate de almacenarlos de forma segura y con respaldos.
⌘I