La bandeja de entrada (inbox) es el flujo para incorporar documentos externos (PDFs de facturas de proveedores, tickets escaneados o ficheros Facturae, además de nóminas) y convertirlos automáticamente en gastos o tickets, o en nóminas, con datos pre-rellenados.

El flujo conceptual tiene cuatro pasos:

  1. Subir el archivo binario con POST /uploads y obtener un upl_<uuid>.
  2. Crear el item de bandeja con POST /inbox referenciando el upload. El sistema escanea el documento en background.
  3. Esperar (o suscribirse al webhook inbox.scanned) hasta que status pase a scanned. La extracción aparece en scan.extraction.
  4. Proponer un documento real con POST /inbox/{id}/proposeBill o proposeTicket. Devuelve un prototipo listo para enviar a POST /bills con un campo adicional fromInbox: { taskId } que vincula los dos documentos.

Para una nómina, usa POST /inbox/{id}/proposePayroll y envía después el prototipo a POST /payrolls con el mismo campo fromInbox.

Los propose* no crean nada en BD: son funciones puras que mapean los datos extraídos al shape esperado por la API de creación. Permite al cliente (o a un agente) revisar y ajustar la propuesta antes de materializarla.

En los ejemplos de esta página, los UUIDs (tas_…, upl_…, con_…) son ilustrativos. Cada empresa tiene los suyos; sustitúyelos por los identificadores reales que devuelve la API.

El prefijo es tas_, no inb_

Los items de la bandeja se identifican con prefijo tas_<uuid> porque internamente son “tareas” (task) pendientes de procesar. Las tareas existen también para otros usos internos; la bandeja es solo una vista de las que provienen de subidas, emails o sincronizaciones externas. Para la API pública, todos los inbox.id son tas_*.

Estados del item

status describe el escaneo automático:

  • pending — el item se acaba de crear, el escaneo no ha terminado. scan es null.
  • scanned — el escaneo terminó con éxito. scan y scan.extraction están poblados.
  • scan_failed — el escaneo terminó sin datos extraíbles (PDF ilegible, imagen sin texto, etc.). scan.completedAt y scan.mimeType están rellenos pero scan.extraction puede ser nulo o muy parcial.

Cómo llegan documentos a la bandeja

Un item puede llegar a la bandeja por cuatro vías. Todas generan items con el mismo schema y se procesan con la misma lógica de escaneo y propuesta; el campo source dice por cuál entró cada uno.

API (source: upload)

Sube el binario con POST /uploads y crea el item con POST /inbox referenciando el upload. Es la única vía que esta API permite generar directamente, y la que debe usar una integración o un agente que tenga el fichero en la mano. El item se crea con status: pending y el escaneo se encola al momento.

Buzón por correo (source: inbox)

Cada empresa puede activar una dirección de correo exclusiva (del tipo nombre-a1b2c3d4e5@inbox.facturadirecta.com). Cualquier correo que llegue a ella crea un item por cada adjunto (facturas de compra, tickets o nóminas en PDF, imagen o Facturae) y además guarda el cuerpo del mensaje como un PDF adicional, marcado con contentSubtype: inboxEmailBody. Es la vía pensada para que el usuario reenvíe lo que recibe de sus proveedores.

Lo que una integración o un agente debe saber:

  • Dónde está la dirección. En GET /settings/company, content.inbox.email.address. Solo se devuelve si el buzón está activo (active: true); el buzón se activa desde Ajustes › Integraciones, no por API, y requiere el módulo inbox (content.modules.inbox).
  • No se verifica el remitente. Trata la dirección como confidencial: dásela al usuario de la empresa, no la publiques en sitios de terceros.
  • Escaneo automático o manual. content.inbox.email.autoScan dice si lo recibido se escanea al llegar (planes con escaneos ilimitados). Si es false, el item queda en la bandeja con status: pending hasta que un usuario lo escanee desde la interfaz; la API no puede forzar ese escaneo.
  • Los items llegan con source: inbox y se pueden filtrar así en GET /inbox.

Google Drive (source: googleDrive)

El usuario puede enlazar desde Ajustes › Integraciones una carpeta de su Google Drive (con permiso de solo lectura). FacturaDirecta la revisa periódicamente y crea un item por cada fichero nuevo que aparece en ella, con el mismo tratamiento que un adjunto de correo. Requiere el módulo googleDrive (content.modules.googleDrive).

Lo que una integración o un agente debe saber:

  • Qué carpeta es. En GET /settings/company, content.inbox.googleDrive: linked dice si hay carpeta enlazada, folderId es su identificador en Drive y folderUrl la URL para abrirla o indicársela al usuario. El nombre de la carpeta no se guarda.
  • Si está parada. error distinto de null es el mensaje que ve el usuario cuando la sincronización se ha detenido por un error permanente (credenciales caducadas, carpeta borrada). La única solución es que el usuario vuelva a enlazar la carpeta desde la interfaz.
  • Los items llegan con source: googleDrive.

