Factuarea APIDevelopers

Escáner de documentos

Sube facturas de proveedores y recibos, revisa los datos extraídos y crea borradores de gasto vinculados.

El escáner conserva juntos el original, las evidencias de extracción, el historial de revisión y el vínculo con el gasto. Crea borradores de gasto justificados por facturas del proveedor o facturas simplificadas aptas; no emite facturas ni registra pagos. Revisa los datos fiscales antes de convertir. Un tipo impositivo desconocido no equivale a un cero verificado.

Acceso y subida

La empresa debe tener acceso al escáner/OCR. Usa purchase_invoices:read para consultar, purchase_invoices:write para subir, revisar y convertir, y purchase_invoices:delete para archivar. La empresa vinculada sigue siendo el límite de autorización para API keys, OAuth y MCP.

Sube hasta 20 archivos PDF, JPEG o PNG: 20 MiB por archivo, 100 MiB por petición, 25 páginas por documento (POST /v1/purchase_scans). Un cuerpo de petición que supera el límite del borde se rechaza antes de llegar a la aplicación con 413 payload_too_large. Dentro de un cuerpo admitido, un archivo demasiado grande o que supera el límite del lote devuelve un 413 de la aplicación: scan_file_too_large, scan_batch_too_large o scan_page_limit_exceeded. Los archivos pasan controles de seguridad y se almacenan cifrados. Una respuesta 202 contiene data.accepted y data.rejected; indica admisión, no que el OCR haya terminado. Si se rechazan todos los archivos, la respuesta 422 conserva el resultado del lote. Comprueba cada elemento. Reintenta el mismo lote con el mismo orden de archivos, payload e Idempotency-Key.

curl https://api.factuarea.com/v1/purchase_scans \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Idempotency-Key: scanner-upload-batch-001" \
  -F "files[]=@invoice.pdf" \
  -F "files[]=@receipt.jpg"

Empieza siempre con una clave fact_test_: el sandbox ejecuta el mismo contrato con una extracción simulada determinista, sin proveedor de OCR y sin consumir cuota. Pasa a fact_live_ solo cuando el flujo esté validado.

Procesamiento y revisión

Consulta GET /v1/purchase_scans/{id}. received espera procesamiento; queued y processing están en curso. Si el escaneo automático está desactivado, inicia explícitamente el documento recibido mediante POST /v1/purchase_scans/{id}/retry. needs_review expone campos extraídos, confianza, evidencias de página/zona, incidencias y review_fields (el catálogo de nombres de campo que aún está pendiente de revisión). failed admite reintento solo si es recuperable. duplicate requiere una decisión. Respeta available_actions y los indicadores de capacidad en lugar de deducirlos únicamente del estado. Archiva un escaneo que no se esté procesando con DELETE /v1/purchase_scans/{id}; es reversible con POST /v1/purchase_scans/{id}/restore — comprueba available_actions de nuevo después, ya que restaurar no recupera un original ya purgado.

Usa PUT /v1/purchase_scans/{id}/review con la expected_version observada. Envía solo las correcciones revisadas: los campos y líneas omitidos se conservan; { "value": null } borra un campo. Los identificadores públicos son UUID v7 en id, supplier_id y line_id. Los decimales son cadenas. Un parche de línea usa update, add sin line_id (lo asigna el servidor) o remove con un line_id existente.

Cada línea puede conservar hasta cinco datos de compra tal como aparecen impresos en el original: supplier_sku, unit_code, price_unit_code, package_quantity y measured_base_quantity. Son opcionales y nunca se infieren: un valor ausente queda null sin incidencia y nunca bloquea la conversión, y package_quantity/measured_base_quantity nunca se convierten por sí solos en cantidad facturada ni en una conversión de unidades.

lines[].catalog_selection es una decisión aparte, solo humana: el OCR nunca la escribe. En el parche de revisión, omitir catalog_selection conserva la selección guardada, null desvincula la línea (su evidencia de compra no se toca) y un objeto la sustituye. Al guardar, solo se verifican contra el catálogo de la empresa que hace la petición las selecciones nuevas o cambiadas; un producto, variante, presentación u oferta de proveedor de otra empresa —o que no existe— responde 422 sin guardar nada ni cambiar la versión:

// PUT /v1/purchase_scans/{id}/review con un producto de otra empresa
{"expected_version": 7, "lines": [{"line_id": "019c...", "fields": {}, "catalog_selection": {"product_id": "<producto de otra empresa>"}}]}
// → 422, error.code = "validation_failed", error.param = "lines.0.catalog_selection.product_id"

Una referencia desactivada se puede seguir vinculando al guardar. La conversión vuelve a resolver la selección contra la empresa del borrador; si ya no es válida, responde 422 y no crea la factura — el escaneo sigue revisable para corregir o desvincular la línea.

Conversión y conflictos

Tras guardar, usa la nueva versión para POST /v1/purchase_scans/{id}/convert. La conversión crea atómicamente un borrador de gasto y vincula su original — es irreversible, y repetirla sobre un escaneo ya convertido devuelve 409 purchase_scan_already_converted. Consulta purchase_invoice_id para continuar en Gastos. Se requieren EUR y datos fiscales resueltos; los gastos simplificados y las facturas completas de proveedores siguen sus respectivas reglas de validación. La API pública y MCP requieren un proveedor existente cuando sea obligatorio. Crear proveedores o autorizar excepciones de duplicado requiere un usuario interactivo autorizado en la app. external_id lo defines tú en otros recursos, salvo el prefijo purchase-scan:, reservado para el puente interno y rechazado con 422 parameter_invalid_value si lo envías.

