> ## Documentation Index
> Fetch the complete documentation index at: https://docs-mx.taxo.ws/llms.txt
> Use this file to discover all available pages before exploring further.

# Download Document

> Downloads a specific document from a completed job in XML or PDF format

## Description

This endpoint allows you to download individual documents from a completed extraction job. Use the job's `publicId` and the specific document ID to perform the download.

<Warning>
  Los documentos solo están disponibles para descargas durante 30 días después de completado el job.
</Warning>

## Parameters

<ParamField path="publicId" type="string" required>
  ID público único del job completado (ej. "JOB20250104123456789A")
</ParamField>

<ParamField path="documentId" type="string" required>
  ID único del documento a descargar
</ParamField>

<ParamField query="type" type="string">
  Formato del archivo a descargar:

  * `XML`: Archivo XML original del SAT (por defecto)
  * `PDF`: Representación visual del documento

  **Nota:** No todos los documentos tienen ambos formatos disponibles.
</ParamField>

<ParamField query="download" type="boolean">
  Si es `true`, incluye el header `Content-Disposition: attachment` para forzar la descarga en navegadores.
  Por defecto: `false`
</ParamField>

## Response

La respuesta es el contenido binario del archivo solicitado.

### Headers de respuesta

<ResponseField name="Content-Type" type="string">
  Tipo MIME del archivo:

  * `application/xml` para archivos XML
  * `application/pdf` para archivos PDF
</ResponseField>

<ResponseField name="Content-Length" type="number">
  Tamaño del archivo en bytes
</ResponseField>

<ResponseField name="Content-Disposition" type="string">
  Header de descarga (solo si se especifica `download=true`)

  Ejemplo: `attachment; filename="factura_12345678-1234-1234-1234-123456789012.xml"`
</ResponseField>

<ResponseField name="X-Document-UUID" type="string">
  UUID fiscal del documento (solo para facturas CFDI)
</ResponseField>

<ResponseField name="X-Document-Type" type="string">
  Tipo de documento descargado
</ResponseField>

<ResponseField name="X-File-Format" type="string">
  Formato del archivo: `XML` o `PDF`
</ResponseField>

## Ejemplos

<CodeGroup>
  ```bash cURL - Descargar XML theme={null}
  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"
  ```

  ```bash cURL - Descargar PDF theme={null}
  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"
  ```

  ```javascript Node.js theme={null}
  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);
  }
  ```

  ```python Python theme={null}
  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}')
  ```

  ```java Java theme={null}
  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());
  }
  ```
</CodeGroup>

## Descarga masiva de documentos

Para descargar múltiples documentos eficientemente:

<CodeGroup>
  ```javascript Descarga paralela con límite de concurrencia theme={null}
  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}`);
  }
  ```

  ```python Descarga con barra de progreso theme={null}
  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())
  ```
</CodeGroup>

## Formatos disponibles por tipo de documento

<AccordionGroup>
  <Accordion title="Facturas (INVOICE)" icon="file-invoice">
    * **XML**: Archivo CFDI original del SAT con todos los datos fiscales
    * **PDF**: Representación visual de la factura (cuando está disponible)
  </Accordion>

  <Accordion title="Constancia de Situación Fiscal (TAX_STATUS)" icon="certificate">
    * **PDF**: Único formato disponible para constancias fiscales
  </Accordion>

  <Accordion title="Retenciones (TAX_RETENTION)" icon="receipt">
    * **XML**: Archivo XML de la retención
    * **PDF**: Representación visual (cuando está disponible)
  </Accordion>
</AccordionGroup>

## Códigos de error

<ResponseExample>
  ```json Error 404 - Documento no encontrado theme={null}
  {
    "error": {
      "code": "DOCUMENT_NOT_FOUND",
      "message": "El documento solicitado no existe o no pertenece a esta extracción",
      "details": {
        "extractionId": "JOB20250104123456789A",
        "documentId": "doc_invalid"
      }
    }
  }
  ```

  ```json Error 410 - Documento expirado theme={null}
  {
    "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
      }
    }
  }
  ```

  ```json Error 415 - Formato no soportado theme={null}
  {
    "error": {
      "code": "UNSUPPORTED_FORMAT",
      "message": "El formato solicitado no está disponible para este documento",
      "details": {
        "requestedFormat": "PDF",
        "availableFormats": ["XML"]
      }
    }
  }
  ```
</ResponseExample>

## Mejores prácticas

<Tip>
  **Verificación de integridad**: Calcula hashes MD5 o SHA256 de los archivos descargados para verificar su integridad.
</Tip>

<Warning>
  **Límites de descarga**: Respeta los rate limits para evitar ser bloqueado. Máximo 1000 descargas por hora.
</Warning>

<Info>
  **Almacenamiento local**: Los documentos descargados son válidos legalmente. Asegúrate de almacenarlos de forma segura y con respaldos.
</Info>