Pantalla de Tareas (source: scanner)

Subida manual desde la pantalla de Tareas de la interfaz. No interviene la API.

Adjuntos del item

content.attachments es la lista de ficheros del item. Por lo general es un único adjunto (el subido inicialmente), pero algunos orígenes adjuntan varios (por ejemplo un email con factura PDF + cuerpo HTML).

Cada adjunto tiene url S3 directa (permanente mientras el item no se borre físicamente), name, size, contentType (MIME) y opcionalmente contentSubtype:

  • facturae — el adjunto es un fichero Facturae XML. El sistema lo parsea con un extractor nativo (no LLM), por lo que la extracción es exacta y rápida.
  • inboxEmailBody — cuerpo del email recibido (cuando source es inbox).

Extracción y scan

Cuando status es scanned, content.scan contiene metadatos del escaneo y los datos estructurados:

  • scan.completedAt — cuándo terminó.
  • scan.docType — tipo de documento detectado (p. ej. IMAGE, PDF).
  • scan.mimeType — MIME del archivo.
  • scan.text — texto plano extraído. Solo aparece en GET /inbox/{id}, no en el listado (optimización de tamaño).
  • scan.extraction — datos estructurados. El shape depende del tipo de documento detectado: InboxExtractionInvoice para facturas y tickets, InboxExtractionPayroll para nóminas.

La extracción contiene campos como date, name (emisor), contact (NIF/CIF), number, total, currency, direction (RECEIVE o EMIT)… La API expone estos campos como strings sin tipar para no acoplar la API a la versión actual del extractor; nuevos campos pueden aparecer sin cambio de contrato.

Webhook inbox.scanned

Cuando un escaneo termina (cualquier status final, incluido scan_failed), se dispara el evento de webhook inbox.scanned. Suscribirse permite reaccionar en cuanto los datos están disponibles en vez de hacer polling. Ver Webhooks para los detalles del flujo.

El evento es el mismo tanto si los datos se han obtenido con el escáner como si el adjunto era un Facturae y se ha leído de forma nativa: para el consumidor el item queda procesado igual y solo cambia cómo se produjo la extracción.

Operaciones

Lista de items

GET /{companyId}/inbox devuelve los items de la bandeja.

Parámetros de consulta específicos:

  • status — filtra por estado del escaneo: pending, scanned, scan_failed.
  • source — filtra por origen: upload, inbox, googleDrive, scanner.
  • minDate, maxDate — rango por fecha de creación del item (createdAt), con los dos extremos incluidos. Cada uno admite:
    • Una fecha y hora ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ). Se compara el instante exacto, con su hora y su desfase horario si lo lleva (+02:00). Los : pueden ir sin codificar; el + se codifica como %2B, o llega como un espacio.
    • Solo la fecha (YYYY-MM-DD). Cuenta el día entero en la zona horaria de la empresa (por defecto, Europe/Madrid): minDate=2026-10-07&maxDate=2026-10-07 devuelve los items creados ese día.
  • archived — controla qué items aparecen en el resultado:
    • false (por defecto): solo activos.
    • true: solo borrados.
    • all: ambos.

Parámetros globales aceptados:

Acepta además los parámetros estándar offset, limit y el header accept-version. Ver Paginación y Autenticación.

Notas:

  • scan.text no se incluye en los items del listado para evitar cargas grandes. Para verlo, usa GET /inbox/{id}.
  • Por defecto, los items borrados se omiten. Esto es coherente con el comportamiento del DELETE /inbox/{id} (borrado recuperable, no físico).
Copy as cURL
shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://app.facturadirecta.com/api/$COMPANY_ID/inbox?status=scanned&source=upload&limit=50"

Crear item (subir documento)

POST /{companyId}/inbox crea un nuevo item de bandeja a partir de un upload previo.

Flujo de dos pasos:

  1. Subir el binario con POST /uploads (multipart) → recibes upl_<uuid>.
  2. Crear el inbox item con este endpoint, pasando el upl_<uuid> en el campo upload.

Parámetros del body:

  • upload (obligatorio) — upl_<uuid> devuelto por POST /uploads.
  • title (opcional) — título descriptivo. Si se omite, se usa el nombre del archivo del upload.
  • idempotencyKey (opcional) — clave para que el mismo documento no entre dos veces en la bandeja. Si ya existe un item activo (sin archivar) con esa clave en la empresa, se devuelve ese item con su estado actual y no se crea otro. No caduca mientras el item siga activo y no compara el fichero: con la misma clave devuelve el item existente aunque envíes otro documento. Lo habitual es usar una clave natural del documento, por ejemplo un hash del fichero.

