API de Transcripción

La API de Transcripción permite extraer texto imágenes/PDF mediante reconocimiento automático (ASR/OCR). Soporta el envío de uno o varios archivos y, cuando corresponde, fusiona automáticamente anverso/reverso de un mismo documento para devolver un único resultado.

Autenticación

Utiliza la cabecera unificada X-CapData-Token. El valor puede ser la API Key de un Propietario, una Agencia o el token de un Agente.

Ejemplo de Cabecera
X-CapData-Token: tu_api_key_o_token

Endpoint

POST /api/transcribe

Recibe uno o varios archivos y devuelve sus transcripciones. La petición debe ser multipart/form-data.

Parámetros (multipart/form-data)

Ejemplo: un archivo

cURL (multipart/form-data)
curl -X POST "https://capdata.es/api/transcribe" \
  -H "X-CapData-Token: TU_TOKEN" \
  -F "files=@/ruta/llamada_cliente.m4a" \
  -F "language=es"

Respuesta (200 OK)

application/json
{
  "results": [
    {
      "filename": "llamada_cliente.m4a",
      "text": "Transcripción completa de la llamada...",
      "language_detected": "es",
      "duration_seconds": 184.2,
      "merged": false
    }
  ]
}

Ejemplo: fusión anverso/reverso (dos archivos)

Si subes dos archivos que representan las dos caras de un mismo documento (p. ej. anverso/reverso), el sistema intentará fusionarlos automáticamente.

cURL (dos archivos: anverso y reverso)
curl -X POST "https://capdata.es/api/transcribe" \
  -H "X-CapData-Token: TU_TOKEN" \
  -F "files[]=@/ruta/reserva_anverso.pdf" \
  -F "files[]=@/ruta/reserva_reverso.pdf"

Respuesta (200 OK) con fusión

application/json
{
  "results": [
    {
      "filename": "reserva",
      "text": "Texto unificado de anverso y reverso en orden de lectura...",
      "language_detected": "es",
      "merged": true,
      "merged_from": ["reserva_anverso.pdf", "reserva_reverso.pdf"]
    }
  ]
}
Cómo detectamos anverso/reverso: cuando hay dos archivos cuyo nombre base coincide y contienen sufijos como _anverso/_reverso, -anverso/-reverso, _front/_back o -front/-back, el servicio los fusiona en un único resultado. El contenido se concatena en orden lógico (anverso → reverso).

Transcripción Especializada de Facturas

Este servicio automatiza la lectura de facturas, las transforma en datos JSON y gestiona el ciclo de vida del documento y del proveedor asociado.

POST /api/transcribe-invoice

Envía uno o varios archivos de factura para su procesamiento. Esta acción consume 1 token por cada archivo procesado, si es un PDF 1 token por cada página.

Cuerpo de la Petición (multipart/form-data)

La petición debe ser de tipo multipart/form-data. Los archivos deben enviarse bajo la clave files.

Ejemplo (cURL)
curl -X POST "https://capdata.es/api/transcribe-invoice" \
     -H "X-CapData-Token: tu_api_key_o_token" \
     -F "files=@/ruta/factura_A.pdf" \
     -F "files=@/ruta/factura_B.jpg"

Respuesta Exitosa (200 OK)

Devuelve un array resultados con los datos estructurados, una vista previa en base64 y una URL segura para la visualización completa del documento.

application/json
{
    "resultados": [
        {
            "source_file": "factura_A.pdf",
            "supplier_contact_id": 42,
            "supplier_invoice_id": 101,
            "data": {
                "numero_factura": "FAC-2025-001",
                "proveedor_nif": "B12345678",
                "total_factura": 121.00,
                // ...
            },
            "preview_image_base64": "data:application/pdf;base64,JVBERi0x...",
            "full_view_url": "https://capdata.es/portal/slug-cliente/serve/invoice_blob/101"
        }
    ],
    "tokens_remaining": 98
}

Visualización del Documento Original (Flujo en 2 Pasos)

La URL full_view_url devuelta debe ser llamada desde tu backend (server-to-server), añadiendo la misma cabecera de autenticación. Tu servidor recibirá el archivo binario (PDF, JPG) y deberá retransmitirlo (stream) a tu usuario final.

Este patrón de "proxy" es crucial para no exponer nunca tu API Key en el lado del cliente.


Ejemplo Práctico Interactivo

Utiliza esta herramienta para probar el flujo completo de la API de Transcripción de Facturas.

Petición de Transcripción

El siguiente código HTML y JavaScript muestra cómo crear un formulario para que el usuario seleccione una factura, y cómo enviarla a la API de CapData de forma segura.

HTML del Formulario

Crea un formulario simple con un input para el archivo, un botón, y un área para mostrar los resultados.

HTML
<!-- Contenedor para la subida de facturas -->
<div>
    <label for="invoiceFileInput">Seleccionar Factura:</label>
    <input type="file" id="invoiceFileInput" accept=".pdf,.jpg,.jpeg,.png">
    <button id="uploadButton">Transcribir Factura</button>
</div>

<!-- Área para mostrar el estado y los resultados -->
<div id="statusArea" style="margin-top: 15px;"></div>

<!-- Contenedor para la respuesta JSON (oculto por defecto) -->
<pre id="responseContainer" style="display:none;"><code class="language-json"></code></pre>

JavaScript (usando `fetch`)

Este script se encarga de escuchar el clic en el botón, construir la petición `multipart/form-data` y manejar la respuesta de la API.