Una versión obsoleta devuelve 409 stale_scan_version: recarga y concilia los cambios antes de reintentar. Un error 422 de conversión expone los campos que debes corregir en error.details.field_errors. Reutiliza la clave de idempotencia solo para la misma mutación; después de cambiar la revisión, usa una clave nueva. El escáner distingue un duplicado exacto (mismo archivo) de un duplicado fiscal (mismo proveedor y número de documento); la resolución pública de duplicados (POST /v1/purchase_scans/{id}/duplicate_resolution, irreversible) admite link_existing con purchase_invoice_id o archive — la API v1 no autoriza forzar un duplicado exacto; esa excepción requiere un usuario interactivo autorizado en la app. Archivar es reversible; restaurar no recupera un original ya purgado por retención.

Originales, correo y clientes

Descarga los originales conservados mediante GET /v1/purchase_scans/{id}/source como datos binarios; requiere la capacidad export sobre el escaneo, o 403 forbidden_action. Una API key cuyo creador ya no es miembro de la empresa se rechaza igual, tanto en esta descarga como en cualquier escritura.

GET /v1/purchase_scan_emails lista remitente, asunto y adjuntos aceptados/rechazados; las entradas aceptadas enlazan con los identificadores de escaneo. La app configura el buzón del escáner: una allowlist de direcciones o dominios remitentes, y verificación SPF/DKIM/DMARC — solo DMARC pass admite la ingesta, todo lo demás queda parked o rejected con un reason, y el veredicto bruto se publica como sender_authentication. La recepción desactivada o sin configurar se muestra explícitamente. Todo correo admitido queda en needs_review: el buzón nunca crea automáticamente un proveedor ni un borrador, ni siquiera con un remitente en la allowlist.

El SDK TypeScript expone purchaseScans y purchaseScanEmails; las respuestas binarias ofrecen toBuffer(). La CLI admite repetir --file-files para subir un lote. Ambos conservan los errores por archivo y admiten claves de idempotencia explícitas. Consulta el SDK, la CLI y el catálogo MCP.

MCP utiliza el mismo contrato de revisión, versión y conversión. La subida y descarga binaria siguen siendo operaciones REST. En el asistente web, las tools del escáner consultan datos y navegan a la revisión humana. Las fotos/PDF del WhatsApp vinculado entran en este mismo escáner persistente; el canal puede proponer una conversión versionada a borrador para confirmación explícita o devolver un enlace de revisión si quedan procesamiento o correcciones pendientes.

Consulta GET /v1/purchase_invoices/expense_categories (o MCP list_purchase_invoice_expense_categories) y usa un id devuelto para expense_category. Las categorías pertenecen a la empresa autenticada; se rechazan nombres e identificadores de otras empresas.

Cuota mensual y diaria

Empresario incluye 100 escaneos al mes y 10 al día por empresa; Enterprise no tiene límite de escaneos del plan. Todos los usuarios de la empresa comparten ambas cuotas. Un escaneo guardado con deferred_reason=ocr_daily_quota_reached y deferred_until espera al reinicio de la cuota diaria. El procesamiento se reanuda automáticamente: conserva su ID y no vuelvas a subirlo ni llames repetidamente a retry.

El detalle limita attempts y audit a las últimas 50 entradas; utiliza attempts_total y audit_total para consultar los recuentos completos.

El consumo de OCR se mide cada mes, por empresa, compartido por todos los canales del escáner (app, v1, MCP, buzón) y por el asistente de WhatsApp. Una vez agotada, iniciar una nueva extracción (subida o reintento) responde 429 ocr_company_quota_exceeded con Retry-After contando los segundos hasta que la cuota se reinicia el día 1 del mes siguiente. Un archivo ya admitido y en cola no se rechaza retroactivamente.

Filtros y estadísticas

Los filtros de GET /v1/purchase_scans se combinan con AND entre filtros y con OR entre los valores separados por comas de un mismo filtro: filter[document_kind] (invoice, simplified_qualified, ticket, delivery_note, other, undetermined), filter[file_kind] (pdf/image), filter[supplier_link_state], filter[has_issues] (sobre issue_count > 0), filter[sender], filter[source] y filter[supplier_id] (hasta 20 valores cada uno). Toda referencia se comprueba contra tu empresa antes de ejecutar la consulta; una que no le pertenezca responde 422 sin revelar si existe en otra empresa:

GET /v1/purchase_scans?filter[document_kind]=ticket,invoice&filter[has_issues]=true&filter[sender]=@proveedor.example

issue_count en cada fila es el recuento de campos aún pendientes de revisar —el mismo número que mostraría la pantalla de revisión al abrirla, sin ediciones—, no el recuento bruto de códigos de incidencia del escaneo. uploaded_by es exclusivo de la SPA y queda excluido de v1 y MCP, que solo publican uploaded_by.name.

GET /v1/purchase_scans/stats admite scope=inbox (por defecto) o scope=history y devuelve recuentos por estado, origen, tipo de documento, tipo de archivo, estado del vínculo con el proveedor e incidencias bajo facets.

En esta página

¿Te echamos una mano?Contactar con soporte