Notas:

  • El item se crea con status: pending y se encola el escaneo en background.
  • La respuesta es el item recién creado. Suscríbete al webhook inbox.scanned o haz polling sobre status para detectar cuándo termina el escaneo.

Parámetros globales aceptados: accept-version.

Ejemplo de request JSON

Heredado del ejemplo default del openapi:

json
{
  "upload": "upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b",
  "title": "factura.pdf"
}
Copy as cURL
shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"upload":"upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b","title":"factura.pdf"}' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/inbox"

Obtener un item

GET /{companyId}/inbox/{id} devuelve el detalle de un item por su ID.

A diferencia del listado, scan.text sí se incluye (texto plano extraído del documento).

Parámetros globales aceptados: accept-version.

Copy as cURL
shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://app.facturadirecta.com/api/$COMPANY_ID/inbox/tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c"

Borrar un item

DELETE /{companyId}/inbox/{id} borra el item de forma recuperable (no lo elimina físicamente: queda con archived: true). Los items borrados:

  • No aparecen en el listado por defecto.
  • Conservan sus adjuntos (las URLs S3 siguen siendo accesibles).
  • Pueden seguir consultándose por ID si conoces el tas_*.
  • Aparecen en listados con archived=true o archived=all.

Si vinculas un inbox item a un documento real vía fromInbox, el sistema borra el item automáticamente (por defecto). Ese es el camino habitual; este endpoint cubre los casos en que quieres borrarlo manualmente sin crear un documento (p. ej. spam).

Respuesta: { result: true }.

Parámetros globales aceptados: accept-version.

Copy as cURL
shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \
  "https://app.facturadirecta.com/api/$COMPANY_ID/inbox/tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c"

Proponer factura de compra

POST /{companyId}/inbox/{id}/proposeBill toma un item escaneado y construye un prototipo de factura de compra mapeando los datos extraídos al shape de BillWrite. El endpoint no crea nada en BD: devuelve un objeto para que el cliente lo revise, lo ajuste y lo envíe a POST /bills.

Idempotente y retry-safe. Llamarlo varias veces sobre el mismo item devuelve el mismo prototipo (suponiendo que la extracción no haya cambiado).

Parámetros del body (todos opcionales):

  • contactId — si se indica, salta el matching automático y usa este contacto en el prototipo.

Respuesta:

json
{
  "content": { "...": "BillWrite — prototipo listo para POST /bills" },
  "matchedContact": null,
  "candidateContacts": [],
  "newContactProposal": { "...": "Contact — proto-contacto con datos extraídos" }
}
  • content — el prototipo de bill. Su uuid se omite intencionadamente; lo asigna POST /bills o lo eliges tú al crear. Si la factura está en una moneda distinta de la de la empresa, content.main.exchangeRate trae el tipo de cambio de la fecha de la factura (transactionDate), el mismo que tomaría la web.
  • matchedContact — contacto único encontrado por la API a partir del NIF/CIF/nombre extraídos. null si no se encontró ninguno o si hay varios candidatos.
  • candidateContacts — lista de candidatos cuando el matching encontró varios. Vacío si hay 0 o 1 match.
  • newContactProposal — proto-contacto con los datos extraídos. Útil cuando el matching no encontró candidatos: puedes crearlo con POST /contacts y enlazarlo al bill.

Flujo recomendado tras la propuesta:

  1. Revisa el content y ajústalo si quieres (cambiar líneas, impuestos, añadir notas). Si cambias la moneda o la fecha de la factura, quita exchangeRate para que POST /bills tome el de la nueva fecha.
  2. Si matchedContact es null:
    • O bien usa candidateContacts para elegir uno.
    • O bien crea uno desde newContactProposal con POST /contacts.
    • Pon el ID resultante en content.main.contact.
  3. Llama a POST /bills con { content, fromInbox: { taskId, archive } }:
    • taskId es el tas_* del item de la bandeja.
    • archive: true (por defecto) borra el item de la bandeja tras crear el bill.
    • Esto vincula el bill al inbox; se puede ver desde la interfaz.

Parámetros globales aceptados: accept-version.

Ejemplo de request JSON

Sin body (la API usa la extracción tal cual):

json
{}

Con contacto fijado:

json
{
  "contactId": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3"
}
Copy as cURL
shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{}' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/inbox/tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c/proposeBill"

Proponer ticket

POST /{companyId}/inbox/{id}/proposeTicket es equivalente a Proponer factura de compra pero construye un prototipo con subtype: "ticket" (ver Factura de compra vs ticket en la página de bills).

Útil cuando el documento escaneado es un ticket de caja (sin proveedor identificado) en vez de una factura formal.