JavaScript
document.addEventListener("DOMContentLoaded", () => {
    const fileInput = document.getElementById("invoiceFileInput");
    const uploadButton = document.getElementById("uploadButton");
    const statusArea = document.getElementById("statusArea");
    const responseContainer = document.getElementById("responseContainer");
    const jsonCodeBlock = responseContainer.querySelector("code");

    // IMPORTANTE: Gestiona tu API Key de forma segura.
    // Nunca la expongas directamente en el código del frontend en producción.
    const API_KEY = "TU_X-CAPDATA-TOKEN_AQUI";
    const API_ENDPOINT = "https://capdata.es/api/transcribe-invoice";

    uploadButton.addEventListener("click", async () => {
        const file = fileInput.files[0];

        if (!file) {
            statusArea.textContent = "Por favor, selecciona un archivo primero.";
            return;
        }

        // --- 1. Preparar la petición ---
        const formData = new FormData();
        formData.append("files", file); // La clave debe ser "files"

        // --- 2. Actualizar la UI y realizar la llamada ---
        statusArea.textContent = "Transcribiendo, por favor espera...";
        uploadButton.disabled = true;
        responseContainer.style.display = "none";

        try {
            const response = await fetch(API_ENDPOINT, {
                method: "POST",
                headers: {
                    "X-CapData-Token": API_KEY
                },
                body: formData
            });

            const responseData = await response.json();

            if (!response.ok) {
                // Si la API devuelve un error (4xx, 5xx), lo lanzamos
                throw new Error(responseData.error || `Error HTTP: ${response.status}`);
            }

            // --- 3. Procesar la respuesta exitosa ---
            statusArea.textContent = "¡Transcripción completada con éxito!";
            jsonCodeBlock.textContent = JSON.stringify(responseData, null, 2);
            responseContainer.style.display = "block";

            // Opcional: Resaltar la sintaxis si usas Prism.js
            if (window.Prism) {
                Prism.highlightElement(jsonCodeBlock);
            }
            
            // Aquí guardarías `responseData.resultados[0].full_view_url`
            // para usarlo en la Fase 2.

        } catch (error) {
            statusArea.textContent = `Error: ${error.message}`;
            console.error("Error en la transcripción:", error);
        } finally {
            uploadButton.disabled = false;
        }
    });
});

Fase 2: Visualización Segura (Backend)

Al hacer clic en la imagen de vista previa, se simula el siguiente paso. En una aplicación real, tu frontend no llama directamente a la full_view_url. En su lugar, llama a un endpoint en tu propio backend, el cual actúa como un "proxy" para obtener el archivo de forma segura, como se muestra en el siguiente ejemplo de código.

Python (Flask) - Ejemplo de Backend Proxy
import requests
from flask import Blueprint, Response, abort

erp_bp = Blueprint("erp_routes", __name__)

# Esta función es un placeholder: debes implementar cómo tu ERP
# recupera la URL y la API Key que guardó tras la Fase 1.
def get_capdata_info_from_your_db(invoice_id):
    # Lógica para buscar en tu base de datos...
    # return {"full_view_url": "...", "api_key": "..."}
    pass

@erp_bp.route("/ver-factura-capdata/<int:capdata_invoice_id>")
def ver_factura_capdata(capdata_invoice_id):
    """
    Este endpoint actúa como un proxy para obtener de forma segura
    un archivo de CapData y servirlo al usuario final.
    """
    # 1. Obtener la URL y API Key desde tu base de datos
    capdata_info = get_capdata_info_from_your_db(capdata_invoice_id)
    if not capdata_info:
        abort(404, "Información de la factura no encontrada.")

    capdata_url = capdata_info["full_view_url"]
    api_key = capdata_info["api_key"]

    headers = {"X-CapData-Token": api_key}

    try:
        # 2. Hacer la petición a CapData en modo "stream"
        response_from_capdata = requests.get(
            capdata_url, headers=headers, stream=True, timeout=60
        )
        response_from_capdata.raise_for_status()

        # 3. Retransmitir la respuesta (el archivo) al usuario final
        return Response(
            response_from_capdata.iter_content(chunk_size=8192),
            content_type=response_from_capdata.headers["Content-Type"]
        )

    except requests.exceptions.RequestException as e:
        status_code = e.response.status_code if e.response is not None else 503
        abort(status_code, "No se pudo obtener el documento desde CapData.")

Formatos y límites


Consumo de tokens y comportamiento


Errores y respuestas de fallo

402 Payment Required (tokens insuficientes)

Se devuelve cuando el actor no dispone de saldo suficiente para procesar los archivos solicitados. La respuesta incluye el saldo actual y los tokens necesarios para completar la operación.

application/json
{
  "error": "Tokens insuficientes.",
  "tokens_remaining": 0,
  "tokens_needed": 2
}

400 Bad Request (validación)

Se devuelve cuando la petición no cumple las validaciones (por ejemplo: no se envió files, el tipo de archivo no es válido, el tamaño supera el límite o hay combinaciones inválidas).

application/json
{
  "error": "Validación fallida.",
  "details": [
    {"field": "files", "message": "Debes adjuntar al menos un archivo."},
    {"field": "files[0]", "message": "Formato no admitido (.exe)."},
    {"field": "files[1]", "message": "El tamaño supera el máximo permitido."}
  ]
}

500 Internal Server Error

Error inesperado en el procesamiento del archivo o en un proveedor externo. Reintenta pasado unos minutos.

application/json
{
  "error": "Error interno de servidor. Inténtalo de nuevo más tarde."
}
Recomendación: maneja explícitamente los estados 402, 400 y 500 en el cliente. Muestra mensajes claros y, en caso de 402, ofrece opciones para recargar saldo o reducir el número de archivos.