Misma forma de respuesta y mismo flujo de uso que proposeBill. El prototipo enviado a POST /bills ya viene con subtype: "ticket".

Copy as cURL
shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{}' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/inbox/tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c/proposeTicket"

Proponer nómina

POST /{companyId}/inbox/{id}/proposePayroll es equivalente a las anteriores pero construye un prototipo de nómina (PayrollWrite). El item debe haber sido escaneado y reconocido como nómina por el extractor (scan.extraction debe ser InboxExtractionPayroll).

Misma forma de respuesta: { content, matchedContact, candidateContacts, newContactProposal }. El contacto en este caso es el empleado.

Para crear la nómina real tras revisar el prototipo, llama a POST /payrolls con { content, fromInbox: { taskId, archive } }.

Copy as cURL
shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{}' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/inbox/tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c/proposePayroll"

Vinculación con fromInbox

Al crear un bill o un payroll desde un inbox item, se incluye en el body del create el campo opcional:

json
{
  "content": { "...": "..." },
  "fromInbox": {
    "taskId": "tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c",
    "archive": true
  }
}
  • taskId — el tas_* del inbox item.
  • archive (opcional, default true) — si true, el item de la bandeja se borra automáticamente al crear el documento.

fromInbox está disponible en POST /bills y POST /payrolls. No está disponible en POST /invoices (no hay flujo “subir factura de venta a inbox” a día de hoy).

El vínculo persiste: la factura o nómina creada queda enlazada al inbox item en sus attachments, y la interfaz puede mostrar el original.

Recomendaciones

  • Suscríbete al webhook inbox.scanned en vez de hacer polling sobre el listado. El polling funciona pero gasta cuota innecesariamente.
  • Usa idempotencyKey al crear items si tu proceso puede enviar dos veces el mismo documento (jobs reencolados, reenvíos periódicos…). Te ahorra duplicados sin límite de tiempo, mientras el item no se archive. Es un mecanismo propio de la bandeja: POST /inbox no admite la cabecera Idempotency-Key del resto de operaciones. Ver Idempotencia.
  • Llama a propose* solo cuando status: scanned. Si lo llamas antes, la propuesta saldrá vacía o sin datos extraídos.
  • Para Facturae, la extracción es nativa y exacta: usa contentSubtype: "facturae" para identificarlos en el listado y saber que la propuesta será fiable sin ajuste manual.
  • fromInbox.archive: false solo si necesitas mantener el inbox activo (por ejemplo, para vincularlo a varios bills). El uso normal es borrarlo.
  • Para integraciones agentic, los propose* son ideales: solo requieren inbox:read, no bills:write ni payrolls:write. Un agente puede proponer y dejar al humano (o a otro agente con scopes superiores) el acto de crear.

Errores comunes

  • 400 — minDate o maxDate es una fecha y hora que no existe, como 2026-02-30T10:00:00Z. Ver Fechas que no existen.
  • 400 ValidationError — upload con formato inválido o que no existe.
  • 404 Not Found — tas_* no existe en la empresa.
  • 409 Conflict — propose* sobre un item con status: pending (el escaneo aún no ha terminado).
  • 409 Conflict — proposePayroll sobre un item cuya extracción no es de nómina.

Ver Errores y validaciones para el formato general.

Referencia exhaustiva

Esta página cubre los matices funcionales y los casos típicos. Para la referencia exhaustiva de todos los campos del body y la respuesta, consulta el Swagger UI o el openapi crudo.

Endpoints

MétodoPathoperationIdScopesDescripción
GET/{companyId}/inboxgetInboxItemsinbox:readLista de items en la bandeja de entrada
GET/{companyId}/inbox/{id}getInboxIteminbox:readDetalle de un item de la bandeja
POST/{companyId}/inboxcreateInboxIteminbox:writeSubir un documento a la bandeja de entrada
POST/{companyId}/inbox/{id}/proposeBillproposeBillinbox:readPropuesta de factura de compra desde un item de la bandeja
POST/{companyId}/inbox/{id}/proposePayrollproposePayrollinbox:readPropuesta de nómina desde un item de la bandeja
POST/{companyId}/inbox/{id}/proposeTicketproposeTicketinbox:readPropuesta de ticket de compra desde un item de la bandeja
DELETE/{companyId}/inbox/{id}deleteInboxIteminbox:writeArchivar un item de la bandeja

Scopes

  • inbox:read — Lectura de la bandeja de entrada de documentos pendientes de procesar.
  • inbox:write — Subida y gestión de documentos en la bandeja de entrada.

Empieza gratis y factura con flow

Únete a los más de 90.000 negocios que ya gestionan sus facturas de forma rápida y sencilla con FacturaDirecta
Empieza Gratis

Fácil de usar · Sin permanencia · Sin tarjeta de crédito