# FacturaDirecta para desarrolladores > FacturaDirecta es un programa online de facturación, impuestos y contabilidad para autónomos y pequeñas empresas en España. Se conecta con asistentes de IA mediante un servidor MCP, con la terminal y los agentes de código mediante un CLI, y con cualquier software mediante una API REST y webhooks. Todo está disponible en todos los planes, también en el Gratis. - API REST v1.0.10: 187 operaciones. URL base: https://app.facturadirecta.com/api. OpenAPI: https://app.facturadirecta.com/openapi.json - Webhooks: 41 eventos. AsyncAPI: https://app.facturadirecta.com/asyncapi.json - Servidor MCP v0.4.3: https://mcp.facturadirecta.com (OAuth 2.1, Streamable HTTP), 35 herramientas. Catálogo en JSON: https://mcp.facturadirecta.com/tools.json - CLI v0.17.0: `curl -fsSL https://raw.githubusercontent.com/facturadirecta/cli/main/install.sh | sh`. Comandos en JSON: https://github.com/facturadirecta/cli/releases/latest/download/commands.json - Cada página de esta documentación tiene su versión Markdown en `index.md`. ## Conecta tu IA (servidor MCP) - [Servidor MCP](https://www.facturadirecta.com/desarrolladores/mcp/index.md): Conecta Claude, ChatGPT, Codex, Cursor o cualquier asistente compatible con MCP a tu cuenta de FacturaDirecta con una sola URL. Consulta facturas, gastos y contabilidad en lenguaje natural. - [Herramientas del servidor MCP](https://www.facturadirecta.com/desarrolladores/mcp/herramientas/index.md): Todas las herramientas del servidor MCP de FacturaDirecta: qué hace cada una, qué acciones expone, si solo consulta o modifica datos y qué permisos necesita. ## Terminal y agentes de código (CLI) - [CLI](https://www.facturadirecta.com/desarrolladores/cli/index.md): Instala el CLI de FacturaDirecta para consultar y automatizar facturas, gastos, contactos y contabilidad desde la terminal, en scripts o con agentes de código como Claude Code y Codex. - [Comandos del CLI](https://www.facturadirecta.com/desarrolladores/cli/comandos/index.md): Todos los comandos del CLI de FacturaDirecta con sus opciones: facturas, presupuestos, gastos, contactos, productos, contabilidad, webhooks y más. ## API REST - [API REST](https://www.facturadirecta.com/desarrolladores/api/index.md): API REST de FacturaDirecta: guías de autenticación, paginación, errores e idempotencia, y referencia de cada recurso con ejemplos listos para copiar. - [Explorador de la API](https://www.facturadirecta.com/desarrolladores/api/explorador/): Explora la especificación OpenAPI de FacturaDirecta y prueba cada operación desde el navegador con tu usuario o tu API key. - [Especificación OpenAPI](https://app.facturadirecta.com/openapi.json): contrato completo de la API en OpenAPI 3.0. ## Guías de la API - [API Pública de FacturaDirecta](https://www.facturadirecta.com/desarrolladores/api/guias/overview/index.md): Conecta tu software con FacturaDirecta mediante la API REST pública. Accede a contactos, productos, facturas, presupuestos, albaranes, gastos, bancos, webhooks y más. - [Inicio rápido con la API](https://www.facturadirecta.com/desarrolladores/api/guias/quickstart/index.md): Haz tu primera llamada a la API de FacturaDirecta en minutos. Guía paso a paso con ejemplos de cURL. - [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/index.md): La API pública de FacturaDirecta soporta dos métodos de autenticación, OAuth2 y API key. - [Paginación y filtros estándar](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/index.md): La API pública usa tres patrones de listado distintos, no uno. Cada operación que devuelve una lista escoge el patrón que mejor encaja con el recurso. - [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/index.md): Las respuestas de error de la API pública siguen siempre la misma forma base, independientemente del recurso. - [Idempotencia y reintentos seguros](https://www.facturadirecta.com/desarrolladores/api/guias/idempotency/index.md): Cuando una petición que modifica datos termina en timeout o se corta la conexión, no sabes si el servidor llegó a ejecutarla. - [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/index.md): Cada vez que se crea, modifica, elimina o recupera un documento, FacturaDirecta guarda una copia completa de cómo queda. - [Catálogo de impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/index.md): Esta guía trata los tipos impositivos que se aplican a las líneas de los documentos (IVA, IRPF, IGIC…). - [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/index.md): Esta guía cubre cómo se integra VeriFactu con la API pública de FacturaDirecta. - [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/index.md): Esta guía cubre cómo se integra TicketBAI con la API pública de FacturaDirecta. - [Facturas rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/index.md): Una factura rectificativa corrige una factura emitida previamente. - [Facturas sustitutivas (F3)](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-sustitutivas/index.md): Una factura sustitutiva es una factura completa que reemplaza a un conjunto de facturas simplificadas previamente emitidas. - [Flujo de pedidos y órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/guias/orders-flow/index.md): El pedido de cliente (clientOrder) registra un encargo en firme de un cliente antes de su entrega y facturación. ## Referencia de la API: Ventas - [Facturas de venta](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/index.md): Una factura de venta (invoice) es el documento fiscal que emite la empresa para cobrar a sus clientes. (16 operaciones) - [Facturas de venta recurrentes](https://www.facturadirecta.com/desarrolladores/api/referencia/recurring/index.md): Una factura recurrente es una plantilla de factura programada: cuando llega el momento configurado, FacturaDirecta genera automáticamente la factura real a partir de la plantilla. (10 operaciones) - [Presupuestos](https://www.facturadirecta.com/desarrolladores/api/referencia/estimates/index.md): Un presupuesto es una propuesta económica que envías a un cliente antes de emitir la factura correspondiente. (13 operaciones) - [Albaranes](https://www.facturadirecta.com/desarrolladores/api/referencia/delivery-notes/index.md): Un albarán documenta la entrega de bienes o servicios a un cliente. (13 operaciones) - [Pedidos](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/index.md): Un pedido de cliente registra un encargo en firme de un cliente antes de su entrega y facturación. (13 operaciones) ## Referencia de la API: Compras y gastos - [Facturas de compra y tickets](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/index.md): Una factura de compra representa un gasto: una factura que has recibido de un proveedor, o un ticket de caja (con o sin identificación fiscal del proveedor). (12 operaciones) - [Órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/index.md): Una orden de compra registra un encargo en firme a un proveedor antes de la recepción de la mercancía y su facturación. (13 operaciones) - [Bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/index.md): 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… (7 operaciones) - [Uploads (subida de archivos)](https://www.facturadirecta.com/desarrolladores/api/referencia/uploads/index.md): El recurso uploads permite subir archivos binarios a FacturaDirecta para luego vincularlos como adjuntos a documentos (facturas, gastos, presupuestos, albaranes, nóminas, facturas recurrentes) o… (2 operaciones) ## Referencia de la API: Contactos y productos - [Contactos](https://www.facturadirecta.com/desarrolladores/api/referencia/contacts/index.md): Un contacto representa una persona física o empresa con la que mantienes una relación contable: clientes a los que facturas, proveedores de los que recibes gastos y empleados a los que pagas nóminas. (8 operaciones) - [Productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/index.md): Un producto es un concepto facturable preconfigurado que se reutiliza en líneas de documentos (facturas, presupuestos, albaranes, facturas de compra). (10 operaciones) - [Campos personalizados](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/index.md): El recurso customFields permite descubrir y gestionar las definiciones de campos personalizados de una empresa. (6 operaciones) ## Referencia de la API: Tesorería - [Bancos y cuentas de tesorería](https://www.facturadirecta.com/desarrolladores/api/referencia/banks/index.md): Un banco (bank) en FacturaDirecta es una cuenta de tesorería de la empresa: una cuenta corriente con IBAN, una cuenta de tarjeta o una caja en efectivo. (4 operaciones) - [Extractos bancarios](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/index.md): Un extracto bancario (statement) es un movimiento individual de una cuenta bancaria sincronizada con FacturaDirecta. (5 operaciones) - [Métodos de pago](https://www.facturadirecta.com/desarrolladores/api/referencia/payment-methods/index.md): Un método de pago describe cómo se cobra o se paga un documento. (7 operaciones) ## Referencia de la API: Contabilidad, impuestos y nóminas - [Diario contable](https://www.facturadirecta.com/desarrolladores/api/referencia/journal/index.md): El diario contable es la lista cronológica de apuntes (líneas de asiento) generados por todos los documentos contabilizables de la empresa: facturas de venta, facturas de compra, nóminas, asientos… (1 operaciones) - [Informes](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/index.md): Los informes devuelven, ya calculadas, las mismas cifras que los informes de la web. Son de solo lectura y se generan en el momento a partir de la contabilidad de la empresa. (5 operaciones) - [Declaraciones de impuestos](https://www.facturadirecta.com/desarrolladores/api/referencia/tax-declarations/index.md): Una declaración de impuestos (taxDeclaration) es un modelo de impuestos que el usuario ha generado y guardado en FacturaDirecta desde la sección Impuestos: un 303 trimestral, un 347 anual, un 130…… (5 operaciones) - [Nóminas](https://www.facturadirecta.com/desarrolladores/api/referencia/payrolls/index.md): Una nómina (payroll) representa la retribución mensual a un empleado: salario bruto, deducciones, retenciones de IRPF y aportaciones de Seguridad Social, tanto del trabajador como de la empresa. (12 operaciones) ## Referencia de la API: Cuenta y configuración - [Configuración de la empresa](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/index.md): Bajo /settings/... vive la configuración de la empresa que la API pública expone como solo lectura: la ficha de la empresa y su forma de trabajar, los impuestos disponibles, las series de… (5 operaciones) - [Perfil de usuario](https://www.facturadirecta.com/desarrolladores/api/referencia/profile/index.md): El recurso profile devuelve los datos básicos del usuario autenticado y las empresas disponibles para la credencial actual. (1 operaciones) - [API keys](https://www.facturadirecta.com/desarrolladores/api/referencia/api-keys/index.md): Una API key es una credencial por empresa para autenticar integraciones de máquina a máquina. (5 operaciones) - [Actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/index.md): El registro de actividad es el historial de lo que pasa en la empresa: altas, modificaciones y eliminaciones de documentos, envíos por email y su entrega, accesos de los usuarios, sincronizaciones… (1 operaciones) ## Referencia de la API: Búsqueda y análisis - [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/index.md): La búsqueda global localiza en una sola llamada cualquier elemento de la empresa a partir de un texto: un cliente por su nombre o su NIF, una factura por su número, un producto por su referencia o… (1 operaciones) - [Consulta analítica](https://www.facturadirecta.com/desarrolladores/api/referencia/query/index.md): La consulta analítica agrega los datos de la empresa en una sola llamada: eliges una fuente, las medidas que quieres sumar, las dimensiones por las que agrupar, un periodo y unos filtros, y la API… (1 operaciones) ## Webhooks - [Webhooks](https://www.facturadirecta.com/desarrolladores/webhooks/index.md): Los webhooks notifican en tiempo real cuando ocurren cambios en los recursos de tu empresa. - [Especificación AsyncAPI](https://app.facturadirecta.com/asyncapi.json): los 41 eventos y sus payloads. ## Optional - [llms-full.txt](https://www.facturadirecta.com/desarrolladores/llms-full.txt): toda la documentación de esta sección en un solo fichero. - [FacturaDirecta](https://www.facturadirecta.com/llms.txt): el producto, los planes y los precios. --- # Servidor MCP > Conecta Claude, ChatGPT, Codex, Cursor o cualquier asistente compatible con MCP a tu cuenta de FacturaDirecta con una sola URL. Consulta facturas, gastos y contabilidad en lenguaje natural. El servidor MCP de FacturaDirecta conecta tu asistente de IA con tu cuenta. Una vez conectado, puedes preguntarle por tus facturas, presupuestos, gastos, contactos, productos, contabilidad o impuestos en lenguaje natural, y él consulta tus datos reales para responderte. También puedes pedirle que cree facturas, registre cobros o envíe documentos: te enseña lo que va a hacer y te pide confirmación antes. MCP (Model Context Protocol) es el estándar abierto con el que los asistentes de IA se conectan a otras aplicaciones. Nuestro servidor es remoto: no hay nada que instalar. ## Antes de empezar - Una cuenta de FacturaDirecta. Sirve cualquier plan, también el Gratis. - Un asistente compatible con servidores MCP remotos: Claude, ChatGPT, Codex, Cursor, VS Code y muchos otros. - Nada más. No hacen falta API keys ni client ID: inicias sesión con tu usuario de FacturaDirecta y eliges qué permisos das. ## Conecta tu asistente Todos los clientes usan la misma URL: `https://mcp.facturadirecta.com`. ### Claude En claude.ai, la aplicación de escritorio y la móvil: 1. Abre **Ajustes → Conectores** y pulsa **Añadir conector personalizado** (o usa el botón *Añadir a Claude* de esta página). 2. Pega la URL `https://mcp.facturadirecta.com`. No hay que rellenar client ID ni secreto. 3. Pulsa **Añadir** y después **Conectar**. FacturaDirecta te muestra qué aplicación pide acceso y con qué permisos. 4. Inicia sesión con tu usuario de FacturaDirecta y autoriza el acceso. Listo. En los planes Team y Enterprise de Claude puede que tenga que añadir el conector un administrador de la organización. ### Claude Code ```shell claude mcp add --transport http facturadirecta https://mcp.facturadirecta.com ``` Después, dentro de Claude Code, escribe `/mcp`, elige `facturadirecta` e inicia sesión. ### ChatGPT 1. En **Ajustes → Seguridad e inicio de sesión**, activa el **modo desarrollador**. 2. En **Ajustes → Plugins**, pulsa **+** y pega la URL `https://mcp.facturadirecta.com`. 3. Inicia sesión con tu usuario de FacturaDirecta y autoriza el acceso. Disponible en los planes de ChatGPT que permiten conectores personalizados (Plus, Pro, Business, Enterprise y Edu). ### Codex Codex CLI, la extensión de IDE y la aplicación de escritorio de ChatGPT comparten configuración: ```shell codex mcp add facturadirecta --url https://mcp.facturadirecta.com codex mcp login facturadirecta ``` ### Cursor, VS Code y otros clientes Añade un servidor MCP remoto con la URL. El cliente se registra solo y te pide iniciar sesión. En Cursor (`~/.cursor/mcp.json`): ```json { "mcpServers": { "facturadirecta": { "url": "https://mcp.facturadirecta.com" } } } ``` En VS Code (`.vscode/mcp.json`): ```json { "servers": { "facturadirecta": { "type": "http", "url": "https://mcp.facturadirecta.com" } } } ``` ## Tú decides qué puede hacer - **Actúa con tu usuario.** El asistente ve las empresas y los datos a los que tú tienes acceso en FacturaDirecta, nunca más. - **Permisos explícitos.** Al conectar, FacturaDirecta te enseña qué aplicación pide acceso, a dónde vuelve la autorización y qué permisos solicita. - **Consulta o modificación, siempre declarado.** Cada herramienta indica si solo consulta datos o si puede cambiarlos. Los asistentes piden tu confirmación antes de usar las que modifican algo. - **Varias empresas.** Si trabajas con más de una, el asistente te preguntará con cuál hacerlo; el servidor no guarda ninguna empresa activa. - **Desconexión.** Puedes desconectarlo cuando quieras desde los ajustes de conectores de tu asistente. ## Tratamiento de datos - El servidor no guarda tus datos: consulta FacturaDirecta en el momento y devuelve el resultado a tu asistente. - No lee tus conversaciones: solo recibe los parámetros de cada consulta. - Por seguridad registramos qué herramienta se usa y sus filtros (fechas, estados, paginación). Nunca registramos tus credenciales, textos libres ni importes. - Más detalles en la [política de privacidad](https://www.facturadirecta.com/legal/privacidad/). ## Problemas frecuentes **El asistente no ve ninguna herramienta.** Comprueba que completaste el inicio de sesión y autorizaste el acceso. Si no, desconecta el conector y vuelve a conectarlo. **Trabaja con la empresa que no es.** Dile en la conversación con qué empresa quieres trabajar; también puedes preguntarle qué empresas ve. **Mi cliente solo admite tokens fijos.** Algunos diálogos de "MCP personalizado" solo aceptan cabeceras o tokens estáticos y no OAuth. En ese caso usa la línea de comandos del cliente (por ejemplo `codex mcp add`) o el [CLI de FacturaDirecta](https://www.facturadirecta.com/desarrolladores/cli/) con una API key. ## Detalles técnicos - **URL:** `https://mcp.facturadirecta.com` (también responde en `/mcp`). - **Transporte:** Streamable HTTP sin estado. Compatible con la especificación MCP 2026-07-28 y con los clientes que aún usan las versiones de 2025. - **Autorización:** OAuth 2.1 con PKCE S256, registro dinámico de clientes y documentos de metadatos de cliente (CIMD). Metadatos del recurso en [`/.well-known/oauth-protected-resource`](https://mcp.facturadirecta.com/.well-known/oauth-protected-resource). - **Catálogo en JSON:** [`https://mcp.facturadirecta.com/tools.json`](https://mcp.facturadirecta.com/tools.json), sin autenticación: cada herramienta con su resumen, su estado, sus permisos y su esquema de entrada. - **Herramientas:** una de consulta por área (facturas, presupuestos, compras, bancos, impuestos…) y otra de escritura en las que admiten cambios, más la búsqueda, la consulta analítica, la lectura de documentos y la subida de ficheros. Cada herramienta de área expone sus acciones, cada una con su operación de la API: consulta la [referencia de herramientas](https://www.facturadirecta.com/desarrolladores/mcp/herramientas/). --- # Herramientas del servidor MCP > Todas las herramientas del servidor MCP de FacturaDirecta: qué hace cada una, qué acciones expone, si solo consulta o modifica datos y qué permisos necesita. Estas son las herramientas que tu asistente ve al conectar el servidor MCP de FacturaDirecta. Están agrupadas por área: una herramienta de consulta para facturas, presupuestos, compras, bancos, impuestos y el resto, y otra de escritura en las áreas que admiten cambios. Además están la búsqueda, la consulta analítica (sumar, contar y clasificar en una sola llamada), la lectura de documentos y la subida de ficheros. Cada herramienta de área expone sus acciones, cada una con su operación de la API, y el asistente pide el detalle de una acción solo cuando la va a usar. Las herramientas de escritura piden al asistente que te enseñe lo que va a crear, modificar o enviar, y que espere tu confirmación. La lista exacta que ve tu asistente depende de los permisos que concedas al conectar. Esta página se actualiza sola con cada versión del servidor. Servidor: https://mcp.facturadirecta.com · versión 0.4.3 · OAuth 2.1 · Streamable HTTP ## `whoami` — Identidad y empresas del usuario Solo consulta datos. Devuelve el email del usuario autenticado y las empresas a las que tiene acceso, con su identificador (companyId) y nombre. Úsala al inicio de la conversación. No existe empresa activa en el servidor: si el usuario tiene más de una empresa, pregúntale con cuál trabajar e indica su companyId en cada llamada posterior; con una sola empresa no hace falta indicarlo. Para saber cómo trabaja la empresa (moneda, borrador o definitivo, bloqueo contable, módulos del plan, buzón de la bandeja) llama después a get_company_settings de facturadirecta_settings. ## `search` — Buscar en FacturaDirecta Solo consulta datos. Permisos: `bills:read`, `clientOrders:read`, `contacts:read`, `deliveryNotes:read`, `estimates:read`, `invoices:read`, `payrolls:read`, `products:read`, `purchaseOrders:read`, `recurring:read`. Busca un texto en los datos de FacturaDirecta del usuario, con el mismo criterio que la búsqueda rápida de la aplicación, y devuelve resultados citables con su id, título y URL. Cubre contactos, facturas de venta, presupuestos, albaranes, pedidos, órdenes de compra, facturas de compra y tickets, nóminas, productos y facturas recurrentes, y busca por nombre, NIF o email del contacto, número o referencia del documento y texto de sus líneas y notas (todas las palabras, sin distinguir acentos ni mayúsculas; 'F-2026-12' encuentra 'F-2026-0012'). Úsala cuando el usuario mencione un cliente, proveedor, producto, concepto o número de documento y necesites localizarlo; después usa fetch con el id para leer el documento completo. Devuelve hasta 20 resultados repartidos entre tipos; para consultas por fechas, estados o importes, o para recorrer todos los resultados de un tipo, usa las acciones list_* con sus filtros (admiten el mismo parámetro search). Sin companyId busca en todas las empresas del usuario. | Parámetro | Tipo | Obligatorio | Descripción | |---|---|---|---| | `query` | `string` | sí | Texto a buscar (al menos dos caracteres): nombre, NIF o email de un contacto, número o referencia de un documento, concepto de una línea, nombre o referencia de un producto… | | `companyId` | `string` | no | Empresa sobre la que operar (ver whoami). Obligatorio si el usuario tiene más de una empresa; con una sola se resuelve automáticamente. | ## `fetch` — Leer un documento de FacturaDirecta Solo consulta datos. Permisos: `bills:read`, `clientOrders:read`, `contacts:read`, `deliveryNotes:read`, `estimates:read`, `invoices:read`, `payrolls:read`, `products:read`, `purchaseOrders:read`, `recurring:read`. Devuelve el contenido completo y legible de un resultado de search (contacto, factura de venta, presupuesto, albarán, pedido, orden de compra, factura de compra o ticket, nómina, producto o factura recurrente) junto con su URL para citarlo. Úsala después de search, con el id que devolvió; la empresa va codificada en el id. | Parámetro | Tipo | Obligatorio | Descripción | |---|---|---|---| | `id` | `string` | sí | Identificador devuelto por search, con la forma tipo:empresa:id (p. ej. invoice:com_123:inv_456). Tipos: contact, invoice, estimate, deliveryNote, clientOrder, purchaseOrder, bill, payroll, product, recurring. | ## `upload_to_inbox` — Subir un fichero a la bandeja de entrada Puede modificar datos. Permisos: `inbox:write`. Sube un fichero (factura de un proveedor, ticket o nómina: PDF, imagen JPEG, PNG, HEIC, WebP o TIFF, o XML; hasta 10 MB) a la bandeja de entrada de FacturaDirecta, que extrae sus datos en segundo plano. En url indica un enlace https público y directo al fichero (portales de proveedores, enlaces de descarga, carpetas públicas) o el contenido en una URL data: en base64 (hasta 300 KB; entonces filename es obligatorio). No sirven los enlaces privados de Drive, Dropbox o OneDrive ni las páginas que exigen sesión: la tool devuelve un error explicándolo. Un fichero adjuntado al chat no tiene enlace público: no inventes una URL ni una ruta local; pide un enlace público y directo o, como con uno grande o sin enlace, que el usuario lo suba a la bandeja de entrada (por web o por email) y sigue con list_inbox_items. Antes de llamar, muestra al usuario qué fichero vas a subir (enlace o nombre) y a qué empresa, y pide su confirmación explícita; envía idempotencyKey para que un reintento no cree dos items. Devuelve el item creado (id tas_…, status, title y el adjunto con su url pública) y el siguiente paso: consultar get_inbox_item hasta que status sea scanned, preparar el gasto con propose_bill o propose_ticket y crearlo con create_bill, o vincular el fichero a una factura de compra existente con add_bill_attachments e inboxIds. | Parámetro | Tipo | Obligatorio | Descripción | |---|---|---|---| | `companyId` | `string` | no | Empresa sobre la que operar (ver whoami). Obligatorio si el usuario tiene más de una empresa; con una sola se resuelve automáticamente. | | `url` | `string` | sí | Enlace https público y directo al fichero (sin sesión ni contraseña), o URL data: con el contenido en base64 (data:application/pdf;base64,…; hasta 300 KB). | | `filename` | `string` | no | Nombre del fichero con extensión (factura-marzo.pdf). Obligatorio con data:; con https se toma del origen o del último segmento del enlace. | | `contentType` | `string` | no | Tipo MIME del fichero (application/pdf, image/jpeg…), solo si el origen no lo indica. | | `title` | `string` | no | Título del item en la bandeja; por defecto, el nombre del fichero. | | `idempotencyKey` | `string` | no | Clave de idempotencia (UUID recomendado): un reintento con la misma clave devuelve el mismo item en vez de crear otro, mientras siga en la bandeja. | ## `facturadirecta_invoices` — FacturaDirecta · Facturas Solo consulta datos. Permisos: `invoices:read`. Facturas de venta: listar, consultar, crear, modificar, registrar cobros, enviar por email, PDF, Facturae, sustitutivas de simplificadas, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool solo lee; las escrituras del dominio están en facturadirecta_invoices_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_invoice_facturae (RO): Generar la factura en formato facturae - create_invoice_pdf (RO): Generar la factura en formato PDF - get_invoice (RO): Obtener una factura - get_invoice_attachments (RO): Listar adjuntos de una factura de venta - get_invoice_version (RO): Versión de la factura - list_invoice_versions (RO, paginada): Versiones de la factura - list_invoices (RO, paginada): Lista de facturas | Acción | Qué hace | Operación de la API | |---|---|---| | `create_invoice_facturae` | Generar la factura en formato facturae | `createInvoiceFacturae` | | `create_invoice_pdf` | Generar la factura en formato PDF | `createInvoicePDF` | | `get_invoice` | Obtener una factura | `getInvoice` | | `get_invoice_attachments` | Listar adjuntos de una factura de venta | `getInvoiceAttachments` | | `get_invoice_version` | Versión de la factura | `getInvoiceVersion` | | `list_invoice_versions` | Versiones de la factura | `getInvoiceVersions` | | `list_invoices` | Lista de facturas | `getInvoices` | ## `facturadirecta_invoices_write` — FacturaDirecta · Facturas (escritura) Puede modificar datos. Permisos: `invoices:write`. Facturas de venta: listar, consultar, crear, modificar, registrar cobros, enviar por email, PDF, Facturae, sustitutivas de simplificadas, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_invoices. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Emitir una factura (main.draft false), anularla (main.voided true) o subsanarla es un acto fiscal: con VeriFactu o TicketBAI activos se registra y se envía a Hacienda al momento y no se deshace. Crea siempre con draft true y emite solo si el usuario lo pide para esa factura, avisándole antes; anular no corrige ni borra. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_invoice_attachments: Vincular adjuntos a una factura - create_invoice: Crear factura - create_invoice_payments: Crear pagos para una factura - create_substitution_invoice: Crear factura sustitutiva de simplificadas - send_invoice: Enviar factura por correo electrónico - update_invoice: Actualizar factura - update_invoice_tags: Actualizar etiquetas de factura | Acción | Qué hace | Operación de la API | |---|---|---| | `add_invoice_attachments` | Vincular adjuntos a una factura | `addInvoiceAttachments` | | `create_invoice` | Crear factura | `createInvoice` | | `create_invoice_payments` | Crear pagos para una factura | `createInvoicePayments` | | `create_substitution_invoice` | Crear factura sustitutiva de simplificadas | `createSubstitutionInvoice` | | `send_invoice` | Enviar factura por correo electrónico | `sendInvoice` | | `update_invoice` | Actualizar factura | `updateInvoice` | | `update_invoice_tags` | Actualizar etiquetas de factura | `updateInvoiceTags` | ## `facturadirecta_estimates` — FacturaDirecta · Presupuestos Solo consulta datos. Permisos: `estimates:read`. Presupuestos (también las facturas proforma, que son presupuestos con isProforma): listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool solo lee; las escrituras del dominio están en facturadirecta_estimates_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_estimate_pdf (RO): Generar el presupuesto en formato PDF - get_estimate (RO): Obtener un presupuesto - get_estimate_attachments (RO): Listar adjuntos de un presupuesto - get_estimate_version (RO): Versión de el presupuesto - list_estimate_versions (RO, paginada): Versiones de el presupuesto - list_estimates (RO, paginada): Lista de presupuestos | Acción | Qué hace | Operación de la API | |---|---|---| | `create_estimate_pdf` | Generar el presupuesto en formato PDF | `createEstimatePDF` | | `get_estimate` | Obtener un presupuesto | `getEstimate` | | `get_estimate_attachments` | Listar adjuntos de un presupuesto | `getEstimateAttachments` | | `get_estimate_version` | Versión de el presupuesto | `getEstimateVersion` | | `list_estimate_versions` | Versiones de el presupuesto | `getEstimateVersions` | | `list_estimates` | Lista de presupuestos | `getEstimates` | ## `facturadirecta_estimates_write` — FacturaDirecta · Presupuestos (escritura) Puede modificar datos. Permisos: `estimates:write`. Presupuestos (también las facturas proforma, que son presupuestos con isProforma): listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_estimates. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_estimate_attachments: Vincular adjuntos a un presupuesto - create_estimate: Crear presupuesto - send_estimate: Enviar presupuesto por correo electrónico - update_estimate: Actualizar presupuesto - update_estimate_tags: Actualizar etiquetas de presupuesto | Acción | Qué hace | Operación de la API | |---|---|---| | `add_estimate_attachments` | Vincular adjuntos a un presupuesto | `addEstimateAttachments` | | `create_estimate` | Crear presupuesto | `createEstimate` | | `send_estimate` | Enviar presupuesto por correo electrónico | `sendEstimate` | | `update_estimate` | Actualizar presupuesto | `updateEstimate` | | `update_estimate_tags` | Actualizar etiquetas de presupuesto | `updateEstimateTags` | ## `facturadirecta_client_orders` — FacturaDirecta · Pedidos Solo consulta datos. Permisos: `clientOrders:read`. Pedidos de cliente: listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool solo lee; las escrituras del dominio están en facturadirecta_client_orders_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_client_order_pdf (RO): Generar el pedido en formato PDF - get_client_order (RO): Obtener un pedido - get_client_order_attachments (RO): Listar adjuntos de un pedido - get_client_order_version (RO): Versión de el pedido - list_client_order_versions (RO, paginada): Versiones de el pedido - list_client_orders (RO, paginada): Lista de pedidos | Acción | Qué hace | Operación de la API | |---|---|---| | `create_client_order_pdf` | Generar el pedido en formato PDF | `createClientOrderPDF` | | `get_client_order` | Obtener un pedido | `getClientOrder` | | `get_client_order_attachments` | Listar adjuntos de un pedido | `getClientOrderAttachments` | | `get_client_order_version` | Versión de el pedido | `getClientOrderVersion` | | `list_client_order_versions` | Versiones de el pedido | `getClientOrderVersions` | | `list_client_orders` | Lista de pedidos | `getClientOrders` | ## `facturadirecta_client_orders_write` — FacturaDirecta · Pedidos (escritura) Puede modificar datos. Permisos: `clientOrders:write`. Pedidos de cliente: listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_client_orders. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_client_order_attachments: Vincular adjuntos a un pedido - create_client_order: Crear pedido - send_client_order: Enviar pedido por correo electrónico - update_client_order: Actualizar pedido - update_client_order_tags: Actualizar etiquetas de pedido | Acción | Qué hace | Operación de la API | |---|---|---| | `add_client_order_attachments` | Vincular adjuntos a un pedido | `addClientOrderAttachments` | | `create_client_order` | Crear pedido | `createClientOrder` | | `send_client_order` | Enviar pedido por correo electrónico | `sendClientOrder` | | `update_client_order` | Actualizar pedido | `updateClientOrder` | | `update_client_order_tags` | Actualizar etiquetas de pedido | `updateClientOrderTags` | ## `facturadirecta_delivery_notes` — FacturaDirecta · Albaranes Solo consulta datos. Permisos: `deliveryNotes:read`. Albaranes: listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool solo lee; las escrituras del dominio están en facturadirecta_delivery_notes_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_delivery_note_pdf (RO): Generar el albarán en formato PDF - get_delivery_note (RO): Obtener un albarán - get_delivery_note_attachments (RO): Listar adjuntos de un albarán - get_delivery_note_version (RO): Versión de el albarán - list_delivery_note_versions (RO, paginada): Versiones de el albarán - list_delivery_notes (RO, paginada): Lista de albaranes | Acción | Qué hace | Operación de la API | |---|---|---| | `create_delivery_note_pdf` | Generar el albarán en formato PDF | `createDeliveryNotePDF` | | `get_delivery_note` | Obtener un albarán | `getDeliveryNote` | | `get_delivery_note_attachments` | Listar adjuntos de un albarán | `getDeliveryNoteAttachments` | | `get_delivery_note_version` | Versión de el albarán | `getDeliveryNoteVersion` | | `list_delivery_note_versions` | Versiones de el albarán | `getDeliveryNoteVersions` | | `list_delivery_notes` | Lista de albaranes | `getDeliveryNotes` | ## `facturadirecta_delivery_notes_write` — FacturaDirecta · Albaranes (escritura) Puede modificar datos. Permisos: `deliveryNotes:write`. Albaranes: listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_delivery_notes. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_delivery_note_attachments: Vincular adjuntos a un albarán - create_delivery_note: Crear albarán - send_delivery_note: Enviar albarán por correo electrónico - update_delivery_note: Actualizar albarán - update_delivery_note_tags: Actualizar etiquetas de albarán | Acción | Qué hace | Operación de la API | |---|---|---| | `add_delivery_note_attachments` | Vincular adjuntos a un albarán | `addDeliveryNoteAttachments` | | `create_delivery_note` | Crear albarán | `createDeliveryNote` | | `send_delivery_note` | Enviar albarán por correo electrónico | `sendDeliveryNote` | | `update_delivery_note` | Actualizar albarán | `updateDeliveryNote` | | `update_delivery_note_tags` | Actualizar etiquetas de albarán | `updateDeliveryNoteTags` | ## `facturadirecta_bills` — FacturaDirecta · Compras Solo consulta datos. Permisos: `bills:read`. Compras: facturas de compra y tickets de gasto: listar, consultar, crear, modificar, registrar pagos, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Esta tool solo lee; las escrituras del dominio están en facturadirecta_bills_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_bill (RO): Obtener una factura de compra o ticket - get_bill_attachments (RO): Listar adjuntos de una factura de compra o ticket - get_bill_version (RO): Versión de la factura de compra o el ticket - list_bill_versions (RO, paginada): Versiones de la factura de compra o el ticket - list_bills (RO, paginada): Lista de facturas de compra o tickets | Acción | Qué hace | Operación de la API | |---|---|---| | `get_bill` | Obtener una factura de compra o ticket | `getBill` | | `get_bill_attachments` | Listar adjuntos de una factura de compra o ticket | `getBillAttachments` | | `get_bill_version` | Versión de la factura de compra o el ticket | `getBillVersion` | | `list_bill_versions` | Versiones de la factura de compra o el ticket | `getBillVersions` | | `list_bills` | Lista de facturas de compra o tickets | `getBills` | ## `facturadirecta_bills_write` — FacturaDirecta · Compras (escritura) Puede modificar datos. Permisos: `bills:write`. Compras: facturas de compra y tickets de gasto: listar, consultar, crear, modificar, registrar pagos, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_bills. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_bill_attachments: Vincular adjuntos a una factura de compra o ticket - create_bill: Crear factura de compra o ticket - create_bill_payments: Crear pagos para una factura de compra o ticket - update_bill: Actualizar factura de compra o ticket - update_bill_tags: Actualizar etiquetas de factura de compra o ticket | Acción | Qué hace | Operación de la API | |---|---|---| | `add_bill_attachments` | Vincular adjuntos a una factura de compra o ticket | `addBillAttachments` | | `create_bill` | Crear factura de compra o ticket | `createBill` | | `create_bill_payments` | Crear pagos para una factura de compra o ticket | `createBillPayments` | | `update_bill` | Actualizar factura de compra o ticket | `updateBill` | | `update_bill_tags` | Actualizar etiquetas de factura de compra o ticket | `updateBillTags` | ## `facturadirecta_purchase_orders` — FacturaDirecta · Órdenes de compra Solo consulta datos. Permisos: `purchaseOrders:read`. Órdenes de compra a proveedores: listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool solo lee; las escrituras del dominio están en facturadirecta_purchase_orders_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_purchase_order_pdf (RO): Generar la orden de compra en formato PDF - get_purchase_order (RO): Obtener una orden de compra - get_purchase_order_attachments (RO): Listar adjuntos de una orden de compra - get_purchase_order_version (RO): Versión de la orden de compra - list_purchase_order_versions (RO, paginada): Versiones de la orden de compra - list_purchase_orders (RO, paginada): Lista de órdenes de compra | Acción | Qué hace | Operación de la API | |---|---|---| | `create_purchase_order_pdf` | Generar la orden de compra en formato PDF | `createPurchaseOrderPDF` | | `get_purchase_order` | Obtener una orden de compra | `getPurchaseOrder` | | `get_purchase_order_attachments` | Listar adjuntos de una orden de compra | `getPurchaseOrderAttachments` | | `get_purchase_order_version` | Versión de la orden de compra | `getPurchaseOrderVersion` | | `list_purchase_order_versions` | Versiones de la orden de compra | `getPurchaseOrderVersions` | | `list_purchase_orders` | Lista de órdenes de compra | `getPurchaseOrders` | ## `facturadirecta_purchase_orders_write` — FacturaDirecta · Órdenes de compra (escritura) Puede modificar datos. Permisos: `purchaseOrders:write`. Órdenes de compra a proveedores: listar, consultar, crear, modificar, enviar por email, PDF, etiquetas, adjuntos y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_purchase_orders. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_purchase_order_attachments: Vincular adjuntos a una orden de compra - create_purchase_order: Crear orden de compra - send_purchase_order: Enviar orden de compra por correo electrónico - update_purchase_order: Actualizar orden de compra - update_purchase_order_tags: Actualizar etiquetas de orden de compra | Acción | Qué hace | Operación de la API | |---|---|---| | `add_purchase_order_attachments` | Vincular adjuntos a una orden de compra | `addPurchaseOrderAttachments` | | `create_purchase_order` | Crear orden de compra | `createPurchaseOrder` | | `send_purchase_order` | Enviar orden de compra por correo electrónico | `sendPurchaseOrder` | | `update_purchase_order` | Actualizar orden de compra | `updatePurchaseOrder` | | `update_purchase_order_tags` | Actualizar etiquetas de orden de compra | `updatePurchaseOrderTags` | ## `facturadirecta_payrolls` — FacturaDirecta · Nóminas Solo consulta datos. Permisos: `payrolls:read`. Nóminas: listar, consultar, crear, modificar, registrar pagos, etiquetas, adjuntos y versiones. Contienen datos personales de los empleados: muéstralos solo si el usuario los pide. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Esta tool solo lee; las escrituras del dominio están en facturadirecta_payrolls_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_payroll (RO): Obtener una nómina - get_payroll_attachments (RO): Listar adjuntos de una nómina - get_payroll_version (RO): Versión de la nómina - list_payroll_versions (RO, paginada): Versiones de la nómina - list_payrolls (RO, paginada): Lista de nóminas | Acción | Qué hace | Operación de la API | |---|---|---| | `get_payroll` | Obtener una nómina | `getPayroll` | | `get_payroll_attachments` | Listar adjuntos de una nómina | `getPayrollAttachments` | | `get_payroll_version` | Versión de la nómina | `getPayrollVersion` | | `list_payroll_versions` | Versiones de la nómina | `getPayrollVersions` | | `list_payrolls` | Lista de nóminas | `getPayrolls` | ## `facturadirecta_payrolls_write` — FacturaDirecta · Nóminas (escritura) Puede modificar datos. Permisos: `payrolls:write`. Nóminas: listar, consultar, crear, modificar, registrar pagos, etiquetas, adjuntos y versiones. Contienen datos personales de los empleados: muéstralos solo si el usuario los pide. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_payrolls. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_payroll_attachments: Vincular adjuntos a una nómina - create_payroll: Crear nómina - create_payroll_payments: Crear pagos para una nómina - update_payroll: Actualizar nómina - update_payroll_tags: Actualizar etiquetas de nómina | Acción | Qué hace | Operación de la API | |---|---|---| | `add_payroll_attachments` | Vincular adjuntos a una nómina | `addPayrollAttachments` | | `create_payroll` | Crear nómina | `createPayroll` | | `create_payroll_payments` | Crear pagos para una nómina | `createPayrollPayments` | | `update_payroll` | Actualizar nómina | `updatePayroll` | | `update_payroll_tags` | Actualizar etiquetas de nómina | `updatePayrollTags` | ## `facturadirecta_inbox` — FacturaDirecta · Escaneos Solo consulta datos. Permisos: `inbox:read`. Escaneos: la bandeja de entrada de documentos pendientes de procesar (OCR) y la subida de ficheros: listar, consultar, proponer gasto, ticket o nómina a partir de un documento, archivar. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool solo lee; las escrituras del dominio están en facturadirecta_inbox_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_inbox_item (RO): Detalle de un item de la bandeja - list_inbox_items (RO, paginada): Lista de items en la bandeja de entrada - propose_bill (RO): Propuesta de factura de compra desde un item de la bandeja - propose_payroll (RO): Propuesta de nómina desde un item de la bandeja - propose_ticket (RO): Propuesta de ticket de compra desde un item de la bandeja | Acción | Qué hace | Operación de la API | |---|---|---| | `get_inbox_item` | Detalle de un item de la bandeja | `getInboxItem` | | `list_inbox_items` | Lista de items en la bandeja de entrada | `getInboxItems` | | `propose_bill` | Propuesta de factura de compra desde un item de la bandeja | `proposeBill` | | `propose_payroll` | Propuesta de nómina desde un item de la bandeja | `proposePayroll` | | `propose_ticket` | Propuesta de ticket de compra desde un item de la bandeja | `proposeTicket` | ## `facturadirecta_inbox_write` — FacturaDirecta · Escaneos (escritura) Puede modificar datos. Permisos: `inbox:write`. Escaneos: la bandeja de entrada de documentos pendientes de procesar (OCR) y la subida de ficheros: listar, consultar, proponer gasto, ticket o nómina a partir de un documento, archivar. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Esta tool escribe; las lecturas del dominio están en facturadirecta_inbox. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - delete_inbox_item: Archivar un item de la bandeja | Acción | Qué hace | Operación de la API | |---|---|---| | `delete_inbox_item` | Archivar un item de la bandeja | `deleteInboxItem` | ## `facturadirecta_contacts` — FacturaDirecta · Contactos Solo consulta datos. Permisos: `contacts:read`. Contactos: clientes, proveedores y empleados: listar (con búsqueda por nombre, NIF o email), consultar, crear, modificar, etiquetas y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Esta tool solo lee; las escrituras del dominio están en facturadirecta_contacts_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_contact (RO): Obtener un contacto - get_contact_version (RO): Versión de el contacto - list_contact_versions (RO, paginada): Versiones de el contacto - list_contacts (RO, paginada): Lista de contactos | Acción | Qué hace | Operación de la API | |---|---|---| | `get_contact` | Obtener un contacto | `getContact` | | `get_contact_version` | Versión de el contacto | `getContactVersion` | | `list_contact_versions` | Versiones de el contacto | `getContactVersions` | | `list_contacts` | Lista de contactos | `getContacts` | ## `facturadirecta_contacts_write` — FacturaDirecta · Contactos (escritura) Puede modificar datos. Permisos: `contacts:write`. Contactos: clientes, proveedores y empleados: listar (con búsqueda por nombre, NIF o email), consultar, crear, modificar, etiquetas y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_contacts. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_contact: Crear contacto - update_contact: Actualizar contacto - update_contact_tags: Actualizar etiquetas de contacto | Acción | Qué hace | Operación de la API | |---|---|---| | `create_contact` | Crear contacto | `createContact` | | `update_contact` | Actualizar contacto | `updateContact` | | `update_contact_tags` | Actualizar etiquetas de contacto | `updateContactTags` | ## `facturadirecta_products` — FacturaDirecta · Productos Solo consulta datos. Permisos: `products:read`. Productos y servicios, con su stock y movimientos: listar (con búsqueda por nombre), consultar, crear, modificar, ajustar stock y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Esta tool solo lee; las escrituras del dominio están en facturadirecta_products_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_product (RO): Obtener un producto - get_product_stock (RO): Stock de un producto - get_product_version (RO): Versión de el producto - list_product_stock_movements (RO, paginada): Movimientos de stock de un producto - list_product_versions (RO, paginada): Versiones de el producto - list_products (RO, paginada): Lista de productos | Acción | Qué hace | Operación de la API | |---|---|---| | `get_product` | Obtener un producto | `getProduct` | | `get_product_stock` | Stock de un producto | `getProductStock` | | `get_product_version` | Versión de el producto | `getProductVersion` | | `list_product_stock_movements` | Movimientos de stock de un producto | `listProductStockMovements` | | `list_product_versions` | Versiones de el producto | `getProductVersions` | | `list_products` | Lista de productos | `getProducts` | ## `facturadirecta_products_write` — FacturaDirecta · Productos (escritura) Puede modificar datos. Permisos: `products:write`. Productos y servicios, con su stock y movimientos: listar (con búsqueda por nombre), consultar, crear, modificar, ajustar stock y versiones. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_products. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - adjust_product_stock: Ajuste manual de stock - create_product: Crear producto - update_product: Actualizar producto | Acción | Qué hace | Operación de la API | |---|---|---| | `adjust_product_stock` | Ajuste manual de stock | `adjustProductStock` | | `create_product` | Crear producto | `createProduct` | | `update_product` | Actualizar producto | `updateProduct` | ## `facturadirecta_automation` — FacturaDirecta · Tareas recurrentes Solo consulta datos. Permisos: `invoices:read`, `recurring:read`. Tareas recurrentes: facturas de venta recurrentes (plantillas que generan facturas periódicamente): listar, consultar, crear, modificar, PDF de muestra, etiquetas y adjuntos. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool solo lee; las escrituras del dominio están en facturadirecta_automation_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_recurring_invoice_pdf (RO): Generar muestra de la factura de venta recurrente en formato PDF - get_recurring_invoice (RO): Obtener una factura de venta recurrente - get_recurring_invoice_attachments (RO): Listar adjuntos de una factura recurrente - list_recurring_invoices (RO, paginada): Lista de facturas de venta recurrentes | Acción | Qué hace | Operación de la API | |---|---|---| | `create_recurring_invoice_pdf` | Generar muestra de la factura de venta recurrente en formato PDF | `createRecurringInvoicePDF` | | `get_recurring_invoice` | Obtener una factura de venta recurrente | `getRecurringInvoice` | | `get_recurring_invoice_attachments` | Listar adjuntos de una factura recurrente | `getRecurringInvoiceAttachments` | | `list_recurring_invoices` | Lista de facturas de venta recurrentes | `getRecurringInvoices` | ## `facturadirecta_automation_write` — FacturaDirecta · Tareas recurrentes (escritura) Puede modificar datos. Permisos: `invoices:write`, `recurring:write`. Tareas recurrentes: facturas de venta recurrentes (plantillas que generan facturas periódicamente): listar, consultar, crear, modificar, PDF de muestra, etiquetas y adjuntos. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_automation. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - add_recurring_invoice_attachments: Vincular adjuntos a una factura recurrente - create_recurring_invoice: Crea factura de venta recurrente - update_recurring_invoice: Actualizar factura recurrente - update_recurring_invoice_tags: Actualizar etiquetas de factura de venta recurrente | Acción | Qué hace | Operación de la API | |---|---|---| | `add_recurring_invoice_attachments` | Vincular adjuntos a una factura recurrente | `addRecurringInvoiceAttachments` | | `create_recurring_invoice` | Crea factura de venta recurrente | `createRecurringInvoice` | | `update_recurring_invoice` | Actualizar factura recurrente | `updateRecurringInvoice` | | `update_recurring_invoice_tags` | Actualizar etiquetas de factura de venta recurrente | `updateRecurringInvoiceTags` | ## `facturadirecta_activity` — FacturaDirecta · Actividad Solo consulta datos. Permisos: `activity:read`. Actividad: el registro de altas, modificaciones, borrados, envíos por email, accesos y tareas automáticas de la empresa, con quién y cuándo; paginación por cursor. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - list_activity (RO, paginada): Registro de actividad de la empresa | Acción | Qué hace | Operación de la API | |---|---|---| | `list_activity` | Registro de actividad de la empresa | `getActivity` | ## `facturadirecta_banks` — FacturaDirecta · Bancos Solo consulta datos. Permisos: `banks:read`, `statements:read`. Bancos y cuentas de tesorería, con su saldo contable, el saldo según la entidad, el estado del enlace y los movimientos pendientes (related), extractos bancarios y su conciliación con asientos contables. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Esta tool solo lee; las escrituras del dominio están en facturadirecta_banks_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_bank (RO): Obtener un banco - get_bank_version (RO): Versión de el banco - get_statement (RO): Obtener un extracto bancario - list_bank_versions (RO, paginada): Versiones de el banco - list_banks (RO, paginada): Lista de bancos - list_statements (RO, paginada): Lista de extractos bancarios | Acción | Qué hace | Operación de la API | |---|---|---| | `get_bank` | Obtener un banco | `getBank` | | `get_bank_version` | Versión de el banco | `getBankVersion` | | `get_statement` | Obtener un extracto bancario | `getStatement` | | `list_bank_versions` | Versiones de el banco | `getBankVersions` | | `list_banks` | Lista de bancos | `getBanks` | | `list_statements` | Lista de extractos bancarios | `getStatements` | ## `facturadirecta_banks_write` — FacturaDirecta · Bancos (escritura) Puede modificar datos. Permisos: `statements:write`. Bancos y cuentas de tesorería, con su saldo contable, el saldo según la entidad, el estado del enlace y los movimientos pendientes (related), extractos bancarios y su conciliación con asientos contables. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_banks. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - reconcile_statement: Conciliar extracto bancario con asiento contable existente - reconcile_statement_with_new_transaction: Conciliar extracto bancario creando un nuevo asiento contable - unreconcile_statement: Desconciliar extracto bancario | Acción | Qué hace | Operación de la API | |---|---|---| | `reconcile_statement` | Conciliar extracto bancario con asiento contable existente | `reconcileStatement` | | `reconcile_statement_with_new_transaction` | Conciliar extracto bancario creando un nuevo asiento contable | `reconcileStatementWithNewTransaction` | | `unreconcile_statement` | Desconciliar extracto bancario | `unreconcileStatement` | ## `facturadirecta_accounting` — FacturaDirecta · Asientos contables Solo consulta datos. Permisos: `accounting:read`. Asientos contables: el diario con filtros por fechas, cuenta y documento. Solo con el módulo de Contabilidad; para totales de ingresos, gastos y beneficio usa los informes. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - list_journal (RO, paginada): Diario contable | Acción | Qué hace | Operación de la API | |---|---|---| | `list_journal` | Diario contable | `getJournal` | ## `facturadirecta_tax_declarations` — FacturaDirecta · Impuestos Solo consulta datos. Permisos: `taxDeclarations:read`. Impuestos: los modelos de la AEAT que la empresa ha generado y guardado (303, 111, 115, 130, 202, 180, 190, 347, 349 y 390): listar por modelo, ejercicio y periodo con su resultado, consultar uno con sus casillas y, en las informativas, sus registros por tercero, y descargar su PDF y su fichero de presentación. Solo lectura: la API no genera ni presenta modelos. Los tipos de IVA e IRPF que se aplican a los documentos están en la configuración (get_sales_taxes, get_purchases_taxes). Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_tax_declaration_aeat_file (RO): Generar el fichero de presentación de un modelo de impuestos - create_tax_declaration_pdf (RO): Generar el PDF de un modelo de impuestos - get_tax_declaration (RO): Obtener un modelo de impuestos - get_tax_declaration_attachments (RO): Listar adjuntos de un modelo de impuestos - list_tax_declarations (RO, paginada): Lista de modelos de impuestos | Acción | Qué hace | Operación de la API | |---|---|---| | `create_tax_declaration_aeat_file` | Generar el fichero de presentación de un modelo de impuestos | `createTaxDeclarationAeatFile` | | `create_tax_declaration_pdf` | Generar el PDF de un modelo de impuestos | `createTaxDeclarationPDF` | | `get_tax_declaration` | Obtener un modelo de impuestos | `getTaxDeclaration` | | `get_tax_declaration_attachments` | Listar adjuntos de un modelo de impuestos | `getTaxDeclarationAttachments` | | `list_tax_declarations` | Lista de modelos de impuestos | `getTaxDeclarations` | ## `facturadirecta_reports` — FacturaDirecta · Informes Solo consulta datos. Permisos: `accounting:read`. Informes, calculados en el servidor con las mismas cifras que la web: resumen de resultados (ingresos, gastos y beneficio) y sus documentos, pérdidas y ganancias, balance de situación y sumas y saldos. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_balance_sheet (RO): Balance de situación - get_profit_loss (RO): Pérdidas y ganancias - get_results_summary (RO): Resumen de resultados - list_results_detail (RO, paginada): Detalle del resumen de resultados - list_trial_balance (RO, paginada): Sumas y saldos | Acción | Qué hace | Operación de la API | |---|---|---| | `get_balance_sheet` | Balance de situación | `getBalanceSheet` | | `get_profit_loss` | Pérdidas y ganancias | `getProfitLoss` | | `get_results_summary` | Resumen de resultados | `getResultsSummary` | | `list_results_detail` | Detalle del resumen de resultados | `getResultsDetail` | | `list_trial_balance` | Sumas y saldos | `getTrialBalance` | ## `facturadirecta_query` — FacturaDirecta · Consultas Solo consulta datos. Permisos: `accounting:read`, `bills:read`, `contacts:read`, `invoices:read`. Consulta analítica: suma, cuenta y clasifica en una sola llamada las facturas de venta, las compras, sus líneas, los contactos o los apuntes del diario: mejores clientes o proveedores, productos más vendidos y su margen, facturas vencidas y cuánto suman, compras pendientes de pago, gastos por proveedor, ventas por provincia o país, días de cobro, comparativa con el periodo anterior y desglose por meses. Sustituye a paginar un listado y sumar. Llama antes a describe con la acción query: devuelve las fuentes, medidas, dimensiones y filtros admitidos y ejemplos. Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - query (RO): Consulta analítica | Acción | Qué hace | Operación de la API | |---|---|---| | `query` | Consulta analítica | `analyticsQuery` | ## `facturadirecta_settings` — FacturaDirecta · Configuración Solo consulta datos. Permisos: `customFields:read`, `paymentMethods:read`, `settings:read`. Configuración de la empresa: su ficha y forma de trabajar (datos fiscales, moneda, borrador o definitivo, VeriFactu y TicketBAI, bloqueo contable, buzón de la bandeja y módulos del plan), series de numeración, impuestos de venta y de compra, plantillas de documento, campos personalizados y métodos de pago (los genéricos de la empresa y, con el filtro contact, los de cada cliente o proveedor). Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. Esta tool solo lee; las escrituras del dominio están en facturadirecta_settings_write. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - get_company_settings (RO): Ficha y forma de trabajar de la empresa - get_custom_field (RO): Obtener campo personalizado - get_payment_method (RO): Obtener un método de pago - get_payment_method_version (RO): Versión de el método de pago - get_purchases_taxes (RO): Lista de impuestos de compra disponibles - get_sales_taxes (RO): Lista de impuestos de venta disponibles - get_series (RO): Lista de series de numeración - get_themes (RO): Lista de plantillas de documentos disponibles - list_custom_fields (RO, paginada): Lista de campos personalizados - list_payment_method_versions (RO, paginada): Versiones de el método de pago - list_payment_methods (RO, paginada): Lista de métodos de pago | Acción | Qué hace | Operación de la API | |---|---|---| | `get_company_settings` | Ficha y forma de trabajar de la empresa | `getCompanySettings` | | `get_custom_field` | Obtener campo personalizado | `getCustomField` | | `get_payment_method` | Obtener un método de pago | `getPaymentMethod` | | `get_payment_method_version` | Versión de el método de pago | `getPaymentMethodVersion` | | `get_purchases_taxes` | Lista de impuestos de compra disponibles | `getPurchasesTaxes` | | `get_sales_taxes` | Lista de impuestos de venta disponibles | `getSalesTaxes` | | `get_series` | Lista de series de numeración | `getSeries` | | `get_themes` | Lista de plantillas de documentos disponibles | `getThemes` | | `list_custom_fields` | Lista de campos personalizados | `getCustomFields` | | `list_payment_method_versions` | Versiones de el método de pago | `getPaymentMethodVersions` | | `list_payment_methods` | Lista de métodos de pago | `getPaymentMethods` | ## `facturadirecta_settings_write` — FacturaDirecta · Configuración (escritura) Puede modificar datos. Permisos: `customFields:write`, `paymentMethods:write`. Configuración de la empresa: su ficha y forma de trabajar (datos fiscales, moneda, borrador o definitivo, VeriFactu y TicketBAI, bloqueo contable, buzón de la bandeja y módulos del plan), series de numeración, impuestos de venta y de compra, plantillas de documento, campos personalizados y métodos de pago (los genéricos de la empresa y, con el filtro contact, los de cada cliente o proveedor). Tool gateway: llama con {"action": "...", "params": {...}}. Llama {"action": "describe", "params": {"action": "NOMBRE"}} para el contrato completo de una acción (parámetros, cuerpo, respuesta, ejemplos, permiso), o sin params para el índice del dominio. En las acciones con cuerpo, envía el objeto de describe.body.schema bajo params.body. Esta tool escribe; las lecturas del dominio están en facturadirecta_settings. Antes de llamar a cualquier acción de esta tool, muestra al usuario un resumen de lo que vas a crear, modificar, enviar o borrar, incluida la empresa, y pide su confirmación explícita. Crea los documentos como borrador salvo que el usuario pida lo contrario. Envía idempotencyKey para que un reintento no repita la operación. Toda acción admite companyId (ver whoami); con una sola empresa se resuelve solo. ACCIONES (RO = solo lectura): - create_custom_field: Crear campo personalizado - create_payment_method: Crear método de pago - delete_custom_field: Borrar campo personalizado - restore_custom_field: Recuperar campo personalizado - update_custom_field: Actualizar campo personalizado - update_payment_method: Actualizar método de pago | Acción | Qué hace | Operación de la API | |---|---|---| | `create_custom_field` | Crear campo personalizado | `createCustomField` | | `create_payment_method` | Crear método de pago | `createPaymentMethod` | | `delete_custom_field` | Borrar campo personalizado | `deleteCustomField` | | `restore_custom_field` | Recuperar campo personalizado | `restoreCustomField` | | `update_custom_field` | Actualizar campo personalizado | `updateCustomField` | | `update_payment_method` | Actualizar método de pago | `updatePaymentMethod` | --- # CLI > Instala el CLI de FacturaDirecta para consultar y automatizar facturas, gastos, contactos y contabilidad desde la terminal, en scripts o con agentes de código como Claude Code y Codex. El CLI de FacturaDirecta lleva toda la API pública a la terminal: un comando para cada operación, tablas legibles para personas y JSON para scripts y agentes. Funciona en macOS, Linux y Windows. ## Primeros pasos El asistente de configuración te guía para iniciar sesión y elegir empresa: ```shell facturadirecta setup ``` Si trabajas con un agente de código, `setup` también instala la skill `facturadirecta-cli` en Claude Code o Codex, para que el agente sepa usar el CLI por su cuenta. ## Iniciar sesión Con tu usuario de FacturaDirecta: se abre el navegador para autenticarte. El modo de solo lectura es el predeterminado; pide acceso completo solo cuando necesites modificar datos. ```shell facturadirecta login # Solo lectura facturadirecta login --scope full # Lectura y escritura ``` Sin navegador en esa máquina (SSH, contenedores, agentes sin escritorio), usa el inicio de sesión por dispositivo: el CLI muestra una URL y un código para autorizarlo desde cualquier otro dispositivo, incluido el móvil. ```shell facturadirecta login --device ``` También puedes usar una API key de **Ajustes → API e integraciones**. Cada clave da acceso a una sola empresa: ```shell facturadirecta login --api-key --company ``` ## Elegir empresa ```shell facturadirecta whoami # Usuario, acceso y empresas disponibles facturadirecta auth use-company # Empresa por defecto del perfil ``` Cada comando resuelve la empresa en este orden: `--company`, la variable de entorno `FACTURADIRECTA_COMPANY`, la empresa fijada en el perfil y, si solo tienes una, esa. ## Ejemplos ```shell facturadirecta invoices list --state overdue --summary -q # Facturas vencidas con su saldo pendiente facturadirecta accounting summary --min-date 2026-01-01 --group-by month # Ingresos, gastos y resultado por mes facturadirecta search "Acme" # Busca contactos, productos y documentos facturadirecta invoices list --ids-only | xargs -I{} facturadirecta invoices pdf {} # PDFs en lote ``` Todos los comandos, con sus opciones, están en la [referencia de comandos](https://www.facturadirecta.com/desarrolladores/cli/comandos/). ## Formatos de salida | Opción | Qué devuelve | |---|---| | *(ninguna)* | Tabla legible en la terminal | | `--json`, `-j` | JSON completo | | `--quiet`, `-q` | Solo los datos en JSON, sin metadatos | | `--summary` | Resumen compacto por elemento: número, fecha, contacto, totales, estado, saldo pendiente y enlace | | `--ids-only` | Solo los identificadores, uno por línea | | `--count` | Solo el número de resultados | | `--jq ` | Filtro jq integrado sobre el JSON | ## Uso desde agentes y scripts Con `--agent` (o `-j`, `-q`, `--jq`) la salida es JSON y los errores van a stderr como una línea JSON con `code`, `message`, `status`, `detail` y `hint`. El código de salida indica la clase de error: | Código | Significado | |---|---| | `0` | Correcto | | `2` | Uso o entrada no válida | | `3` | Sesión, solo lectura o permisos | | `4` | No encontrado | | `5` | Empresa sin indicar | | `6` | Error de la API o de red | | `1` | Cualquier otro error | Los listados devuelven 25 resultados por defecto; usa `--limit` (hasta 500) y `--offset` para paginar. ## Mantenerlo al día ```shell facturadirecta update ``` Las credenciales se guardan en un fichero local con permisos `0600` y puedes tener varios perfiles con `--profile`. ¿Prefieres hablar con tu asistente en lugar de escribir comandos? Usa el [servidor MCP](https://www.facturadirecta.com/desarrolladores/mcp/). --- # Comandos del CLI > Todos los comandos del CLI de FacturaDirecta con sus opciones: facturas, presupuestos, gastos, contactos, productos, contabilidad, webhooks y más. Referencia completa de los comandos del CLI, generada a partir de la ayuda de la última versión publicada. En la terminal tienes la misma información con `facturadirecta --help`. Versión 0.17.0 · instalación: `curl -fsSL https://raw.githubusercontent.com/facturadirecta/cli/main/install.sh | sh` ## `facturadirecta accounting balance-sheet` — Balance de situación a una fecha (módulo de Contabilidad) Activo, patrimonio neto y pasivo a --date (por defecto, el 31 de diciembre del año en curso), con los epígrafes del Plan General Contable. --compare añade el saldo al final del periodo anterior o en la misma fecha del año anterior; --breakdown, el saldo al final de cada mes, trimestre o año desde --start-date. Si no cuadra aparece la fila 'imbalance' y un aviso en diagnostics. Contrato completo: facturadirecta describe get_balance_sheet ``` facturadirecta accounting balance-sheet [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Solo los apuntes de documentos que tienen todas estas etiquetas | | `--breakdown string` | Saldos al final de cada mes. Valores: none, month, quarter, year | | `--company string` | ID de la empresa | | `--compare string` | Añade una columna con el saldo en una fecha de referencia. Valores: none, previousPeriod, previousYear | | `--date string` | Fecha del saldo en formato YYYY-MM-DD | | `--detail string` | Nivel de detalle, el mismo que en la web. Valores: summary, accounts, full | | `--difference` | Con compare, añade la columna difference: periodo menos referencia | | `--include-closure` | Incluye los asientos de regularización y de cierre fechados el último día del periodo | | `--start-date string` | Primer día del periodo en formato YYYY-MM-DD, para breakdown y compare=previousPeriod | | `--variance` | Con compare, añade la columna variance: variación sobre la referencia, como fracción (0.2 es un 20 %) | ## `facturadirecta accounting profit-loss` — Cuenta de pérdidas y ganancias oficial (módulo de Contabilidad) Epígrafes del Plan General Contable con las cifras y opciones del informe de la web. --detail summary|accounts|full; --breakdown y --compare como en `accounting results`; --revenue-share añade el porcentaje sobre la cifra de negocios. Sin el módulo de Contabilidad la API responde que el plan no lo incluye. Contrato completo: facturadirecta describe get_profit_loss ``` facturadirecta accounting profit-loss [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Solo los apuntes de documentos que tienen todas estas etiquetas | | `--breakdown string` | Divide el periodo en meses. Valores: none, month, quarter, year | | `--company string` | ID de la empresa | | `--compare string` | Añade una columna con un periodo de referencia. Valores: none, previousPeriod, previousYear | | `--detail string` | Nivel de detalle, el mismo que en la web. Valores: summary, accounts, full | | `--difference` | Con compare, añade la columna difference: periodo menos referencia | | `--end-date string` | Último día del periodo en formato YYYY-MM-DD, incluido | | `--exclude-revenue-clearing` | Excluye el asiento de regularización de ingresos y gastos en todo el periodo, aunque se incluya el cierre | | `--include-closure` | Incluye los asientos de regularización y de cierre fechados el último día del periodo | | `--revenue-share` | Añade, tras cada columna de periodo, otra con el peso de cada fila sobre el importe neto de la cifra de negocios, como fracción | | `--start-date string` | Primer día del periodo en formato YYYY-MM-DD | | `--total` | Con breakdown, añade la columna total con el periodo completo | | `--variance` | Con compare, añade la columna variance: variación sobre la referencia, como fracción (0.2 es un 20 %) | ## `facturadirecta accounting results` — Ingresos, gastos y beneficio por origen, con desglose o comparativa (todos los planes) Ventas facturadas, compras y gastos, nóminas, amortizaciones, diferencias de cambio y otros ingresos o gastos del periodo, con los totales y el beneficio: las cifras del panel de inicio y del informe «Resumen de resultados» de la web. --breakdown reparte el periodo en meses, trimestres o años (--total añade el periodo completo); --compare añade el periodo anterior o el mismo del año anterior (--difference, --variance). Como máximo 5 años. Contrato completo: facturadirecta describe get_results_summary ``` facturadirecta accounting results [flags] ``` | Opción | Descripción | |---|---| | `--breakdown string` | Divide el periodo en meses. Valores: none, month, quarter, year | | `--company string` | ID de la empresa | | `--compare string` | Añade una columna con un periodo de referencia. Valores: none, previousPeriod, previousYear | | `--difference` | Con compare, añade la columna difference: periodo menos referencia | | `--end-date string` | Último día del periodo en formato YYYY-MM-DD, incluido | | `--start-date string` | Primer día del periodo en formato YYYY-MM-DD | | `--total` | Con breakdown, añade la columna total con el periodo completo | | `--variance` | Con compare, añade la columna variance: variación sobre la referencia, como fracción (0.2 es un 20 %) | ## `facturadirecta accounting results-documents` — Documentos que forman una cifra del resumen de resultados (todos los planes) Facturas, gastos, nóminas y movimientos de una categoría del resumen en el periodo, con su tipo, fecha e importe. Con la categoría y las fechas de una columna de `accounting results`, la suma es la cifra de esa celda. Contrato completo: facturadirecta describe list_results_detail ``` facturadirecta accounting results-documents [flags] ``` | Opción | Descripción | |---|---| | `--category string` | Obligatorio. Valores: invoices, currencyGains, otherIncome, bills, payrolls, depreciation, currencyLosses, otherExpenses | | `--company string` | ID de la empresa | | `--end-date string` | Último día del periodo en formato YYYY-MM-DD, incluido | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--offset string` | Posición de inicio de los resultados | | `--start-date string` | Primer día del periodo en formato YYYY-MM-DD | ## `facturadirecta accounting trial-balance` — Balance de sumas y saldos por cuenta (módulo de Contabilidad) Saldo anterior, debe, haber y saldo final de cada cuenta en el periodo. --account filtra por prefijo ('6' = gastos, '430' = clientes); --third-party con el id de un contacto, producto o banco da solo sus apuntes (--third-party-detail añade su fila bajo cada cuenta); --groups 1,3 añade subtotales. Pagina con --limit y --offset; totals son los del periodo completo. Contrato completo: facturadirecta describe list_trial_balance ``` facturadirecta accounting trial-balance [flags] ``` | Opción | Descripción | |---|---| | `--account string` | Cuenta contable completa o prefijo de cuenta: 43 incluye todas las cuentas de clientes y 430000 solo esa cuenta | | `--all-these-tags strings` | Solo los apuntes de documentos que tienen todas estas etiquetas | | `--company string` | ID de la empresa | | `--end-date string` | Último día del periodo en formato YYYY-MM-DD, incluido | | `--exclude-balance0` | Omite las filas con saldo final cero | | `--exclude-revenue-clearing` | Excluye el asiento de regularización de ingresos y gastos en todo el periodo, aunque se incluya el cierre | | `--groups strings` | Añade filas de subtotal por los primeros 1. Valores: 1, 2, 3 | | `--include-closure` | Incluye los asientos de regularización y de cierre fechados el último día del periodo | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--offset string` | Posición de inicio de los resultados | | `--show-currency` | Separa cada cuenta por moneda y añade currencyAmounts con los importes en la moneda original | | `--start-date string` | Primer día del periodo en formato YYYY-MM-DD | | `--third-party string` | Solo los apuntes de este tercero (contacto, producto o banco), con el criterio del balance y de pérdidas y ganancias: sus propios… | | `--third-party-detail` | Añade bajo cada cuenta una fila por tercero | ## `facturadirecta api` — Llamar a cualquier operación de la API por su nombre (el de describe) Llama a cualquier operación de la API, también las que no tienen comando propio, por su nombre de acción (list_client_orders, el mismo que usa el servidor MCP) o su operationId (getClientOrders). `facturadirecta describe ` muestra sus parámetros, cuerpo y ejemplos. Los ids de la ruta van como argumentos, en orden; la empresa, con --company (o la del perfil). Los parámetros de query, con -p nombre=valor (nombres del contrato). El cuerpo, con --data '', --data @fichero.json o por stdin. La respuesta sale tal cual en JSON. Las escrituras necesitan 'login --scope full' y los borrados irreversibles, confirmación (--yes en scripts y agentes). --dry-run imprime la petición sin enviarla. ``` facturadirecta api [ids...] [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Cuerpo JSON en línea o @fichero; sin --data, stdin si la operación exige cuerpo | | `--dry-run` | Imprimir la petición (sin credenciales) sin enviarla | | `--idempotency-key string` | Clave de idempotencia (UUID recomendado): un reintento con la misma clave no repite la operación | | `--output string` | Guardar el cuerpo de la respuesta en este fichero | | `-p, --param strings` | Parámetro de query (o de ruta) con el nombre del contrato: nombre=valor; repetible | | `--yes` | Confirmar un borrado irreversible sin preguntar | ## `facturadirecta api-keys create` — Crear una API key (JSON por stdin o --data) Crear una API key (JSON por stdin o --data) Cuerpo (--data o stdin): {"name" (obligatorio), "scopes" (obligatorio)} Contrato completo: facturadirecta describe create_public_api_key ``` facturadirecta api-keys create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta api-keys delete` — Eliminar una API key Eliminar una API key Contrato completo: facturadirecta describe delete_public_api_key ``` facturadirecta api-keys delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta api-keys get` — Obtener una API key por UUID Obtener una API key por UUID Contrato completo: facturadirecta describe get_public_api_key ``` facturadirecta api-keys get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta api-keys list` — Listar API keys Listar API keys Contrato completo: facturadirecta describe list_public_api_keys ``` facturadirecta api-keys list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta auth login` — Iniciar sesión en FacturaDirecta ``` facturadirecta auth login [flags] ``` | Opción | Descripción | |---|---| | `--api-key string` | Usar API Key en lugar de OAuth | | `--company string` | ID de la empresa (obligatorio con --api-key) | | `--device` | Iniciar sesión por dispositivo (RFC 8628): sin navegador en esta máquina, muestra un código para autorizar desde cualquier dispositivo | | `--no-browser` | No abrir el navegador; solo imprimir la URL (o el código) para hacerlo a mano | | `--no-connection` | No elegir empresas ni permisos: acceso a todas tus empresas con los permisos de tu rol (inicia sesión con el cliente OAuth facturadirecta-api) | | `--port int` | Puerto local para el callback OAuth (0 = uno libre elegido por el sistema) | | `--profile string` | Nombre del perfil (por defecto: default) | | `--scope string` | Modo de acceso: 'readonly' (por defecto) o 'full' (por defecto: readonly) | ## `facturadirecta auth logout` — Cerrar sesión Cierra la sesión del perfil y borra sus credenciales. Si el perfil tiene conexión con FacturaDirecta (empresas y permisos elegidos al iniciar sesión), la revoca antes; si no se puede revocar, avisa y cierra la sesión igualmente. ``` facturadirecta auth logout [flags] ``` | Opción | Descripción | |---|---| | `--profile string` | Nombre del perfil a eliminar (por defecto: default) | ## `facturadirecta auth permissions` — Gestionar a qué empresas accede el CLI y con qué permisos Abre en el navegador la página de FacturaDirecta donde se eligen las empresas a las que accede el CLI con el perfil activo y sus permisos por área. Los cambios se aplican al momento, sin volver a iniciar sesión. Con --no-browser solo imprime la URL; con --json, un objeto con la conexión. ``` facturadirecta auth permissions [flags] ``` | Opción | Descripción | |---|---| | `--no-browser` | No abrir el navegador; solo imprimir la URL | ## `facturadirecta auth profile` — Ver perfil de usuario y empresas disponibles Ver perfil de usuario y empresas disponibles Contrato completo: facturadirecta describe get_profile ``` facturadirecta auth profile ``` ## `facturadirecta auth status` — Ver el estado de la sesión actual ``` facturadirecta auth status ``` ## `facturadirecta auth use-company` — Seleccionar la empresa activa ``` facturadirecta auth use-company [companyId] ``` ## `facturadirecta banks get` — Obtener un banco por UUID, con sus saldos si se piden con --related Obtiene un banco o cuenta de tesorería, con la misma forma que cada elemento del listado. Con --related se añaden bajo related el saldo contable (balance: el de la pantalla de bancos, el que sirve para la contabilidad), el saldo según la entidad (linkedBalance: el último que informó el banco, con su fecha; informativo), el estado de la conexión automática (link, sin credenciales) y los movimientos pendientes de conciliar (statements). --related admite varios valores, repetido o separado por comas. --balance-date da el saldo contable al cierre de ese día (y pide balance por sí sola). El id de cada banco (ban_…) está en content.uuid. Contrato completo: facturadirecta describe get_bank ``` facturadirecta banks get [flags] ``` | Opción | Descripción | |---|---| | `--balance-date string` | Saldo contable al cierre de esta fecha (AAAA-MM-DD); implica --related balance | | `--company string` | ID de la empresa | | `--related strings` | Datos adicionales en related: balance (saldo contable), linkedBalance (saldo según la entidad), link (estado del enlace) y statements (movimientos pendientes); repetible o separado por comas | | `--url` | Imprimir solo la URL del elemento en la web de FacturaDirecta | ## `facturadirecta banks list` — Listar cuentas bancarias, con sus saldos si se piden con --related Lista los bancos y cuentas de tesorería activos de la empresa. Con --related se añaden bajo related el saldo contable (balance: el de la pantalla de bancos, el que sirve para la contabilidad), el saldo según la entidad (linkedBalance: el último que informó el banco, con su fecha; informativo), el estado de la conexión automática (link, sin credenciales) y los movimientos pendientes de conciliar (statements). --related admite varios valores, repetido o separado por comas. --balance-date da el saldo contable al cierre de ese día (y pide balance por sí sola). El id de cada banco (ban_…) está en content.uuid. Contrato completo: facturadirecta describe list_banks ``` facturadirecta banks list [flags] ``` | Opción | Descripción | |---|---| | `--balance-date string` | Saldo contable al cierre de esta fecha (AAAA-MM-DD); implica --related balance | | `--company string` | ID de la empresa | | `--currency string` | Moneda del banco (Formato ISO 4217) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Datos adicionales en related: balance (saldo contable), linkedBalance (saldo según la entidad), link (estado del enlace) y statements (movimientos pendientes); repetible o separado por comas | | `--sort-by strings` | Orden de los resultados. Opciones: title, currency, creationDate, modificationDate (prefijo - = descendente) | ## `facturadirecta bills attachments add` — Vincular uploads existentes al documento Vincular uploads existentes al documento Contrato completo: facturadirecta describe add_bill_attachments ``` facturadirecta bills attachments add ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta bills attachments delete` — Eliminar un adjunto por su índice Eliminar un adjunto por su índice Contrato completo: facturadirecta describe delete_bill_attachment ``` facturadirecta bills attachments delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta bills attachments list` — Listar adjuntos del documento Listar adjuntos del documento Contrato completo: facturadirecta describe get_bill_attachments ``` facturadirecta bills attachments list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta bills attachments upload` — Subir y vincular archivos locales al documento Subir y vincular archivos locales al documento Operaciones de la API: create_upload, commit_upload, add_bill_attachments. Contrato de cada una: facturadirecta describe ``` facturadirecta bills attachments upload ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta bills create` — Crear un gasto (JSON por stdin o --data) Crear un gasto (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags", "fromInbox"} Contrato completo: facturadirecta describe create_bill ``` facturadirecta bills create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta bills delete` — Eliminar un gasto Eliminar un gasto Contrato completo: facturadirecta describe delete_bill ``` facturadirecta bills delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta bills get` — Obtener un gasto por UUID Obtener un gasto por UUID Contrato completo: facturadirecta describe get_bill ``` facturadirecta bills get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Permite obtener datos adicionales relacionados con la factura de compra o ticket. Valores: taxIds, balance, state, journal | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--url` | Imprimir solo la URL del elemento en la web de FacturaDirecta | ## `facturadirecta bills list` — Listar gastos Listar gastos Contrato completo: facturadirecta describe list_bills ``` facturadirecta bills list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener la factura de compra o ticket para ser incluido en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener la factura de compra o ticket para ser incluido en los resultados | | `--company string` | ID de la empresa | | `--contact strings` | Proveedor de la factura | | `--country string` | País de la dirección fiscal del proveedor (Formato ISO 3166-1 Alpha-2) | | `--currency string` | Moneda de la factura de compra o ticket (Formato ISO 4217) | | `--draft string` | Comportamiento de las facturas provisionales. Valores: only, all | | `--has-contact` | Factura de compra con contacto, utiliza el valor 'false' para obtener las facturas de compra o tickets sin proveedor asociado | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-date string` | Fecha máxima de registro de la factura de compra o ticket en formato YYYY-MM-DD | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-total string` | Importe total máximo de la factura de compra o ticket | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha mínima de registro de la factura de compra o ticket en formato YYYY-MM-DD | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-total string` | Importe total mínimo de la factura de compra o ticket | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Permite obtener datos adicionales relacionados con la factura de compra o ticket. Valores: taxIds, balance, state, journal | | `--search string` | Búsqueda aproximada en el texto del documento (título, líneas, notas) y en el nombre de su contacto: todas las palabras, sin… | | `--sort-by strings` | Orden de los resultados. Opciones: date, total, currency, country, creationDate, modificationDate (prefijo - = descendente) | | `--state strings` | Estados de factura de compra o ticket a incluir. Valores: draft, pending, overdue, paid, overpaid, voided | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--totals` | Añade totals: recuento y suma de base, impuestos, total y pendiente de TODO lo que cumple los filtros (no solo la página), por moneda. En la terminal sale bajo la tabla; en JSON, con -j o --jq .totals (-q solo da los elementos) | ## `facturadirecta bills pay` — Registrar pagos de un gasto (JSON por stdin o --data) Registra uno o varios pagos del gasto (JSON por stdin o --data: {"payments": [{"bank": "ban_…", "amount": …, "date": "AAAA-MM-DD"}]}). Si el cobro o pago corresponde a un movimiento del extracto bancario, o la cuenta está en una moneda distinta de la de la empresa y de la del documento, no lo registres aquí: concilia el movimiento con "facturadirecta statements reconcile-new --data '{"document":""}'". El movimiento queda conciliado y la API calcula la diferencia de cambio. Cuerpo (--data o stdin): {"payments" (obligatorio), "related"} Contrato completo: facturadirecta describe create_bill_payments ``` facturadirecta bills pay [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta bills tags` — Actualizar etiquetas de un gasto (JSON por stdin o --data) Actualizar etiquetas de un gasto (JSON por stdin o --data) Cuerpo (--data o stdin): {"tags" (obligatorio), "tagsOperation" (obligatorio)} Contrato completo: facturadirecta describe update_bill_tags ``` facturadirecta bills tags [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta bills update` — Actualizar un gasto (JSON por stdin o --data) Actualizar un gasto (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags", "tagsOperation"} Contrato completo: facturadirecta describe update_bill ``` facturadirecta bills update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta contacts create` — Crear un contacto (datos en JSON por stdin o --data) Crear un contacto (datos en JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags"} Contrato completo: facturadirecta describe create_contact ``` facturadirecta contacts create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos del contacto en JSON | ## `facturadirecta contacts delete` — Eliminar un contacto Eliminar un contacto Contrato completo: facturadirecta describe delete_contact ``` facturadirecta contacts delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta contacts get` — Obtener un contacto por UUID Obtener un contacto por UUID Contrato completo: facturadirecta describe get_contact ``` facturadirecta contacts get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Permite obtener datos adicionales relacionados con el contacto. Valores: receivePaymentMethod, sendPaymentMethod, paymentMethods | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--url` | Imprimir solo la URL del elemento en la web de FacturaDirecta | ## `facturadirecta contacts list` — Listar contactos Listar contactos Contrato completo: facturadirecta describe list_contacts ``` facturadirecta contacts list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener el contacto para ser incluido en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener el contacto para ser incluido en los resultados | | `--company string` | ID de la empresa | | `--country string` | País de la dirección fiscal del contacto (Formato ISO 3166-1 Alpha-2) | | `--external-id string` | ID externo del contacto | | `--fiscal-id string` | NIF o identificador fiscal del contacto | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--is-client` | Contacto con rol de cliente | | `--is-employee` | Contacto con rol de empleado | | `--is-provider` | Contacto con rol de proveedor | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--offset string` | Posición de inicio de los resultados | | `--phone string` | Teléfono del contacto | | `--related strings` | Permite obtener datos adicionales relacionados con el contacto. Valores: receivePaymentMethod, sendPaymentMethod, paymentMethods | | `--search string` | Búsqueda aproximada por nombre (incluyendo apellidos y "mostrar cómo"/"nombre comercial"), email y NIF | | `--sort-by strings` | Orden de los resultados. Opciones: title, country, creationDate, modificationDate (prefijo - = descendente) | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | ## `facturadirecta contacts tags` — Actualizar etiquetas de un contacto (JSON por stdin o --data) Actualizar etiquetas de un contacto (JSON por stdin o --data) Cuerpo (--data o stdin): {"tags" (obligatorio), "tagsOperation" (obligatorio)} Contrato completo: facturadirecta describe update_contact_tags ``` facturadirecta contacts tags [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta contacts update` — Actualizar un contacto (datos en JSON por stdin o --data) Actualizar un contacto (datos en JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags", "tagsOperation"} Contrato completo: facturadirecta describe update_contact ``` facturadirecta contacts update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos del contacto en JSON | ## `facturadirecta delivery-notes attachments add` — Vincular uploads existentes al documento Vincular uploads existentes al documento Contrato completo: facturadirecta describe add_delivery_note_attachments ``` facturadirecta delivery-notes attachments add ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta delivery-notes attachments delete` — Eliminar un adjunto por su índice Eliminar un adjunto por su índice Contrato completo: facturadirecta describe delete_delivery_note_attachment ``` facturadirecta delivery-notes attachments delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta delivery-notes attachments list` — Listar adjuntos del documento Listar adjuntos del documento Contrato completo: facturadirecta describe get_delivery_note_attachments ``` facturadirecta delivery-notes attachments list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta delivery-notes attachments upload` — Subir y vincular archivos locales al documento Subir y vincular archivos locales al documento Operaciones de la API: create_upload, commit_upload, add_delivery_note_attachments. Contrato de cada una: facturadirecta describe ``` facturadirecta delivery-notes attachments upload ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta delivery-notes create` — Crear un albarán (JSON por stdin o --data) Crear un albarán (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags"} Contrato completo: facturadirecta describe create_delivery_note ``` facturadirecta delivery-notes create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta delivery-notes delete` — Eliminar un albarán Eliminar un albarán Contrato completo: facturadirecta describe delete_delivery_note ``` facturadirecta delivery-notes delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta delivery-notes get` — Obtener un albarán por UUID Obtener un albarán por UUID Contrato completo: facturadirecta describe get_delivery_note ``` facturadirecta delivery-notes get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Permite obtener datos adicionales relacionados con el albarán. Valores: taxIds | ## `facturadirecta delivery-notes list` — Listar albaranes Listar albaranes Contrato completo: facturadirecta describe list_delivery_notes ``` facturadirecta delivery-notes list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener el albarán para ser incluido en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener el albarán para ser incluido en los resultados | | `--company string` | ID de la empresa | | `--contact strings` | Cliente del albarán | | `--country string` | País de la dirección de facturación (Formato ISO 3166-1 Alpha-2) | | `--currency string` | Moneda del albarán (Formato ISO 4217) | | `--emails string` | Búsqueda parcial en los emails del documento (campo main.emails) | | `--formatted-series string` | Serie del albarán | | `--has-contact` | Albarán con contacto, utiliza el valor 'false' para obtener albaranes sin contacto asociado | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-date string` | Fecha máxima del albarán en formato YYYY-MM-DD | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-number string` | Número secuencial máximo del albarán | | `--max-total string` | Importe total máximo del albarán | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha mínima del albarán en formato YYYY-MM-DD | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-number string` | Número secuencial mínimo del albarán | | `--min-total string` | Importe total mínimo del albarán | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Permite obtener datos adicionales relacionados con el albarán. Valores: taxIds | | `--search string` | Búsqueda aproximada en el texto del documento (título, líneas, notas) y en el nombre de su contacto: todas las palabras, sin… | | `--series string` | Serie del albarán sin aplicar posibles formatos de año (## ó ####) | | `--sort-by strings` | Orden de los resultados. Opciones: date, series, formattedSeries, number, total, currency, country, creationDate, modificationDate (prefijo - = descendente) | ## `facturadirecta delivery-notes pdf` — Generar PDF de un albarán Generar PDF de un albarán Contrato completo: facturadirecta describe create_delivery_note_pdf ``` facturadirecta delivery-notes pdf [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--mode string` | Modo de renderizado del PDF (inline, attachment, print) (por defecto: inline) | ## `facturadirecta delivery-notes send` — Enviar un albarán por email (JSON por stdin o --data) Enviar un albarán por email (JSON por stdin o --data) Cuerpo (--data o stdin): {"from", "to", "cc", "bcc", "subject", "html"} Contrato completo: facturadirecta describe send_delivery_note ``` facturadirecta delivery-notes send [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta delivery-notes tags` — Actualizar etiquetas (JSON por stdin o --data) Actualizar etiquetas (JSON por stdin o --data) Cuerpo (--data o stdin): {"tags" (obligatorio), "tagsOperation" (obligatorio)} Contrato completo: facturadirecta describe update_delivery_note_tags ``` facturadirecta delivery-notes tags [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta delivery-notes update` — Actualizar un albarán (JSON por stdin o --data) Actualizar un albarán (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "syncContactData", "tags", "tagsOperation"} Contrato completo: facturadirecta describe update_delivery_note ``` facturadirecta delivery-notes update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta describe` — Contrato de una operación de la API: parámetros, cuerpo, respuesta, ejemplos y notas Muestra el contrato de una operación de la API sin llamarla: parámetros, forma y esquema del cuerpo, esquema de la respuesta, ejemplos de la documentación, notas de uso y los comandos del CLI que la llaman. Usa los mismos nombres de acción que el servidor MCP. El objetivo puede ser una acción (list_invoices) o su operationId (getInvoices), la ruta de un comando (invoices list) o una sección (invoices, delivery-notes). Sin objetivo, lista las secciones. Con -j, -q o --agent la salida es JSON. ``` facturadirecta describe [objetivo...] [flags] ``` | Opción | Descripción | |---|---| | `--example int` | Solo el cuerpo de ejemplo n de la documentación (1 por defecto), listo para --data | | `--schema string` | Solo el esquema JSON completo del cuerpo (body) o de la respuesta (response) | ## `facturadirecta estimates attachments add` — Vincular uploads existentes al documento Vincular uploads existentes al documento Contrato completo: facturadirecta describe add_estimate_attachments ``` facturadirecta estimates attachments add ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta estimates attachments delete` — Eliminar un adjunto por su índice Eliminar un adjunto por su índice Contrato completo: facturadirecta describe delete_estimate_attachment ``` facturadirecta estimates attachments delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta estimates attachments list` — Listar adjuntos del documento Listar adjuntos del documento Contrato completo: facturadirecta describe get_estimate_attachments ``` facturadirecta estimates attachments list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta estimates attachments upload` — Subir y vincular archivos locales al documento Subir y vincular archivos locales al documento Operaciones de la API: create_upload, commit_upload, add_estimate_attachments. Contrato de cada una: facturadirecta describe ``` facturadirecta estimates attachments upload ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta estimates create` — Crear un presupuesto (JSON por stdin o --data) Crear un presupuesto (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags"} Contrato completo: facturadirecta describe create_estimate ``` facturadirecta estimates create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta estimates delete` — Eliminar un presupuesto Eliminar un presupuesto Contrato completo: facturadirecta describe delete_estimate ``` facturadirecta estimates delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta estimates get` — Obtener un presupuesto por UUID Obtener un presupuesto por UUID Contrato completo: facturadirecta describe get_estimate ``` facturadirecta estimates get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Permite obtener datos adicionales relacionados con el presupuesto. Valores: taxIds | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--url` | Imprimir solo la URL del elemento en la web de FacturaDirecta | ## `facturadirecta estimates list` — Listar presupuestos Listar presupuestos Contrato completo: facturadirecta describe list_estimates ``` facturadirecta estimates list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener el presupuesto para ser incluido en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener el presupuesto para ser incluido en los resultados | | `--company string` | ID de la empresa | | `--contact strings` | Cliente del presupuesto | | `--country string` | País de la dirección de facturación (Formato ISO 3166-1 Alpha-2) | | `--currency string` | Moneda del presupuesto (Formato ISO 4217) | | `--emails string` | Búsqueda parcial en los emails del documento (campo main.emails) | | `--formatted-series string` | Serie del presupuesto | | `--has-contact` | Presupuesto con contacto, utiliza el valor 'false' para obtener presupuestos sin contacto asociado | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-date string` | Fecha máxima del presupuesto en formato YYYY-MM-DD | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-number string` | Número secuencial máximo del presupuesto | | `--max-total string` | Importe total máximo del presupuesto | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha mínima del presupuesto en formato YYYY-MM-DD | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-number string` | Número secuencial mínimo del presupuesto | | `--min-total string` | Importe total mínimo del presupuesto | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Permite obtener datos adicionales relacionados con el presupuesto. Valores: taxIds | | `--search string` | Búsqueda aproximada en el texto del documento (título, líneas, notas) y en el nombre de su contacto: todas las palabras, sin… | | `--series string` | Serie del presupuesto sin aplicar posibles formatos de año (## ó ####) | | `--sort-by strings` | Orden de los resultados. Opciones: date, series, formattedSeries, number, total, currency, country, creationDate, modificationDate (prefijo - = descendente) | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | ## `facturadirecta estimates pdf` — Generar PDF de un presupuesto Generar PDF de un presupuesto Contrato completo: facturadirecta describe create_estimate_pdf ``` facturadirecta estimates pdf [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--mode string` | Modo de renderizado del PDF (inline, attachment, print) (por defecto: inline) | ## `facturadirecta estimates send` — Enviar un presupuesto por email (JSON por stdin o --data) Enviar un presupuesto por email (JSON por stdin o --data) Cuerpo (--data o stdin): {"from", "to", "cc", "bcc", "subject", "html"} Contrato completo: facturadirecta describe send_estimate ``` facturadirecta estimates send [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta estimates tags` — Actualizar etiquetas (JSON por stdin o --data) Actualizar etiquetas (JSON por stdin o --data) Cuerpo (--data o stdin): {"tags" (obligatorio), "tagsOperation" (obligatorio)} Contrato completo: facturadirecta describe update_estimate_tags ``` facturadirecta estimates tags [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta estimates update` — Actualizar un presupuesto (JSON por stdin o --data) Actualizar un presupuesto (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "syncContactData", "tags", "tagsOperation"} Contrato completo: facturadirecta describe update_estimate ``` facturadirecta estimates update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta fetch` — Detalle legible de un resultado de search (id tipo:empresa:id) Devuelve el elemento como texto legible (título, contacto, estado, líneas, totales…) más metadatos y URL. Con --json o --quiet devuelve el objeto completo; en terminal imprime el texto. Operaciones de la API: get_invoice, get_estimate, get_bill, get_contact, get_product. Contrato de cada una: facturadirecta describe ``` facturadirecta fetch ``` ## `facturadirecta inbox add` — Subir un archivo a la bandeja (upload + create) Subir un archivo a la bandeja (upload + create) Operaciones de la API: create_upload, commit_upload, create_inbox_item. Contrato de cada una: facturadirecta describe ``` facturadirecta inbox add [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--idempotency-key string` | Clave de idempotencia (opcional) | | `--title string` | Título del item (opcional) | ## `facturadirecta inbox archive` — Archivar un item de la bandeja Archivar un item de la bandeja Contrato completo: facturadirecta describe delete_inbox_item ``` facturadirecta inbox archive [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta inbox get` — Obtener un item de la bandeja por UUID Obtener un item de la bandeja por UUID Contrato completo: facturadirecta describe get_inbox_item ``` facturadirecta inbox get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta inbox list` — Listar items de la bandeja de entrada Listar items de la bandeja de entrada Contrato completo: facturadirecta describe list_inbox_items ``` facturadirecta inbox list [flags] ``` | Opción | Descripción | |---|---| | `--archived string` | Filtrar por estado de archivado: false. Valores: false, true, all | | `--company string` | ID de la empresa | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-date string` | Fecha y hora máxima de creación del item (createdAt), incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha y hora mínima de creación del item (createdAt), incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--offset string` | Posición de inicio de los resultados | | `--source string` | Filtrar por origen del item. Valores: upload, inbox, googleDrive, scanner | | `--status string` | Filtrar por estado del escaneo. Valores: pending, scanned, scan_failed | ## `facturadirecta inbox propose-bill` — Generar propuesta de factura de compra a partir de un item de la bandeja Generar propuesta de factura de compra a partir de un item de la bandeja Contrato completo: facturadirecta describe propose_bill ``` facturadirecta inbox propose-bill [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--contact-id string` | UUID del contacto (omite el matching automático) | ## `facturadirecta inbox propose-payroll` — Generar propuesta de nómina a partir de un item de la bandeja Generar propuesta de nómina a partir de un item de la bandeja Contrato completo: facturadirecta describe propose_payroll ``` facturadirecta inbox propose-payroll [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--contact-id string` | UUID del contacto (omite el matching automático) | ## `facturadirecta inbox propose-ticket` — Generar propuesta de ticket de compra a partir de un item de la bandeja Generar propuesta de ticket de compra a partir de un item de la bandeja Contrato completo: facturadirecta describe propose_ticket ``` facturadirecta inbox propose-ticket [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--contact-id string` | UUID del contacto (omite el matching automático) | ## `facturadirecta invoices attachments add` — Vincular uploads existentes al documento Vincular uploads existentes al documento Contrato completo: facturadirecta describe add_invoice_attachments ``` facturadirecta invoices attachments add ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta invoices attachments delete` — Eliminar un adjunto por su índice Eliminar un adjunto por su índice Contrato completo: facturadirecta describe delete_invoice_attachment ``` facturadirecta invoices attachments delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta invoices attachments list` — Listar adjuntos del documento Listar adjuntos del documento Contrato completo: facturadirecta describe get_invoice_attachments ``` facturadirecta invoices attachments list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta invoices attachments upload` — Subir y vincular archivos locales al documento Subir y vincular archivos locales al documento Operaciones de la API: create_upload, commit_upload, add_invoice_attachments. Contrato de cada una: facturadirecta describe ``` facturadirecta invoices attachments upload ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta invoices create` — Crear una factura (JSON por stdin o --data) Crear una factura (JSON por stdin o --data). main.draft decide si nace borrador (true) o emitida (false). Emitirla le asigna número y, si la empresa tiene VeriFactu o TicketBAI activos, genera el registro de facturación y lo envía a la AEAT o a la Diputación Foral en ese momento: ya no vuelve a ser borrador y solo se puede anular, rectificar o subsanar. Si se omite, se aplica sales.newInvoiceMode de la empresa, que puede ser final: envíalo siempre. Cuerpo (--data o stdin): {"content" (obligatorio), "tags", "payments"} Contrato completo: facturadirecta describe create_invoice ``` facturadirecta invoices create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta invoices delete` — Eliminar una factura Eliminar una factura Contrato completo: facturadirecta describe delete_invoice ``` facturadirecta invoices delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta invoices facturae` — Generar Facturae XML de una factura Generar Facturae XML de una factura Contrato completo: facturadirecta describe create_invoice_facturae ``` facturadirecta invoices facturae [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta invoices get` — Obtener una factura por UUID Obtener una factura por UUID Contrato completo: facturadirecta describe get_invoice ``` facturadirecta invoices get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Permite obtener datos adicionales relacionados con la factura. Valores: taxIds, balance, state, journal | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--url` | Imprimir solo la URL del elemento en la web de FacturaDirecta | ## `facturadirecta invoices list` — Listar facturas Listar facturas Contrato completo: facturadirecta describe list_invoices ``` facturadirecta invoices list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener la factura para ser incluida en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener la factura para ser incluida en los resultados | | `--company string` | ID de la empresa | | `--contact strings` | Cliente de la factura | | `--country string` | País de la dirección de facturación (Formato ISO 3166-1 Alpha-2) | | `--currency string` | Moneda de la factura (Formato ISO 4217) | | `--draft string` | Comportamiento de las facturas provisionales. Valores: only, all | | `--emails string` | Búsqueda parcial en los emails del documento (campo main.emails) | | `--formatted-series string` | Serie de la factura | | `--has-contact` | Factura con contacto, utiliza el valor 'false' para obtener las facturas simplificadas | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-date string` | Fecha máxima de la factura en formato YYYY-MM-DD | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-number string` | Número secuencial máximo de la factura | | `--max-total string` | Importe total máximo de la factura | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha mínima de la factura en formato YYYY-MM-DD | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-number string` | Número secuencial mínimo de la factura | | `--min-total string` | Importe total mínimo de la factura | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Permite obtener datos adicionales relacionados con la factura. Valores: taxIds, balance, state, journal | | `--search string` | Búsqueda aproximada en el texto del documento (título, líneas, notas) y en el nombre de su contacto: todas las palabras, sin… | | `--series string` | Serie de la factura sin aplicar posibles formatos de año (## ó ####) | | `--sort-by strings` | Orden de los resultados. Opciones: date, series, formattedSeries, number, total, currency, country, creationDate, modificationDate (prefijo - = descendente) | | `--state strings` | Estados de factura a incluir. Valores: draft, pending, overdue, paid, overpaid, voided | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--totals` | Añade totals: recuento y suma de base, impuestos, total y pendiente de TODO lo que cumple los filtros (no solo la página), por moneda. En la terminal sale bajo la tabla; en JSON, con -j o --jq .totals (-q solo da los elementos) | ## `facturadirecta invoices pay` — Registrar pagos de una factura (JSON por stdin o --data) Registra uno o varios cobros de la factura (JSON por stdin o --data: {"payments": [{"bank": "ban_…", "amount": …, "date": "AAAA-MM-DD"}]}). Si el cobro o pago corresponde a un movimiento del extracto bancario, o la cuenta está en una moneda distinta de la de la empresa y de la del documento, no lo registres aquí: concilia el movimiento con "facturadirecta statements reconcile-new --data '{"document":""}'". El movimiento queda conciliado y la API calcula la diferencia de cambio. Cuerpo (--data o stdin): {"payments" (obligatorio), "related"} Contrato completo: facturadirecta describe create_invoice_payments ``` facturadirecta invoices pay [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta invoices pdf` — Generar PDF de una factura Generar PDF de una factura Contrato completo: facturadirecta describe create_invoice_pdf ``` facturadirecta invoices pdf [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--mode string` | Modo: attachment, inline, print (por defecto: inline) | ## `facturadirecta invoices send` — Enviar una factura por email (JSON por stdin o --data) Enviar una factura por email (JSON por stdin o --data) Cuerpo (--data o stdin): {"from", "to", "cc", "bcc", "subject", "html", "sendFacturae", "continueWithoutFacturae"} Contrato completo: facturadirecta describe send_invoice ``` facturadirecta invoices send [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta invoices substitution` — Crear factura sustitutiva (JSON por stdin o --data) Crear factura sustitutiva (JSON por stdin o --data) Cuerpo (--data o stdin): {"contact" (obligatorio), "substitutedInvoices" (obligatorio), "docNumber", "date", "notes", "internalNotes", "tags"} Contrato completo: facturadirecta describe create_substitution_invoice ``` facturadirecta invoices substitution [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta invoices tags` — Actualizar etiquetas de una factura (JSON por stdin o --data) Actualizar etiquetas de una factura (JSON por stdin o --data) Cuerpo (--data o stdin): {"tags" (obligatorio), "tagsOperation" (obligatorio)} Contrato completo: facturadirecta describe update_invoice_tags ``` facturadirecta invoices tags [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta invoices update` — Actualizar una factura (JSON por stdin o --data) Actualizar una factura (JSON por stdin o --data). Sustituye el contenido: parte de la lectura, cambia lo necesario y reenvía el content completo. Pasar main.draft a false emite la factura y pasar main.voided a true la anula; con VeriFactu o TicketBAI activos, las dos operaciones generan registros que se envían a Hacienda y no se pueden revertir. Una factura ya emitida con VeriFactu solo admite cambios subsanables (verifactuOperation.subsanacion) y con TicketBAI no admite cambios en su contenido. Cuerpo (--data o stdin): {"content" (obligatorio), "syncContactData", "verifactuOperation", "tags", "tagsOperation"} Contrato completo: facturadirecta describe update_invoice ``` facturadirecta invoices update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta journal` — Diario contable (apuntes uno a uno; para totales, `accounting results`) Diario contable (apuntes uno a uno; para totales, `accounting results`) Contrato completo: facturadirecta describe list_journal ``` facturadirecta journal [flags] ``` | Opción | Descripción | |---|---| | `--account string` | Prefijo o cuenta completa (1-6 dígitos; ej.: 6 = gastos, 64 = personal, 700000 = ventas) | | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener el documento que contiene al apunte para ser incluido en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener el documento que contiene al apunte para ser incluido en los resultados | | `--company string` | ID de la empresa | | `--document string` | ID del documento al que se IMPUTAN los apuntes (contacto, producto o banco, como subcuenta) | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--id string` | ID del documento que ORIGINA los apuntes (asiento, factura, gasto, nómina o remesa): su asiento completo | | `--journal-priority string` | Indica un valor para obtener solo asientos del diario según su tipo | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-date string` | Fecha máxima del apunte en formato YYYY-MM-DD | | `--min-date string` | Fecha mínima del apunte en formato YYYY-MM-DD | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Permite obtener datos adicionales relacionados con el registro del diario. Valores: tags, currency, idAccounting | | `--sort-by strings` | Orden de los resultados. Opciones: date (prefijo - = descendente) | ## `facturadirecta payment-methods create` — Crear un método de pago (JSON por stdin o --data) Crear un método de pago (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio)} Contrato completo: facturadirecta describe create_payment_method ``` facturadirecta payment-methods create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta payment-methods delete` — Eliminar un método de pago Eliminar un método de pago Contrato completo: facturadirecta describe delete_payment_method ``` facturadirecta payment-methods delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta payment-methods get` — Obtener un método de pago por UUID Obtener un método de pago por UUID Contrato completo: facturadirecta describe get_payment_method ``` facturadirecta payment-methods get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta payment-methods list` — Listar métodos de pago Listar métodos de pago Contrato completo: facturadirecta describe list_payment_methods ``` facturadirecta payment-methods list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--contact string` | Id del contacto para el que se obtendrán los métodos de pago | | `--include-global` | Indica si quieres incluir en los resultados los métodos de pago globales (asignables a todos los clientes) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--offset string` | Posición de inicio de los resultados | | `--sort-by strings` | Orden de los resultados. Opciones: title, creationDate, modificationDate (prefijo - = descendente) | ## `facturadirecta payment-methods update` — Actualizar un método de pago (JSON por stdin o --data) Actualizar un método de pago (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio)} Contrato completo: facturadirecta describe update_payment_method ``` facturadirecta payment-methods update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta payrolls attachments add` — Vincular uploads existentes al documento Vincular uploads existentes al documento Contrato completo: facturadirecta describe add_payroll_attachments ``` facturadirecta payrolls attachments add ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta payrolls attachments delete` — Eliminar un adjunto por su índice Eliminar un adjunto por su índice Contrato completo: facturadirecta describe delete_payroll_attachment ``` facturadirecta payrolls attachments delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta payrolls attachments list` — Listar adjuntos del documento Listar adjuntos del documento Contrato completo: facturadirecta describe get_payroll_attachments ``` facturadirecta payrolls attachments list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta payrolls attachments upload` — Subir y vincular archivos locales al documento Subir y vincular archivos locales al documento Operaciones de la API: create_upload, commit_upload, add_payroll_attachments. Contrato de cada una: facturadirecta describe ``` facturadirecta payrolls attachments upload ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta payrolls create` — Crear una nómina (JSON por stdin o --data) Crear una nómina (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags", "payments", "fromInbox"} Contrato completo: facturadirecta describe create_payroll ``` facturadirecta payrolls create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta payrolls delete` — Eliminar una nómina Eliminar una nómina Contrato completo: facturadirecta describe delete_payroll ``` facturadirecta payrolls delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta payrolls get` — Obtener una nómina por UUID Obtener una nómina por UUID Contrato completo: facturadirecta describe get_payroll ``` facturadirecta payrolls get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Permite obtener datos adicionales relacionados con la nómina. Valores: balance, state, journal | ## `facturadirecta payrolls list` — Listar nóminas Listar nóminas Contrato completo: facturadirecta describe list_payrolls ``` facturadirecta payrolls list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener la nómina para ser incluido en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener la nómina para ser incluido en los resultados | | `--company string` | ID de la empresa | | `--contact strings` | Empleado | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-date string` | Fecha máxima de la nómina en formato YYYY-MM-DD | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-total string` | Importe total máximo de la nómina | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha mínima de la nómina en formato YYYY-MM-DD | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-total string` | Importe total mínimo de la nómina | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Permite obtener datos adicionales relacionados con la nómina. Valores: balance, state, journal | | `--search string` | Búsqueda aproximada en el texto del documento (título, líneas, notas) y en el nombre de su contacto: todas las palabras, sin… | | `--sort-by strings` | Orden de los resultados. Opciones: date, total, creationDate, modificationDate (prefijo - = descendente) | | `--state strings` | Estado de la nómina. Valores: pending, overdue, paid, overpaid | ## `facturadirecta payrolls pay` — Registrar pagos de una nómina (JSON por stdin o --data) Registra uno o varios pagos de la nómina (JSON por stdin o --data: {"payments": [{"bank": "ban_…", "amount": …, "date": "AAAA-MM-DD"}]}). Si el cobro o pago corresponde a un movimiento del extracto bancario, o la cuenta está en una moneda distinta de la de la empresa y de la del documento, no lo registres aquí: concilia el movimiento con "facturadirecta statements reconcile-new --data '{"document":""}'". El movimiento queda conciliado y la API calcula la diferencia de cambio. Cuerpo (--data o stdin): {"payments" (obligatorio), "related"} Contrato completo: facturadirecta describe create_payroll_payments ``` facturadirecta payrolls pay [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta payrolls tags` — Actualizar etiquetas de una nómina (JSON por stdin o --data) Actualizar etiquetas de una nómina (JSON por stdin o --data) Cuerpo (--data o stdin): {"tags" (obligatorio), "tagsOperation" (obligatorio)} Contrato completo: facturadirecta describe update_payroll_tags ``` facturadirecta payrolls tags [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta payrolls update` — Actualizar una nómina (JSON por stdin o --data) Actualizar una nómina (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags", "tagsOperation"} Contrato completo: facturadirecta describe update_payroll ``` facturadirecta payrolls update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta products create` — Crear un producto (datos en JSON por stdin o --data) Crear un producto (datos en JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio)} Contrato completo: facturadirecta describe create_product ``` facturadirecta products create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos del producto en JSON | ## `facturadirecta products delete` — Eliminar un producto Eliminar un producto Contrato completo: facturadirecta describe delete_product ``` facturadirecta products delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta products get` — Obtener un producto por UUID Obtener un producto por UUID Contrato completo: facturadirecta describe get_product ``` facturadirecta products get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--url` | Imprimir solo la URL del elemento en la web de FacturaDirecta | ## `facturadirecta products list` — Listar productos Listar productos Contrato completo: facturadirecta describe list_products ``` facturadirecta products list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--external-id string` | ID externo del producto | | `--is-depreciable` | Bien de inversión | | `--is-purchases` | Producto de compra | | `--is-sales` | Producto de venta | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--offset string` | Posición de inicio de los resultados | | `--purchases-description string` | Búsqueda aproximada por descripción de compra | | `--sales-description string` | Búsqueda aproximada por descripción de venta | | `--search string` | Búsqueda aproximada en el título, la referencia y las descripciones del producto: todas las palabras, sin distinguir acentos ni… | | `--sku string` | Código de producto (SKU) | | `--sort-by strings` | Orden de los resultados. Opciones: title, sku, salesDescription, purchasesDescription, creationDate, modificationDate (prefijo - = descendente) | | `--summary` | Resumen compacto por elemento en las salidas JSON: id, número, fecha, contacto, totales, estado, saldo pendiente, número de adjuntos y url (en facturas y gastos pide related=state,balance) | | `--title string` | Búsqueda aproximada por nombre | ## `facturadirecta products update` — Actualizar un producto (JSON por stdin o --data) Actualizar un producto (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio)} Contrato completo: facturadirecta describe update_product ``` facturadirecta products update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta query` — Consulta analítica: suma, cuenta y clasifica facturas, compras, contactos o apuntes en una llamada La consulta analítica de la API: una fuente (--from invoices, invoiceLines, bills, billLines, contacts o journal), medidas (--measures), hasta dos dimensiones por las que agrupar (--by), un periodo (--period) con comparativa (--compare) o desglose (--breakdown), filtros (--filter), orden (--order), límite (--limit) y, con dos dimensiones, límite por grupo (--limit-per-group). Responde los grupos ordenados y los totales del conjunto filtrado: en la terminal, una tabla; con -j o -q, el JSON de la API (columns, rows, totals, other, truncated). Sustituye a listar y sumar: «mis cinco mejores clientes», «¿qué producto vendo más?», «¿cuánto suman las facturas vencidas?», «gastos de septiembre por proveedor», «provincias con más facturación frente al periodo anterior». El vocabulario de cada fuente (medidas, dimensiones y filtros) está en `facturadirecta describe query`; un nombre fuera del vocabulario responde 400 con el más parecido. Las cifras de invoices y bills son las de los documentos (base, impuestos, total y pendiente), y las de invoiceLines y billLines, las de sus líneas (producto, cantidad e importe sin impuestos); las de journal, las del diario, y exigen el módulo de Contabilidad. Con varias monedas, --amounts company da los importes en la moneda de la empresa en vez de separarlos por moneda. --period: un año (2026), un trimestre (2026-Q3), un mes (2026-09) o dos fechas (2026-01-01..2026-06-30; una de las dos puede faltar); --start y --end fijan cada extremo. Sin periodo, el año natural en curso. --filter (repetible): campo=valor (eq), 'campo in a,b' (in), campo>=valor y campo<=valor (gte, lte), 'campo between a,b' y campo^=prefijo (startsWith). true y false son booleanos; en gte, lte y between los números son números. --order (repetible): medida o dimensión, con :asc o :desc (por defecto, desc). --data: la petición completa en JSON ('{…}', @fichero o stdin) en vez de las flags. Cuerpo (--data o stdin): {"from" (obligatorio), "measures", "dimensions", "period", "compare", "breakdown", "filters", "orderBy", "limit", "limitPerGroup", "includeOther", "amounts"} Contrato completo: facturadirecta describe query ``` facturadirecta query [flags] ``` | Opción | Descripción | |---|---| | `--amounts string` | Moneda de los importes en invoices, bills y sus líneas: document (por defecto, la de cada documento, separados por moneda) o company (la de la empresa, en un solo grupo) | | `--breakdown string` | Desglose por tramos naturales: month, quarter o year (añade la dimensión period) | | `--by strings` | Dimensiones por las que agrupar, hasta dos (contact, fiscalId, contact.region, country, series, state, month…; en journal, account, thirdParty, category…) | | `--company string` | ID de la empresa | | `--compare string` | Comparativa: previousPeriod o previousYear (añade _reference, _difference y _variance a cada medida) | | `--data string` | Petición completa en JSON ('{…}' o @fichero), en vez de las flags; sin --data ni --from, se lee de stdin | | `--end string` | Último día del periodo, incluido (AAAA-MM-DD) | | `--filter strings` | Filtro, repetible: campo=valor, 'campo in a,b', campo>=valor, campo<=valor, 'campo between a,b', campo^=prefijo | | `--from string` | Fuente: invoices, invoiceLines, bills, billLines, contacts o journal | | `--limit int` | Filas como máximo, entre 1 y 500 (por defecto, 10) | | `--limit-per-group int` | Con dos dimensiones (--by), filas como máximo de la segunda dentro de cada valor de la primera («los tres productos más vendidos a cada cliente»); no se combina con --compare, --breakdown ni --other | | `--measures strings` | Medidas, separadas por comas (count, contacts, totalBeforeTaxes, total, pending, average, daysToPayment.median…; en journal, net, income, expenses, result…). Sin indicar, la medida por defecto de la fuente | | `--order strings` | Orden, repetible: medida o dimensión, con :asc o :desc (por defecto, desc) | | `--other` | Añade other: cuántos grupos quedan fuera de --limit y la suma de sus medidas | | `--period string` | Periodo: 2026, 2026-Q3, 2026-09 o 2026-01-01..2026-06-30 (por defecto, el año natural en curso) | | `--start string` | Primer día del periodo, incluido (AAAA-MM-DD) | ## `facturadirecta recurring attachments add` — Vincular uploads existentes al documento Vincular uploads existentes al documento Contrato completo: facturadirecta describe add_recurring_invoice_attachments ``` facturadirecta recurring attachments add ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta recurring attachments delete` — Eliminar un adjunto por su índice Eliminar un adjunto por su índice Contrato completo: facturadirecta describe delete_recurring_invoice_attachment ``` facturadirecta recurring attachments delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta recurring attachments list` — Listar adjuntos del documento Listar adjuntos del documento Contrato completo: facturadirecta describe get_recurring_invoice_attachments ``` facturadirecta recurring attachments list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta recurring attachments upload` — Subir y vincular archivos locales al documento Subir y vincular archivos locales al documento Operaciones de la API: create_upload, commit_upload, add_recurring_invoice_attachments. Contrato de cada una: facturadirecta describe ``` facturadirecta recurring attachments upload ... [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta recurring create` — Crear una factura recurrente (JSON por stdin o --data) Crear una factura recurrente (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "tags"} Contrato completo: facturadirecta describe create_recurring_invoice ``` facturadirecta recurring create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta recurring delete` — Eliminar una factura recurrente Eliminar una factura recurrente Contrato completo: facturadirecta describe delete_recurring_invoice ``` facturadirecta recurring delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta recurring get` — Obtener una factura recurrente por UUID Obtener una factura recurrente por UUID Contrato completo: facturadirecta describe get_recurring_invoice ``` facturadirecta recurring get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Permite obtener datos adicionales relacionados con la factura recurrente. Valores: taxIds, nextScheduledTime, upcomingSceheduledTimes | ## `facturadirecta recurring list` — Listar facturas recurrentes Listar facturas recurrentes Contrato completo: facturadirecta describe list_recurring_invoices ``` facturadirecta recurring list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener la factura recurrente para ser incluida en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener la factura recurrente para ser incluida en los resultados | | `--company string` | ID de la empresa | | `--contact strings` | Cliente de la factura recurrente | | `--country string` | País de la dirección de facturación (Formato ISO 3166-1 Alpha-2) | | `--currency string` | Moneda de la factura recurrente (Formato ISO 4217) | | `--enabled` | Indica si la factura recurrente está activa | | `--freq string` | Unidad de la frecuencia de ejecución. Valores: yearly, monthly, weekly, daily | | `--has-contact` | Factura recurrente con contacto, utiliza el valor 'false' para obtener facturas recurrentes sin contacto asociado | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--interval string` | Número de días/semanas/meses/años entre ejecuciones | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-next-scheduled-date string` | Fecha y hora máxima de la próxima ejecución de la factura (nextScheduledTime), incluida, en formato ISO 8601… | | `--max-total string` | Importe total máximo de la factura recurrente | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-next-scheduled-date string` | Fecha y hora mínima de la próxima ejecución de la factura (nextScheduledTime), incluida, en formato ISO 8601… | | `--min-total string` | Importe total mínimo de la factura recurrente | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Permite obtener datos adicionales relacionados con la factura recurrente. Valores: taxIds, nextScheduledTime, upcomingSceheduledTimes | | `--search string` | Búsqueda aproximada en el texto de la factura recurrente (título, líneas, notas) y en el nombre de su contacto: todas las… | | `--series string` | Serie de la factura recurrente sin aplicar posibles formatos de año (## ó ####) | | `--sort-by strings` | Orden de los resultados. Opciones: nextScheduledTime, series, total, currency, country, creationDate, modificationDate (prefijo - = descendente) | ## `facturadirecta recurring pdf` — Generar PDF de una factura recurrente Generar PDF de una factura recurrente Contrato completo: facturadirecta describe create_recurring_invoice_pdf ``` facturadirecta recurring pdf [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--mode string` | Modo de renderizado del PDF (inline, attachment, print) (por defecto: inline) | ## `facturadirecta recurring tags` — Actualizar etiquetas (JSON por stdin o --data) Actualizar etiquetas (JSON por stdin o --data) Cuerpo (--data o stdin): {"tags" (obligatorio), "tagsOperation" (obligatorio)} Contrato completo: facturadirecta describe update_recurring_invoice_tags ``` facturadirecta recurring tags [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta recurring update` — Actualizar una factura recurrente (JSON por stdin o --data) Actualizar una factura recurrente (JSON por stdin o --data) Cuerpo (--data o stdin): {"content" (obligatorio), "syncContactData", "tags", "tagsOperation"} Contrato completo: facturadirecta describe update_recurring_invoice ``` facturadirecta recurring update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta search` — Buscar contactos, productos, facturas, presupuestos y gastos por texto o número Busca en una sola llamada: contactos por nombre, NIF o email; productos por nombre; facturas y presupuestos por número ("FAC-2026-12", "F 12", "12"); y los últimos documentos de los contactos encontrados. Cada resultado lleva un id `tipo:empresa:id` para `fetch` y la URL en la web. Sin empresa fijada y con varias empresas, busca en hasta 5. Acota con --type para reducir las llamadas a la API. Operaciones de la API: list_contacts, list_products, list_invoices, list_estimates, list_bills. Contrato de cada una: facturadirecta describe ``` facturadirecta search [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa (sin ella, la resuelta como en cualquier comando; con varias sin fijar, hasta 5) | | `--type strings` | Tipos a buscar: invoice, estimate, bill, contact, product (por defecto todos) | ## `facturadirecta settings company` — Ver la ficha de la empresa y su forma de trabajar Datos fiscales y de contacto, moneda, idioma y zona horaria, modo borrador o definitivo de facturas y gastos, impuestos, numeración, fechas de inicio y bloqueo contable, VeriFactu y TicketBAI, remitentes de email, stock, portal de clientes, canales de entrada de la bandeja (buzón por correo y Google Drive) y módulos del plan. Cambia muy poco: consúltala al empezar a trabajar con una empresa. inbox.email.address es el buzón de la bandeja: todo correo que llegue a esa dirección entra en la bandeja sin verificar el remitente, así que no la publiques. Es solo lectura; la configuración se cambia en la web (content.webUrl, también con --url). Contrato completo: facturadirecta describe get_company_settings ``` facturadirecta settings company [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--url` | Imprimir solo la URL de los ajustes de la empresa en la web de FacturaDirecta | ## `facturadirecta settings series` — Ver series configuradas para un tipo de documento (invoice, estimate, deliveryNote, clientOrder, purchaseOrder) Ver series configuradas para un tipo de documento (invoice, estimate, deliveryNote, clientOrder, purchaseOrder) Contrato completo: facturadirecta describe get_series ``` facturadirecta settings series [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta settings taxes-purchases` — Ver impuestos de compras configurados Ver impuestos de compras configurados Contrato completo: facturadirecta describe get_purchases_taxes ``` facturadirecta settings taxes-purchases [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--tax-group strings` | Grupo de impuestos. Valores: IVA, IVA_RE, IGIC, IPSI, IRPF | ## `facturadirecta settings taxes-sales` — Ver impuestos de ventas configurados Ver impuestos de ventas configurados Contrato completo: facturadirecta describe get_sales_taxes ``` facturadirecta settings taxes-sales [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--tax-group strings` | Grupo de impuestos. Valores: IVA, IVA_RE, IGIC, IPSI, IRPF, VATEU | ## `facturadirecta settings themes` — Ver temas de diseño disponibles Ver temas de diseño disponibles Contrato completo: facturadirecta describe get_themes ``` facturadirecta settings themes [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta setup` — Configuración inicial guiada Asistente de configuración inicial: autenticación y selección de empresa. ``` facturadirecta setup ``` ## `facturadirecta statements get` — Obtener un extracto bancario por UUID Obtener un extracto bancario por UUID Contrato completo: facturadirecta describe get_statement ``` facturadirecta statements get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta statements list` — Listar extractos bancarios Listar extractos bancarios Contrato completo: facturadirecta describe list_statements ``` facturadirecta statements list [flags] ``` | Opción | Descripción | |---|---| | `--bank string` | Identificador del banco para filtrar los extractos | | `--company string` | ID de la empresa | | `--currency string` | Moneda del extracto bancario (Formato ISO 4217) | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--matched-document string` | Identificador del asiento contable con el que está conciliado el extracto | | `--max-amount string` | Importe máximo del movimiento | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-date string` | Fecha máxima de la operación en formato YYYY-MM-DD | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-amount string` | Importe mínimo del movimiento | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha mínima de la operación en formato YYYY-MM-DD | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--offset string` | Posición de inicio de los resultados | | `--sort-by strings` | Orden de los resultados. Opciones: date, amount, currency, state, creationDate, modificationDate (prefijo - = descendente) | | `--state string` | Estado de conciliación del extracto. Valores: pending, reconciled, ignored | ## `facturadirecta statements reconcile` — Conciliar un extracto bancario (JSON por stdin o --data) Conciliar un extracto bancario (JSON por stdin o --data) Cuerpo (--data o stdin): {"transactionId" (obligatorio)} Contrato completo: facturadirecta describe reconcile_statement ``` facturadirecta statements reconcile [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta statements reconcile-new` — Conciliar un extracto creando un nuevo asiento (JSON por stdin o --data) Crea un asiento contable y lo concilia con el movimiento del extracto en una sola llamada. Para cobrar o pagar una factura de venta, un gasto o una nómina con este movimiento, envía solo {"document": ""} (inv_, bil_ o par_): la API construye el asiento, también cuando el banco o el documento están en otra moneda (apunte del banco en su moneda con su contravalor, documento saldado a su tipo de cambio y diferencia de cambio en su cuenta). Añade amount (en la moneda del documento) solo para un cobro o pago parcial con documento y banco en monedas distintas. Usa lines (excluyente con document) solo para movimientos sin documento, como comisiones o traspasos. Si el cobro o pago ya está registrado, usa "statements reconcile" con su transactionId. Cuerpo (--data o stdin): {"document", "amount", "lines", "title"} Contrato completo: facturadirecta describe reconcile_statement_with_new_transaction ``` facturadirecta statements reconcile-new [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta statements unreconcile` — Desconciliar un extracto bancario Desconciliar un extracto bancario Contrato completo: facturadirecta describe unreconcile_statement ``` facturadirecta statements unreconcile [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--delete-transaction` | Si es true, además de desconciliar se archiva el asiento contable asociado | ## `facturadirecta tax-declarations aeat-file` — Devolver el fichero de presentación de la AEAT del modelo (url y filename) El fichero con el diseño de registro BOE del modelo guardado, el mismo que descarga el botón «Descargar fichero AEAT» de la web; se genera una vez y se reutiliza mientras el modelo no cambie. La presentación sigue haciéndose desde la Sede de la AEAT. El 390 no tiene fichero y responde 404. Contrato completo: facturadirecta describe create_tax_declaration_aeat_file ``` facturadirecta tax-declarations aeat-file [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta tax-declarations attachments list` — Listar adjuntos del documento Listar adjuntos del documento Contrato completo: facturadirecta describe get_tax_declaration_attachments ``` facturadirecta tax-declarations attachments list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta tax-declarations get` — Obtener un modelo con sus casillas (y sus registros por tercero con --related records) Devuelve el modelo con sus casillas (boxes) tal como se escriben en el fichero de la AEAT: key es el número oficial de la casilla o la clave de control, title el texto del formulario y value el importe, código o texto. --related records añade, en las informativas, los registros por tercero (datos personales de terceros); --related journal, el asiento contable. El IBAN sale enmascarado salvo con el scope banks:readIban. Contrato completo: facturadirecta describe get_tax_declaration ``` facturadirecta tax-declarations get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--related strings` | Información adicional: records (registros por tercero de las informativas) y journal (el asiento contable); repetible o separado por comas | | `--url` | Imprimir solo la URL del elemento en la web de FacturaDirecta | ## `facturadirecta tax-declarations list` — Listar modelos de impuestos (del más reciente al más antiguo) Lista los modelos guardados con su sobre: modelo, ejercicio, periodo, resultado (positivo a ingresar, negativo a compensar o devolver; vacío en las informativas), si tiene fichero AEAT y cuántos adjuntos. Las casillas solo salen con --related boxes (un 303 trae unas sesenta: pide pocas filas) y el asiento contable con --related journal. La fecha de un modelo es el último día de su periodo. Contrato completo: facturadirecta describe list_tax_declarations ``` facturadirecta tax-declarations list [flags] ``` | Opción | Descripción | |---|---| | `--all-these-tags strings` | Listado de todas las etiquetas que debe contener el modelo de impuestos para ser incluido en los resultados | | `--any-of-these-tags strings` | Listado de cualquiera de las etiquetas que debe contener el modelo de impuestos para ser incluido en los resultados | | `--company string` | ID de la empresa | | `--fiscal-period strings` | Periodo: trimestre (1T…4T). Valores: 1T, 2T, 3T, 4T, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 1P, 2P, 3P | | `--fiscal-year string` | Ejercicio | | `--form strings` | Número de modelo. Valores: 111, 115, 130, 180, 190, 202, 303, 347, 349, 390 | | `--has-tags` | Solo con etiquetas (true) o solo sin etiquetas (false) | | `--kind strings` | Tipo de modelo: autoliquidaciones (selfAssessment) o declaraciones informativas (informative). Valores: selfAssessment, informative | | `--limit string` | Número máximo de resultados, valores permitidos entre 1 y 500 (por defecto: 25) | | `--max-creation-date string` | Fecha y hora máxima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--max-date string` | Fecha máxima del modelo (el último día de su periodo) en formato YYYY-MM-DD | | `--max-modification-date string` | Fecha y hora máxima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-creation-date string` | Fecha y hora mínima de creación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--min-date string` | Fecha mínima del modelo (el último día de su periodo) en formato YYYY-MM-DD | | `--min-modification-date string` | Fecha y hora mínima de modificación del documento, incluida, en formato ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ); el + de un desfase… | | `--offset string` | Posición de inicio de los resultados | | `--related strings` | Información adicional por modelo: boxes (las casillas) y journal (el asiento contable); repetible o separado por comas | | `--sort-by strings` | Orden de los resultados. Opciones: date, total, fiscalYear, creationDate, modificationDate (prefijo - = descendente) | | `--status strings` | Estado del modelo. Valores: generated | ## `facturadirecta tax-declarations pdf` — Generar el PDF resumen del modelo (casillas y valores) y devolver su URL temporal El PDF que imprime la web, generado una vez y reutilizado mientras el modelo no cambie. No es el fichero de presentación de la AEAT (`aeat-file`) ni los adjuntos del modelo (`attachments list`). Contrato completo: facturadirecta describe create_tax_declaration_pdf ``` facturadirecta tax-declarations pdf [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--mode string` | Modo: attachment, inline, print (por defecto: inline) | ## `facturadirecta update` — Actualizar facturadirecta a la última versión ``` facturadirecta update [flags] ``` | Opción | Descripción | |---|---| | `--force` | Actualizar sin pedir confirmación | | `--version string` | Actualizar a una versión específica (ej: v0.5.0) | ## `facturadirecta uploads commit` — Sellar un upload presigned tras el PUT a S3 Sellar un upload presigned tras el PUT a S3 Contrato completo: facturadirecta describe commit_upload ``` facturadirecta uploads commit [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta uploads create` — Solicitar un slot de upload (devuelve URL presigned + uploadId) Solicitar un slot de upload (devuelve URL presigned + uploadId) Contrato completo: facturadirecta describe create_upload ``` facturadirecta uploads create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--content-type string` | MIME type (opcional) | ## `facturadirecta uploads file` — Subir un archivo local (create + PUT S3 + commit) y mostrar el uploadId Subir un archivo local (create + PUT S3 + commit) y mostrar el uploadId Operaciones de la API: create_upload, commit_upload. Contrato de cada una: facturadirecta describe ``` facturadirecta uploads file [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta webhooks endpoints create` — Crear un endpoint (JSON por stdin o --data) Crear un endpoint (JSON por stdin o --data) Cuerpo (--data o stdin): {"name" (obligatorio), "url" (obligatorio), "events" (obligatorio)} Contrato completo: facturadirecta describe create_public_webhook_endpoint ``` facturadirecta webhooks endpoints create [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta webhooks endpoints delete` — Eliminar un endpoint Eliminar un endpoint Contrato completo: facturadirecta describe delete_public_webhook_endpoint ``` facturadirecta webhooks endpoints delete [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--yes` | Confirmar el borrado sin preguntar (es irreversible) | ## `facturadirecta webhooks endpoints disable` — Desactivar un endpoint Desactivar un endpoint Contrato completo: facturadirecta describe disable_public_webhook_endpoint ``` facturadirecta webhooks endpoints disable [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta webhooks endpoints enable` — Activar un endpoint Activar un endpoint Contrato completo: facturadirecta describe enable_public_webhook_endpoint ``` facturadirecta webhooks endpoints enable [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta webhooks endpoints get` — Obtener un endpoint Obtener un endpoint Contrato completo: facturadirecta describe get_public_webhook_endpoint ``` facturadirecta webhooks endpoints get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta webhooks endpoints list` — Listar endpoints Listar endpoints Contrato completo: facturadirecta describe list_public_webhook_endpoints ``` facturadirecta webhooks endpoints list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta webhooks endpoints rotate-secret` — Rotar el secreto de un endpoint Rotar el secreto de un endpoint Contrato completo: facturadirecta describe rotate_public_webhook_endpoint_secret ``` facturadirecta webhooks endpoints rotate-secret [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta webhooks endpoints update` — Actualizar un endpoint (JSON por stdin o --data) Actualizar un endpoint (JSON por stdin o --data) Cuerpo (--data o stdin): {"name", "url", "events"} Contrato completo: facturadirecta describe update_public_webhook_endpoint ``` facturadirecta webhooks endpoints update [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--data string` | Datos en JSON | ## `facturadirecta webhooks events get` — Obtener un evento Obtener un evento Contrato completo: facturadirecta describe get_public_webhook_event ``` facturadirecta webhooks events get [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta webhooks events list` — Listar eventos Listar eventos Contrato completo: facturadirecta describe list_public_webhook_events ``` facturadirecta webhooks events list [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | | `--cursor string` | Cursor de paginación: el nextCursor de la página anterior, tal cual se recibió | | `--endpoint-id string` | Filtrar por endpoint | | `--limit string` | Número máximo de resultados (máx. 100) | | `--status string` | Filtrar por estado (pending, delivered, failed) | | `--type string` | Filtrar por tipo de evento | ## `facturadirecta webhooks events retry` — Reintentar envío de un evento Reintentar envío de un evento Contrato completo: facturadirecta describe retry_public_webhook_event ``` facturadirecta webhooks events retry [flags] ``` | Opción | Descripción | |---|---| | `--company string` | ID de la empresa | ## `facturadirecta whoami` — Quién soy: usuario, acceso, empresa que usarán los comandos y empresas disponibles Muestra el perfil activo, el usuario autenticado, el modo de acceso, la conexión con FacturaDirecta (empresas y permisos elegidos para el CLI), la empresa que usaría cualquier comando (--company > FACTURADIRECTA_COMPANY > perfil > única empresa) y todas las empresas disponibles, con los permisos de la conexión en cada una. Con --json o --quiet devuelve un objeto compacto. Operaciones de la API: get_profile, get_public_api_key_info. Contrato de cada una: facturadirecta describe ``` facturadirecta whoami [flags] ``` | Opción | Descripción | |---|---| | `--company string` | Empresa a comprobar (como la usaría cualquier comando con --company) | ## Opciones globales | Opción | Descripción | |---|---| | `--agent` | Modo agente (equivale a --quiet) | | `--count` | Solo el número de resultados | | `--ids-only` | Solo IDs, uno por línea | | `--jq string` | Filtro jq integrado (implica --json) | | `-j, --json` | Salida en formato JSON | | `--profile string` | Perfil de autenticación a usar | | `-q, --quiet` | Solo datos JSON, sin metadatos | --- # API REST > API REST de FacturaDirecta: guías de autenticación, paginación, errores e idempotencia, y referencia de cada recurso con ejemplos listos para copiar. La API pública de FacturaDirecta es una API REST con JSON. Con ella puedes crear y consultar facturas, presupuestos, gastos, contactos, productos, nóminas o apuntes contables, y suscribirte a webhooks para enterarte al momento de cada cambio. Está disponible en todos los planes, también en el Gratis, y cada empresa puede tener sus propios entornos de pruebas con API keys independientes. - **Autenticación:** API key por empresa en la cabecera `facturadirecta-api-key`, u OAuth 2.0 con PKCE para actuar en nombre de un usuario. Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). - **Límites:** sin cupo mensual de peticiones, solo un límite de ritmo por minuto que depende del plan. Ver [precios](https://www.facturadirecta.com/pequenos-negocios/precios/). - **Contrato:** la especificación OpenAPI completa está siempre publicada y al día. --- # API Pública de FacturaDirecta > Conecta tu software con FacturaDirecta mediante la API REST pública. Accede a contactos, productos, facturas, presupuestos, albaranes, gastos, bancos, webhooks y más. La **API pública de FacturaDirecta** te permite integrar tu software con el sistema de facturación. Está disponible en todos los planes, incluido **Gratis**. Cada plan aplica su propio límite de peticiones por minuto: 30 en Gratis, 60 en Bronce, 120 en Plata, 300 en Oro y 600 en Diamante. El detalle y las cabeceras que devuelve la API están en [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/#límite-de-peticiones). Algunos recursos dependen además del plan contratado: los [pedidos](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/), las [órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/) y el control de stock de los [productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/) forman parte del **Módulo Inventario**. Puedes automatizar procesos como crear facturas desde un CRM, sincronizar contactos con un ERP, conectar una tienda online o generar informes personalizados con datos de facturación y contabilidad. La API es REST, usa JSON y se documenta mediante OpenAPI. > **Recomendación**: antes de empezar a desarrollar, crea un [entorno de pruebas > (sandbox)](https://help.facturadirecta.com/es/articles/15157082-entorno-de-pruebas-sandbox) > para hacer tus primeras llamadas sin afectar a los datos reales de tu empresa. ## URL base y referencia OpenAPI Todas las URLs de la API pública en producción tienen este prefijo: ```text https://app.facturadirecta.com/api ``` Recursos técnicos: - **Swagger UI / referencia navegable**: `https://www.facturadirecta.com/api` - **OpenAPI crudo**: `https://app.facturadirecta.com/openapi.json` Puedes importar el OpenAPI en herramientas como Postman o Insomnia, o generar clientes automáticamente con herramientas tipo OpenAPI Generator. ## Autenticación La API admite dos métodos de autenticación: ### API key Recomendado para integraciones de una empresa concreta: scripts internos, conectores, plugins o automatizaciones. Crea la clave desde FacturaDirecta y envíala en la cabecera: ```http facturadirecta-api-key: TU_CLAVE_API ``` Ventajas: - El acceso se limita a los permisos asignados a la clave. - Cada clave pertenece a la empresa donde se creó. - No depende de una sesión de usuario. Consulta [API keys](https://www.facturadirecta.com/desarrolladores/api/referencia/api-keys/) para la referencia técnica y [API keys en Ajustes](https://help.facturadirecta.com/es/articles/15157077-api-keys) para crear la clave desde la interfaz. ### OAuth2 Recomendado para aplicaciones que conectan con empresas de terceros o necesitan que un usuario autorice scopes concretos. Usa el flujo Authorization Code con PKCE y envía el token en la cabecera: ```http Authorization: Bearer access_token ``` Consulta [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/) para el detalle del flujo OAuth2, scopes y endpoints de autorización. ## Abrir un resultado en FacturaDirecta Las respuestas de documentos, contactos, productos y bancos incluyen el campo opcional `webUrl`. Es la URL de la ficha del elemento en la aplicación web. Puedes mostrarla como enlace para que el usuario pase de tu integración al elemento correspondiente sin construir rutas internas. `webUrl` requiere una sesión de FacturaDirecta con acceso a la empresa y no es un endpoint de la API. No envíes `$ACCESS_TOKEN` a esa URL. Los recursos sin ficha propia, como métodos de pago, extractos, campos personalizados, webhooks, claves de API, uploads e inbox, no incluyen este campo. ## Recursos disponibles La API ofrece acceso, entre otros, a estos recursos: | Recurso | Para qué sirve | |---|---| | [Perfil](https://www.facturadirecta.com/desarrolladores/api/referencia/profile/) | Información del usuario autenticado y empresas accesibles. | | [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/) | Encontrar contactos, documentos y productos a partir de un texto, en todos los tipos a la vez. | | [Contactos](https://www.facturadirecta.com/desarrolladores/api/referencia/contacts/) | Clientes, proveedores, empleados y otros contactos. | | [Productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/) | Catálogo de productos y servicios. | | [Facturas de venta](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/) | Crear, consultar, actualizar, enviar, anular o descargar facturas. | | [Facturas recurrentes](https://www.facturadirecta.com/desarrolladores/api/referencia/recurring/) | Automatizaciones de facturación recurrente. | | [Presupuestos](https://www.facturadirecta.com/desarrolladores/api/referencia/estimates/) | Presupuestos y proformas. | | [Albaranes](https://www.facturadirecta.com/desarrolladores/api/referencia/delivery-notes/) | Albaranes de entrega. | | [Pedidos](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/) | Encargos de clientes, con estado por línea. Requiere el Módulo Inventario. | | [Órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/) | Encargos a proveedores, con estado por línea. Requiere el Módulo Inventario. | | [Facturas de compra y tickets](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/) | Gastos, compras y tickets. | | [Nóminas](https://www.facturadirecta.com/desarrolladores/api/referencia/payrolls/) | Nóminas de empleados. | | [Bancos](https://www.facturadirecta.com/desarrolladores/api/referencia/banks/) | Cuentas bancarias y tesorería. | | [Extractos bancarios](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/) | Extractos y conciliación. | | [Métodos de pago](https://www.facturadirecta.com/desarrolladores/api/referencia/payment-methods/) | Métodos de cobro y pago. | | [Diario contable](https://help.facturadirecta.com/es/articles/15084462-diario-contable) | Asientos y movimientos contables. | | [Declaraciones de impuestos](https://www.facturadirecta.com/desarrolladores/api/referencia/tax-declarations/) | Modelos de impuestos generados (303, 347, 390…): casillas, PDF y fichero de presentación. Solo lectura. | | [Consulta analítica](https://www.facturadirecta.com/desarrolladores/api/referencia/query/) | Totales, rankings y comparativas de facturas, compras, contactos y diario en una sola llamada, para informes propios y agentes de IA. Solo lectura. | | [Configuración](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/) | Series, impuestos, plantillas y configuración fiscal. | | [Bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/) | Procesamiento de documentos recibidos. | | [Uploads](https://www.facturadirecta.com/desarrolladores/api/referencia/uploads/) | Subida de archivos. | | [Webhooks](https://help.facturadirecta.com/es/articles/13905261-webhooks) | Endpoints y eventos para recibir cambios en tiempo real. | > **API pública y Zapier no tienen el mismo alcance**: Zapier expone solo > algunas acciones de FacturaDirecta. Aunque la API pública incluye > **Albaranes**, Zapier no permite crear albaranes. Usa la API pública > directamente si necesitas automatizar ese recurso. ## Webhooks Los **webhooks** permiten recibir notificaciones en tiempo real cuando ocurre un cambio relevante en tu empresa: una factura creada, un contacto modificado, un gasto borrado, etc. En lugar de consultar la API periódicamente, configuras una URL HTTPS y FacturaDirecta envía un `POST` a tu servidor cuando se produce un evento al que estás suscrito. Los webhooks están disponibles en todos los planes, incluido **Gratis**. Consulta: - [Webhooks](https://help.facturadirecta.com/es/articles/13905261-webhooks) — payload, catálogo de eventos, firma y referencia técnica de endpoints/eventos. - [Configurar webhooks](https://help.facturadirecta.com/es/articles/15097369-configurar-webhooks) — cómo crear endpoints desde Ajustes. ## Primeros pasos recomendados 1. Crea o selecciona un sandbox. 2. Crea una API key con los permisos mínimos necesarios. 3. Haz una primera llamada de lectura, por ejemplo listar facturas o contactos. 4. Prueba una operación de escritura en sandbox. 5. Si tu integración necesita reaccionar a cambios, configura webhooks. Consulta [Inicio rápido con la API](https://www.facturadirecta.com/desarrolladores/api/guias/quickstart/) para una primera conexión paso a paso. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/overview.md --- # Inicio rápido con la API > Haz tu primera llamada a la API de FacturaDirecta en minutos. Guía paso a paso con ejemplos de cURL. Esta guía te ayuda a hacer tu primera llamada a la API pública de FacturaDirecta. En pocos pasos tendrás una conexión funcional usando una API key. > **Recomendación**: haz tus primeras pruebas en un [entorno de pruebas > (sandbox)](https://help.facturadirecta.com/es/articles/15157082-entorno-de-pruebas-sandbox) > antes de usar la API en producción. ## Requisitos previos Necesitas: - Una cuenta en FacturaDirecta. - Una empresa real o sandbox. - El **companyId** de esa empresa. - Una **API key** con los permisos necesarios. Si todavía no tienes la clave o el `companyId`, consulta [API keys en Ajustes](https://help.facturadirecta.com/es/articles/15157077-api-keys). ## Variables para los ejemplos Guarda estos valores en variables de entorno para copiar los ejemplos con menos riesgo de error: ```shell export fd_company_id="com_tu_empresa" export fd_api_key="tu_api_key" ``` La URL base de producción es: ```shell export fd_api_base="https://app.facturadirecta.com/api" ``` ## Primera llamada: listar facturas Vamos a listar facturas de venta: ```shell curl -s \ -H "facturadirecta-api-key: $fd_api_key" \ "$fd_api_base/$fd_company_id/invoices?limit=5" ``` Si todo va bien, recibirás una respuesta JSON. Las listas paginadas devuelven normalmente `pagination` e `items`: ```json { "pagination": { "offset": 0, "limit": 5, "total": 42 }, "items": [ { "uuid": "inv_...", "main": { "date": "2026-01-15", "docNumber": { "series": "F-##-", "number": 1 }, "currency": "EUR" } } ] } ``` > El contenido exacto de cada factura depende del schema público `Invoice`. > Consulta [Facturas de venta](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/) para el detalle del recurso. ## Probar un recurso más simple: contactos Si tu empresa todavía no tiene facturas, puedes probar con contactos: ```shell curl -s \ -H "facturadirecta-api-key: $fd_api_key" \ "$fd_api_base/$fd_company_id/contacts?limit=5" ``` Para leer un contacto concreto, usa su ID: ```shell curl -s \ -H "facturadirecta-api-key: $fd_api_key" \ "$fd_api_base/$fd_company_id/contacts/con_..." ``` ## Crear una factura de prueba Cuando ya puedas leer datos, prueba una operación de escritura en sandbox. Este ejemplo crea una factura completa mínima. Crea un fichero `invoice.json`: ```json { "content": { "main": { "contact": "con_2e3f4a18-9b7c-4d6a-8e1f-5c2d3b4a7e9f", "date": "2026-05-13", "dueDate": "2026-06-12", "currency": "EUR", "docNumber": { "series": "F-##-" }, "fiscalPosition": "general", "account": "700000", "theme": "default", "draft": true, "voided": false, "simplified": false, "lines": [ { "account": "700000", "quantity": 1, "unitPrice": 100, "discount": 0, "discountRate": 0, "lineTotal": 100, "tax": ["S_IVA_21"], "text": "Servicios de consultoría" } ] } } } ``` Envíalo a la API: ```shell curl -s \ -H "facturadirecta-api-key: $fd_api_key" \ -H "Content-Type: application/json" \ -d @invoice.json \ "$fd_api_base/$fd_company_id/invoices" ``` Notas: - Usa `draft: true` para que la factura sea provisional durante las pruebas. - Sustituye `contact`, `theme`, `account`, `tax` y la serie por valores válidos de tu empresa. - Si tu empresa usa TicketBAI o VeriFactu, revisa las guías específicas antes de crear facturas definitivas. ## Errores comunes ### 401 Unauthorized La API key no es válida o no se ha enviado. Comprueba: - que la cabecera se llama `facturadirecta-api-key`; - que la clave está copiada completa; - que la clave no ha sido eliminada. ### 403 Forbidden La clave existe, pero no tiene permisos para la operación. Comprueba: - que incluye el scope necesario, por ejemplo `invoices:read` para leer facturas; - que el `companyId` corresponde a la empresa donde se creó la API key. ### 404 Not Found El recurso no existe o la URL no es correcta. Comprueba: - que el `companyId` es correcto; - que el path del endpoint existe; - que el ID del recurso (`inv_...`, `con_...`) pertenece a esa empresa. ## Ritmo de llamadas Cada empresa tiene un límite de peticiones por minuto según su plan (30 en Gratis, 60 en Bronce, 120 en Plata, 300 en Oro y 600 en Diamante). Cada respuesta lleva las cabeceras `X-RateLimit-Limit`, `X-RateLimit-Remaining` y `X-RateLimit-Reset`; úsalas para espaciar las llamadas en cargas masivas. Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/#límite-de-peticiones). ## Siguientes pasos - Consulta la [API Pública de FacturaDirecta](https://help.facturadirecta.com/es/articles/13538823-api-publica-de-facturadirecta) para ver la visión general. - Revisa [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/) para API keys y OAuth2. - Revisa [Paginación y filtros estándar](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/). - Revisa [Idempotencia y reintentos seguros](https://www.facturadirecta.com/desarrolladores/api/guias/idempotency/) antes de automatizar altas, cobros o una migración. - Explora la [referencia OpenAPI](https://www.facturadirecta.com/desarrolladores/api/explorador/). - Configura [webhooks](https://help.facturadirecta.com/es/articles/13905261-webhooks) si tu integración necesita recibir cambios en tiempo real. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/quickstart.md --- # Autenticación > La API pública de FacturaDirecta soporta dos métodos de autenticación, OAuth2 y API key. La API pública de FacturaDirecta soporta dos métodos de autenticación, [OAuth2](#oauth2) y [API key](#api-key). Cada operación acepta ambos; tú eliges el que mejor encaje con tu integración. Ambos comparten los mismos scopes y operan sobre el mismo conjunto de endpoints. ## Endpoint base Todas las URLs de la API pública en producción tienen el prefijo: ``` https://app.facturadirecta.com/api ``` A partir de ahí, los paths que se documentan en cada recurso (por ejemplo `/{companyId}/contacts`) se concatenan a esta base. Recursos auxiliares: - **Referencia OpenAPI navegable (Swagger UI):** `https://www.facturadirecta.com/api` - **Especificación OpenAPI cruda (`openapi.json`):** `https://app.facturadirecta.com/openapi.json` ## OAuth2 Recomendado cuando un usuario conecta su propia empresa de FacturaDirecta a tu aplicación. Tú obtienes un token en su nombre y operas dentro de su empresa. FacturaDirecta usa Keycloak como servicio de autorización, con realm `facturadirecta`. El flujo es `authorizationCode` estándar: 1. Rediriges al usuario a la URL de autorización con `client_id=facturadirecta-api` y los scopes que necesitas. 2. El usuario autentica y autoriza los scopes. 3. Keycloak redirige a tu callback con un `code`. 4. Intercambias `code` por `access_token` (y opcionalmente `refresh_token`) en el endpoint de token. 5. Usas el `access_token` en el header `Authorization: Bearer ` para cada llamada a la API. **URLs del servicio de autorización en producción:** | Endpoint | URL | |---|---| | Autorización | `https://auth.facturadirecta.com/auth/realms/facturadirecta/protocol/openid-connect/auth` | | Token | `https://auth.facturadirecta.com/auth/realms/facturadirecta/protocol/openid-connect/token` | | Refresh | `https://auth.facturadirecta.com/auth/realms/facturadirecta/protocol/openid-connect/token` | La fuente autoritativa es el `openapi.json` público en `https://app.facturadirecta.com/openapi.json`, sección `components.securitySchemes.oAuth.flows.authorizationCode`. **`client_id`** para integraciones externas es `facturadirecta-api` y **no requiere `client_secret`**. La API pública solo acepta tokens emitidos para clientes OAuth autorizados por FacturaDirecta. Los clientes gestionados por FacturaDirecta para integraciones propias, como conectores MCP autorizados y el CLI, pueden usar su propio `client_id`. Un token válido del realm no basta si el cliente OAuth que lo emitió no está autorizado para la API pública. En esos clientes, el usuario elige además las empresas y los permisos de cada conexión. La autorización efectiva es la intersección entre lo que admite el token, lo concedido a la conexión, el rol actual del usuario y el plan de la empresa. Los cambios de **Aplicaciones conectadas** se aplican sin repetir el flujo OAuth. **`offline_access`** como scope te permite obtener un `refresh_token` para operar sin nueva intervención del usuario. Cerrar la sesión de la web o de la aplicación móvil no revoca los tokens del MCP, del CLI ni de las integraciones OAuth que el usuario ya ha autorizado. Estas autorizaciones se retiran desde **Aplicaciones conectadas** o por el mecanismo de revocación de la integración. Los tokens de la propia sesión web o móvil sí dejan de aceptarse al cerrar esa sesión. ### Ejemplo de cabecera ```http Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIi... ``` ## API key Si tu integración solo necesita acceder a una empresa concreta que tú administras, puedes generar una API key desde **Ajustes > Integraciones** en la propia empresa de FacturaDirecta, o crearla por API desde `POST /apiKeys`. Al crear una API key le asignas los scopes que va a poder usar (los mismos que están disponibles para OAuth2, salvo `offline_access` que para una API key no tiene sentido). ### Ejemplo de cabecera ```http facturadirecta-api-key: aB3cD9.xY7zN2qK4mP8rT5sJ1hF6gL0vW3uE ``` Formato: `.`. Es lo que devuelve `POST /apiKeys` en el campo `apiKey` de la respuesta (solo visible en la creación; tras eso, FacturaDirecta solo conserva el hash). La API key se envía como header propio, **no** como `Authorization: Bearer`. ## Versión de la API Todas las peticiones aceptan el header `accept-version` con el número de versión de la API. Si no lo envías, se usa la versión vigente definida por el servidor. Para producción se recomienda fijarlo explícitamente: ```http accept-version: 1.0.10 ``` El servidor solo aplica versiones que se hayan publicado. Si envías una versión desconocida o con un formato no válido, aplica la versión vigente. La cabecera `X-Api-Version` de la respuesta indica qué versión se ha aplicado. ## Scopes Cada operación de la API declara los scopes que requiere. Los scopes siguen el patrón `:`: - `:read` — permite leer (`GET`). - `:write` — permite crear, modificar y borrar (`POST`, `PUT`, `DELETE`). El conjunto exacto de scopes y su descripción está en el openapi público y en cada página de recurso de esta documentación. Pide siempre los scopes mínimos necesarios para la integración. Dos scopes se salen del patrón y conviene conocerlos: - **`banks:readIban`** — scope adicional que añade el IBAN completo a las consultas de bancos y de métodos de pago. Ver [Bancos](https://www.facturadirecta.com/desarrolladores/api/referencia/banks/). - **`files:write`** — permite subir archivos para adjuntarlos a documentos. Ver [Uploads](https://www.facturadirecta.com/desarrolladores/api/referencia/uploads/). Los documentos del **Módulo Inventario** tienen sus propios scopes: `clientOrders:read` y `clientOrders:write` para [pedidos](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/), y `purchaseOrders:read` y `purchaseOrders:write` para [órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/). Tener el scope no basta: también se aplican el rol del usuario y el plan de la empresa. En una aplicación conectada se comprueba además la empresa y el nivel concedido para el recurso. Si el plan no incluye los documentos del Módulo Inventario, las escrituras responden `403`. Las operaciones de stock de productos siguen usando `products:read` y `products:write`, y también dependen de que el plan incluya el control de stock. ## Límite de peticiones Cada empresa tiene un **límite de peticiones por minuto** que depende de su plan. El límite se cuenta por empresa (no por credencial) en ventanas de un minuto naturales, y solo se aplica al tráfico de integración: llamadas con API key y con clientes OAuth de integración. | Plan | Peticiones por minuto | |---|---| | Gratis | 30 | | Bronce | 60 | | Plata | 120 | | Oro | 300 | | Diamante | 600 | Esos son los valores de la familia de planes vigente. Algunas cuentas con planes anteriores o con condiciones particulares tienen otro límite: no lo supongas a partir del plan, léelo de la cabecera de la respuesta. Cada respuesta incluye estas cabeceras: ```http X-RateLimit-Limit: 120 X-RateLimit-Remaining: 118 X-RateLimit-Reset: 1789012860 ``` - **`X-RateLimit-Limit`** — peticiones permitidas en la ventana actual. - **`X-RateLimit-Remaining`** — peticiones que te quedan en esta ventana. - **`X-RateLimit-Reset`** — timestamp Unix (segundos) en que empieza la ventana siguiente. Cuando una petición supera el límite, la respuesta añade además **`Retry-After`** con los segundos que faltan para la ventana siguiente. Hoy esa petición **se sigue atendiendo**: el exceso queda registrado pero no se rechaza. Aun así, diseña la integración para respetar el límite —lee `X-RateLimit-Remaining` y espacia las llamadas— y para tratar un `429 Too Many Requests` con reintento tras `Retry-After`. ## Errores de autenticación - **`401 Unauthorized`** — token expirado, ausente, mal formado, emitido por un cliente OAuth no autorizado para la API pública, o API key inválida. Refresca el token (si tienes `refresh_token`), revisa el `client_id` o regenera la API key. - **`403 Forbidden`** — la credencial es válida, pero falta un scope, el rol no permite la operación, el plan no incluye la API o la aplicación conectada no tiene concedida esa empresa o ese recurso. - **`403 Forbidden` con `manageUrl`** — el usuario puede abrir esa página para ampliar la conexión y repetir la llamada sin volver a autorizar la aplicación. - **`403 Forbidden` con `companyId` ajeno** — el token no tiene acceso a la empresa indicada en el path. En OAuth, el usuario solo accede a empresas en las que tiene rol y, cuando hay una conexión, a las que haya autorizado. Una API key solo accede a la empresa en la que se creó. Para el formato general de errores ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/). ## Recomendaciones - **No registres tokens ni API keys en logs**, ni siquiera durante desarrollo. - **Limita los scopes** al mínimo necesario. Si solo lees contactos, no pidas `contacts:write`. - **Una API key por integración.** Si una integración deja de usarse, rota la API key antes de borrarla. - **Para multi-tenant SaaS**, OAuth2 con `offline_access` es el patrón correcto. API keys son por empresa y no escalan a "una clave para varias empresas". Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/authentication.md --- # Paginación y filtros estándar > La API pública usa tres patrones de listado distintos, no uno. Cada operación que devuelve una lista escoge el patrón que mejor encaja con el recurso. La API pública usa **tres patrones de listado** distintos, no uno. Cada operación que devuelve una lista escoge el patrón que mejor encaja con el recurso. La página de cada recurso indica cuál aplica: - **Offset paginado** (`{ pagination, items }`) — el más común. Para recursos navegables por páginas. - **Cursor paginado** (`{ items, hasMore, nextCursor }`) — para secuencias temporales largas (eventos de webhook, actividad, versiones). El cliente avanza pasando el cursor que recibe con cada página. - **Sin paginación** (`{ items }`) — para catálogos pequeños y listas anidadas, donde devolver todo el conjunto de una vez es razonable. ## Patrón A — Offset paginado Aplica a la mayoría de recursos: `contacts`, `products`, `invoices`, `recurring`, `estimates`, `deliveryNotes`, `bills`, `payrolls`, `banks`, `statements`, `paymentMethods`, `journal`, `inbox`. ### Parámetros - **`offset`** — posición de inicio (basada en cero). Por defecto `0`. - **`limit`** — número máximo de resultados. Entre `1` y `500`. Si no se indica, el servidor aplica un valor por defecto razonable. Ejemplo: ``` GET /{companyId}/contacts?offset=100&limit=50 ``` devuelve hasta 50 contactos a partir del 101º (índice 100). ### Respuesta ```json { "pagination": { "offset": 0, "limit": 50, "total": 124 }, "items": [ { "...": "..." } ] } ``` - **`pagination.offset`** y **`pagination.limit`** repiten los valores efectivamente aplicados (útil cuando no envías uno y quieres saber el default). - **`pagination.total`** es el total de elementos que cumplen los filtros aplicados, **no** el total absoluto del recurso. - **`items`** es siempre el nombre del array. Para recorrer el resultado completo, incrementa `offset` en pasos de `limit` hasta que `offset + items.length >= total`. Para sumar en vez de recorrer: los listados de [facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#totales-del-conjunto-filtrado) y de [facturas de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/#totales-del-conjunto-filtrado) admiten `totals=true`, y la [consulta analítica](https://www.facturadirecta.com/desarrolladores/api/referencia/query/) agrupa y compara sin paginar. ## Patrón B — Cursor paginado Aplica a: - `GET /{companyId}/activity` (`getActivity`), el [registro de actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/). - `GET /{companyId}//{id}/versions`, las [versiones de un documento](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). - `GET /{companyId}/webhooks/events` (`listPublicWebhookEvents`), los [eventos de webhook](https://www.facturadirecta.com/desarrolladores/webhooks/). ### Parámetros - **`limit`** — número máximo de resultados. Máximo `100`. - **`cursor`** — opcional. Para la primera página se omite. Para las siguientes, se envía el `nextCursor` que devolvió la página anterior, **tal cual se recibió**. ### Respuesta ```json { "items": [ { "...": "..." } ], "hasMore": true, "nextCursor": "NDgyODc3" } ``` - **`items`** ordenados de más reciente a más antiguo. La actividad y las versiones admiten además el orden inverso con `sortBy=timestamp`. - **`hasMore`** indica si quedan más resultados detrás de esta página. - **`nextCursor`** es el cursor de la página siguiente, o `null` cuando `hasMore` es `false`. - **No hay `pagination.total`.** En este patrón no se devuelve el total. ### Cómo paginar 1. Primera petición sin `cursor`: ``` GET /{companyId}/activity?limit=100 ``` 2. Procesa `items`. 3. Si `nextCursor` no es `null`, pásalo en la siguiente petición con los mismos filtros y el mismo `sortBy`: ``` GET /{companyId}/activity?limit=100&cursor=NDgyODc3 ``` 4. Repite hasta que `nextCursor` sea `null` (o, lo que es lo mismo, `hasMore` sea `false`). ### Notas - **El cursor es opaco.** Es una cadena que identifica la posición de la última fila de la página; su formato no forma parte del contrato y puede cambiar. No lo construyas a partir de los datos del listado (por ejemplo, del `id` del último elemento) ni lo interpretes: un valor que no haya devuelto la API responde `400`. - **El cursor marca una posición, no una consulta.** Si cambias los filtros o el `sortBy` entre dos páginas, la petición sigue siendo válida, pero la paginación continúa desde esa posición con la nueva consulta. Para recorrer un listado completo, mantén los mismos parámetros en todas las páginas. - Para reanudar una sincronización desde un punto conocido (por ejemplo, tras una caída), guarda el `nextCursor` de la última página que procesaste con éxito y úsalo como `cursor` en la próxima ejecución. En la actividad y las versiones, pide además `sortBy=timestamp` para avanzar hacia lo más reciente. ## Patrón C — Sin paginación Aplica a operaciones que devuelven el conjunto completo sin paginar: - **Adjuntos de documento:** `getInvoiceAttachments`, `getRecurringInvoiceAttachments`, `getEstimateAttachments`, `getDeliveryNoteAttachments`, `getBillAttachments`, `getPayrollAttachments`. - **Catálogos de empresa:** `getSalesTaxes`, `getPurchasesTaxes`, `getThemes`, `getSeries`. - **Listas de gestión:** `listPublicWebhookEndpoints`, `listPublicApiKeys`. ### Respuesta ```json { "items": [ { "...": "..." } ] } ``` No hay `pagination` ni `hasMore`. Asume que la cantidad de elementos es manejable: típicamente decenas, no miles. Si un recurso de este tipo crece más allá de lo razonable, su endpoint se migrará a uno de los patrones paginados. ## Filtros estándar de fecha Las listas con paginación offset de facturas, presupuestos, pedidos, órdenes de compra, albaranes, nóminas, facturas de compra, facturas recurrentes, contactos, productos, bancos, métodos de pago, movimientos bancarios y campos personalizados aceptan además cuatro filtros temporales comunes: - **`minCreationDate`** / **`maxCreationDate`** — rango por fecha de creación del documento en FacturaDirecta. - **`minModificationDate`** / **`maxModificationDate`** — rango por fecha de última modificación. Los dos extremos del rango se incluyen. Cada filtro admite: - Una fecha y hora ISO 8601 (`YYYY-MM-DDTHH:mm:ss.sssZ`). Se compara el instante exacto, con 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`): el filtro mínimo empieza en el primer instante de ese día y el máximo acaba en el último. Ejemplos: ``` GET /{companyId}/invoices?minCreationDate=2026-01-01&maxCreationDate=2026-03-31 GET /{companyId}/invoices?minModificationDate=2026-04-01T08:00:00.000Z GET /{companyId}/invoices?minModificationDate=2026-04-01T10:00:00%2B02:00 ``` Una fecha que no existe, como `2026-02-30` o `2026-02-30T00:00:00.000Z`, responde `400` con el nombre del parámetro en el mensaje. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). Estos filtros existen para detectar cambios desde un timestamp conocido (sincronizaciones incrementales). Cada recurso documento añade además sus propios filtros de fecha semánticos (por ejemplo `minDate`, `maxDate` en facturas y presupuestos, sobre la fecha del documento, no la de creación). ## Ordenación Los recursos exponen un parámetro `sortBy` cuando soportan ordenación explícita. Los valores admitidos dependen del recurso y se documentan en su página. Sin `sortBy`, cada recurso aplica un orden por defecto razonable (habitualmente fecha descendente para documentos, alfabético para catálogos). En el patrón de cursor, los eventos de webhook tienen un orden fijo: `created_at` descendente, sin `sortBy`. La actividad y las versiones admiten `sortBy=-timestamp` (por defecto, lo más reciente primero) y `sortBy=timestamp`. ## Recomendaciones - **Pide solo lo que necesitas.** `limit=50` cubre la mayoría de casos de UI y reduce latencia. - **Para sincronizaciones masivas en patrón A**, filtra por `minModificationDate` con el timestamp de tu última sincronización exitosa en vez de paginar el recurso completo desde cero. - **Para la actividad, las versiones y los eventos de webhook (patrón B)**, persiste el cursor de la última página procesada y úsalo en la siguiente sesión: no recorras todo el historial cada vez. - **Comprueba `pagination.total` antes de paginar** en el patrón A; si es pequeño, una sola petición basta. - **No asumas orden estable** en el patrón A si no envías `sortBy`. Si necesitas consistencia entre páginas, fíjalo explícitamente. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/pagination.md --- # Errores y validaciones > Las respuestas de error de la API pública siguen siempre la misma forma base, independientemente del recurso. Las respuestas de error de la API pública siguen siempre la misma forma base, independientemente del recurso. Esta guía documenta los códigos HTTP que verás, el formato JSON, y cómo distinguir entre los distintos tipos. ## Forma general de la respuesta Toda respuesta de error tiene como mínimo: ```json { "statusCode": 400, "message": "Mensaje legible que explica el problema" } ``` Puede incluir además: - **`type`** — categoría del error cuando aplica. - **`errors`** — array con detalles por campo en errores de validación (solo en `400 ValidationError`). - **`code`** — motivo estable de algunos errores de autorización. - **`requiredScope`** — scope necesario para completar la operación. - **`companyId`** — empresa en la que se ha denegado el acceso. - **`manageUrl`** — página donde el usuario puede ampliar una aplicación conectada, cuando esa acción resuelve el error. `Content-Type` siempre es `application/json`. ## Códigos HTTP | Código | Significado típico | |---|---| | `400 Bad Request` | El cuerpo o los parámetros no cumplen el contrato. Si es un fallo de validación de campos, viene con `errors[]` (ver más abajo). | | `401 Unauthorized` | Falta credencial, está expirada o es inválida. Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). | | `403 Forbidden` | Credencial válida pero sin los scopes necesarios, o sin acceso a la empresa indicada en el path. | | `404 Not Found` | El recurso no existe o no pertenece a la empresa del path. | | `409 Conflict` | La operación choca con el estado actual: identificador duplicado, borrado de un recurso con dependencias, etc. También lo devuelve una petición con `Idempotency-Key` cuando la original sigue en curso. Ver [Idempotencia](https://www.facturadirecta.com/desarrolladores/api/guias/idempotency/). | | `422 Unprocessable Entity` | La `Idempotency-Key` enviada ya se usó con otra petición distinta. Ver [Idempotencia](https://www.facturadirecta.com/desarrolladores/api/guias/idempotency/). También lo devuelve una consulta de la [actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/) que tarda demasiado (`activity_query_timeout`): acótala con `minDate` y `maxDate`. Y un [informe](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/) o una [consulta analítica](https://www.facturadirecta.com/desarrolladores/api/referencia/query/) que tardan demasiado (`report_query_timeout`): acota el periodo, el nivel de detalle, las dimensiones o los filtros. | | `429 Too Many Requests` | Demasiadas escrituras simultáneas del mismo tipo en la empresa. Reintenta con backoff. Ver [Límite de peticiones](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/#límite-de-peticiones). | | `500 Internal Server Error` | Error inesperado del servidor. No es un error del cliente; conviene reintentar tras un retraso. | ## Errores de validación (`400`) Cuando el body o los parámetros fallan el JSON Schema, la respuesta incluye un array `errors` con un objeto por cada problema: ```json { "statusCode": 400, "message": "Validation failed", "type": "ValidationError", "errors": [ { "path": "content.main.fiscalId", "message": "Formato de NIF/CIF inválido para España" }, { "path": "content.main.lines.0.unitPrice", "message": "Debe ser un número mayor que 0" } ] } ``` Cada entrada del array localiza el campo problemático y explica qué falla. Para integraciones que muestran el error al usuario final, basta con mostrar el `message` de cada entrada; el `path` ayuda al developer a depurar. ## Fechas que no existen (`400`) Los filtros de fecha de los listados tienen que ser una fecha del calendario. Una fecha con la forma correcta que no existe, como `2026-02-30`, `2026-13-01` o `2025-02-29`, responde `400` con un `message` que nombra el parámetro. El filtro nunca se ignora ni se sustituye por otro valor. ```json { "statusCode": 400, "message": "'minDate' no es una fecha válida (AAAA-MM-DD)" } ``` Qué admite cada filtro depende del campo que filtra: - **Filtros por día**, como la fecha de un documento: solo admiten `AAAA-MM-DD`, sin hora. Son `minDate` y `maxDate` de [facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/), [presupuestos](https://www.facturadirecta.com/desarrolladores/api/referencia/estimates/), [pedidos](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/), [órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/), [albaranes](https://www.facturadirecta.com/desarrolladores/api/referencia/delivery-notes/), [nóminas](https://www.facturadirecta.com/desarrolladores/api/referencia/payrolls/), [facturas de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/), [movimientos bancarios](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/) y [diario](https://www.facturadirecta.com/desarrolladores/api/referencia/journal/). Una fecha que no existe responde con el mensaje anterior. - **Filtros por instante**, como la fecha de creación o la de un registro de actividad: admiten fecha y hora ISO 8601 o solo la fecha, que cuenta el día entero. Son los filtros comunes `minCreationDate`, `maxCreationDate`, `minModificationDate` y `maxModificationDate` (ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/#filtros-estándar-de-fecha)), `minDate` y `maxDate` de la [bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/) y de la [actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/), y `minNextScheduledDate` y `maxNextScheduledDate` de [facturas recurrentes](https://www.facturadirecta.com/desarrolladores/api/referencia/recurring/). Con solo la fecha (`2026-02-30`), el mensaje es el anterior; con fecha y hora (`2026-02-30T10:00:00.000Z`), es `Valor incorrecto para 'minCreationDate': debe ser una fecha y hora en formato ISO 8601`. El mismo nombre puede ser de los dos tipos: `minDate` de las facturas es un día, y el de la bandeja de entrada, un instante. La página de cada recurso indica cuál es. Una fecha con otra forma, como `2026-2-3` o `03/02/2026`, también responde `400`. ## Errores de negocio (`400`, `409`) Algunos errores no son fallos de schema sino reglas de negocio: - Crear una factura con una serie ya consumida. - Borrar un contacto que tiene documentos asociados. - Modificar un presupuesto convertido en factura. Estos errores vienen como `400` o `409` (según el caso) con un `message` descriptivo. Cuando el mensaje incluye datos relevantes (ID de documento bloqueante, valor duplicado), se devuelven en campos adicionales del JSON. ## Errores de autenticación y autorización (`401`, `403`) - **`401`** — el token o la API key son rechazados antes de evaluar permisos. Casos típicos: token expirado, API key revocada, header mal formado. - **`403`** — la credencial es válida pero le falta algo: - Scopes insuficientes para la operación (por ejemplo, intentar `POST` con scope `read`). - La empresa del path no es accesible para esta credencial. - El rol del usuario en la empresa no permite la operación. Con OAuth se aplican los scopes del token y los permisos del rol del usuario en esa empresa, también si es un rol personalizado. El `403` por permisos indica el scope en `requiredScope` y el motivo en `code`: ```json { "statusCode": 403, "message": "No tienes permisos para realizar esta operación", "requiredScope": "invoices:write", "code": "user_role_insufficient", "companyId": "com_…" } ``` | `code` | Motivo | Qué hacer | | --- | --- | --- | | `token_scope_missing` | El token o la API key no incluye el scope. | Volver a autorizar pidiendo el scope, o usar una API key que lo tenga. | | `user_role_insufficient` | El usuario no pertenece a la empresa o su rol no lo permite. El nivel de rol «solo lo asignado» no da acceso por la API. | Pedir el permiso a un administrador de la empresa: volver a autorizar no lo arregla. | | `plan_api_not_available` | El plan de la empresa no incluye el acceso por la API. | Cambiar de plan. | | `connection_permission_insufficient` | La aplicación conectada no tiene el permiso necesario en esa empresa. | Abrir `manageUrl`, ampliar el permiso y repetir la llamada. | | `company_not_in_connection` | La empresa no está autorizada para esa aplicación conectada. | Abrir `manageUrl`, añadir la empresa y repetir la llamada. | | `connection_required` | El cliente MCP o CLI exige una conexión, pero la petición no la presenta. | Volver a conectar la aplicación o iniciar sesión de nuevo en el CLI. | | `connection_revoked` | La conexión no existe, se ha desconectado o no corresponde al usuario del token. | Iniciar una conexión nueva. | `companyId` solo aparece si el usuario pertenece a la empresa del path. `manageUrl` aparece cuando el acceso se puede ampliar desde **Aplicaciones conectadas**. Usa `code`, no el texto, para distinguir los casos. Un contacto puede tener varias facetas. Si una escritura afecta, por ejemplo, a un contacto que también es proveedor, la credencial necesita permiso sobre todas las facetas implicadas. El `403` usa `connection_permission_insufficient` o `user_role_insufficient` y explica el permiso que falta. Para detalles del flujo de auth y rotación de tokens, ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). ## Límites del plan Cuando una operación no puede completarse porque la empresa supera una capacidad del plan contratado, la respuesta incluye el código estable `plan_limit_exceeded` dentro de `errors`: ```json { "statusCode": 403, "message": "La empresa está en modo solo lectura por exceder el límite de clientes contratados (10). Por favor, amplía tu suscripción o reduce el número de clientes de tu empresa.", "errors": [ { "message": "La empresa está en modo solo lectura por exceder el límite de clientes contratados (10). Por favor, amplía tu suscripción o reduce el número de clientes de tu empresa.", "code": "plan_limit_exceeded" } ] } ``` El mismo código se devuelve al crear una factura con el cupo del plan agotado (por año natural: 100 en Gratis, 2.000 en Bronce, 5.000 en Plata y 12.000 en Oro; sin cupo en Diamante). Solo bloquea crear facturas: el resto de operaciones sigue disponible y la ampliación es subir de plan. Se comportan igual otras capacidades: número de empleados, productos, productos con control de stock, campos personalizados, almacenes y **documentos recurrentes activos**. En todos los casos, el mensaje indica el tope alcanzado. El estado HTTP es siempre `403`. Usa `errors[].code`, no el texto por separado, para identificar este caso. No reintentes la misma operación hasta ampliar el plan o reducir el uso que supera la capacidad. ### Funcionalidades no incluidas en el plan El mismo código `plan_limit_exceeded` identifica el `403` de una funcionalidad entera que el plan no incluye: las escrituras de [pedidos](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/) y [órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/) («Los pedidos de cliente y las órdenes de compra no están disponibles en tu plan») y las operaciones de stock de [productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/) («El control de stock no está disponible en tu plan»). La diferencia con un tope de capacidad está solo en el mensaje: aquí no hay uso que reducir, y la salida es un plan o un módulo que incluya la funcionalidad. FacturaDirecta también envía un e-mail de aviso al propietario de la empresa, al usuario que hizo la solicitud —o al creador de la API key— y a los usuarios con permiso completo de configuración de empresa. El aviso identifica la petición y el motivo. Se envía como máximo una vez por empresa cada 7 días, aunque el error se repita. ### Informes no incluidos en el plan Los [informes contables](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/) (pérdidas y ganancias, balance de situación y sumas y saldos) necesitan el módulo de Contabilidad. Sin él responden `403` con `plan_limit_exceeded` y la funcionalidad que falta en `hint`: ```json { "statusCode": 403, "message": "Este informe no está disponible en tu plan", "errors": [ { "message": "Este informe no está disponible en tu plan", "code": "plan_limit_exceeded", "hint": { "features": ["fullAccounting"] } } ] } ``` Este error no envía el e-mail de aviso a los administradores. Para saber de antemano si una empresa tiene el módulo, una aplicación conectada puede leer `accountingModule` en el [perfil](https://www.facturadirecta.com/desarrolladores/api/referencia/profile/). El [resumen de resultados](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/) está disponible en todos los planes. ## Errores de idempotencia (`400`, `409`, `422`) Las peticiones con cabecera `Idempotency-Key` pueden devolver cuatro códigos estables en `errors[0].code`: | Código HTTP | `errors[0].code` | Significado | |---|---|---| | `400` | `invalid_idempotency_key` | La clave no cumple el formato (de 16 a 255 caracteres ASCII visibles, sin espacios). | | `409` | `idempotency_key_in_use` | Hay otra petición en curso con la misma clave. La respuesta trae `Retry-After`. | | `422` | `idempotency_key_reused` | La clave ya se usó con otra petición: otro método, otra ruta u otro cuerpo. | | `409` | `idempotency_result_not_replayable` | La petición original se completó, pero su respuesta llevaba un secreto que no se conserva. `errors[0].resource` identifica lo que se creó. | El detalle de cada caso y qué hacer está en [Idempotencia](https://www.facturadirecta.com/desarrolladores/api/guias/idempotency/). ## Recursos no encontrados (`404`) `404` significa siempre **una de estas dos cosas**: - El ID no existe en la empresa indicada. - El ID existe pero pertenece a otra empresa (la API no revela que el recurso existe en otro tenant). Trata ambos casos como "no existe para mí" en tu integración. ## Estrategia recomendada de manejo - **Diferencia errores del cliente (4xx) de errores del servidor (5xx).** Los 4xx no deben reintentarse sin cambiar el input; los 5xx sí, con backoff exponencial. - **Para `400 ValidationError`, recorre `errors[]`** y muestra al usuario los `message` específicos en vez del `message` general. - **Reintentos seguros.** Envía la cabecera `Idempotency-Key` en las operaciones que modifican datos: si reintentas con la misma clave tras un timeout o un corte de conexión, la operación no se ejecuta dos veces y recibes la respuesta original. Ver [Idempotencia](https://www.facturadirecta.com/desarrolladores/api/guias/idempotency/). - **No deduzcas estado a partir del código HTTP solo.** Combina código y contenido (`type`, `message`, `errors[].code`) para clasificar el error en tu lógica. ## Ejemplo completo Petición que viola dos validaciones (CIF mal formado y línea con precio negativo): ```http HTTP/1.1 400 Bad Request Content-Type: application/json { "statusCode": 400, "message": "Validation failed", "type": "ValidationError", "errors": [ { "path": "content.main.fiscalId", "message": "Formato de NIF/CIF inválido para España" }, { "path": "content.main.lines.0.unitPrice", "message": "Debe ser un número mayor que 0" } ] } ``` Y conflicto al borrar un contacto con factura asociada: ```http HTTP/1.1 409 Conflict Content-Type: application/json { "statusCode": 409, "message": "No se puede borrar el contacto: tiene 3 facturas asociadas" } ``` Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/errors.md --- # Idempotencia y reintentos seguros > Cuando una petición que modifica datos termina en timeout o se corta la conexión, no sabes si el servidor llegó a ejecutarla. Cuando una petición que modifica datos termina en timeout o se corta la conexión, no sabes si el servidor llegó a ejecutarla. Reintentarla a ciegas puede crear una factura duplicada o registrar dos veces el mismo cobro. La cabecera `Idempotency-Key` resuelve ese caso: envías la misma clave en el reintento y la API garantiza que la operación se ejecuta una sola vez. Si la petición original ya terminó, recibes su respuesta tal cual, sin que se ejecute nada. Es opcional. Si no la envías, la API se comporta como siempre. ## Cómo se usa Genera una clave única por cada operación que quieras hacer y envíala en la cabecera. Si tienes que reintentar, repite **exactamente la misma petición con la misma clave**. ```http Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324 ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -H "Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324" \ -d '{ "content": { "type": "contact", "main": { "name": "Cliente Empresa SL", "fiscalId": "B12345674", "currency": "EUR", "country": "ES", "address": "Pza Mayor, 4", "zipcode": "49004", "city": "Zamora", "accounts": { "client": "430000", "clientCredit": "438000" } } } }' \ "https://app.facturadirecta.com/api/$COMPANY_ID/contacts" ``` ### Formato de la clave - De **16 a 255 caracteres ASCII visibles, sin espacios**. - Recomendamos un **UUID v4**. También vale un identificador con prefijo derivado de tu registro de origen, por ejemplo `migracion-factura-12345`, que es lo natural en una migración: la clave sale del dato y no tienes que guardarla. - El mínimo de 16 caracteres evita colisiones accidentales. Si usaras el número de tu registro tal cual, la factura 123 y el contacto 123 tendrían la misma clave. - **No pongas datos sensibles** en la clave: es una cabecera y queda registrada como tal. - Si la envías entre comillas dobles (`"8e03978e-…"`), las comillas se ignoran: es la misma clave. Una clave que no cumple el formato devuelve `400` con el código `invalid_idempotency_key` y la operación no se ejecuta. ## Qué identifica una clave - **Es única por empresa.** La misma clave en dos empresas distintas son dos claves distintas. - **Dura 24 horas** desde la primera petición. Pasado ese plazo se trata como una clave nueva. - **Queda ligada a la petición concreta**: método, ruta, parámetros de la URL y cuerpo. El orden de las propiedades del JSON y los espacios no cuentan: el mismo contenido serializado de otra forma es la misma petición. - **La credencial no forma parte.** Si rotas tu API key entre la petición original y el reintento, la clave sigue funcionando. Por la misma razón, otra credencial de la misma empresa que envíe la misma clave con exactamente la misma petición recibe la respuesta guardada. ## Qué responde la API | Situación | Respuesta | |---|---| | Primera petición con la clave | Se ejecuta con normalidad. Si termina bien, la respuesta se guarda 24 horas. | | Misma clave y misma petición, con la original ya terminada | El mismo código y el mismo cuerpo que la original, sin ejecutar nada: ni cambios, ni webhooks, ni correos. | | Misma clave, con la original todavía en curso | `409` con el código `idempotency_key_in_use` y la cabecera `Retry-After`. Espera esos segundos y reintenta. | | Misma clave con otra petición (otro método, otra ruta u otro cuerpo) | `422` con el código `idempotency_key_reused`. Usa una clave nueva para cada operación distinta. | | La petición original terminó en error | La clave queda libre y el reintento se ejecuta de nuevo. Única excepción: si el error se produjo cuando los datos ya estaban guardados, el reintento recibe la respuesta de la operación ya hecha, sin duplicarla. | Que los errores no se guarden es deliberado. Si un alta falla por un dato incorrecto, puedes corregirlo y reintentar **con la misma clave**, sin esperar 24 horas ni inventar otra. Es lo que necesitas cuando la clave sale de tu registro de origen. Los errores que se producen antes de ejecutar la operación (`401`, `403`, `429` y los `400` de validación del cuerpo) tampoco consumen la clave. ### Cabeceras de la respuesta | Cabecera | Cuándo aparece | Valor | |---|---|---| | `Idempotency-Key` | En toda respuesta a una petición con clave, en una operación que la admite | La clave aplicada | | `Idempotent-Replayed` | Solo en respuestas repetidas | `true` | | `Idempotent-Original-Date` | Solo en respuestas repetidas | Fecha de la ejecución original, en formato de fecha HTTP | | `Idempotent-Original-Request-Id` | Solo en respuestas repetidas | El `X-Request-Id` de la petición original | Respuesta a un reintento de una petición ya terminada: ```http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324 Idempotent-Replayed: true Idempotent-Original-Date: Thu, 17 Sep 2026 10:15:30 GMT Idempotent-Original-Request-Id: 5f0c2a1e-9b7d-4c3a-8e21-7d4f6a9b0c12 ``` **Si la respuesta no trae la cabecera `Idempotency-Key`, la clave no se aplicó**: esa operación no admite idempotencia y la cabecera se ignoró. Es la forma de comprobarlo desde tu integración. ### Errores El código de cada error va en `errors[0].code`, con la [forma habitual de los errores](https://www.facturadirecta.com/desarrolladores/api/guias/errors/): ```json { "statusCode": 422, "message": "Esta Idempotency-Key ya se usó con otra petición (otro método, otra ruta u otro cuerpo). Usa una clave nueva para cada operación distinta.", "errors": [ { "code": "idempotency_key_reused", "message": "Esta Idempotency-Key ya se usó con otra petición (otro método, otra ruta u otro cuerpo). Usa una clave nueva para cada operación distinta." } ] } ``` | Código HTTP | `errors[0].code` | Qué hacer | |---|---|---| | `400` | `invalid_idempotency_key` | Corrige el formato de la clave. | | `409` | `idempotency_key_in_use` | La original sigue en curso. Espera lo que indique `Retry-After` y reintenta con la misma clave. | | `422` | `idempotency_key_reused` | Esa clave ya identifica otra petición. Genera una clave nueva. | | `409` | `idempotency_result_not_replayable` | Ver «Operaciones que devuelven un secreto». | ## Operaciones que devuelven un secreto `POST /{companyId}/apiKeys` y `POST /{companyId}/webhooks/endpoints` devuelven un secreto que solo se muestra una vez (la API key y el secreto de firma) y que FacturaDirecta no conserva en claro. La clave protege igual contra el duplicado, pero la respuesta no se puede repetir. Un reintento con la misma clave no crea nada y recibe un `409` que identifica lo que se creó la primera vez: ```json { "statusCode": 409, "message": "La petición original se completó, pero su respuesta contenía un secreto que no se conserva.", "errors": [ { "code": "idempotency_result_not_replayable", "message": "La petición original se completó, pero su respuesta contenía un secreto que no se conserva.", "resource": { "type": "apiKey", "id": "" } } ] } ``` `resource.type` es `apiKey` o `webhookEndpoint`. Con ese `id` decides cómo recuperar el secreto perdido: - **API key**: bórrala con `DELETE /{companyId}/apiKeys/{id}` y crea otra con una clave de idempotencia nueva. Ver [API keys](https://www.facturadirecta.com/desarrolladores/api/referencia/api-keys/). - **Endpoint de webhook**: pide un secreto nuevo con `POST /{companyId}/webhooks/endpoints/{endpointId}/rotateSecret`, sin recrear el endpoint. Ver [Webhooks](https://www.facturadirecta.com/desarrolladores/webhooks/). ## Operaciones que admiten la cabecera La admiten **todas las operaciones `POST`, `PUT` y `DELETE`** salvo las de la tabla siguiente. En la referencia de la API, las que la admiten declaran el parámetro `Idempotency-Key`. Los `GET` la ignoran siempre. | Operaciones que no la admiten | Por qué | |---|---| | `PUT /{companyId}/invoices/{id}/pdf` y el equivalente de facturas recurrentes, presupuestos, albaranes, pedidos y órdenes de compra; `PUT /{companyId}/invoices/{id}/facturae` | Son lecturas: generan un fichero temporal. Se pueden repetir sin riesgo. | | `POST /{companyId}/inbox/{id}/proposeBill`, `proposeTicket` y `proposePayroll` | Son lecturas: devuelven un prototipo y no escriben nada. | | `POST /{companyId}/webhooks/endpoints/{endpointId}/rotateSecret` | La respuesta lleva un secreto que no se conserva, y basta con volver a llamarla: el secreto válido es el de la última llamada. | | `POST /{companyId}/uploads` y `POST /{companyId}/uploads/{uploadId}/commit` | Un upload duplicado es inocuo (caduca y se purga solo) y sellar dos veces el mismo upload da el mismo resultado. | | `POST /{companyId}/inbox` | Tiene su propio mecanismo, que cubre otra necesidad. Ver el apartado siguiente. | ## `POST /inbox` es distinto `POST /{companyId}/inbox` no admite la cabecera. Tiene un campo `idempotencyKey` **en el cuerpo** que se llama parecido pero sirve para otra cosa: evitar que el mismo documento entre dos veces en la bandeja de entrada. | | Cabecera `Idempotency-Key` | Campo `idempotencyKey` de `POST /inbox` | |---|---|---| | Para qué sirve | Reintentar una petición tras un error de red | Que el mismo documento no se ingiera dos veces | | Cuánto dura | 24 horas | No caduca mientras el item siga activo (sin archivar) | | Compara la petición | Sí: otro cuerpo con la misma clave da `422` | No: con la misma clave devuelve el item existente, sea cual sea el fichero | | Qué devuelve al repetir | La respuesta original, con `Idempotent-Replayed: true` | El item existente, con su estado actual | | Clave habitual | Un UUID por operación | Una clave natural del documento, por ejemplo un hash del fichero | Si tu proceso reenvía periódicamente los mismos documentos y confía en que la bandeja no los duplique, usa el campo del cuerpo. Ver [Bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/). ## Relación con `content.uuid` En las altas de documentos puedes proponer tú el identificador en `content.uuid`. Un segundo alta con un `uuid` que ya existe en la empresa, aunque el documento esté archivado, devuelve `409`. Sirve para no duplicar, pero el reintento recibe un error en lugar del documento creado, y solo cubre las altas. `Idempotency-Key` es la forma recomendada de reintentar: cubre también modificaciones, borrados, cobros y pagos, y el reintento recibe la respuesta original. Las dos cosas son compatibles. ## Límite de peticiones Una respuesta repetida **cuenta como una petición más** para el [límite de peticiones](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/#límite-de-peticiones) de tu plan, igual que un `409` por clave en uso. Un `429` no consume la clave: el límite se comprueba antes de mirarla. Espera lo que indique `Retry-After` y reintenta con la misma clave. ## Recomendaciones - **Genera la clave antes del primer intento** y guárdala junto a tu registro, o derívala de él. Una clave nueva en cada reintento no protege de nada. - **Reintenta con la misma petición exacta.** Si cambias el cuerpo, usa una clave nueva: con la misma recibirás `422`. - **Ante un timeout o un corte de conexión**, reintenta con la misma clave con espera exponencial. Recibirás la respuesta original, o un `409` con `Retry-After` mientras la primera petición siga en curso. - **Ante un `5xx`**, reintenta también con la misma clave. Si la operación no llegó a guardarse, se ejecuta de nuevo; si llegó a guardarse, recibes su respuesta sin duplicarla. - **Comprueba el eco** `Idempotency-Key` en la respuesta si necesitas estar seguro de que la operación admite la cabecera. - **No reutilices claves** entre operaciones distintas ni las recicles antes de 24 horas. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/idempotency.md --- # Versiones de documentos > Cada vez que se crea, modifica, elimina o recupera un documento, FacturaDirecta guarda una copia completa de cómo queda. Cada vez que se crea, modifica, elimina o recupera un documento, FacturaDirecta guarda una copia completa de cómo queda. La API expone ese historial como **versiones** del documento: quién hizo cada cambio, cuándo, qué campos cambiaron y cómo era el documento en cada momento. Sirve para auditar los cambios de un documento concreto, para averiguar qué se cambió por error y para recuperar esos datos, también de un documento ya eliminado. ## Recursos con versiones | Recurso | Operaciones | Scope | |---|---|---| | Facturas de venta | `GET /{companyId}/invoices/{id}/versions[/{versionId}]` | `invoices:read` | | Presupuestos | `GET /{companyId}/estimates/{id}/versions[/{versionId}]` | `estimates:read` | | Albaranes | `GET /{companyId}/deliveryNotes/{id}/versions[/{versionId}]` | `deliveryNotes:read` | | Pedidos | `GET /{companyId}/clientOrders/{id}/versions[/{versionId}]` | `clientOrders:read` | | Facturas de compra y tickets | `GET /{companyId}/bills/{id}/versions[/{versionId}]` | `bills:read` | | Órdenes de compra | `GET /{companyId}/purchaseOrders/{id}/versions[/{versionId}]` | `purchaseOrders:read` | | Contactos | `GET /{companyId}/contacts/{id}/versions[/{versionId}]` | `contacts:read` | | Productos | `GET /{companyId}/products/{id}/versions[/{versionId}]` | `products:read` | | Bancos | `GET /{companyId}/banks/{id}/versions[/{versionId}]` | `banks:read` | | Métodos de pago | `GET /{companyId}/paymentMethods/{id}/versions[/{versionId}]` | `paymentMethods:read` | | Nóminas | `GET /{companyId}/payrolls/{id}/versions[/{versionId}]` | `payrolls:read` | Las versiones exigen el scope de lectura del recurso, como su `GET /{companyId}//{id}`, y no el de la actividad. ## Qué es una versión Cada versión corresponde a un registro de la [actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/) del documento: - `inserted`: el alta. - `updated`: una modificación. Su `subtype` indica una acción concreta, como `voided` al anular una factura. - `archived`: la eliminación. La copia es el documento tal como estaba al eliminarlo. - `unarchived`: la recuperación. El resto de la actividad del documento (envíos, comentarios…) no crea versiones. El identificador de la versión es el `id` de su registro de actividad. La copia es la que se guardó en su momento: la de un documento antiguo puede no cumplir el esquema actual del recurso. ## Listar las versiones `GET /{companyId}//{id}/versions` devuelve las versiones de un documento, eliminado o no, con **paginación por cursor** (ver el patrón B en la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/)): - `limit`: entre `1` y `100`. Por defecto, `50`. - `cursor`: el `nextCursor` de la página anterior, tal cual se recibió. Se omite en la primera página. - `sortBy`: `-timestamp` (por defecto, lo más reciente primero) o `timestamp`. La respuesta es `{ items, hasMore, nextCursor }`: `nextCursor` es el cursor de la página siguiente, o `null` cuando no hay más versiones. Cada elemento lleva `id`, `timestamp`, `type`, `subtype` y `username`. En las modificaciones añade `changedFields`, con las rutas de los campos que cambiaron respecto a la versión anterior (como mucho 50; si hay más, `changedFieldsTruncated` es `true`). ```json { "items": [ { "id": 482913, "timestamp": "2026-09-15T08:42:17.351Z", "type": "updated", "subtype": null, "username": "ana.garcia@ejemplo.es", "changedFields": ["main.dueDate", "main.lines[1].unitPrice"] }, { "id": 480002, "timestamp": "2026-09-14T17:05:40.120Z", "type": "inserted", "subtype": null, "username": "ana.garcia@ejemplo.es" } ], "hasMore": false, "nextCursor": null } ``` ## Obtener una versión `GET /{companyId}//{id}/versions/{versionId}` devuelve: - `content`: la copia del documento en esa versión, en el mismo formato que `GET /{companyId}//{id}`. - `tags`: sus etiquetas en esa versión, si se guardaron. - `version`: los datos de la versión (`id`, `timestamp`, `type`, `subtype`, `username`). - `previousVersionId` y `changes`: en una modificación, la versión anterior y cada campo que cambió, con su valor antes y después. En el resto de versiones, `null` y `[]`. ```json { "content": { "uuid": "inv_7c1e2f40-5a8b-4d3c-9e6f-2b1a0c9d8e7f", "type": "invoice", "main": { "...": "..." } }, "tags": ["mayorista"], "version": { "id": 482913, "timestamp": "2026-09-15T08:42:17.351Z", "type": "updated", "subtype": null, "username": "ana.garcia@ejemplo.es" }, "previousVersionId": 480002, "changes": [ { "path": "main.dueDate", "before": "2026-10-15", "after": "2026-11-15" }, { "path": "main.lines[1].unitPrice", "before": 120, "after": 12 } ] } ``` Sobre `changes` (y `changedFields`): - Se compara lo que edita el usuario: `main`, los adjuntos (`attachments`) y las etiquetas (`tags`). `accounting`, `books`, `meta` e `import` los rellena el servidor al guardar (el asiento, los libros de impuestos, los datos de gestión y los de la importación) y no se comparan, aunque están completos en `content`. - Algunos campos de `main` también se recalculan al guardar, como los totales, los impuestos o el total de cada línea: al cambiar un precio aparecen junto al precio. - `path` es la ruta del campo: `main.dueDate`, `main.lines[2].unitPrice`, `attachments` o `tags`. - Si el campo no existía en la versión anterior, falta `before`; si ya no existe, falta `after`. - Las listas se comparan por posición: una línea nueva al final aparece como `main.lines[3]` con el valor completo en `after`, y una línea quitada, con el valor completo en `before`. ## Desde la actividad El [registro de actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/) dice qué documentos se modificaron, quién y cuándo (`type=updated`), pero no qué cambió. El `id` de cada registro es también el de su versión: con él pides la versión al recurso del documento (`document.type` y `document.id`) para ver los campos cambiados y sus valores. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/activity?documentType=invoice&type=updated" ``` ## Recuperar datos de una versión La API no tiene una operación de «restaurar»: los datos se recuperan con la operación de modificación del recurso, que aplica las mismas validaciones y restricciones que cualquier otro cambio. 1. Localiza el cambio en las versiones del documento: `changedFields` dice qué campos tocó cada modificación. 2. Pide esa versión: `changes` trae el valor anterior de cada campo, y `previousVersionId`, la versión completa anterior al cambio. 3. Pide el documento actual con `GET /{companyId}//{id}`. 4. Envía el documento actual con los campos recuperados a la operación de modificación (`PUT /{companyId}//{id}`). Cambia solo los campos que quieres recuperar, en lugar de reenviar la copia entera: puede ser de un esquema anterior y lleva campos que se recalculan, como los totales. Ten en cuenta que: - Una factura ya enviada a VeriFactu o TicketBAI no se puede modificar: se corrige con una [rectificativa](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/). - Los cambios recalculan lo que dependa del documento, como su contabilidad o el stock. - Un documento eliminado no se recupera por la API: créalo de nuevo con los datos de la copia, sin su `uuid`. Será un documento nuevo, con otro identificador. ###### Copy as cURL Versiones de una factura: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/invoices/inv_7c1e2f40-5a8b-4d3c-9e6f-2b1a0c9d8e7f/versions" ``` Una versión, con sus cambios: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/invoices/inv_7c1e2f40-5a8b-4d3c-9e6f-2b1a0c9d8e7f/versions/482913" ``` ## Permisos - El scope de lectura del recurso, con los mismos requisitos de rol que su `GET /{companyId}//{id}`. - En los contactos, solo los de las facetas (clientes, proveedores, empleados) que permite tu rol: el resto responde `404`. - Los IBAN de bancos y métodos de pago se ven completos solo con `banks:readIban`; si no, como en el resto de la API, solo sus últimos cuatro dígitos (`iban4`). ## Errores comunes - `404 Not Found`: el documento no existe, no es de ese recurso o no lo puedes ver, o la versión no es de ese documento. - `400 ValidationError`: `versionId` no es un número, `cursor` no es un cursor devuelto por la API o `limit` está fuera de `1`–`100`. - `403 Forbidden`: falta el scope de lectura del recurso. - `422 Unprocessable Entity` (`code: "activity_query_timeout"`): la consulta ha tardado demasiado. Inténtalo con un `limit` menor. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/document-versions.md --- # Catálogo de impuestos > Esta guía trata los tipos impositivos que se aplican a las líneas de los documentos (IVA, IRPF, IGIC…). Esta guía trata los **tipos impositivos** que se aplican a las líneas de los documentos (IVA, IRPF, IGIC…). Los **modelos de impuestos** que FacturaDirecta genera a partir de ellos (303, 347, 390…) son otro recurso: [Declaraciones de impuestos](https://www.facturadirecta.com/desarrolladores/api/referencia/tax-declarations/). Los impuestos en FacturaDirecta no se envían como tasas numéricas inline: se referencian por **identificador** desde un catálogo configurado en la empresa. Cada empresa tiene dos catálogos separados según el documento al que se aplique el impuesto: uno para ventas (facturas, presupuestos, albaranes, recurrentes) y otro para compras (facturas de compra y tickets). Esta guía cubre: - Cómo obtener el catálogo de impuestos de una empresa. - Forma de un impuesto (`SalesTax` / `PurchasesTax`) y sus campos clave. - Categorías funcionales y cuándo usar cada una. - Impuestos autorepercutidos (intracomunitarios, ISP, importación de servicios). - Caso especial `lineAmountIsTax` para IVA importación DUA. - Cómo se aplican impuestos en una línea (`line.tax`). - Cómo se consolida en el documento (`main.taxes`). - Recomendaciones para integradores. ## Dos catálogos: ventas y compras Los catálogos están separados porque las operaciones de venta y compra admiten regímenes distintos. Algunos ejemplos: - En **ventas** existen categorías como `export` (exportación fuera de la UE) o `eu_consumer_vat` (IVA del país de destino para ventas a consumidores finales de la UE, régimen OSS). En compras no. - En **compras** existen categorías como `import_customs` (importación por aduana con DUA), `import_services` (importación de servicios extracomunitarios) o `agriculture` (compensación al régimen especial agrario). En ventas no. Mezclar catálogos no funciona: un `TaxId` del catálogo de ventas **no es válido** en una línea de factura de compra, y viceversa. ## Obtener el catálogo Dos endpoints, ambos bajo el scope `settings:read`: | Endpoint | Devuelve | |---|---| | `GET /{companyId}/settings/taxes/sales` | Catálogo de ventas: `{ items: SalesTax[] }` | | `GET /{companyId}/settings/taxes/purchases` | Catálogo de compras: `{ items: PurchasesTax[] }` | Ambos aceptan un filtro opcional `taxGroup` para acotar la respuesta. Sin paginación: devuelven el conjunto completo (ver el patrón "sin paginación" en la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/)). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/settings/taxes/sales" ``` ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/settings/taxes/purchases?taxGroup=IRPF" ``` ## Forma de un impuesto Los schemas `SalesTax` y `PurchasesTax` comparten la mayoría de campos. Las diferencias se anotan abajo. | Campo | Tipo | Significado | |---|---|---| | `id` | string (`TaxId`) | Identificador estable del impuesto. Es lo que se envía en `line.tax`. | | `value` | number | Porcentaje **en base 1**. IVA 21% → `0.21`. **No** se envía como `21`. | | `taxGroup` | enum | Familia normativa. Ver tabla abajo. | | `category` | enum | Categoría funcional. Determina cómo se trata el impuesto contable y fiscalmente. Ver tabla abajo. | | `current` | boolean | `true` si está vigente actualmente. Los impuestos no vigentes existen en el catálogo para soportar documentos históricos. | | `country` | string | Código país ISO 3166-1 Alpha-2 del país de recaudación. | | `title` | string | Nombre completo del impuesto, legible para humanos. | | `shortTitle` | string | Nombre corto para columnas o listas. | | `account` | string (`AccountCode`) | Cuenta contable asociada al impuesto. | | `description` | string | Explicación breve del propósito. | | `selectorFlags` | array | Dónde puede aplicarse: `"product"`, `"line"`. Array **vacío** indica un impuesto autorepercutido que no se selecciona manualmente. | | `autoApplied` | boolean | Si `true`, es un impuesto autorepercutido. No se envía en `line.tax`. | | `pairedWith` | string (`TaxId`) | ID del impuesto pareja. En un impuesto base apunta al autorepercutido que se generará; en un autorepercutido apunta a su base. | | `lineAmountIsTax` | boolean (solo compras) | Caso especial: el importe de la línea **es la cuota**, no la base. Aplica al IVA importación DUA. | ### Grupos (`taxGroup`) Familias normativas. No se debe asumir que los valores son estables ante cambios fiscales: | Grupo | Sales | Purchases | Significado | |---|---|---|---| | `IVA` | ✓ | ✓ | IVA español estándar. | | `IVA_RE` | ✓ | ✓ | IVA con recargo de equivalencia (régimen de comerciantes minoristas). | | `IGIC` | ✓ | ✓ | Impuesto General Indirecto Canario. | | `IPSI` | ✓ | ✓ | Impuesto sobre la Producción, Servicios e Importación (Ceuta y Melilla). | | `IRPF` | ✓ | ✓ | Retención de IRPF. | | `VATEU` | ✓ | — | IVA de otros países de la UE (régimen OSS). Solo aplica en ventas. | En `VATEU`, algunos países de la UE tienen tipos legítimos del `0%` para operaciones concretas. Si el catálogo devuelve un impuesto con `value: 0`, trátalo como un impuesto aplicable, no como ausencia de impuesto. En el grupo `IRPF`, un impuesto con `value: 0` —como `S_IRPF_0` o `P_IRPF_0`, cuyo título es **Sin IRPF**— no se considera una retención efectiva. Puedes guardar el documento sin contacto. Cualquier retención de IRPF con un valor distinto de cero requiere un contacto asignado y válido para la operación. ### Categorías funcionales (`category`) La categoría es lo que controla el tratamiento real del impuesto. Misma tabla, con qué categoría existe en cada catálogo: | Categoría | Sales | Purchases | Significado | |---|---|---|---| | `standard_vat` | ✓ | ✓ | IVA / IGIC / IPSI estándar (tipos normal, reducido, superreducido y temporales). | | `exempt_vat` | ✓ | ✓ | Operación exenta de IVA. | | `intra_community` | ✓ | ✓ | Operación intracomunitaria. En compras genera automáticamente un par autorepercutido (efecto neto cero). | | `reverse_charge` | ✓ | ✓ | Inversión del sujeto pasivo. El comprador liquida en lugar del vendedor. | | `equivalence_surcharge` | ✓ | ✓ | Recargo de equivalencia. | | `withholding` | ✓ | ✓ | Retención de IRPF general. | | `rental_withholding` | ✓ | ✓ | Retención de IRPF por alquiler de inmueble urbano. | | `pass_through` | ✓ | ✓ | Suplidos: gastos pagados en nombre de un tercero, sin IVA. | | `export` | ✓ | — | Exportación a país fuera de la UE. | | `eu_consumer_vat` | ✓ | — | IVA del país de destino para ventas B2C en la UE (régimen OSS). | | `import_customs` | — | ✓ | Importación de bienes tramitada por aduana (DUA). El IVA se paga en aduana y es deducible. | | `import_services` | — | ✓ | Importación de servicios extracomunitarios. Genera par autorepercutido. | | `import_pending_customs` | — | ✓ | Importación pendiente de despacho aduanero (IVA al 0% temporal). | | `agriculture` | — | ✓ | Compensación a explotaciones en régimen especial agrario. | Como el conjunto de valores puede ampliarse ante cambios normativos, **no asumas un closed set**: trata `category` como un enum extensible y degrada gracefully con valores desconocidos. ### Impuestos autorepercutidos Hay tres categorías que **generan automáticamente** un par de impuestos (uno deducible y otro devengado, con efecto neto cero pero ambos declarables): - `intra_community` en compras (adquisición intracomunitaria de bienes). - `reverse_charge` en compras (inversión del sujeto pasivo). - `import_services` en compras (importación de servicios extracomunitarios). El impuesto autorepercutido tiene `autoApplied: true` y un `selectorFlags` vacío. **No se envía en `line.tax`.** En su lugar, envías el impuesto base y el sistema añade automáticamente su pareja en el agregado del documento. Ejemplo real del catálogo de compras (IVA 21% bienes corrientes intracomunitario): - `P_IVA_21_BC_IC` — impuesto **base** (`category: "intra_community"`, `autoApplied: false`, `pairedWith: "P_IVA_21_BC_IC_AR"`). - `P_IVA_21_BC_IC_AR` — impuesto **autorepercutido** (`autoApplied: true`, `pairedWith: "P_IVA_21_BC_IC"`, `selectorFlags: []`). Sufijos del ID: - `_BC` (también `_BI`, `_SV`): bienes corrientes / bienes de inversión / servicios. Distingue régimen de IVA aplicable. - `_IC`: intracomunitario. `_ISP`: inversión del sujeto pasivo. - `_AR`: autorepercutido (siempre pareja del impuesto sin `_AR`). Para aplicarlo en una línea: - Envías `"tax": ["P_IVA_21_BC_IC"]`. - En `main.taxes` del documento aparecen **ambos** impuestos consolidados, con `base` y `amount` simétricos (efecto neto cero). ### `lineAmountIsTax` (solo compras) Cuando un impuesto tiene `lineAmountIsTax: true`, el comportamiento de la línea que lo usa es especial: **el importe de la línea es directamente la cuota de IVA**, no la base imponible. No se recalcula `base × tipo`. Es el patrón del IVA importación tramitado por aduana (DUA), donde la aduana repercute al importador una cuota global ya calculada. El integrador debe detectar este flag en el catálogo y modelar la línea como "importe = cuota", típicamente con `unitPrice` igual a la cuota notificada en el DUA. ## Aplicar impuestos a una línea El campo `line.tax` es **un array de `TaxId`** (strings): ```json "lines": [ { "text": "Servicios profesionales junio 2026", "quantity": 1, "unitPrice": 1500.00, "tax": ["S_IVA_21", "S_IRPF_15"] } ] ``` Notas: - Los IDs son los `id` de los items devueltos por el endpoint del catálogo correspondiente a la familia del documento (sales o purchases). - Pueden aplicarse varios impuestos a una misma línea, típicamente IVA + retención de IRPF para servicios profesionales. - **No envíes impuestos con `autoApplied: true`**: el sistema los añadirá por su cuenta a partir del impuesto base. - **No mezcles tipos**: si el documento es una `bill` (compra), todos los IDs deben venir del catálogo de compras. El campo `taxIncludedPrices` del documento controla si `unitPrice` ya incluye IVA. Por defecto es `false` (el `unitPrice` es base imponible). Cuando es `true`, los importes de impuestos se calculan hacia atrás a partir del precio total. ## Agregado del documento (`main.taxes`) El documento incluye en `main.taxes` la consolidación por impuesto aplicado, no la lista de impuestos por línea. Forma: ```json "taxes": [ { "id": "S_IVA_21", "base": 1500.00, "amount": 315.00, "companyCurrencyBase": 1500.00, "companyCurrencyAmount": 315.00 }, { "id": "S_IRPF_15", "base": 1500.00, "amount": -225.00, "companyCurrencyBase": 1500.00, "companyCurrencyAmount": -225.00 } ] ``` - **`base`** es la base imponible sobre la que se calcula el impuesto. - **`amount`** es la cuota (positiva para impuestos a pagar, negativa para retenciones). - **`companyCurrencyBase`** y **`companyCurrencyAmount`** son los equivalentes en la moneda de la contabilidad de la empresa, útil cuando el documento está en divisa extranjera. - En **purchases**, cada entrada puede llevar `nonDeductible: true` si el impuesto no es deducible en IRPF (campo del documento `nonDeductibleInIRPF`). **Cómo se calcula:** - Si `main.mode === "calcTotal"` (default), el agregado se **calcula automáticamente** a partir de las líneas. Conviene dejarlo vacío al crear y leerlo de la respuesta. - Si `main.mode === "totalProvided"`, el agregado se **toma como dado** desde el cuerpo. Útil para importar facturas escaneadas con totales fijos del proveedor. ## Recomendaciones para integradores - **Cachea el catálogo por empresa.** Los catálogos cambian con baja frecuencia (cambios normativos o cuando el usuario configura nuevos impuestos). Cachear evita una llamada extra por cada documento creado. - **No asumas IDs estables entre empresas distintas.** El `id` es estable dentro de una empresa, pero dos empresas pueden tener IDs distintos para el "mismo" IVA 21%. - **No asumas IDs estables en el tiempo absoluto.** Pueden definirse nuevos IDs ante cambios normativos. Refresca el catálogo si una llamada falla con `400` por TaxId desconocido. - **Filtra por `current: true`** para ofrecer al usuario solo impuestos vigentes al elegir uno nuevo. Para documentos históricos, conserva los no vigentes. - **Usa `selectorFlags`** para saber dónde es legítimo aplicar un impuesto. Un impuesto con `selectorFlags: []` solo aparece como autorepercutido (no se selecciona). - **Para autorepercutidos, envía el impuesto BASE.** El sistema añadirá el repercutido automáticamente. Si lo intentas a la inversa, la API rechaza la operación. - **Si el documento está en una divisa distinta a la de la contabilidad**, presta atención a los campos `companyCurrencyBase` y `companyCurrencyAmount` del agregado: dependen del tipo de cambio del documento (`exchangeRate`). ## Errores comunes - **`400 ValidationError`** — `TaxId` no existe en el catálogo de la empresa. Verifica que estás consultando el catálogo correcto (sales vs purchases) y que el ID no es de otra empresa. - **`400 ValidationError`** — impuesto con `autoApplied: true` enviado manualmente en `line.tax`. Envía el impuesto base correspondiente. - **`400 ValidationError`** — mezcla de IDs de catálogos distintos (ID de ventas en un `bill`, o viceversa). - **`400 ValidationError`** — totales enviados manualmente que no cuadran con las líneas en modo `calcTotal`. Cambia a `mode: "totalProvided"` o deja los totales vacíos. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/taxes.md --- # VeriFactu > Esta guía cubre cómo se integra VeriFactu con la API pública de FacturaDirecta. **VeriFactu** es el sistema de **facturación electrónica de la AEAT** (Agencia Estatal de Administración Tributaria) para empresas y autónomos del Estado español que **no** estén en territorio foral. Lo establece el Real Decreto 1007/2023, regulando los sistemas informáticos de facturación. Esta guía cubre cómo se integra VeriFactu con la API pública de FacturaDirecta: - Cuándo se activa. - Los **dos modos** del sistema (`mode_verifactu` y `mode_no_verifactu`) y por qué es importante saber en cuál está cada factura. - Qué campos puedes (y debes) enviar en `main.verifactu` y por línea. - Qué metadatos lee tu integración en `meta.verifactu`. - Códigos oficiales: `TipoFactura`, `CalificacionOperacion`, `OperacionExenta`. - Códigos QR y URLs de verificación. - Envío a AEAT, rate limit y reintentos. - Anulación. - Webhooks específicos de VeriFactu. Para el contrato general del recurso de facturas, ver [Facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/). Para el régimen equivalente del País Vasco, ver [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/). ## Cuándo se activa VeriFactu se aplica cuando la **empresa emisora** está dada de alta en VeriFactu desde su configuración fiscal. La activación es administrativa; **no hay un flag por factura para activarlo o desactivarlo**. La única forma de emitir una factura sin generar VeriFactu en una empresa con VeriFactu activo es marcarla como externa (`main.external = true`): es el modo para registrar facturas históricas o emitidas con otro software, **no** una vía para saltarse la obligación. ## Los dos modos: `mode_verifactu` vs `mode_no_verifactu` VeriFactu admite dos modos operativos. La diferencia es **cuándo y cómo se comunican los registros a AEAT**: | Modo | Comportamiento | |---|---| | **`mode_verifactu`** | Envío en **tiempo real** a AEAT. Cada factura se transmite por SOAP a la sede de Hacienda inmediatamente tras firmarla. La cadena de huellas se mantiene también. | | **`mode_no_verifactu`** | Registros **firmados y encadenados localmente**. AEAT no recibe el envío inmediato; los registros quedan disponibles para consulta o auditoría a petición. La empresa sigue obligada a mantener el sistema de facturación íntegro. | Tu integración no decide el modo: lo determina la configuración fiscal de la empresa. El modo en el que se procesó **cada factura concreta** queda registrado en `meta.verifactu.mode` (y en `meta.verifactu.modeAnulacion` si fue anulada). Lee esos campos para saber qué URL de verificación usar en el QR (ver más abajo). ## Campos de entrada: `main.verifactu` Son los únicos campos de VeriFactu que tu integración puede **enviar** en `POST /invoices` o `PUT /invoices/{id}`. El resto lo calcula el servidor. ### Nivel factura | Campo | Tipo | Significado | |---|---|---| | `TipoFactura` | enum (ver tabla) | Tipo de la factura según RD 1619/2012 y régimen rectificativo. Por defecto, el servidor lo deduce: `F1` para completas identificadas y `F2` para simplificadas o completas sin identificación del destinatario. | | `DescripcionOperacion` | string (≤ 500 chars) | Descripción libre de la operación. Útil cuando las líneas no la describen suficientemente. | | `FacturaSimplificadaArt7273` | `"S"` o `"N"` | `"S"` si es una factura simplificada cualificada (con identificación del destinatario, Art. 7.2/7.3 del RD 1619/2012). | | `FechaOperacion` | string `DD-MM-AAAA` | Fecha de operación (devengo) cuando es distinta de la fecha de la factura. Si no la envías, no se comunica ninguna. Ver [Fecha de expedición y fecha de operación](#fecha-de-expedición-y-fecha-de-operación). | | `defaultOperacionExenta` | enum `E1`-`E8` | Valor por defecto de `OperacionExenta` cuando una línea exenta no lo indica explícitamente. `E7` y `E8` solo existen para IGIC. | ### Fecha de expedición y fecha de operación La fecha de expedición del registro (`FechaExpedicionFactura`) es la fecha de la factura, `main.date`: la misma que se imprime y que lleva el QR. Queda fijada al generar el primer registro de alta, porque forma parte del identificador de la factura en AEAT (NIF, serie y número y fecha de expedición). Cambiar `main.date` en una factura ya comunicada no es subsanable: la API lo rechaza con un cambio de tipo `voidOrCorrective` en `hint.verifactuChanges`; anula la factura o emite una rectificativa. Al crear una factura definitiva, `main.date` no puede ser posterior a hoy ni anterior al inicio del periodo de declaración que sigue abierto: | Hasta | Se admite desde | |---|---| | 30 de enero | 1 de octubre del año anterior | | 20 de abril | 1 de enero | | 20 de julio | 1 de abril | | 20 de octubre | 1 de julio | Si el día límite cae en sábado o domingo, el plazo llega al lunes siguiente. Fuera de ese margen la API rechaza la factura con el mensaje «La fecha de factura no puede ser anterior al [fecha], inicio del periodo de declaración que sigue abierto». `FechaOperacion` solo se incluye en el registro si la envías: cuando la operación (el devengo) fue en una fecha distinta de la de la factura, como en rectificativas (fecha de la operación original), claves de régimen `14` y `15` (devengo futuro) o anticipos. Las facturas comunicadas antes de este criterio conservan la fecha de expedición con la que se registraron, el día en que pasaron a definitivas, y su fecha de operación si difería. Reglas que aplica la AEAT y que la API valida antes de enviar: - No puede ser anterior a veinte años ni posterior a un año desde la fecha actual. - Solo puede ser posterior a la fecha de expedición, o futura, si **todos** los detalles del desglose llevan `ClaveRegimen` `14` o `15` (IVA pendiente de devengo). Una factura con una línea de régimen general y otra con clave `15` se rechaza aunque la fecha sea válida para la segunda. - Con `ClaveRegimen` `14` la fecha de operación es obligatoria y posterior a la expedición; además, el destinatario debe tener NIF que empiece por P, Q, S o V y el tipo de factura ser `F1` o `R1`-`R4`. Cambiar `FechaOperacion` en una factura que ya tiene registro de alta es un cambio subsanable: sigue el flujo de [Subsanaciones](#subsanaciones). En las plantillas de tareas recurrentes el campo se descarta al guardar: cada factura generada parte sin fecha de operación y la deduce de su propia fecha. ### Por línea Cada elemento de `lines` puede llevar un sub-objeto `verifactu`: | Campo | Tipo | Significado | |---|---|---| | `ClaveRegimen` | string | Clave del régimen aplicable según AEAT (`01`-`21`). Las claves admitidas y su significado dependen del impuesto: lista L8A para IVA, L8B más `20` y `21` para IGIC, y `01`, `08`, `11`, `18`, `19` y `20` para IPSI. | | `CalificacionOperacion` | enum (ver tabla) | Sólo para operaciones **sujetas** o no sujetas. Mutuamente excluyente con `OperacionExenta`. | | `OperacionExenta` | enum `E1`-`E8` | Sólo para operaciones **exentas**. Mutuamente excluyente con `CalificacionOperacion`. `E7` y `E8` solo existen para IGIC. | Basta con enviar el campo que quieras fijar. Lo que la línea no indica se deduce de su impuesto, igual que cuando ninguna línea lleva datos de VeriFactu: `ClaveRegimen` `01` y `CalificacionOperacion` `S1` para el IVA repercutido, `S2` con inversión del sujeto pasivo, `N2` en operaciones intracomunitarias, y `ClaveRegimen` `02` con `OperacionExenta` `E2` en exportaciones. Por ejemplo, para una operación de tracto sucesivo basta con `{ "ClaveRegimen": "15" }`: el detalle sale como `15` / `S1` con el tipo y la cuota de la línea. El significado de una misma clave puede variar según el impuesto. La clave `18` corresponde al recargo de equivalencia en IVA, al régimen especial del pequeño empresario o profesional en IGIC y a operaciones concretas de la ordenanza del IPSI. FacturaDirecta valida la clave contra el impuesto de la empresa y no exige un recargo de equivalencia por el mero hecho de usar `18`. Si una línea indica `OperacionExenta`, no se le deduce ninguna `CalificacionOperacion`, y viceversa. Para una línea sin impuestos, con IVA 0 % o con un impuesto exento, la causa de exención sale de `OperacionExenta` en la línea o, si no lo indicas, de `defaultOperacionExenta`. ## Códigos oficiales Los enums los define la AEAT en la especificación técnica de VeriFactu y los reproduce literalmente nuestro schema. ### `TipoFactura` | Código | Significado oficial | |---|---| | `F1` | Factura (Art. 6, 7.2 y 7.3 del RD 1619/2012). | | `F2` | Factura simplificada y facturas sin identificación del destinatario Art. 6.1.d) RD 1619/2012. | | `F3` | Factura emitida en sustitución de facturas simplificadas facturadas y declaradas. | | `R1` | Factura rectificativa (Art. 80.1 y 80.2 y error fundado en derecho). | | `R2` | Factura rectificativa (Art. 80.3). | | `R3` | Factura rectificativa (Art. 80.4). | | `R4` | Factura rectificativa (resto). | | `R5` | Factura rectificativa en facturas simplificadas. | Detalles de cuándo usar cada `R#` en la [guía de Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/). Para crear una `F3` se utiliza el [endpoint dedicado de sustitutivas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#crear-factura-sustitutiva). ### Facturas completas sin identificación del destinatario Con VeriFactu activo, el servidor puede deducir `F2` para una factura completa dirigida a un contacto extracomunitario sin identificador fiscal. Informa el nombre, el domicilio y el país del contacto. La factura conserva `main.simplified = false`, y sus datos aparecen en el documento impreso. El registro de alta lleva `FacturaSinIdentifDestinatarioArt61d = "S"` y omite `Destinatarios`. Si la factura es rectificativa, el tipo deducido es `R5`. Este tratamiento no se aplica a facturas sustitutivas F3 ni a empresas con TicketBAI activo. Si envías manualmente un `TipoFactura` que exige identificar al destinatario, la API rechaza la factura hasta que informes un identificador fiscal o uses el tipo `F2` —`R5` para una rectificativa—. También puedes omitir `TipoFactura` para que el servidor lo deduzca. ### `CalificacionOperacion` Aplica solo en líneas **sujetas** o **no sujetas** (no exentas). | Código | Significado oficial | |---|---| | `S1` | Operación sujeta y no exenta — sin inversión del sujeto pasivo. | | `S2` | Operación sujeta y no exenta — con inversión del sujeto pasivo. | | `N1` | Operación no sujeta artículo 7, 14, otros. | | `N2` | Operación no sujeta por reglas de localización. | ### `OperacionExenta` Aplica solo en líneas **exentas**. El significado de cada código depende del impuesto de la empresa (IVA en la península y Baleares, IGIC en Canarias). | Código | IVA (Ley 37/1992) | IGIC | |---|---|---| | `E1` | Exenta por Art. 20. | Exenta por el capítulo I del Decreto Legislativo 1/2025. | | `E2` | Exenta por Art. 21. | Exenta por Art. 11 de la Ley 20/1991. | | `E3` | Exenta por Art. 22. | Exenta por Art. 12 de la Ley 20/1991. | | `E4` | Exenta por Art. 23 y 24. | Exenta por Art. 13 de la Ley 20/1991. | | `E5` | Exenta por Art. 25. | Exenta por Art. 25 de la Ley 19/1994. | | `E6` | Exenta otros. | Exenta por Art. 47 de la Ley 19/1994. | | `E7` | No aplica. | Exenta por Art. 90 del Decreto Legislativo 1/2025 (régimen especial del pequeño empresario o profesional). | | `E8` | No aplica. | Exenta otros. | Para IPSI (Ceuta y Melilla) la AEAT admite `E1`-`E6` y rechaza `E7` y `E8`. Su significado, según el anexo del documento de validaciones de la AEAT: `E1` Art. 7 de la Ley 8/1991; `E2`, `E3` y `E5` Art. 8; `E4` Arts. 9 y 10; `E6` otros supuestos de la Ley 8/1991. > Los códigos `E1`-`E6` de VeriFactu corresponden a artículos > distintos de los `E1`-`E6` de TicketBAI. **No los mezcles** si > tu integración opera con empresas en ambos regímenes. ## Metadatos: `meta.verifactu` Tras guardar una factura definitiva (`main.draft = false`) en una empresa con VeriFactu activo, el servidor rellena `meta.verifactu` con la información operativa. Es **read-only**. | Campo | Significado | |---|---| | `registroAlta` | Objeto con el registro de alta enviado (formato AEAT). | | `registroAltaXml` | XML literal del registro de alta. | | `registroAnulacion` | Objeto con el registro de anulación (cuando aplica). | | `registroAnulacionXml` | XML literal del registro de anulación. | | `mode` | `mode_verifactu` o `mode_no_verifactu`. Determina cómo se envió/registró el alta. | | `modeAnulacion` | Igual que `mode` pero para el registro de anulación. | | `qrUrl` | URL completa del código QR que debe aparecer en la representación impresa de la factura. | | `qr` | Datos codificados del QR (útil para regenerar imagen con tu propia librería QR). | > `registroAlta` y `registroAnulacion` son **objetos abiertos** > (`additionalProperties: true`): siguen el contrato de AEAT, que > puede evolucionar. Si necesitas un campo específico, accede por > nombre; no asumas un conjunto cerrado. ## Códigos QR Cada factura emitida con VeriFactu **debe llevar un código QR impreso** que permite a un comprador (o a AEAT) verificar la factura. El servidor genera la URL y los datos en `meta.verifactu.qrUrl` y `meta.verifactu.qr`. La URL apunta a la sede de AEAT. **Cuál de las dos URLs depende del modo**: | Modo de la factura | URL del QR | |---|---| | `mode_verifactu` | `https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?...` | | `mode_no_verifactu` | `https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQRNoVerifactu?...` | Los entornos de **preproducción** usan `prewww2.aeat.es` con el mismo patrón de path. Si tu integración renderiza PDFs propios y no usa nuestro generador, **respeta la URL tal cual** te llega en `qrUrl`: el QR contiene parámetros firmados/hashados que no puedes reconstruir. ## Envío a AEAT Para facturas en `mode_verifactu`, el envío al webservice SOAP de AEAT es inmediato tras guardar la factura definitiva: 1. Se construye el registro de alta a partir de la factura. 2. Se calcula la huella (hash de los campos relevantes, encadenando con la huella de la factura anterior). 3. Se firma el XML con el certificado de la empresa. 4. Se envía al endpoint SOAP correspondiente (producción o preproducción según configuración). 5. Si AEAT responde OK, el registro se considera aceptado y se dispara el webhook `invoice.verifactu_sent`. Para facturas en `mode_no_verifactu`, los pasos 1-3 son los mismos pero **no hay paso 5**: el registro queda guardado localmente con su huella, listo para que AEAT lo consulte cuando lo requiera. ### Rate limit AEAT puede indicar al sistema que espere antes del próximo envío (parámetro `TiempoEsperaEnvio` en sus respuestas). FacturaDirecta respeta ese rate limit por empresa: si AEAT pide esperar `N` segundos, los siguientes envíos de esa empresa se posponen automáticamente. Tu integración no necesita gestionarlo, pero puede observar el efecto si nota un retraso entre creación de facturas y `meta.verifactu.registroAlta` rellenado. ## Subsanaciones Una subsanación corrige determinados datos de una factura que ya tiene un registro de alta VeriFactu sin anularla. La API pública usa un flujo de confirmación en dos pasos: 1. Envía `PUT /invoices/{id}` con el `content` completo modificado y sin `verifactuOperation`. Si el cambio es subsanable, la API devuelve HTTP 400 con `type: "verifactuSubsanacion"`. Consulta `hint.verifactuChanges.changes`: cada cambio incluye su `path` y la `consequence` que le corresponde. 2. Tras revisar los cambios, repite exactamente el mismo PUT y añade `verifactuOperation: { "subsanacion": true }` al nivel superior del body para confirmarlo. Cuando la confirmación es válida, la respuesta HTTP 200 contiene la factura actualizada. `meta.verifactu.registroAlta.Subsanacion` vale `"S"` y se genera un nuevo registro de alta, encadenado tras el último registro de la empresa. En `mode_verifactu` también se despacha a AEAT; en `mode_no_verifactu` queda firmado y encadenado localmente. La confirmación solo acepta cambios clasificados como `subsanacion`. Los cambios de importes, impuestos o datos del destinatario aparecen como `voidOrCorrective` y siguen devolviendo HTTP 400: debes anular la factura o emitir una rectificativa. No reintentes esos cambios con `verifactuOperation` porque la opción no omite esa validación. ## Anulación Cuando una factura se anula con `PUT /invoices/{id}` enviando `main.voided = true`, el servidor: 1. Genera el registro de anulación. 2. Si está en `mode_verifactu`, lo envía a AEAT por SOAP. 3. Rellena `meta.verifactu.registroAnulacion` y `registroAnulacionXml`. 4. Guarda el modo del envío en `meta.verifactu.modeAnulacion`. 5. Emite el webhook [`invoice.voided`](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#webhooks). ## Webhooks VeriFactu dispone de **dos eventos** relacionados: | Evento | Cuándo se emite | |---|---| | `invoice.created` (general) | Al crear cualquier factura, incluido el caso VeriFactu. No es específico de VeriFactu. | | **`invoice.verifactu_sent`** | Cuando AEAT confirma el alta de una factura concreta (solo en `mode_verifactu`). | | **`verifactu_batch.sent`** | Cuando AEAT confirma el envío de un **lote de eventos** del sistema (eventos de facturación, sumarios periódicos). No corresponde a una factura individual. | A diferencia de TicketBAI (que no emite webhooks específicos), VeriFactu sí permite a tu integración reaccionar a la confirmación de AEAT sin polling. Detalles del formato de entrega (firma HMAC, reintentos) en [Webhooks](https://www.facturadirecta.com/desarrolladores/webhooks/). ## Coexistencia con TicketBAI Una factura emitida con VeriFactu **no se envía también a TicketBAI**: los dos regímenes son excluyentes y se determinan por la configuración fiscal de la empresa. En la práctica: - Empresa con domicilio fiscal en País Vasco → TicketBAI (ver [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/)). - Empresa con domicilio fiscal en el resto del Estado → VeriFactu. Si tu integración opera con empresas en ambos territorios, no asumas la misma información: en unas facturas tendrás `meta.verifactu` rellenado y `meta.ticketbai` ausente, y viceversa. ## Errores comunes - **`CalificacionOperacion` y `OperacionExenta` enviados a la vez** en una línea: son mutuamente excluyentes. Si la operación es exenta, usa `OperacionExenta`. Si no, `CalificacionOperacion`. - **`FechaOperacion` futura con líneas de régimen general**: AEAT solo la admite si todos los detalles del desglose llevan `ClaveRegimen` `14` o `15` (error 1146). La API rechaza la factura antes de enviarla; pon la clave en todas las líneas o quita la fecha. - **Mezclar códigos de TicketBAI y VeriFactu**: los `E1`-`E6` refieren a artículos diferentes en cada régimen. Tu integración debe seleccionar el conjunto correcto según el régimen activo en la empresa. - **Asumir `mode_verifactu` siempre**: si tu integración monta el QR en su propio PDF y elige hardcodear la URL `ValidarQR`, fallará para facturas en `mode_no_verifactu`. Lee siempre `meta.verifactu.qrUrl`. - **No esperar al `invoice.verifactu_sent` antes de considerar enviada**: la respuesta del POST/PUT de la factura te confirma que el alta se ha registrado **localmente y se ha despachado**. La confirmación firmada de AEAT llega después en `invoice.verifactu_sent`. Si tu lógica depende de "AEAT lo tiene", espera al webhook. - **Cambiar manualmente `meta.verifactu`**: es read-only; el servidor lo regenera al guardar. Cualquier cambio se descarta. ## Referencias oficiales La AEAT publica la documentación técnica de VeriFactu, los esquemas XSD, los códigos de error y la especificación del QR en su sede: - **Portal VeriFactu**: [sede.agenciatributaria.gob.es](https://sede.agenciatributaria.gob.es) (sección "Sistemas Informáticos de Facturación y VERI*FACTU"). - **Endpoint de verificación QR** (producción): `https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR` y `…/ValidarQRNoVerifactu`. - **Endpoint de preproducción**: `https://prewww2.aeat.es/...`. Para el significado funcional de cada código (artículos del IVA, casos rectificativos, claves de régimen) consulta siempre la documentación oficial: FacturaDirecta los acepta y los reenvía a AEAT, pero la **autoridad sobre su semántica** es la Agencia Tributaria, no este sistema. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/verifactu.md --- # TicketBAI > Esta guía cubre cómo se integra TicketBAI con la API pública de FacturaDirecta. **TicketBAI** es el régimen de **facturación electrónica obligatoria del País Vasco**. Lo regulan las tres haciendas forales —Araba, Bizkaia y Gipuzkoa— y se aplica a toda factura de venta emitida por empresas y autónomos con obligaciones fiscales en alguno de los tres territorios. Esta guía cubre cómo se integra TicketBAI con la API pública de FacturaDirecta: - Cuándo se activa. - Qué campos puedes (y debes) enviar en `main.ticketbai`. - Qué metadatos lee tu integración en `meta.ticketbai`. - El concepto de **encadenamiento** entre facturas. - El subsistema **Batuz** (exclusivo de Bizkaia) y el servicio **Zuzendu** para subsanaciones. - Anulación y rectificativas. - Qué información de estado no expone la API pública. Para el contrato general del recurso de facturas, ver [Facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/). Para una visión rápida del régimen equivalente en el resto de España, ver [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/). ## Cuándo se activa TicketBAI se aplica cuando la **empresa emisora** está dada de alta en TicketBAI en su diputación foral. La configuración la determina el alta administrativa de la empresa; **no hay un flag por factura para activarlo o desactivarlo**. La única forma de emitir una factura **sin generar TicketBAI** en una empresa con TicketBAI activo es marcarla como externa (`main.external = true`). Esa marca indica que la factura se generó fuera del programa y contabiliza normalmente, pero **no produce información para TicketBAI ni VeriFactu**. Es el modo apropiado para registrar facturas históricas o emitidas con otro software, no para saltarse la obligación electrónica. ### Diputación foral La factura registra su territorio en `meta.ticketbai.diputacionForal`, con uno de los tres valores: | Valor | Diputación | |---|---| | `Araba` | Diputación Foral de Álava | | `Bizkaia` | Diputación Foral de Bizkaia | | `Gipuzkoa` | Diputación Foral de Gipuzkoa | Lo decide el sistema según la configuración fiscal de la empresa; tu integración solo lo lee. ## Bizkaia es distinto: Batuz Bizkaia opera un sistema adicional llamado **Batuz**: el LROE (Libro Registro de Operaciones Económicas) que recibe los XML de ingresos y gastos. Por eso, una factura emitida en Bizkaia genera **dos** documentos XML: el de TicketBAI estándar y el de Batuz. Implicaciones para tu integración: - En Bizkaia tendrás campos `batuz*` adicionales en `meta.ticketbai` (`batuzAccepted`, `batuzAcceptedWithErrors`, `batuzAcceptedZuzendu`, `batuzModel`, `batuzModelZuzendu`). - Existe un campo de entrada **`batuzRentaIngresos`** en `main.ticketbai` para indicar el epígrafe de IRPF de la operación (obligatorio en algunos escenarios de Bizkaia). - Si la factura se emitió originalmente **sin Software Garante** (papel, otro software no certificado) y se sube a Bizkaia para rectificarla, la información llega en un meta alternativo (`InvoiceMetaBatuzNoSG`) en vez del estándar (`InvoiceMetaTicketbai`). Tu integración debe distinguir cuál de los dos viene. En Araba y Gipuzkoa, los campos `batuz*` no aplican. ## Campos de entrada: `main.ticketbai` Son los únicos campos de TicketBAI que tu integración puede **enviar** en `POST /invoices` o `PUT /invoices/{id}`. El resto lo calcula el servidor. | Campo | Tipo | Cuándo se usa | |---|---|---| | `causaExencion` | enum `E1`-`E6` | Cuando la operación está exenta de IVA, indicas el motivo según la clasificación oficial de las diputaciones forales. | | `causaNoSujeta` | enum `OT`, `RL`, `VT`, `IE` | Cuando la operación no está sujeta a IVA, indicas el motivo. | | `claveTipoFacturaRectificativa` | enum `R1`-`R5` | Solo en facturas rectificativas. Identifica el tipo de rectificativa. Ver [Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/) para qué corresponde a cada `R#`. | | `batuzRentaIngresos` | array de 1 elemento `{ epigrafe: string }` | Solo Bizkaia. `epigrafe` es el código de epígrafe IRPF (1-7 caracteres). | > Los códigos `E1`-`E6`, `OT`/`RL`/`VT`/`IE` y `R1`-`R5` son los > oficiales de las haciendas forales. Su significado funcional está > en la documentación técnica enlazada al final de esta página, no > en el código de FacturaDirecta. Antes de enviar uno, **verifica > en la fuente oficial** que tu operación encaja en esa categoría. Si tu integración no envía nada de esto, el servidor genera el XML con los valores por defecto que se hayan calculado a partir de los datos de la factura (régimen, líneas, contacto). Solo se rellenan estos campos cuando tienes una operación atípica que requiere indicarlo explícitamente. ## Metadatos: `meta.ticketbai` Tras guardar una factura definitiva (`main.draft = false`) en una empresa con TicketBAI activo, el servidor rellena `meta.ticketbai` con la información operativa. Es **read-only**: tu integración **lee**, no escribe. ### Campos principales | Campo | Significado | |---|---| | `identificador` | Identificador TicketBAI de la factura. Se genera siguiendo el formato de la diputación correspondiente. | | `ordenEncadenamiento` | Entero ≥ 0. Posición de la factura en la cadena de TicketBAI de la empresa (ver [Encadenamiento](#encadenamiento)). | | `diputacionForal` | `Araba` / `Bizkaia` / `Gipuzkoa` (ver tabla más arriba). | | `mustBeSent` | Campo obsoleto conservado por compatibilidad. Siempre vale `false`; no indica si existe un envío pendiente. | | `sent` | Campo de compatibilidad. En los documentos procesados por el flujo actual permanece en `false`; no indica el resultado del envío. | | `qrUrl`, `qr` | URL y datos del código QR que debe aparecer en la representación impresa. | | `xml` | XML de **alta** firmado y enviado a TicketBAI. | | `encadenamientoFactura` | Referencia a la factura inmediatamente anterior en la cadena (ver siguiente sección). | ### Campos exclusivos de Bizkaia (Batuz) | Campo | Significado | |---|---| | `batuzAccepted` | Campo de compatibilidad que no refleja el estado de los envíos procesados por el flujo actual. | | `batuzAcceptedWithErrors` | Campo de compatibilidad que no refleja si el envío actual se aceptó con avisos. | | `batuzAcceptedZuzendu` | Campo de compatibilidad que no refleja el resultado actual de Zuzendu. Ver [Zuzendu](#zuzendu-bizkaia). | | `batuzModel` | Modelo interno del XML Batuz de alta. No procesar como contrato estable. | | `xmlNoSG` | XML de Batuz cuando la factura se emitió originalmente sin Software Garante. | ### Campos de anulación y rectificación | Campo | Cuándo aparece | |---|---| | `voided` | La factura ha sido anulada. Contiene el XML de anulación y campos de compatibilidad `mustBeSent`/`sent`, ambos inertes para consultar el estado. | | `zuzenduAlta` | La factura ha sido **subsanada** vía Zuzendu (correcciones a una factura previa sin emitir una rectificativa). | > Los campos `xmlModel`, `batuzModel`, `batuzModelZuzendu` contienen > el **modelo interno** desde el que el servidor genera el XML. Sus > claves no son parte del contrato público y pueden cambiar entre > versiones. Si necesitas inspeccionar el XML, usa el campo `xml` > (string) o `xmlNoSG`. ## Encadenamiento TicketBAI exige que **cada factura referencie a la inmediatamente anterior** emitida por la misma empresa, creando una cadena verificable de extremo a extremo. Es la pieza que da resistencia al sistema: no se puede modificar una factura intermedia sin romper la cadena posterior. El servidor calcula automáticamente la referencia y la guarda en `meta.ticketbai.encadenamientoFactura`: | Campo | Significado | |---|---| | `SerieFacturaAnterior` | Serie de la factura previa (puede no estar si la previa no la tenía). | | `NumFacturaAnterior` | Número de la factura previa. | | `FechaExpedicionFacturaAnterior` | Fecha de emisión de la factura previa. | | `SignatureValueFirmaFacturaAnterior` | Valor de la firma electrónica de la factura previa. | Tu integración no escribe estos campos. Pero si los lees (para una herramienta de auditoría o verificación), conviene saber: - El orden lo determina la **expedición**, no la creación. El campo `meta.expeditionTimestamp` de la factura marca el instante en que pasó de provisional a definitiva. - `meta.ticketbai.ordenEncadenamiento` te da el índice numérico dentro de la cadena de la empresa. ## Envío y reintentos El envío a TicketBAI (y a Batuz en Bizkaia) se inicia **al guardar la factura como definitiva**: 1. Se genera el XML. 2. Se firma con el certificado de la empresa. 3. Se envía a la diputación correspondiente. 4. Si la respuesta indica un error técnico, como un timeout o una indisponibilidad temporal, FacturaDirecta reintenta el envío con **backoff exponencial**. 5. Pasadas **24 horas** desde el primer error sin éxito, los reintentos automáticos cesan. Los rechazos provocados por un certificado inválido, caducado o no censado se mantienen pendientes. Cuando la empresa corrige el certificado o instala uno nuevo, FacturaDirecta vuelve a procesar automáticamente las altas y anulaciones afectadas dentro de la ventana fiscal. Los errores propios de los datos del documento siguen siendo definitivos y requieren corregir el documento. Los campos `mustBeSent`, `sent` y `batuzAccepted*` de `meta.ticketbai` no se actualizan con el resultado. Se conservan por compatibilidad y no debes usarlos para monitorizar el envío. > **No hay webhooks específicos de TicketBAI.** A diferencia de > VeriFactu, que emite [`invoice.verifactu_sent`](https://www.facturadirecta.com/desarrolladores/webhooks/), > TicketBAI no genera eventos propios; tu integración debe leer > `meta.ticketbai` cuando le interese conocer el estado. ### Corregir un gasto rechazado por Batuz Si Batuz rechaza el alta de una factura de compra o un ticket y nunca lo acepta, puedes corregir los datos identificativos mediante `PUT /bills/{id}`. Esto incluye el proveedor, su identificación fiscal, el número de factura y sus fechas. Al guardar, el servidor genera otro alta. Si Batuz aceptó el documento alguna vez, esos datos quedan protegidos. Para cambiarlos, anula el documento y crea otro. Esta protección también se aplica si el último intento fue rechazado pero existe una aceptación anterior. ## Anulación Cuando una factura se anula con `PUT /invoices/{id}` enviando `main.voided = true`, el servidor: 1. Genera el XML de anulación. 2. Lo firma y lo envía a la diputación (y a Batuz en Bizkaia). 3. Rellena `meta.ticketbai.voided` con el resultado del envío. 4. Emite el webhook [`invoice.voided`](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#webhooks). La factura **no desaparece**: queda en estado `voided`. La anulación tiene su propia cola de reintentos. El campo `meta.ticketbai.voided.sent` no refleja el resultado del flujo actual. ## Rectificativas Las **facturas rectificativas** se crean con el CRUD normal de facturas (`POST /invoices`), enlazando con `main.correctedInvoice` a la factura original y rellenando `main.ticketbai.claveTipoFacturaRectificativa` con el código `R1`-`R5` apropiado. Hay un caso especial: cuando rectificas una **factura simplificada** desde Bizkaia con `claveTipoFacturaRectificativa = "R5"`, el sistema usa un flujo distinto. Detalles, codificación de cada `R#` y ejemplos en la [guía de Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/). ## Zuzendu (Bizkaia) **Zuzendu** es el servicio de Bizkaia que permite **subsanar** una factura ya enviada **sin emitir una rectificativa**. El típico caso: un error tipográfico en el concepto o en una dirección, que no afecta a importes ni a la naturaleza de la operación. Cuando una factura se subsana vía Zuzendu, el servidor: 1. Genera un XML de alta Zuzendu (con la corrección). 2. Lo envía a Batuz. 3. Rellena `meta.ticketbai.zuzenduAlta` con el `xmlModel` y `xml` correspondientes. 4. FacturaDirecta conserva el resultado fiscal fuera de los campos de compatibilidad de `meta.ticketbai`. Para facturas que se emitieron originalmente **fuera de FacturaDirecta** (sin Software Garante), el meta es distinto: `InvoiceMetaBatuzNoSG` en lugar de `InvoiceMetaTicketbai`. Tu integración debe comprobar la presencia de `meta.batuzNoSG` o similar para detectar este caso antes de procesar campos que solo existen en uno u otro. ## Consultar el estado desde una integración La API pública no expone el estado autoritativo de los envíos TicketBAI. Tampoco hay webhooks específicos. Por tanto, no puedes confirmar una aceptación o un rechazo mediante polling de `meta.ticketbai`. Puedes leer los artefactos de compatibilidad, como el XML generado, pero no uses `mustBeSent`, `sent`, `voided.sent` ni `batuzAccepted*` para tomar decisiones. Si tu integración necesita conocer el resultado fiscal, esa capacidad no está disponible en el contrato público actual. ## Coexistencia con VeriFactu Una factura emitida con TicketBAI **no se envía también a VeriFactu**: los dos regímenes son excluyentes y se determinan por la configuración fiscal de la empresa. En la práctica: - Empresa con domicilio fiscal en País Vasco → TicketBAI. - Empresa con domicilio fiscal en resto del Estado → VeriFactu (ver [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/)). Si tu integración opera con empresas mixtas, no asumas el mismo flujo: en las páginas de factura tendrás `meta.ticketbai` rellenado en unas y `meta.verifactu` en otras. ## Errores comunes - Enviar una factura cuyo cliente tiene un país que no pertenece al catálogo admitido por el esquema de TicketBAI. El servidor rechaza la operación antes de generar el XML e indica el país y su código. Actualiza el contacto con un código válido. En Bizkaia, la misma validación se aplica al contacto de los gastos enviados al Libro de Registro de Operaciones Económicas (LROE). - Enviar `claveTipoFacturaRectificativa` sin que la factura sea realmente rectificativa (no enlazada con `main.correctedInvoice` o no usando una serie de rectificativas). El XML se generará pero la diputación rechazará el envío en validación. - Olvidar `batuzRentaIngresos` en Bizkaia en operaciones donde es obligatorio. La diputación devuelve error de validación en el envío. - Asumir que `meta.ticketbai.sent = true` es inmediato tras `POST`. Si la diputación está sobrecargada, puede tardar minutos; tu integración no debe bloquearse esperándolo. - Procesar el contenido de `xmlModel` (modelo interno) en lugar de `xml` (XML literal). El modelo interno no es contrato estable. ## Referencias oficiales Las haciendas forales publican la documentación técnica completa (esquemas XSD, códigos de error, validaciones) en sus webs: - **Araba**: [web.araba.eus/es/hacienda/ticketbai/documentacion-tecnica](https://web.araba.eus/es/hacienda/ticketbai/documentacion-tecnica) - **Bizkaia (Batuz)**: [www.batuz.eus](https://www.batuz.eus) - **Gipuzkoa**: [www.gipuzkoa.eus/es/web/ogasuna/ticketbai/documentacion-y-normativa](https://www.gipuzkoa.eus/es/web/ogasuna/ticketbai/documentacion-y-normativa) Para el significado funcional de los códigos (`E1`-`E6`, `OT`/`RL`/`VT`/`IE`, `R1`-`R5`) consulta siempre la documentación oficial: FacturaDirecta los acepta y los reenvía a la diputación, pero la **autoridad sobre su semántica** es la hacienda foral, no este sistema. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/ticketbai.md --- # Facturas rectificativas > Una factura rectificativa corrige una factura emitida previamente. Una **factura rectificativa** corrige una factura emitida previamente. La regula el **Art. 80 de la Ley 37/1992 del IVA** (modificación de la base imponible) y el **RD 1496/2003** (errores formales y materiales). FacturaDirecta da soporte completo a este tipo de factura, incluyendo su codificación en TicketBAI, VeriFactu y Facturae. Esta guía cubre: - Cuándo una factura se considera rectificativa. - Cómo crear una rectificativa desde la API. - Los **códigos `R1`-`R5`** y a qué supuesto del IVA corresponde cada uno. - Cómo se traducen esos códigos a TicketBAI, VeriFactu y Facturae. - El caso especial de **R5** (rectificativa de simplificadas). - Series de rectificativas en la configuración. Para sustitutivas (`F3`, consolidación de simplificadas), ver la [guía de Sustitutivas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-sustitutivas/). ## Cómo se identifica una rectificativa FacturaDirecta detecta automáticamente si una factura es rectificativa según tres reglas, en este orden: 1. **Si `main.correctedInvoice` está informado**, la factura es rectificativa **independientemente de la serie** o del total. `correctedInvoice` contiene el ID (`inv_`) de la factura original que se está rectificando. 2. **Si la empresa tiene VeriFactu activo**, la factura es rectificativa **solo si** está asociada a una serie de rectificativas (`invoiceType: complete_correction` o `simplified_correction` en [settings de series](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/#lista-de-series-de-numeración)). 3. **En otro caso** (sin VeriFactu), basta con: - `main.total < 0` (total negativo), o - serie de rectificativas asociada. La regla 1 es la que debe usar tu integración para construir rectificativas explícitas. Las reglas 2 y 3 cubren cómo el sistema clasifica facturas existentes. ## Cómo crear una rectificativa No hay un endpoint dedicado: se usa el `POST /invoices` estándar con los campos correctos. **Pasos mínimos:** 1. Pon en `main.correctedInvoice` el ID de la factura original. 2. Elige la serie de rectificativas adecuada en `main.docNumber.series`: - `complete_correction` para rectificar una completa. - `simplified_correction` para rectificar una simplificada. - Si la empresa no tiene serie de rectificativas configurada, puedes usar la serie normal: la regla 1 sigue identificándola como rectificativa. 3. Si la empresa tiene **TicketBAI** activo, indica `main.ticketbai.claveTipoFacturaRectificativa` con el código `R#` que aplique (ver tabla más abajo). 4. Si la empresa tiene **VeriFactu** activo, indica `main.verifactu.TipoFactura` con el código `R#` que aplique. 5. Para emitir Facturae, rellena los campos `main.facturae.Corrective_ReasonCode`, `main.facturae.Corrective_CorrectionMethod` y, opcionalmente, `main.facturae.Corrective_AdditionalReasonDescription`. > **No uses `main.corrective`.** Ese campo existe en el schema pero > está marcado como reservado para uso futuro y no debe enviarse > aún. El servidor lo ignora. ## Los códigos `R1`-`R5` Los códigos los define la **AEAT** en la especificación de VeriFactu y las **haciendas forales** los adoptan en TicketBAI. Son los mismos valores con la misma semántica funcional en ambos regímenes. | Código | Cuándo se usa | |---|---| | `R1` | Factura rectificativa por **error fundado en derecho** o por los supuestos del **Art. 80.1 y 80.2 LIVA** (modificación de la base imponible por causas tasadas: descuentos posteriores, devoluciones, resolución firme, etc.). | | `R2` | Factura rectificativa por **Art. 80.3 LIVA** (concurso de acreedores). | | `R3` | Factura rectificativa por **Art. 80.4 LIVA** (créditos incobrables: requisitos de antigüedad, reclamación, etc.). | | `R4` | Factura rectificativa **resto** de supuestos no encajados en R1, R2 ni R3. | | `R5` | Factura rectificativa **de una factura simplificada**. Sea cual sea el supuesto, si lo que rectificas es una simplificada, este es el código. | Los códigos se aceptan **literalmente** en tres campos distintos según el régimen: | Campo | Régimen | Ámbito | |---|---|---| | `main.ticketbai.claveTipoFacturaRectificativa` | TicketBAI (País Vasco) | A nivel de factura. | | `main.verifactu.TipoFactura` | VeriFactu (resto del Estado) | Reemplaza al valor por defecto `F1`/`F2`. | | `main.facturae.Corrective_ReasonCode` | Facturae XML | Más granular (ver siguiente sección). | > Para los supuestos exactos del Art. 80 LIVA, consulta la **Ley > 37/1992 del IVA**. FacturaDirecta no decide si una rectificación > encaja en R1, R2 o R3: tu integración (o el contribuyente) elige > el código. ## Facturae El XML Facturae usa una codificación **más granular** que TicketBAI y VeriFactu. Cuando generas un Facturae de una rectificativa (`PUT /invoices/{id}/facturae`), el servidor lo construye a partir de tres campos en `main.facturae`: ### `Corrective_ReasonCode` Código del motivo de rectificación. **Obligatorio** para emitir Facturae de una rectificativa. | Rango | Significado | |---|---| | `01`-`16` | Códigos de errores formales y materiales según el **RD 1496/2003**. Cada número corresponde a un escenario concreto (errores en la denominación, en los datos del receptor, en el importe, etc.). | | `80`-`85` | Supuestos del **Art. 80 Ley 37/92 del IVA** (modificación de la base imponible). | > Los códigos `R1`-`R5` (TicketBAI/VeriFactu) y los códigos > `01`-`85` (Facturae) **no son la misma codificación**. Tu > integración debe elegir el código adecuado para cada formato. La > documentación oficial de Facturae (Gobierno de España, sede de > facturae) recoge la tabla completa. ### `Corrective_CorrectionMethod` Código del criterio empleado para la rectificación. | Código | Significado | |---|---| | `02` | Rectificación por **diferencias** (la nueva factura emite la diferencia respecto a la original). | | `03` | Rectificación por **descuento por volumen** de operaciones durante un periodo. | ### `Corrective_AdditionalReasonDescription` Texto libre (máximo 2500 caracteres) con la ampliación del motivo: descripción y aclaraciones de la rectificativa. Opcional pero recomendado cuando el motivo no sea obvio del código. ## Caso especial: `R5` (simplificadas) Cuando rectificas una **factura simplificada** (con `main.simplified = true`), el código es siempre `R5`, sea cual sea el supuesto regulatorio. En TicketBAI hay un matiz adicional en Bizkaia: el sistema Batuz genera un `Codigo` interno propio para identificar la rectificación de simplificadas. Lo gestiona el servidor automáticamente. Tu integración solo necesita enviar `main.ticketbai.claveTipoFacturaRectificativa = "R5"` y enlazar `main.correctedInvoice`. ## Series de rectificativas Las series con `invoiceType: complete_correction` o `simplified_correction` están configuradas en la empresa y se pueden consultar con [`GET /settings/series/invoice`](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/#lista-de-series-de-numeración). Usar una serie de rectificativas: - Hace que la regla de detección automática clasifique la factura como rectificativa incluso si `main.correctedInvoice` está vacío. - Permite mantener una numeración separada de las facturas normales (típicamente con prefijo `R`). - Es **obligatorio** en empresas con VeriFactu activo cuando no enlazas con `correctedInvoice` (regla 2 de detección). Para empresas con TicketBAI sin VeriFactu, la serie es opcional: basta con `correctedInvoice` o `total < 0` para que el sistema clasifique correctamente. ## El campo `main.corrective` El schema de Invoice incluye un campo booleano `main.corrective` con la descripción: > Reservado para uso futuro. Indicará si la factura es > rectificativa. No utilizar aún, este campo no está activo. **No lo envíes en tu integración**. Hoy, la rectificatividad se detecta por las tres reglas explicadas arriba; el campo `corrective` se activará en una fase posterior con semántica explícita. Hasta entonces, el servidor lo ignora o lo descarta. ## Anulación vs. rectificación Son operaciones distintas: | Operación | Cómo se hace | Efecto fiscal | |---|---|---| | **Anulación** | `PUT /invoices/{id}` con `main.voided = true`. | La factura original se anula. Se contabilizan asientos de reversión. En TicketBAI/VeriFactu se envía un registro de anulación. | | **Rectificación** | `POST /invoices` con `main.correctedInvoice` apuntando a la original. | La factura original **permanece válida**; la rectificativa la modifica (corrige importes, datos formales). | Elige según el caso: si la factura está mal porque no debió emitirse, anula. Si necesitas modificar importes por descuentos, errores formales o causas del Art. 80 LIVA, rectifica. ## Errores comunes - **Enviar `main.corrective = true`**: campo reservado, el servidor lo ignora. La rectificatividad se infiere por las reglas de detección. - **Olvidar `claveTipoFacturaRectificativa` en TicketBAI o `TipoFactura: R#` en VeriFactu**: si la factura es rectificativa pero envías los códigos por defecto (`F1`/`F2`), el envío a la diputación o a AEAT puede fallar en validación. - **Confundir `R1`-`R5` con `Corrective_ReasonCode 01`-`85`**: cada formato (TicketBAI/VeriFactu vs. Facturae) tiene su codificación. No los mezcles. - **Usar `R5` para una completa**: `R5` es exclusivamente para rectificar simplificadas. Para rectificar una completa, usa `R1`-`R4`. - **Crear una rectificativa sin enlazar `correctedInvoice`**: con VeriFactu activo, sin enlace y sin serie de rectificativas, el sistema no la clasificará como tal y AEAT puede rechazar el envío. Siempre que rectifiques una factura concreta, enlaza. ## Referencias - **Ley 37/1992 del IVA**, Art. 80 (supuestos de modificación de la base imponible). - **RD 1619/2012** (Reglamento de facturación). - **RD 1496/2003** (códigos de errores formales/materiales para rectificativas). - **Especificación de VeriFactu** (AEAT): definiciones oficiales de `R1`-`R5`. Ver [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/) para enlaces. - **Documentación de TicketBAI** (haciendas forales): ver [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/). - **Especificación de Facturae**: tabla completa de `Corrective_ReasonCode` en la sede de facturae del Gobierno de España. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/invoices-rectificativas.md --- # Facturas sustitutivas (F3) > Una factura sustitutiva es una factura completa que reemplaza a un conjunto de facturas simplificadas previamente emitidas. Una **factura sustitutiva** es una factura completa que reemplaza a un conjunto de **facturas simplificadas** previamente emitidas. En VeriFactu corresponde al tipo **`F3`**, definido por AEAT como: > Factura emitida en sustitución de facturas simplificadas > facturadas y declaradas. El caso de uso típico es un comercio que emite tickets (simplificadas) durante el mes y, a final de mes, un cliente le pide una factura completa con sus datos fiscales agrupando varios tickets. La sustitutiva consolida esos tickets en un único documento con destinatario identificado. Esta guía cubre: - Por qué la sustitutiva tiene un endpoint dedicado y no se crea con el `POST /invoices` normal. - Las **6 validaciones** que aplica el servidor. - Cómo se construyen automáticamente las líneas. - Diferencia con rectificación. Para rectificativas (`R1`-`R5`), ver [Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/). ## Endpoint dedicado: `POST /invoices/substitution` A diferencia del CRUD normal de facturas, las sustitutivas tienen **un endpoint propio**: ``` POST /{companyId}/invoices/substitution ``` Razón del endpoint dedicado: la construcción del cuerpo de la factura (líneas, totales, separadores, referencias a las originales) la hace **el servidor**. Tu integración solo envía **qué simplificadas se sustituyen** y **a quién se le factura**; el resto se deriva. Por la misma razón, el `POST /invoices` estándar **rechaza** que le envíes `main.substitution = true`: devuelve `400` con mensaje "Substitution invoices cannot be created through the public API". La única vía es el endpoint dedicado. ## Body del request | Campo | Tipo | Requerido | Significado | |---|---|---|---| | `contact` | string | Sí | ID del cliente (`con_*`) al que se emite la factura completa. | | `substitutedInvoices` | string[] | Sí, no vacío | UUIDs (`inv_*`) de las facturas simplificadas a sustituir. Sin duplicados. | | `docNumber` | `{ series, formattedSeries?, number? }` | No | Si no lo envías, el servidor elige la primera serie no oculta de tipo `complete` o `simplified` configurada en la empresa. | | `date` | date (`YYYY-MM-DD`) | No | Fecha de la factura sustitutiva. Por defecto, hoy. | | `notes` | string | No | Notas visibles. Se prefijan automáticamente con `"Factura emitida en sustitución de facturas simplificadas"`. | | `internalNotes` | string | No | Notas internas (no se imprimen). | | `tags` | string[] | No | Etiquetas. | No envías líneas: las genera el servidor (ver siguiente sección). ## Validaciones del servidor Antes de crear la sustitutiva, el servidor aplica las siguientes validaciones sobre las facturas referenciadas en `substitutedInvoices`. **Cualquier fallo devuelve `400`** con un mensaje explicativo. | # | Regla | Mensaje de error si falla | |---|---|---| | 1 | Todas existen y no están borradas | `Invoices not found: ` | | 2 | Todas son simplificadas (`main.simplified === true`) | `Invoice is not a simplified invoice` | | 3 | Ninguna es externa (`main.external !== true`) | `Invoice is an external invoice and cannot be substituted` | | 4 | Todas comparten la misma `currency` | `All substituted invoices must have the same currency` | | 5 | Todas comparten el mismo `taxIncludedPrices` | `All substituted invoices must have the same taxIncludedPrices value` | | 6 | Si alguna tiene `contact`, **todas las que lo tengan** deben tener el mismo | `Substituted invoices with contacts must all have the same contact` | | 7 | Ninguna está ya sustituida en otra F3 | `Some invoices are already substituted:
` | > La regla 6 admite mezcla: simplificadas sin contacto + algunas > con contacto se aceptan, **siempre que todas las que tengan > contacto compartan el mismo `con_*`**. El `contact` del request > (cliente al que se factura la sustitutiva) no tiene por qué > coincidir con esos contactos previos. ## Cómo se construyen las líneas El servidor genera las líneas del cuerpo de la sustitutiva recorriendo `substitutedInvoices` **en el orden enviado**. Para cada simplificada: 1. **Inserta una línea separadora** con `quantity = 0`, `unitPrice = 0`, `lineTotal = 0`, sin impuestos, con `text` autogenerado del formato `" ()"` y `origin` apuntando al `inv_*` de la simplificada. Esa línea cero hace de etiqueta visual del grupo. 2. **Copia todas las líneas** de la simplificada, anotando `origin` con el `inv_*` de la original para trazar la procedencia. El resultado es una factura completa con bloques de líneas, cada bloque encabezado por una línea-separadora. El campo `origin` de cada línea permite reconstruir qué simplificada generó qué línea. Además, el servidor: - Pone `main.substitution = true`. - Rellena `main.substitutedInvoices` con los UUIDs. - Deduce `currency` y `taxIncludedPrices` de las simplificadas. - Genera VeriFactu con `TipoFactura: F3` automáticamente. No tienes que indicarlo en el request. ## Efecto contable y fiscal A diferencia de las facturas normales, la sustitutiva **no genera contabilidad propia**. Las simplificadas originales ya generaron sus asientos en el momento en que se emitieron; la sustitutiva **no duplica esos asientos**: actúa como una agrupación documental. Su función principal es: - Dotar al cliente de una factura completa para su deducibilidad. - Generar la información para el **modelo 347** (declaración informativa de operaciones con terceros), agregando las operaciones a un único cliente. Para los flujos de cobro, **los cobros se siguen registrando en las simplificadas originales**, no en la sustitutiva. La sustitutiva no tiene saldo pendiente: nace consolidada. ## Sustitutiva vs. rectificativa Son operaciones distintas que no deben confundirse: | | Sustitutiva (`F3`) | Rectificativa (`R1`-`R5`) | |---|---|---| | **Propósito** | Agrupar varias simplificadas en una completa para identificar al destinatario. | Corregir una factura previa por errores formales o por Art. 80 LIVA. | | **Documentos involucrados** | Una sustitutiva nueva + N simplificadas originales (válidas). | Una rectificativa nueva + la factura original (válida o corregida). | | **Endpoint** | `POST /invoices/substitution` (dedicado). | `POST /invoices` con `main.correctedInvoice`. | | **Efecto contable** | Ninguno; no duplica asientos. | Genera asientos correctivos. | | **Codificación VeriFactu** | `TipoFactura: F3`, automático. | `TipoFactura: R1`-`R5`, lo eliges tú. | Si lo que quieres es **corregir** una simplificada porque tenía un error, no la sustituyas: ver [Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/). ## Ejemplo ###### Request ```json { "contact": "con_2e3f4a18-9b7c-4d6a-8e1f-5c2d3b4a7e9f", "substitutedInvoices": [ "inv_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c", "inv_4b8c0d15-3a6f-4b8c-c1d9-2e3f4a5b6c7d", "inv_5c9d1e16-4b7a-4c9d-d2e0-3f4a5b6c7d8e" ], "date": "2026-05-31", "notes": "Factura agrupada del mes de mayo." } ``` ###### cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d @substitution.json \ "https://app.facturadirecta.com/api/$COMPANY_ID/invoices/substitution" ``` ###### Respuesta (resumida) ```json { "content": { "uuid": "inv_8f2a3b4c-5d6e-4a7b-9c0d-1e2f3a4b5c6d", "type": "invoice", "main": { "contact": "con_2e3f4a18-9b7c-4d6a-8e1f-5c2d3b4a7e9f", "substitution": true, "substitutedInvoices": [ "inv_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c", "inv_4b8c0d15-3a6f-4b8c-c1d9-2e3f4a5b6c7d", "inv_5c9d1e16-4b7a-4c9d-d2e0-3f4a5b6c7d8e" ], "verifactu": { "TipoFactura": "F3" }, "notes": "Factura emitida en sustitución de facturas simplificadas\nFactura agrupada del mes de mayo.", "lines": [ { "text": "F-26-101 (05 may 2026)", "quantity": 0, "unitPrice": 0, "lineTotal": 0, "tax": [], "origin": "inv_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c" }, { "text": "Producto A", "quantity": 2, "unitPrice": 50, "lineTotal": 100, "tax": ["S_IVA_21"], "origin": "inv_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c" }, { "text": "F-26-115 (12 may 2026)", "quantity": 0, "unitPrice": 0, "lineTotal": 0, "tax": [], "origin": "inv_4b8c0d15-3a6f-4b8c-c1d9-2e3f4a5b6c7d" } ] } } } ``` ## Errores comunes - **Intentar crear una sustitutiva con `POST /invoices`** enviando `main.substitution = true`: el endpoint normal lo rechaza con `400`. Usa el endpoint dedicado. - **Sustituir una factura ya completa** (no simplificada): el servidor rechaza con la regla 2. La sustitutiva es para consolidar simplificadas, no completas. - **Sustituir simplificadas externas**: la regla 3 lo rechaza. Las externas no se envían a VeriFactu, así que no se puede generar una `F3` que las consolide. - **Mezclar monedas o `taxIncludedPrices`**: reglas 4 y 5. Resuélvelo emitiendo varias sustitutivas separadas, una por moneda/configuración. - **Re-sustituir** una simplificada ya incluida en otra `F3`: la regla 7 lo rechaza. Si necesitas modificar una sustitutiva ya emitida, anúlala y emite una nueva. ## Referencias - **Reglamento de facturación (RD 1619/2012)**, Art. 6.1.d) y Art. 7 (simplificadas) y supuestos de factura completa. - **Especificación de VeriFactu** (AEAT): definición oficial de `TipoFactura: F3` y casos de uso. Ver [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/) para enlaces. - Para el ciclo de vida del recurso, ver el [endpoint de creación](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#crear-factura-sustitutiva) en la página de Facturas. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/invoices-sustitutivas.md --- # Flujo de pedidos y órdenes de compra > El pedido de cliente (clientOrder) registra un encargo en firme de un cliente antes de su entrega y facturación. El **pedido de cliente** (`clientOrder`) registra un encargo en firme de un cliente antes de su entrega y facturación. La **orden de compra** (`purchaseOrder`) registra un encargo en firme a un proveedor antes de la recepción de la mercancía. FacturaDirecta conecta ambos documentos **a nivel de línea**: una línea de pedido puede abastecerse con una línea de orden de compra, y sus estados se coordinan automáticamente. Esta guía cubre: - Los **estados de línea** de cada documento y cómo se deriva el estado del documento. - El **vínculo línea-a-línea** entre pedido y orden. - La **propagación de estados** cuando se guarda o se borra una orden. - Cómo **generar una orden de compra** a partir de líneas de pedido pendientes. Los dos documentos forman parte del **Módulo Inventario**: están incluidos en el plan **Diamante** y se pueden contratar como módulo en **Bronce**, **Plata** y **Oro**. Sin esa disponibilidad, cualquier escritura sobre ellos responde `403 Forbidden`. ## Estados de línea Cada línea tiene su propio estado, y el estado del documento se **calcula** a partir de los estados de sus líneas. Las líneas vacías (sin texto ni importe) no cuentan, y un documento sin líneas con contenido queda en `pending`. **Línea de pedido de cliente** — `pending` (pendiente), `ordered` (pedida a proveedor), `ready` (lista para entregar), `delivered` (entregada) o `canceled` (cancelada). Estado del pedido, por prioridad: `pending` si alguna línea está `pending` u `ordered`; si no `ready` si alguna está `ready`; si no `delivered` si alguna está `delivered`; si no `canceled`. **Línea de orden de compra** — `pending` (pendiente de recibir), `received` (recibida) o `canceled`. Estado de la orden, por la misma lógica de prioridad: `pending` > `received` > `canceled`. En ambos documentos, `main.state` se recalcula en cada guardado: el valor que envíes se descarta. > El estado `ordered` de la línea de pedido lo fija FacturaDirecta cuando la > línea se vincula a una orden de compra: no lo asignes manualmente. ## Vínculo línea-a-línea El vínculo es **bidireccional, 1:1 y exclusivo**. Cada línea tiene un `id` estable que la identifica dentro del documento; los vínculos referencian ese `id`: - Línea de pedido: `purchaseOrder` (id de la orden) + `purchaseOrderLine` (id de la línea de orden). Además `provider` (proveedor por línea, usado para generar la orden). - Línea de orden: `clientOrder` (id del pedido) + `clientOrderLine` (id de la línea de pedido). Además `client` (cliente final, trazabilidad). El vínculo se establece **desde la orden de compra**: al crear o guardar una orden cuyas líneas indican `clientOrder`/`clientOrderLine`, FacturaDirecta completa el vínculo recíproco en el pedido. Reglas validadas al guardar: - Los dos campos del vínculo (documento + línea) deben indicarse **juntos**. - Una línea de pedido no puede estar vinculada a **dos** líneas de orden (exclusividad). - El `id` de línea debe ser **único** dentro del documento. - La línea de pedido referenciada debe existir. - El **proveedor** de la línea de pedido debe coincidir con el proveedor de la orden de compra. - El **producto** y la **cantidad** deben coincidir en las dos líneas. Si necesitas comprar unidades adicionales, añade una línea de reposición independiente en la orden en vez de cambiar la cantidad. Desde el **pedido** no se puede tocar nada de eso: intentar crear, cambiar o borrar el vínculo, o cambiar el producto, la cantidad o el proveedor de una línea ya vinculada, devuelve `400`. Tampoco se puede borrar un pedido con líneas vinculadas a órdenes activas. > **Identidad de línea en `PUT`.** Al actualizar un documento por API, las > líneas se sustituyen por las del cuerpo. Una línea que trae un `id` existente > **conserva su identidad** (y sus vínculos); una línea sin `id` es nueva. > Regenerar los `id` en cada `PUT` rompería los vínculos. ## Propagación de estados Al guardar o borrar una orden de compra, el estado de sus líneas se propaga a las líneas de pedido vinculadas (la orden manda; el pedido es el afectado). Cada pedido afectado se guarda con su propio historial y emite el webhook `client_order.updated`. | Estado línea **orden** | Estado línea **pedido** | Resultado | |---|---|---| | `pending` | `pending` o `ready` | pedido → `ordered` | | `received` | `pending` u `ordered` | pedido → `ready` | | `canceled` | `pending` u `ordered` | pedido → `pending` y se rompe el vínculo | Los estados `delivered` y `canceled` del pedido son **terminales** frente a la propagación: la recepción o la cancelación de la mercancía no los pisa. Al **borrar** la orden, las líneas de pedido `ordered` y `ready` vuelven a `pending` y se desvinculan; al **recuperarla**, se re-vinculan solo si las líneas de pedido siguen libres. En sentido contrario no hay propagación: los estados `pending`, `ordered` y `ready` de una línea de pedido **vinculada** son propiedad del servidor. Si guardas el pedido con uno distinto del que hay almacenado, el servidor restaura el suyo en silencio, sin error. Esto evita que un `PUT` construido sobre una copia anterior deshaga una recepción ya confirmada. `delivered` y `canceled` sí los decides tú desde el pedido. ## Generar una orden de compra desde pedidos El flujo típico de compras bajo pedido: 1. Asigna a cada línea de pedido un **proveedor** (`provider`). Si el producto de la línea tiene un proveedor habitual (`purchases.provider` del producto), ese es el valor por defecto que propone la interfaz. El producto debe tener activada su faceta de compra. 2. Crea una **orden de compra** con ese proveedor en `main.contact` y con las líneas de los pedidos correspondientes, indicando en cada línea `clientOrder` y `clientOrderLine` (el `id` de la línea de pedido de origen). 3. Al **guardar** la orden, las líneas de pedido vinculadas pasan a `ordered`. 4. Cuando se recibe la mercancía, cambia el estado de las líneas de orden a `received`: las líneas de pedido pasan a `ready`. 5. Entrega al cliente y marca esas líneas de pedido como `delivered`. Facturar un pedido (convertirlo en factura o albarán) **no** cambia los estados de línea: la entrega y la recepción son actos explícitos. ## Concurrencia Las escrituras de órdenes de compra de una misma empresa se **serializan**, porque cada una puede tocar los pedidos que abastece. Si ya hay 20 escrituras de órdenes en curso en la misma empresa, la siguiente responde `429 Too Many Requests`. Carga las órdenes en serie o con poca concurrencia, y reintenta ante un `429`. ## Efecto sobre el stock Con el control de stock activo, las líneas con producto de estos documentos alimentan magnitudes que se consultan en el [recurso de productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/): - Las líneas de **pedido de cliente** activas suman al stock `committed` (comprometido). - Las líneas de **orden de compra** pendientes de recibir suman al stock `incoming` (previsto). El **stock físico** lo mueve el documento que la empresa haya elegido como disparador. En compras, la opción por defecto es la propia orden: marcar una línea como `received` da entrada a la mercancía. En ventas, la opción por defecto es el albarán, pero la empresa puede elegir que muevan la factura o la propia línea de pedido marcada como `delivered`. Esa elección se configura en la interfaz, no por API, y la deduplicación por cadena de documentos garantiza que una misma mercancía solo se descuenta una vez. El almacén afectado es el de `main.warehouse` del documento, o el almacén por defecto de la empresa si no se indica. Si la empresa tiene configurado bloquear los negativos, un documento que dejaría el stock físico por debajo de cero se rechaza al guardar. ## Scopes - `clientOrders:read` / `clientOrders:write` — pedidos de cliente. - `purchaseOrders:read` / `purchaseOrders:write` — órdenes de compra. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/guides/orders-flow.md --- # Facturas de venta > Una factura de venta (invoice) es el documento fiscal que emite la empresa para cobrar a sus clientes. Una **factura de venta** (`invoice`) es el documento fiscal que emite la empresa para cobrar a sus clientes. Es el recurso más cargado del catálogo: además del flujo CRUD normal toca dos regímenes fiscales electrónicos (**TicketBAI** en País Vasco y **VeriFactu** en el resto de España), tiene flujos especiales para **facturas rectificativas** y **sustitutivas**, y genera asientos contables, PDFs, ficheros Facturae y envíos por email. Para no inflar esta página, los temas regulatorios y los flujos especializados están en guías propias: - [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/) — régimen del País Vasco (Araba/Bizkaia/Gipuzkoa). - [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/) — régimen del resto de España, con AEAT. - [Facturas rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/) — R1-R5, vínculo con `correctedInvoice`, coordinación con TicketBAI/VeriFactu. - [Facturas sustitutivas (F3)](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-sustitutivas/) — consolidar simplificadas en una completa. > En los ejemplos, los UUIDs (`inv_…`, `con_…`, `ban_…`, etc.) son > ilustrativos. ## Subtipos implícitos FacturaDirecta **no tiene un campo `invoiceType`** en el schema de factura. El "tipo" emerge de la combinación de varios campos: | Subtipo | Cómo se identifica | |---|---| | **Completa** | `main.simplified !== true`. Normalmente tiene `main.contact` con datos fiscales. Si TicketBAI no está activo, también puede dirigirse a un contacto extracomunitario sin identificador fiscal; con VeriFactu se registra como F2. | | **Simplificada** | `main.simplified === true`. Típicamente `main.contact` es `null` o el destinatario no tiene datos fiscales suficientes. | | **Externa** | `main.external === true` — generada fuera del programa. **No emite a TicketBAI ni VeriFactu**, aunque sí contabiliza. | | **Sustitutiva** | `main.substitution === true` con `main.substitutedInvoices` rellenado. Se crea con el endpoint dedicado [`POST /invoices/substitution`](#crear-factura-sustitutiva). Corresponde a F3 en VeriFactu. Ver [guía de Sustitutivas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-sustitutivas/). | | **Rectificativa** | Asociada a una factura previa vía `main.correctedInvoice` y/o emitida en una serie de rectificativas (`invoiceType: complete_correction` o `simplified_correction` en [settings](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/)). Ver [guía de Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/). | > El campo `main.corrective` existe en el schema pero está marcado > como **reservado para uso futuro y no debe usarse aún**. Su > activación está pendiente de una fase posterior. ## Estados `state` es un campo **calculado** (no se envía en el body). Se deriva de los flags del documento y del importe pagado vs total: | Estado | Cuándo | |---|---| | `draft` | `main.draft === true`. Provisional, no contabiliza. | | `pending` | Definitiva, sin cobros suficientes y no vencida. | | `overdue` | Definitiva, sin cobrar al llegar `main.dueDate`. | | `paid` | Cobros totales = importe. | | `overpaid` | Cobros totales > importe. | | `voided` | `main.voided === true`. Anulada. | Cada factura tiene un único estado en cada momento: los valores son excluyentes entre sí. En el filtro `state` se pueden indicar varios y se devuelven las facturas cuyo estado sea cualquiera de ellos. Las transiciones no se imponen como máquina de estados: se calculan en cada lectura desde los pagos y fechas actuales. ## Estructura mínima `Invoice` tiene tres bloques raíz: `main` (datos editables), `accounting`/`books` (apuntes y libros, generados) y `meta` (metadatos internos — incluye los xml y respuestas de TicketBAI y VeriFactu, ver [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/) y [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/)). Los campos de `main` más importantes para crear una factura: | Campo | Requerido | Significado | |---|---|---| | `contact` | Sí salvo simplificada | ID del cliente (`con_*`). En simplificadas: `null`. | | `date` | Sí | Fecha de emisión (`YYYY-MM-DD`). | | `dueDate` | Sí | Fecha de vencimiento. Si hay `installments`, se ajusta a la fecha más tardía del calendario. | | `installments` | No | Calendario de vencimientos múltiples; cada elemento contiene `date`, `amount` y `currency`. | | `currency` | Sí | ISO 4217. | | `exchangeRate` | No | Tipo de cambio a la moneda de la empresa. Si no lo envías, se toma el de la fecha de la factura (`date`). En la moneda de la empresa es siempre 1. | | `docNumber` | Sí | `{ series: "...", number?: N }`. Si omites `number`, se asigna automáticamente. | | `fiscalPosition` | Sí | Régimen tributario aplicable. Determina los impuestos disponibles. | | `lines` | Sí | Array de líneas (ver más abajo). | | `theme` | No | ID de la plantilla de impresión. Si se omite, se usa la plantilla de la serie o la predeterminada de la empresa. Obtenible con [`GET /settings/themes`](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/#lista-de-plantillas). | | `language` | No | Idioma en que se imprime y se envía la factura, como código ISO 639-1 (`es`, `ca`, `en`...). Si se omite, se toma el del contacto al asignarlo y, si el contacto no tiene, el idioma base de la plantilla. Solo tiene efecto si la plantilla tiene ese idioma; si no, se imprime en su idioma base. | | `account` | Sí | Cuenta contable de ingresos (típicamente 700*). | | `draft` | Sí | `true` → provisional; `false` → definitiva (contabiliza). | | `voided` | Sí | `true` solo para anular. | | `taxIncludedPrices` | No (default `false`) | Si los precios de las líneas incluyen impuestos. | | `external` | No | `true` para facturas creadas fuera del programa (no emite TicketBAI/VeriFactu). | | `paymentMethod` | No | ID de [método de cobro](https://www.facturadirecta.com/desarrolladores/api/referencia/payment-methods/): uno de la empresa (sin contacto) o uno del contacto de la factura, y de cobro (`receive` o sin dirección). | | `emails`, `emailsCc`, `emailsBcc` | No | Destinatarios por defecto para el envío por email (CSV en un solo string). | | `correctedInvoice` | No | ID de la factura rectificada cuando esta es rectificativa. Ver [Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/). | | `verifactu` | No | Parámetros específicos VeriFactu (`TipoFactura`, `DescripcionOperacion`, etc.). Ver [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/). | | `ticketbai` | No | Parámetros específicos TicketBAI (`causaExencion`, `claveTipoFacturaRectificativa`, etc.). Ver [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/). | | `customFields` | No | Mapa de valores indexado por el ID estable `cfi_` de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las definiciones borradas siguen siendo válidas; un ID desconocido o con formato incorrecto produce `400 Bad Request`. | | `owner` | No | Usuario responsable de la factura. Se usa con roles personalizados para limitar la visibilidad a documentos asignados. | | `warehouse` | No | Almacén del documento a efectos de stock. Solo tiene efecto si la empresa usa el control de stock y varios almacenes; si no lo indicas se usa el almacén por defecto. Ver [Productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/#control-de-stock). | Si creas la factura con OAuth y no envías `owner`, la API asigna como responsable al usuario autenticado. Si usas una apiKey, la factura queda sin responsable salvo que envíes `owner` en el body. ### Vencimientos múltiples Para dividir el cobro de una factura, envía `main.installments` al crearla o actualizarla. Cada elemento requiere: - `date` — fecha del vencimiento en formato `YYYY-MM-DD`. - `amount` — importe previsto para ese vencimiento. - `currency` — moneda del importe en formato ISO 4217. Por ejemplo, una factura de 121 EUR puede dividirse en dos vencimientos: ```json { "dueDate": "2026-08-15", "installments": [ { "date": "2026-07-15", "amount": 60.5, "currency": "EUR" }, { "date": "2026-08-15", "amount": 60.5, "currency": "EUR" } ] } ``` Incluye estos campos dentro de `content.main` en `POST /{companyId}/invoices` o `PUT /{companyId}/invoices/{invoiceId}`. El servidor ajusta el importe del último elemento para que la suma coincida con el total calculado de la factura y establece `dueDate` en la fecha más tardía. Con un método de cobro por domiciliación SEPA, cada vencimiento pendiente se puede seleccionar como un cobro separado al preparar una remesa. Si no necesitas dividir el cobro, `dueDate` basta para representar un único vencimiento. Cada elemento de `lines` (`InvoiceMainLine`): | Campo | Requerido | Significado | |---|---|---| | `account` | Sí | Cuenta contable de la línea (override de `main.account`). | | `quantity` | Sí | Cantidad. | | `unitPrice` | Sí | Precio unitario. | | `discount` | Sí | Descuento absoluto en la moneda de la factura. | | `discountRate` | Sí | Descuento porcentual (0–100). | | `lineTotal` | Sí | Total de la línea (`quantity * unitPrice * (1 - discountRate/100) - discount`). | | `tax` | Sí | Array de IDs de impuestos. Ver [guía de Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). | | `text` | Sí | Descripción. | | `origin` | No | ID del documento de origen (presupuesto, albarán o pedido de cliente) para trazar procedencia. | | `document` | No | ID del producto si la línea se genera de catálogo. | | `facturae`, `verifactu` | No | Sub-campos específicos para Facturae y VeriFactu por línea. | ## Enlace a la aplicación Las respuestas que devuelven una factura incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de facturas](#lista-de-facturas) - [Crear factura](#crear-factura) - [Crear factura sustitutiva](#crear-factura-sustitutiva) - [Obtener una factura](#obtener-una-factura) - [Actualizar factura](#actualizar-factura) - [Anular factura](#anular-factura) - [Borrar factura](#borrar-factura) - [Actualizar etiquetas](#actualizar-etiquetas) - [Registrar cobros](#registrar-cobros) - [Enviar por email](#enviar-por-email) - [Generar PDF](#generar-pdf) - [Generar Facturae](#generar-facturae) - [Adjuntos](#adjuntos) ## Lista de facturas `GET /{companyId}/invoices` devuelve las facturas de la empresa, paginadas. **Parámetros de consulta específicos** (los más comunes; lista completa en el Swagger UI): - **`search`** — búsqueda aproximada en el texto de la factura (título, líneas, notas) y en el nombre de su cliente: todas las palabras, sin distinguir acentos ni mayúsculas. Es la búsqueda rápida del listado de la aplicación; la [búsqueda global](https://www.facturadirecta.com/desarrolladores/api/referencia/search/) la lanza sobre todos los tipos a la vez. - **`minDate`**, **`maxDate`** — rango por fecha de la factura. - **`series`** — filtrar por serie de numeración. - **`contact`** — filtrar por ID de cliente. - **`state`** — filtrar por estado (`draft`, `pending`, `overdue`, `paid`, `overpaid`, `voided`). Cada factura solo tiene uno; si se indican varios, el filtro incluye cualquiera de ellos. - **`draft`** — por defecto solo se incluyen facturas definitivas; `only` devuelve solo borradores y `all` mezcla definitivas y borradores. Los borradores no generan asientos contables, por lo que al incluirlos el recuento puede no coincidir con el diario. - **`simplified`** — `true` para solo simplificadas. - **`external`** — `true` para solo externas. - **`voided`** — `true` para solo anuladas. - **`sortBy`** — admite `date`, `series`, `formattedSeries`, `number`, `total`, `currency`, `country`, `creationDate` y `modificationDate`. Repite el parámetro para combinar criterios y antepone `-` para orden descendente (por ejemplo, `sortBy=-date&sortBy=number`). - **Tags**: `allTheseTags` y `anyOfTheseTags` filtran por etiquetas concretas; `hasTags=true` devuelve solo facturas con al menos una etiqueta y `hasTags=false`, solo facturas sin etiquetas. - **`totals`** — `true` añade a la respuesta las sumas de todas las facturas que cumplen los filtros, no solo de la página. Ver [Totales del conjunto filtrado](#totales-del-conjunto-filtrado). **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/invoices?minDate=2026-01-01&state=pending&limit=50" ``` ### Totales del conjunto filtrado Con `totals=true`, la respuesta añade `totals`: el recuento y las sumas de todas las facturas que cumplen los filtros, con una fila por moneda. Es la forma de responder «¿cuánto suman las facturas vencidas?» sin recorrer las páginas. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/invoices?state=overdue&limit=1&totals=true" ``` ```json { "pagination": { "offset": 0, "limit": 1, "total": 3 }, "items": [ { "...": "..." } ], "totals": [ { "currency": "EUR", "count": 3, "total": 1815, "totalBeforeTaxes": 1500, "taxes": 315, "pending": 1815, "companyCurrencyPending": 1815 } ] } ``` | Campo | Significado | |---|---| | `currency` | Moneda de las facturas de la fila. | | `count` | Facturas que cumplen los filtros. | | `total`, `totalBeforeTaxes`, `taxes` | Suma del total, de la base imponible y de los impuestos (`total` menos `totalBeforeTaxes`), en la moneda de la fila. | | `pending` | Importe pendiente de cobro, en positivo, en la moneda de la fila. Negativo si se ha cobrado de más. | | `companyCurrencyPending` | El pendiente en la moneda de la empresa. | Para agrupar esas sumas por cliente, provincia, estado o mes, o para compararlas con otro periodo, usa la [consulta analítica](https://www.facturadirecta.com/desarrolladores/api/referencia/query/). ## Crear factura `POST /{companyId}/invoices` crea una factura nueva. **Body:** `{ content: Invoice, tags?: string[] }`. El `content` contiene todo el cuerpo de la factura (típicamente solo `main`; los campos calculados se generan en el servidor). **Reglas de negocio relevantes:** - Si **omites `docNumber.number`**, se asigna automáticamente basándose en el último número emitido para esa serie ese año. La serie controla además la sustitución de `##`/`####` por el año (ver [settings](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/#lista-de-series-de-numeración)). - Si **omites `draft`**, se aplica el valor por defecto de la empresa (`sales.newInvoiceMode`, ver [settings](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/)), que puede ser `final`: la factura nace definitiva, es decir, emitida. Envía `draft` siempre de forma explícita (`true` para un borrador). - Guardar la factura como definitiva (`draft: false`) la **emite**: se le asigna número y, si la empresa tiene TicketBAI o VeriFactu activos y la factura encaja en su ámbito (no es `external`), se genera el registro fiscal y se envía al organismo en el momento del guardado. Una factura emitida no vuelve a ser provisional y, con esos sistemas, no se puede modificar ni borrar: solo anular, rectificar o, con VeriFactu, subsanar. Ver [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/) y [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/). - `main.simplified` es **obligatorio** en el schema; envíalo explícitamente. Si `main.contact` es `null`, debe ser `true`. **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON (factura completa, regalo a un cliente) ```json { "content": { "main": { "contact": "con_2e3f4a18-9b7c-4d6a-8e1f-5c2d3b4a7e9f", "date": "2026-05-13", "dueDate": "2026-06-12", "currency": "EUR", "docNumber": { "series": "F-##-" }, "fiscalPosition": "general", "account": "700000", "theme": "default", "draft": false, "voided": false, "simplified": false, "lines": [ { "account": "700000", "quantity": 1, "unitPrice": 100, "discount": 0, "discountRate": 0, "lineTotal": 100, "tax": ["S_IVA_21"], "text": "Servicios de consultoría" } ] } }, "tags": ["consultoria"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d @invoice.json \ "https://app.facturadirecta.com/api/$COMPANY_ID/invoices" ``` ## Crear factura sustitutiva `POST /{companyId}/invoices/substitution` crea una **factura completa que sustituye a un conjunto de facturas simplificadas** (F3 en VeriFactu). Es un endpoint dedicado porque la construcción del cuerpo (líneas, separadores, totales) la hace el servidor. **Body:** - **`contact`** (obligatorio) — ID del cliente al que se le emite la factura completa. - **`substitutedInvoices`** (obligatorio) — array no vacío de UUIDs de facturas simplificadas a sustituir. Sin duplicados. - **`date`**, **`docNumber`**, **`notes`**, **`internalNotes`**, **`tags`** — opcionales. **Validaciones:** - Todas las facturas referenciadas deben existir y **ser simplificadas** (`main.simplified === true`). - **Ninguna** puede ser externa (`main.external === true`). - **Misma `currency`** en todas. - **Mismo `taxIncludedPrices`** en todas. - Si alguna tiene `main.contact`, todas las que lo tengan deben compartir el mismo `con_*`. - Ninguna puede estar ya sustituida en otra F3. Detalles del flujo y ejemplos: ver [guía de Facturas sustitutivas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-sustitutivas/). **Parámetros globales aceptados:** `accept-version`. ## Obtener una factura `GET /{companyId}/invoices/{id}` devuelve una factura por ID. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/invoices/inv_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e" ``` ## Actualizar factura `PUT /{companyId}/invoices/{id}` reemplaza el `content` de la factura. Las líneas se reemplazan por las que envíes y los campos opcionales que no envíes se eliminan. Los campos obligatorios que no envíes conservan su valor, igual que `docNumber` y `meta`, que no cambian. Lo que FacturaDirecta rellena al crear (`account`, `dueDate`, `fiscalPosition`, `theme`…) se rellena igual al actualizar si no lo envías. Si no envías `exchangeRate`, se conserva el anterior mientras no cambien ni la moneda ni la fecha; si cambia alguna de las dos, se toma el tipo de cambio de la nueva fecha. **Reglas relevantes:** - `docNumber` **inmutable** una vez asignado. - Pasar `main.voided=true` **anula** la factura (ver [Anular](#anular-factura)). - Si la factura ya tiene un registro de alta VeriFactu, el primer intento de hacer un cambio subsanable devuelve HTTP 400 con `type: "verifactuSubsanacion"`. Revisa la clasificación de los cambios en `hint.verifactuChanges.changes` y, si quieres confirmar la subsanación, repite el mismo PUT añadiendo este campo al nivel superior del body: ```json { "verifactuOperation": { "subsanacion": true } } ``` El segundo PUT devuelve la factura actualizada y genera un nuevo registro de alta con `meta.verifactu.registroAlta.Subsanacion = "S"`. Esta confirmación no permite modificar importes, impuestos ni datos del destinatario: esos cambios requieren anular o emitir una factura rectificativa. Ver [Subsanaciones VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/#subsanaciones). - Si la factura tiene metadatos de TicketBAI ya enviados, algunos cambios disparan reemisión o anulación + nueva emisión. Ver la [guía de TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/). **Parámetros globales aceptados:** `accept-version`. ## Anular factura **No existe un endpoint `/void` dedicado.** La anulación se hace con `PUT /{companyId}/invoices/{id}` enviando `main.voided = true`. **Efectos:** - El estado pasa a `voided`. - Se generan asientos contables de **reversión**. - Si la factura está en TicketBAI o VeriFactu, se prepara el envío de anulación al organismo (`meta.ticketbai.voided` / `meta.verifactu.registroAnulacion`). - Se emite el webhook **`invoice.voided`**. **Anular no es corregir.** Anular es declarar que la factura no tenía que emitirse: conserva su número y queda visible como anulada, sin dejar huecos en la numeración. Si la factura tenía que existir pero contiene errores, lo que corresponde es una [subsanación](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/#subsanaciones) (VeriFactu) o una factura rectificativa. **Restaurar una factura anulada.** Sin TicketBAI ni VeriFactu, un `PUT` con `main.voided = false` la restaura (la actividad lo registra como `unvoided`). Si la factura tiene información de VeriFactu, TicketBAI o Batuz, la restauración devuelve HTTP 400: el registro de anulación ya se ha comunicado y no se puede revertir. Una factura **anulada no es lo mismo que una factura borrada**: la anulada queda en el sistema con estado `voided`; la borrada se conserva con `archived: true` y desaparece de los listados (ver siguiente sección). ## Borrar factura `DELETE /{companyId}/invoices/{id}` borra la factura (borrado recuperable). **Restricciones:** - Solo se pueden borrar facturas **no contabilizadas** (típicamente en `draft`). Si la factura ya generó asientos contables, el borrado fallará: usa **anulación** en su lugar. - El borrado es **lógico y recuperable** (`archived = true`); la factura no se elimina físicamente y desaparece de los listados. **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/invoices/inv_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e" ``` ## Actualizar etiquetas `PUT /{companyId}/invoices/{id}/tags` reemplaza el array de tags de la factura sin tocar el `content`. Body: `{ tags: string[] }`. Ver los recursos análogos ([bills](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/), [estimates](https://www.facturadirecta.com/desarrolladores/api/referencia/estimates/)) para el patrón común de tags. ## Registrar cobros `POST /{companyId}/invoices/{id}/payments` registra uno o varios cobros contra la factura. **Parámetros de cada cobro:** - **`bank`** (obligatorio) — ID de la cuenta bancaria (`ban_*`) donde se anota el cobro. - **`amount`** (opcional) — importe en la moneda de la factura. Si lo omites, se cobra el saldo pendiente completo. Solo se admite un cobro sin `amount` y debe ir en la última posición del array. - **`date`** (opcional) — fecha del cobro. Por defecto se usa la fecha actual. - **`companyCurrencyAmount`** (opcional) — importe real en la moneda de la empresa cuando la factura usa otra moneda. Si lo omites, el sistema aplica el tipo de cambio de la fecha del cobro. - **`description`**, **`notes`** (opcionales) — descripción visible del movimiento y notas adicionales. La moneda de la cuenta bancaria debe coincidir con la moneda de la empresa o con la de la factura. Si es una tercera moneda, la API devuelve `400` porque la petición no indica el importe real recibido en esa cuenta. Para registrar el cobro o pago a partir de un movimiento del extracto bancario (también desde una cuenta en una tercera moneda), concilia el movimiento con `document` en [conciliar creando nuevo asiento](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/#conciliar-creando-nuevo-asiento): la API construye el asiento con el importe real del movimiento y la diferencia de cambio. En una factura en moneda extranjera cobrada en una cuenta que usa la moneda de la empresa, envía `amount` en la moneda de la factura y `companyCurrencyAmount` en la moneda de la cuenta. El apunte bancario queda así en la misma moneda que el extracto y se puede conciliar con él. **Body:** ```json { "payments": [ { "date": "2026-08-11", "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "amount": 7000, "companyCurrencyAmount": 5909.56 } ], "related": ["journal"] } ``` `related: ["journal"]` incluye en la respuesta los apuntes que afectan al saldo de la factura. Toma de `related.journal` el `id` del asiento `tra_*` que se ha generado y úsalo como `transactionId` para [`PUT /statements/{id}/reconciliation`](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/#conciliar-con-asiento-existente). Después puedes localizar el movimiento conciliado con `GET /statements?matchedDocument=`. **Efectos:** - Genera los apuntes contables del banco y del cliente. - Si los cobros cierran el saldo, el estado pasa a `paid`. Si lo exceden, pasa a `overpaid`. - Emite el webhook **`invoice.updated`** (no hay evento específico de cobro). **Parámetros globales aceptados:** `accept-version`. ## Enviar por email `PUT /{companyId}/invoices/{id}/send` envía la factura por email desde la cuenta SMTP configurada en la empresa. **Body** (todos opcionales): - **`to`**, **`cc`**, **`bcc`** — CSV de destinatarios. Si los omites, se usan `main.emails`, `main.emailsCc`, `main.emailsBcc` del documento. - **`subject`** — asunto custom; en su defecto se aplica el configurado por la empresa. - **`html`** — cuerpo HTML del mensaje. - **`sendFacturae`** — `true` para adjuntar también el XML Facturae (la factura debe ser elegible: cliente con `taxCode`, etc.). - **`continueWithoutFacturae`** — `true` para no fallar si el Facturae no puede generarse (sin él, solo se envía el PDF). **Efectos:** - Adjunta automáticamente el PDF de la factura. - Adjunta el Facturae XML si se solicita y es elegible. - Emite el webhook **`invoice.sent`** al completarse. **Parámetros globales aceptados:** `accept-version`. ## Generar PDF `PUT /{companyId}/invoices/{id}/pdf` genera el PDF de la factura aplicando la plantilla `main.theme`. **Respuesta:** `{ url, filename }`. La URL es temporal (~24h); vuelve a llamar al endpoint para obtener una nueva si caduca. **Parámetros globales aceptados:** `accept-version`. ## Generar Facturae `PUT /{companyId}/invoices/{id}/facturae` genera el XML Facturae **versión 3.2.1** (formato estándar B2B español). **Respuesta:** `{ url, filename }` con la URL del XML. **Restricciones:** - La factura debe ser elegible (cliente con `taxCode`, dirección fiscal completa, etc.). - Para facturas rectificativas, los campos `Corrective_*` se generan automáticamente desde `main.correctedInvoice` y los campos VeriFactu/TicketBAI. **Parámetros globales aceptados:** `accept-version`. ## Adjuntos Patrón estándar compartido con el resto de documentos: - **`POST /{companyId}/invoices/{id}/attachments`** — body `{ uploadIds: string[] }`. Adjunta archivos previamente subidos con [Uploads](https://www.facturadirecta.com/desarrolladores/api/referencia/uploads/). - **`GET /{companyId}/invoices/{id}/attachments`** — lista los adjuntos. - **`DELETE /{companyId}/invoices/{id}/attachments/{attachmentIndex}`** — borra por **índice** (0-based) en el array. **Límite:** **10 adjuntos por documento**. El POST con un total superior devuelve `400`. ## Webhooks Eventos que emite el recurso: - **`invoice.created`** — alta de factura. - **`invoice.updated`** — modificación del `content`, incluido el registro de pagos. - **`invoice.archived`** — borrado (lógico, recuperable). - **`invoice.unarchived`** — recuperación de una factura borrada (no expuesta hoy por API pública; reservada para procesos internos). - **`invoice.sent`** — envío por email completado. - **`invoice.voided`** — anulación. - **`invoice.verifactu_sent`** — envío exitoso a AEAT confirmado. Ver [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/). > No existen eventos `invoice.ticketbai_*`. Para TicketBAI, el > estado se consulta en `meta.ticketbai` del documento. Ver > [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/). Detalle de la entrega (HMAC, reintentos, formato) en [Webhooks](https://www.facturadirecta.com/desarrolladores/webhooks/). ## Recomendaciones - **Tres lecturas obligadas antes de tu integración**: la [guía de TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/), la [guía de VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/) y la [guía de Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). Conocer cuándo y cómo se emite electrónicamente la factura evita la mayoría de los errores reportados. - **Para crear, deja que el servidor asigne el número**: no envíes `docNumber.number` salvo que tengas un caso de uso muy concreto (importaciones desde otro sistema). Así evitas colisiones y errores de huecos en la numeración. - **Para integraciones que envían a clientes finales**, registra los webhooks `invoice.created`, `invoice.voided`, `invoice.sent` y `invoice.verifactu_sent`. Son los que disparan acciones externas (notificaciones, replicación a CRMs, descargas). - **Multiprovincias**: si tu integración opera con empresas en País Vasco y en el resto de España, no asumas el mismo flujo. Lee ambas guías regulatorias. - **Factura externa**: úsala con cuidado. `main.external = true` marca la factura como creada fuera del programa. Contabiliza normalmente, pero **no la envía a TicketBAI ni VeriFactu**. Es el modo apropiado para registrar facturas históricas o las emitidas con otro software fiscal, pero **no** para evitar la obligación electrónica. - **Rectificativas y sustitutivas**: cada una tiene su flujo específico. Las rectificativas se crean con el CRUD normal marcando `correctedInvoice` y la serie apropiada; las sustitutivas tienen un endpoint dedicado. Lee las guías antes de implementar. ## Versiones `GET /{companyId}/invoices/{id}/versions` lista las versiones de la factura: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/invoices/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/invoices/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` — combinaciones incoherentes: - `main.contact = null` con `main.simplified = false`. - `main.exchangeRate <= 0`. - `main.docNumber.series` no presente en `settings.numbering.invoice`. - `line.tax` con un `TaxId` que no está en el catálogo de impuestos de venta de la empresa. - `400` en `POST /payments` — el array contiene más de un pago sin `amount`, o el pago sin `amount` no está en la última posición. - `400` en `POST /invoices/substitution` — alguna restricción no se cumple (factura no simplificada, monedas distintas, ya sustituida...). - `404 Not Found` — factura inexistente o borrada. - `409 Conflict` — intento de borrar una factura ya contabilizada. Anula en su lugar. - `413 Payload Too Large` — adjuntos por encima del límite (10). Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. ## Referencia exhaustiva Esta página resume el contrato y los matices funcionales del recurso. Para el contrato completo del schema (todos los campos del `Invoice`, los `$ref` a `documentCounterpart`, `invoiceMainFacturae`, `invoiceMainVerifactu`, `invoiceMetaTicketbai`, `invoiceMetaVerifactu`, etc.), consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). Los detalles regulatorios y los flujos especiales viven en las guías dedicadas: - [TicketBAI](https://www.facturadirecta.com/desarrolladores/api/guias/ticketbai/) - [VeriFactu](https://www.facturadirecta.com/desarrolladores/api/guias/verifactu/) - [Rectificativas](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-rectificativas/) - [Sustitutivas (F3)](https://www.facturadirecta.com/desarrolladores/api/guias/invoices-sustitutivas/) ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/invoices` | `getInvoices` | `invoices:read` | Lista de facturas | | GET | `/{companyId}/invoices/{id}` | `getInvoice` | `invoices:read` | Obtener una factura | | GET | `/{companyId}/invoices/{id}/attachments` | `getInvoiceAttachments` | `invoices:read` | Listar adjuntos de una factura de venta | | GET | `/{companyId}/invoices/{id}/versions` | `getInvoiceVersions` | `invoices:read` | Versiones de la factura | | GET | `/{companyId}/invoices/{id}/versions/{versionId}` | `getInvoiceVersion` | `invoices:read` | Versión de la factura | | POST | `/{companyId}/invoices` | `createInvoice` | `invoices:write` | Crear factura | | POST | `/{companyId}/invoices/{id}/attachments` | `addInvoiceAttachments` | `invoices:write` | Vincular adjuntos a una factura | | POST | `/{companyId}/invoices/{id}/payments` | `createInvoicePayments` | `invoices:write` | Crear pagos para una factura | | POST | `/{companyId}/invoices/substitution` | `createSubstitutionInvoice` | `invoices:write` | Crear factura sustitutiva de simplificadas | | PUT | `/{companyId}/invoices/{id}` | `updateInvoice` | `invoices:write` | Actualizar factura | | PUT | `/{companyId}/invoices/{id}/facturae` | `createInvoiceFacturae` | `invoices:read` | Generar la factura en formato facturae | | PUT | `/{companyId}/invoices/{id}/pdf` | `createInvoicePDF` | `invoices:read` | Generar la factura en formato PDF | | PUT | `/{companyId}/invoices/{id}/send` | `sendInvoice` | `invoices:write` | Enviar factura por correo electrónico | | PUT | `/{companyId}/invoices/{id}/tags` | `updateInvoiceTags` | `invoices:write` | Actualizar etiquetas de factura | | DELETE | `/{companyId}/invoices/{id}` | `deleteInvoice` | `invoices:write` | Borrar factura | | DELETE | `/{companyId}/invoices/{id}/attachments/{attachmentIndex}` | `deleteInvoiceAttachment` | `invoices:write` | Eliminar adjunto de una factura de venta | ## Scopes - **`invoices:read`** — Lectura de facturas. - **`invoices:write`** — Modificación de facturas. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/invoices.md --- # Facturas de venta recurrentes > Una factura recurrente es una plantilla de factura programada: cuando llega el momento configurado, FacturaDirecta genera automáticamente la factura real a partir de la plantilla. Una factura recurrente es una **plantilla de factura programada**: cuando llega el momento configurado, FacturaDirecta genera automáticamente la factura real a partir de la plantilla. Útil para suscripciones, mantenimientos, alquileres, retainers y cualquier facturación periódica con importes y conceptos estables. > En los ejemplos de esta página: > > - Los **UUIDs** (`aut_…`, `con_…`, `upl_…`) son ilustrativos. Cada > empresa tiene los suyos; sustitúyelos por los identificadores reales > que devuelve la API. > - El **ID de impuesto** `S_IVA_21` es el del catálogo por defecto > para IVA 21% en ventas. Recupera el catálogo real con > `GET /{companyId}/settings/taxes/sales`. Ver > [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). ## Conceptualmente: una automatización con `action: createDocument` Una factura recurrente es **técnicamente una automatización** (`content.type: "automation"`). Tiene un **trigger** (cuándo se dispara) y una **action** (qué hace al dispararse, en este caso crear una factura). El prefijo del identificador es `aut_` (de "automation"). La API pública expone hoy un único uso de las automatizaciones: facturas de venta recurrentes (`action.type: "createDocument"`, `action.template.type: "invoice"`). El recurso puede extenderse a otras recurrencias en el futuro. ## Doble scope A diferencia del resto de recursos, cada operación de recurring requiere **dos scopes simultáneamente**: - Las operaciones de **lectura** requieren `recurring:read` **y** `invoices:read`. - Las operaciones de **escritura** requieren `recurring:write` **y** `invoices:write`. El motivo: la recurrente toca dos dominios — la programación (recurring) y la factura que genera (invoices). Quien gestiona recurrentes debe poder leer/escribir ambos. ## Estructura - `content.type` — siempre `"automation"`. - `content.uuid` — identificador inmutable, prefijo `aut_`. - `content.main`: - `title` — nombre descriptivo de la recurrente. - `enabled` — `true`/`false`. Si `false`, las próximas ejecuciones programadas se omiten sin perderse en el calendario. - `trigger` — cuándo se dispara (ver [Trigger y rrule](#trigger-y-rrule)). - `action` — qué hace al dispararse (ver [Action: plantilla de factura](#action-plantilla-de-factura)). - `content.attachments` — adjuntos vinculados (ver [Adjuntos](#adjuntos)). ### Trigger y `rrule` El trigger sigue el estándar RFC 5545 (iCalendar) en una versión simplificada. Forma: ```json "trigger": { "type": "recurring", "rrule": { "freq": "monthly", "interval": 1, "dtstart": "2026-06-01T06:00:00.000Z", "until": "2027-06-01T06:00:00.000Z", "bymonthday": 1 }, "daysAdvance": 0 } ``` Campos de `rrule`: - **`freq`** (obligatorio) — frecuencia base. Valores: `daily`, `weekly`, `monthly`, `yearly`. - **`interval`** (obligatorio, entero ≥1) — cada cuántas unidades de `freq`. Ejemplos: `freq: "monthly", interval: 1` (mensual); `freq: "monthly", interval: 3` (trimestral); `freq: "weekly", interval: 2` (cada dos semanas). - **`dtstart`** (obligatorio) — fecha-hora de inicio en ISO 8601. Solo se evalúan ejecuciones a partir de esta fecha. - **`until`** o **`count`** (opcional, mutuamente excluyentes) — fin del calendario: por fecha o por número de ejecuciones totales. - **`byweekday`** (opcional, 0-6) — para `freq` `weekly` o `monthly`, fija el día de la semana (0=lunes, 6=domingo). - **`bymonthday`** (opcional) — para `freq` `monthly` o `yearly`, día del mes (1-31 o `-1` para el último día del mes). Para `freq: monthly` el máximo aceptado es 28 (para evitar meses sin ese día). - **`bysetpos`** (opcional) — para `freq: monthly`, posición ordinal del día de la semana en el mes: 1-4 para "el primer/segundo/.../cuarto `byweekday`", `-1` para "el último `byweekday`". Requiere `byweekday`. Ejemplo: `byweekday: 2, bysetpos: -1` = "el último miércoles del mes". - **`bymonth`** (opcional, 1-12) — para `freq: yearly`, mes del año. Otros campos del trigger: - **`daysAdvance`** — reservado para uso futuro. Indicar siempre `0`. ### Action: plantilla de factura `content.main.action` describe qué documento se genera. Forma: ```json "action": { "type": "createDocument", "template": { "type": "invoice", "main": { "docNumber": { "series": "FP" }, "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "currency": "EUR", "lines": [ { "quantity": 1, "unitPrice": 100, "tax": ["S_IVA_21"], "text": "Mantenimiento mensual" } ] } } } ``` `template.main` es una estructura equivalente a la de una factura de venta normal (ver [Facturas de venta](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/)). Puede incluir `customFields`, un mapa de valores indexado por el ID estable `cfi_` de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las definiciones borradas siguen siendo válidas; un ID desconocido o con formato incorrecto produce `400 Bad Request`. Cuando llega el momento programado, FacturaDirecta crea una factura con los datos de esta plantilla, asignando un nuevo número de documento y la fecha actual. ## Enlace a la aplicación Las respuestas que devuelven una factura recurrente incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de facturas recurrentes](#lista-de-facturas-recurrentes) - [Crear factura recurrente](#crear-factura-recurrente) - [Obtener una factura recurrente](#obtener-una-factura-recurrente) - [Actualizar factura recurrente](#actualizar-factura-recurrente) - [Borrar factura recurrente](#borrar-factura-recurrente) - [Actualizar etiquetas](#actualizar-etiquetas) - [Generar muestra en PDF](#generar-muestra-en-pdf) - [Adjuntos](#adjuntos) ## Lista de facturas recurrentes `GET /{companyId}/recurring/invoices` devuelve las facturas recurrentes de la empresa, paginadas. **Parámetros de consulta específicos:** - **`minNextScheduledDate`**, **`maxNextScheduledDate`** — rango por la próxima ejecución programada (el `nextScheduledTime` de `related`), con los dos extremos incluidos. Útil para identificar qué recurrentes están a punto de ejecutarse o cuáles ya vencieron. Cada uno admite: - Una fecha y hora ISO 8601 (`YYYY-MM-DDTHH:mm:ss.sssZ`). Se compara el instante exacto, con 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`). - **`search`** — búsqueda aproximada en el texto de la factura recurrente (título, líneas, notas) y en el nombre de su cliente: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - **`series`** — serie de numeración de la factura plantilla. - **`enabled`** — `true`/`false` para filtrar activas/inactivas. - **`freq`** — frecuencia (`daily`/`weekly`/`monthly`/`yearly`). - **`interval`** — intervalo entero. - **`contact`** — ID de contacto de la factura plantilla. - **`hasContact`** — `true`/`false`. - **`minTotal`**, **`maxTotal`** — rango de importe de la plantilla. - **`currency`** — código ISO 4217. - **`country`** — código país ISO 3166-1 Alpha-2. - **`allTheseTags`**, **`anyOfTheseTags`** — filtros por etiquetas concretas. - **`hasTags`** — `true` devuelve solo facturas recurrentes con al menos una etiqueta y `false`, solo facturas recurrentes sin etiquetas. - **`sortBy`** — campo de orden. - **`related`** — valores admitidos: - `nextScheduledTime` — devuelve la próxima fecha-hora prevista. - `upcomingSceheduledTimes` — devuelve hasta las próximas 5 fechas programadas. (Nombre con typo histórico en el openapi; se mantiene por compatibilidad.) - `taxIds` — devuelve en `related.objects` el detalle de los impuestos aplicados en la plantilla. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices?enabled=true&freq=monthly&related=nextScheduledTime" ``` ## Crear factura recurrente `POST /{companyId}/recurring/invoices` crea una factura recurrente. **Parámetros del body:** - `content.type` — siempre `"automation"`. - `content.main.title` (obligatorio) — nombre descriptivo. - `content.main.enabled` (opcional, por defecto `true`). - `content.main.trigger` (obligatorio) — ver [Trigger y rrule](#trigger-y-rrule). - `content.main.action` (obligatorio) — ver [Action: plantilla de factura](#action-plantilla-de-factura). - `tags` (opcional). **Notas:** - La primera ejecución no recupera fechas anteriores al día de creación. Si `dtstart` está en el pasado, el calendario conserva esa referencia, pero el servidor espera a la primera ocurrencia del mismo día o posterior. Así evita generar una factura histórica que no solicitaste. - Si la primera ocurrencia vence por un error técnico, sigue pendiente durante el resto de su periodo. Una recurrente mensual, por ejemplo, puede recuperar esa primera factura hasta que llegue la siguiente ocurrencia mensual. - Si `enabled: false`, la recurrente se guarda pero las ejecuciones programadas se omiten hasta que se reactive. - Los planes 2026 limitan el número de recurrentes **activas a la vez** (Gratis 2, Bronce 20, Plata 200, Oro y Diamante sin límite; cuentan todas las automatizaciones activas, no solo las facturas). Con el cupo lleno, crear una recurrente con `enabled: true` devuelve `403` con el código `plan_limit_exceeded` (ver [Errores](https://www.facturadirecta.com/desarrolladores/api/guias/errors/)); crearla con `enabled: false` siempre es posible. Desactivar o borrar una recurrente libera hueco. - La respuesta es la recurrente creada completa, en el mismo formato que [Obtener una factura recurrente](#obtener-una-factura-recurrente). **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON Recurrente mensual el día 1 de cada mes, desde junio de 2026 durante 12 meses: ```json { "content": { "type": "automation", "main": { "title": "Mantenimiento mensual - Acme", "enabled": true, "trigger": { "type": "recurring", "rrule": { "freq": "monthly", "interval": 1, "dtstart": "2026-06-01T06:00:00.000Z", "count": 12, "bymonthday": 1 } }, "action": { "type": "createDocument", "template": { "type": "invoice", "main": { "docNumber": { "series": "FP" }, "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "currency": "EUR", "lines": [ { "quantity": 1, "unitPrice": 100, "tax": ["S_IVA_21"], "text": "Mantenimiento mensual" } ] } } } } } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@recurringInvoice.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices" ``` ## Obtener una factura recurrente `GET /{companyId}/recurring/invoices/{id}` devuelve una factura recurrente por ID. **Parámetros específicos:** - `related` (opcional) — mismos valores que en [Lista de facturas recurrentes](#lista-de-facturas-recurrentes) (`nextScheduledTime`, `upcomingSceheduledTimes`, `taxIds`). **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f?related=nextScheduledTime&related=upcomingSceheduledTimes" ``` ## Actualizar factura recurrente `PUT /{companyId}/recurring/invoices/{id}` sustituye el contenido de la recurrente. Dentro de `action.template` se aplica la misma regla que al actualizar una factura: las líneas se reemplazan por las que envíes, los campos opcionales que no envíes se eliminan y los obligatorios que no envíes conservan su valor, igual que la serie (`docNumber`) y `meta`. Casos típicos: - Cambiar la plantilla (`action.template`): por ejemplo, subir el importe de la suscripción mensual. Los documentos ya emitidos no se ven afectados; solo las próximas ejecuciones generan facturas con el nuevo importe. - Cambiar la frecuencia (`trigger.rrule`): el calendario conserva `dtstart` como referencia y descarta las ocurrencias anteriores al día de la actualización si todavía no hubo una ejecución correcta. - Activar o desactivar (`enabled`): conserva la configuración. Al reactivar una recurrente sin ejecuciones correctas, no se recuperan ocurrencias anteriores al día de la actualización. Reactivar con el cupo de recurrentes activas del plan lleno devuelve `403` `plan_limit_exceeded`; editar una recurrente que ya está activa no consume hueco. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@recurringInvoice.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f" ``` ## Borrar factura recurrente `DELETE /{companyId}/recurring/invoices/{id}` elimina la recurrente. Las facturas ya emitidas por la recurrente **se conservan**. Solo se cancela el calendario de ejecuciones futuras. Si quieres pausar temporalmente sin perder configuración, usa `enabled: false` en lugar de borrar. **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/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f" ``` ## Actualizar etiquetas `PUT /{companyId}/recurring/invoices/{id}/tags` reemplaza el conjunto de etiquetas sin tocar el resto del contenido. ###### Ejemplo de request JSON ```json { "tags": ["suscripcion", "premium"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["suscripcion","premium"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/tags" ``` ## Generar muestra en PDF `PUT /{companyId}/recurring/invoices/{id}/pdf` genera una URL temporal con una **muestra** de la factura que se emitiría, en PDF. Es solo una previsualización: no crea ninguna factura real ni avanza el calendario. **Parámetros del body:** - `mode` (opcional) — `"attachment"` (URL para descargar como adjunto, por defecto), `"inline"` (visualización en línea) o `"print"` (URL HTML que carga el PDF y lo envía a imprimir). **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"mode":"inline"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/pdf" ``` ## Adjuntos Una factura recurrente puede tener adjuntos vinculados (p. ej. un contrato base). Estos adjuntos viajan en cada factura generada por la recurrente. ### Listar adjuntos `GET /{companyId}/recurring/invoices/{id}/attachments` devuelve los adjuntos vinculados. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/attachments" ``` ### Vincular adjuntos `POST /{companyId}/recurring/invoices/{id}/attachments` añade adjuntos a partir de uploads previos. **Parámetros del body:** - `uploadIds` — array de identificadores `upl_`. Mínimo uno. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"uploadIds":["upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/attachments" ``` ### Eliminar un adjunto `DELETE /{companyId}/recurring/invoices/{id}/attachments/{attachmentIndex}` elimina un adjunto. `attachmentIndex` es la posición en el array (basada en cero). ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/attachments/0" ``` ## Recomendaciones - **Empieza con `enabled: false`** mientras pruebas: la recurrente queda configurada sin generar facturas. - **Usa `related=nextScheduledTime`** al consultar para saber cuándo se ejecutará por próxima vez. Y `upcomingSceheduledTimes` para ver las siguientes 5 fechas (útil para validar que el `rrule` resuelve al calendario que esperas). - **Para suscripciones que cambian de precio**, actualiza la plantilla (`action.template.main.lines[].unitPrice`) en cualquier momento. Las ejecuciones futuras usan el nuevo precio sin tocar las pasadas. - **`dtstart` en el futuro** es válido y útil para programar recurrentes que arranquen más adelante. - **Para pausar temporalmente**, prefiere `enabled: false` sobre borrado. Conserva todo el calendario y la configuración. ## Errores comunes - `400` — `minNextScheduledDate` o `maxNextScheduledDate` es una fecha o una fecha y hora que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` — `rrule` con combinación inválida (`until` y `count` a la vez, `bymonthday > 28` con `freq: monthly`, `bysetpos` sin `byweekday`...). - `400 ValidationError` — `daysAdvance != 0` (reservado, debe quedar en cero). - `400 ValidationError` — `action.template.type` distinto de `invoice`. Hoy es el único valor soportado. - `409 Conflict` — `series` o `docNumber` de la plantilla en conflicto con otra factura emitida. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/recurring/invoices` | `getRecurringInvoices` | `invoices:read`, `recurring:read` | Lista de facturas de venta recurrentes | | GET | `/{companyId}/recurring/invoices/{id}` | `getRecurringInvoice` | `invoices:read`, `recurring:read` | Obtener una factura de venta recurrente | | GET | `/{companyId}/recurring/invoices/{id}/attachments` | `getRecurringInvoiceAttachments` | `invoices:read`, `recurring:read` | Listar adjuntos de una factura recurrente | | POST | `/{companyId}/recurring/invoices` | `createRecurringInvoice` | `invoices:write`, `recurring:write` | Crea factura de venta recurrente | | POST | `/{companyId}/recurring/invoices/{id}/attachments` | `addRecurringInvoiceAttachments` | `invoices:write`, `recurring:write` | Vincular adjuntos a una factura recurrente | | PUT | `/{companyId}/recurring/invoices/{id}` | `updateRecurringInvoice` | `invoices:write`, `recurring:write` | Actualizar factura recurrente | | PUT | `/{companyId}/recurring/invoices/{id}/pdf` | `createRecurringInvoicePDF` | `invoices:read`, `recurring:read` | Generar muestra de la factura de venta recurrente en formato PDF | | PUT | `/{companyId}/recurring/invoices/{id}/tags` | `updateRecurringInvoiceTags` | `invoices:write`, `recurring:write` | Actualizar etiquetas de factura de venta recurrente | | DELETE | `/{companyId}/recurring/invoices/{id}` | `deleteRecurringInvoice` | `invoices:write`, `recurring:write` | Borrar factura de venta recurrente | | DELETE | `/{companyId}/recurring/invoices/{id}/attachments/{attachmentIndex}` | `deleteRecurringInvoiceAttachment` | `invoices:write`, `recurring:write` | Eliminar adjunto de una factura recurrente | ## Scopes - **`invoices:read`** — Lectura de facturas. - **`invoices:write`** — Modificación de facturas. - **`recurring:read`** — Lectura de documentos recurrentes. - **`recurring:write`** — Modificación de documentos recurrentes. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/recurring.md --- # Presupuestos > Un presupuesto es una propuesta económica que envías a un cliente antes de emitir la factura correspondiente. Un presupuesto es una propuesta económica que envías a un cliente antes de emitir la factura correspondiente. Tiene líneas de detalle, totales, condiciones de pago y un ciclo de vida con estados (`pending`, `accepted`, `rejected`, `closed`). Cuando el cliente acepta el presupuesto, lo normal es convertirlo en factura. **La conversión en sí no es una operación de esta API**: se hace desde la interfaz. El presupuesto convertido refleja la factura resultante en su `related` cuando se consulta. ## Estados `content.main.baseState` controla el estado **declarado** del presupuesto. Es lo que tú marcas desde el propio documento: - `pending` — presupuesto enviado o en negociación, sin decisión. - `accepted` — el cliente lo ha aceptado. - `rejected` — el cliente lo ha rechazado. - `closed` — cerrado sin esperar respuesta (por ejemplo, plazo vencido). El estado **efectivo** que ves en la interfaz puede no coincidir con `baseState` si existen documentos relacionados. Por ejemplo, un presupuesto `accepted` que se ha convertido en factura aparece como facturado, aunque su `baseState` siga siendo `accepted`. Campos derivados que se rellenan típicamente al cambiar `baseState`: - `acceptedDate`, `acceptedBy` — quién aceptó y cuándo. - `rejectedDate`, `rejectedBy` — equivalente para rechazos. Estos campos no son obligatorios al crear el presupuesto. ## Factura proforma Un presupuesto puede marcarse como **factura proforma** con `content.main.isProforma: true`. Una factura proforma es un documento previo a la factura definitiva que se presenta al cliente con apariencia de factura, pero sin efectos fiscales reales. Es habitual en escenarios como pagos anticipados, exportaciones o aprobaciones internas del cliente que exigen un documento con formato de factura antes de emitir la definitiva. A nivel del sistema, una factura proforma **sigue siendo un presupuesto**: usa los mismos endpoints, mismos estados (`baseState`) y la misma estructura de líneas y totales. Solo cambia su presentación: - **Nombre del PDF generado** — usa el prefijo "proforma" (o el configurado en la plantilla del documento) en vez de "presupuesto". - **Plantilla de email al enviarlo** — usa la plantilla `email_proforma` (con asunto y cuerpo propios) en vez de `email_estimate`. Configura ambas plantillas en los ajustes de la empresa si quieres mensajes distintos para presupuestos y proformas. - **Etiquetado interno** — aparece como "proforma" en los listados de la app y en los nombres de fichero, no como "presupuesto". `isProforma` **no implica cambios fiscales**: no genera asientos contables, no se reporta a VeriFactu/TicketBAI y no consume serie de facturación. Para emitir la factura real al cliente, crea un nuevo documento de tipo invoice cuando corresponda. ## Estructura del presupuesto Forma común a los documentos de venta de FacturaDirecta: - `content.type` — siempre `"estimate"`. - `content.uuid` — identificador inmutable. - `content.main` — datos del documento (contacto, fechas, divisa, líneas, totales, condiciones, plantilla). Puede incluir `owner`, el usuario responsable del presupuesto cuando trabajas con roles personalizados. - `content.attachments` — adjuntos vinculados (ver [Adjuntos](#adjuntos)). - `content.meta` — metadatos internos. En respuestas, además, en el nivel raíz: `tags`, `creationDate`, `modificationDate`, `related`. Si creas el presupuesto con OAuth y no envías `owner`, la API asigna como responsable al usuario autenticado. Si usas una apiKey, queda sin responsable salvo que envíes `owner` en el body. `content.main.customFields` es un mapa de valores indexado por el ID estable `cfi_` de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las claves deben corresponder a definiciones existentes. También se aceptan definiciones borradas para conservar presupuestos históricos; un ID desconocido o con formato incorrecto produce `400 Bad Request`. ### Líneas de detalle `content.main.lines` es un array. Cada línea requiere `text`, `quantity` y `unitPrice`. Otros campos relevantes: - `discount` o `discountRate` — descuento absoluto o porcentual. - `tax` — array de **identificadores de impuesto** (`TaxId`) que aplican a la línea. Los IDs se obtienen del catálogo de **ventas** de la empresa con `GET /{companyId}/settings/taxes/sales`. Pueden aplicarse varios impuestos a una misma línea, típicamente IVA + retención de IRPF. Ver [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/) para el modelo completo (catálogos, autorepercutidos, agregado en el documento). > En los ejemplos de esta página: > > - Los **UUIDs** (`con_2c4f8e21-…`, `est_8d3b6c14-…`, `upl_6f8e9b42-…`) son > ilustrativos. Cada empresa tiene los suyos; sustitúyelos por los > identificadores reales que devuelve la API en cada caso. > - Los **IDs de impuestos** (`S_IVA_21`, `S_IRPF_15`) son los del > catálogo por defecto del sistema. Pueden variar por empresa o > comunidad fiscal; recupera el catálogo real con > `GET /{companyId}/settings/taxes/sales`. - `lineTotal` — total de la línea; la API lo calcula si lo omites. - `document` — ID del producto del catálogo si la línea proviene de uno. - `account` — cuenta contable de la línea; por defecto se toma de la cuenta asociada al producto o, en su defecto, la de ventas de la empresa. **Importes y totales.** Los totales del documento (`total`, `totalBeforeTaxes`, `linesTotal`, `taxes`) se calculan a partir de las líneas. **Conviene dejarlos vacíos al crear o actualizar** y leerlos de la respuesta. Enviarlos manualmente solo está justificado para importaciones que fijan totales históricos. `taxIncludedPrices: true` indica que `unitPrice` ya incluye IVA. Por defecto es `false`. Cambia el cálculo de `taxes` y `total`. Por defecto el documento se trata como presupuesto. Para marcarlo como factura proforma, pon `content.main.isProforma: true` (ver [Factura proforma](#factura-proforma)). ## Enlace a la aplicación Las respuestas que devuelven un presupuesto incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de presupuestos](#lista-de-presupuestos) - [Crear presupuesto](#crear-presupuesto) - [Obtener un presupuesto](#obtener-un-presupuesto) - [Actualizar presupuesto](#actualizar-presupuesto) - [Borrar presupuesto](#borrar-presupuesto) - [Actualizar etiquetas de presupuesto](#actualizar-etiquetas-de-presupuesto) - [Enviar presupuesto por correo](#enviar-presupuesto-por-correo) - [Generar el presupuesto en PDF](#generar-el-presupuesto-en-pdf) - [Adjuntos](#adjuntos) ## Lista de presupuestos `GET /{companyId}/estimates` devuelve los presupuestos de la empresa, paginados. **Parámetros de consulta principales:** - `search` — búsqueda aproximada en el texto del presupuesto (título, líneas, notas) y en el nombre de su cliente: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - `minDate`, `maxDate` — rango de fechas (`YYYY-MM-DD`). - `series`, `formattedSeries` — serie de numeración (en formato canónico o como aparece al usuario). - `minNumber`, `maxNumber` — rango de número de documento. - `contact` — ID de contacto. - `hasContact` — `true`/`false` para filtrar con o sin contacto. - `minTotal`, `maxTotal` — rango de importe total. - `currency` — código ISO 4217 (`EUR`, `USD`...). - `country` — código país ISO 3166-1 Alpha-2. - `emails` — buscar por destinatario. - `allTheseTags`, `anyOfTheseTags` — filtros por etiquetas concretas. - `hasTags` — `true` devuelve solo presupuestos con al menos una etiqueta y `false`, solo presupuestos sin etiquetas. - `sortBy` — campo de orden. - `related` — recursos a expandir. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) para los parámetros comunes (`offset`, `limit`, `hasMore`). ###### Ejemplo de respuesta JSON ```json { "pagination": { "offset": 0, "limit": 50, "total": 42 }, "items": [ { "content": { "type": "estimate", "uuid": "est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c", "main": { "title": "Presupuesto 2026/0041 — Acme Diseño", "baseState": "pending", "docNumber": "2026/0041", "series": "default", "date": "2026-05-03", "dueDate": "2026-06-02", "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "currency": "EUR", "lines": [ { "text": "Diseño de identidad corporativa", "quantity": 1, "unitPrice": 1800.00, "tax": ["S_IVA_21"] } ], "linesTotal": 1800.00, "totalBeforeTaxes": 1800.00, "taxes": [ { "id": "S_IVA_21", "base": 1800.00, "amount": 378.00, "companyCurrencyBase": 1800.00, "companyCurrencyAmount": 378.00 } ], "total": 2178.00 } }, "tags": ["q2-2026"], "creationDate": "2026-05-03T09:12:00.000Z", "modificationDate": "2026-05-03T09:12:00.000Z" } ] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates?minDate=2026-01-01&limit=50" ``` ## Crear presupuesto `POST /{companyId}/estimates` crea un presupuesto. **Parámetros del body:** - `content.type` — siempre `"estimate"`. - `content.main` — campos del documento. `lines` es lo importante; los totales se calculan a partir de ellas. - `tags` (opcional). **Notas:** - `language` (opcional): idioma en que se imprime y se envía el documento, como código ISO 639-1 (`es`, `ca`, `en`...). Si se omite, el del contacto al asignarlo y, si no tiene, el idioma base de la plantilla. Solo tiene efecto si la plantilla tiene ese idioma. - Si `contact` apunta a un ID existente, sus datos fiscales y de envío se toman de ese contacto. - `series` y `docNumber` se asignan automáticamente si no los envías, según la numeración configurada para presupuestos. - La respuesta es el presupuesto creado completo, en el formato de [Obtener un presupuesto](#obtener-un-presupuesto). ###### Ejemplo de request JSON ```json { "content": { "type": "estimate", "main": { "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "date": "2026-05-03", "dueDate": "2026-06-02", "currency": "EUR", "lines": [ { "text": "Diseño de identidad corporativa", "quantity": 1, "unitPrice": 1800.00, "tax": ["S_IVA_21"] } ] } }, "tags": ["q2-2026"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{ "content": { "type": "estimate", "main": { "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "date": "2026-05-03", "currency": "EUR", "lines": [ { "text": "Diseño de identidad corporativa", "quantity": 1, "unitPrice": 1800.00, "tax": ["S_IVA_21"] } ] } } }' \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates" ``` ## Obtener un presupuesto `GET /{companyId}/estimates/{id}` devuelve un presupuesto por ID. **Parámetros:** - `related` (opcional) — recursos relacionados a expandir, por ejemplo `invoices` para ver si el presupuesto ha derivado en factura. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c?related=invoices" ``` ## Actualizar presupuesto `PUT /{companyId}/estimates/{id}` sustituye el contenido del presupuesto. Las líneas se reemplazan por las que envíes y los campos opcionales que no envíes se eliminan. Los campos obligatorios que no envíes conservan su valor, igual que `docNumber` y `meta`, que no cambian. Lo que FacturaDirecta rellena al crear (`account`, `dueDate`, `fiscalPosition`, `theme`…) se rellena igual al actualizar si no lo envías. Si no envías `exchangeRate`, se conserva el anterior mientras no cambien ni la moneda ni la fecha; si cambia alguna de las dos, se toma el tipo de cambio de la nueva fecha. Para cambiar el estado del documento, envía el contacto y las líneas inalterados junto con el nuevo `baseState` (y, si aplica, `acceptedDate` / `rejectedDate`). **Restricciones:** - Si el presupuesto ya está convertido en una factura, las modificaciones pueden estar limitadas o producir efectos secundarios. Antes de actualizar un `accepted`, consulta su `related`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@estimate.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c" ``` ## Borrar presupuesto `DELETE /{companyId}/estimates/{id}` elimina un presupuesto. **Restricciones.** No se puede borrar un presupuesto que ya esté vinculado a una factura emitida. La API devuelve `409 Conflict` con el detalle del documento que bloquea el borrado. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c" ``` ## Actualizar etiquetas de presupuesto `PUT /{companyId}/estimates/{id}/tags` reemplaza el conjunto de etiquetas sin tocar el resto del contenido. ###### Ejemplo de request JSON ```json { "tags": ["q2-2026", "agencia", "prioridad-alta"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["q2-2026","agencia","prioridad-alta"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/tags" ``` ## Enviar presupuesto por correo `PUT /{companyId}/estimates/{id}/send` envía el presupuesto por correo electrónico. Los destinatarios, asunto y cuerpo se toman de la plantilla configurada para presupuestos; los campos del body permiten **sobreescribirlos** puntualmente. **Parámetros del body** (todos opcionales): - `to`, `cc`, `bcc` — listas de destinatarios; sobrescriben los valores de la plantilla. - `from` — remitente; debe ser una dirección autorizada en la empresa. - `subject` — asunto. - `html` — cuerpo del email en HTML. Si no envías body, se usa enteramente la plantilla del presupuesto. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"to":["compras@acme.es"],"subject":"Presupuesto 2026/0041"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/send" ``` ## Generar el presupuesto en PDF `PUT /{companyId}/estimates/{id}/pdf` genera una URL temporal con el presupuesto en PDF. **Parámetros del body:** - `mode` (opcional) — `"attachment"` (URL para descargar como adjunto, por defecto), `"inline"` (visualización en línea) o `"print"` (URL HTML que carga el PDF y lo envía a imprimir). La URL devuelta es válida solo durante el periodo indicado en la respuesta. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"mode":"inline"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/pdf" ``` ## Adjuntos Un presupuesto puede tener varios adjuntos vinculados (PDFs de referencia, documentación técnica, etc.). Los adjuntos **no se suben en el body del presupuesto**: primero se crean con `POST /uploads` y después se vinculan al presupuesto con `POST /estimates/{id}/attachments` enviando los `uploadIds`. Una vez vinculados, los adjuntos viven dentro del recurso y se pueden listar o eliminar de forma independiente. ### Listar adjuntos `GET /{companyId}/estimates/{id}/attachments` devuelve los adjuntos vinculados al presupuesto. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/attachments" ``` ### Vincular adjuntos `POST /{companyId}/estimates/{id}/attachments` añade adjuntos al presupuesto a partir de uploads previos. **Parámetros del body:** - `uploadIds` — array de identificadores `upl_` obtenidos con `POST /uploads`. Mínimo uno. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"uploadIds":["upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b","upl_7a9b0c53-2e4d-4b8a-a3f5-0d2e6f8a4b7c"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/attachments" ``` ### Eliminar un adjunto `DELETE /{companyId}/estimates/{id}/attachments/{attachmentIndex}` elimina un adjunto del presupuesto. `attachmentIndex` es la posición del adjunto en el array (basada en cero). ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/estimates/est_8d3b6c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/attachments/0" ``` ## Versiones `GET /{companyId}/estimates/{id}/versions` lista las versiones de el presupuesto: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/estimates/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/estimates/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` — falta `contact`, o líneas con `quantity` o `unitPrice` inválidos. - `400 ValidationError` — `baseState` con valor no admitido. - `409 Conflict` — borrado o modificación incompatible con un documento relacionado (típicamente, factura ya emitida). - `409 Conflict` — `series` o `docNumber` duplicado en la empresa. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general de respuestas de error. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/estimates` | `getEstimates` | `estimates:read` | Lista de presupuestos | | GET | `/{companyId}/estimates/{id}` | `getEstimate` | `estimates:read` | Obtener un presupuesto | | GET | `/{companyId}/estimates/{id}/attachments` | `getEstimateAttachments` | `estimates:read` | Listar adjuntos de un presupuesto | | GET | `/{companyId}/estimates/{id}/versions` | `getEstimateVersions` | `estimates:read` | Versiones de el presupuesto | | GET | `/{companyId}/estimates/{id}/versions/{versionId}` | `getEstimateVersion` | `estimates:read` | Versión de el presupuesto | | POST | `/{companyId}/estimates` | `createEstimate` | `estimates:write` | Crear presupuesto | | POST | `/{companyId}/estimates/{id}/attachments` | `addEstimateAttachments` | `estimates:write` | Vincular adjuntos a un presupuesto | | PUT | `/{companyId}/estimates/{id}` | `updateEstimate` | `estimates:write` | Actualizar presupuesto | | PUT | `/{companyId}/estimates/{id}/pdf` | `createEstimatePDF` | `estimates:read` | Generar el presupuesto en formato PDF | | PUT | `/{companyId}/estimates/{id}/send` | `sendEstimate` | `estimates:write` | Enviar presupuesto por correo electrónico | | PUT | `/{companyId}/estimates/{id}/tags` | `updateEstimateTags` | `estimates:write` | Actualizar etiquetas de presupuesto | | DELETE | `/{companyId}/estimates/{id}` | `deleteEstimate` | `estimates:write` | Borrar presupuesto | | DELETE | `/{companyId}/estimates/{id}/attachments/{attachmentIndex}` | `deleteEstimateAttachment` | `estimates:write` | Eliminar adjunto de un presupuesto | ## Scopes - **`estimates:read`** — Lectura de presupuestos. - **`estimates:write`** — Modificación de presupuestos. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/estimates.md --- # Albaranes > Un albarán documenta la entrega de bienes o servicios a un cliente. Un albarán documenta la **entrega** de bienes o servicios a un cliente. Es el justificante de que el suministro se ha realizado, normalmente antes de emitir la factura correspondiente. Tiene líneas de detalle, estados de entrega y un ciclo de vida propio. > En los ejemplos de esta página: > > - Los **UUIDs** (`con_2c4f8e21-…`, `den_3a7b9c14-…`, `upl_6f8e9b42-…`) > son ilustrativos. Cada empresa tiene los suyos; sustitúyelos por > los identificadores reales que devuelve la API. > - El **ID de impuesto** `S_IVA_21` es el del catálogo por defecto > para IVA 21% en ventas. Recupera el catálogo real con > `GET /{companyId}/settings/taxes/sales`. Ver > [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). ## Relación con la factura La API pública **no expone una operación de conversión** albarán → factura. La conversión, cuando aplica, se hace desde la interfaz. Si necesitas emitir una factura a partir de los datos de uno o varios albaranes, crea la factura directamente con `POST /invoices` con las líneas que correspondan. Un albarán convertido o asociado a una factura mantiene su estado en `baseState`, pero puede aparecer en su `related` con el documento de venta resultante cuando se consulta. ## Estados `content.main.baseState` controla el estado declarado del albarán: - `pending` — emitido pero pendiente de entrega. - `delivered` — entregado al cliente. - `closed` — cerrado sin entregar (caducado, cancelado o anulado). - `rejected` — el cliente ha rechazado la entrega. Campos que se rellenan típicamente al cambiar `baseState`: - `deliveredDate`, `deliveredTo` — fecha de entrega y persona del cliente que la ha recibido (firmante o responsable de recepción). - `deliveryReceipt` — recibí firmado. Contiene `signatureImage` (imagen de la firma manuscrita en formato data URL) y `signedAt` (fecha y hora de la firma). - `rejectedDate`, `rejectedBy` — fecha y responsable del rechazo, si aplica. Ninguno de estos campos es obligatorio al crear el albarán; se rellenan al evolucionar el documento. ## Estructura del albarán Forma común a los documentos de venta de FacturaDirecta: - `content.type` — siempre `"deliveryNote"`. - `content.uuid` — identificador inmutable. - `content.main` — datos del documento (contacto, fechas, divisa, líneas, totales, condiciones, plantilla). Puede incluir `owner`, el usuario responsable del albarán cuando trabajas con roles personalizados, y `warehouse`, el almacén del documento a efectos de stock (si no lo indicas se usa el almacén por defecto de la empresa; ver [Productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/#control-de-stock)). - `content.attachments` — adjuntos vinculados (ver [Adjuntos](#adjuntos)). - `content.meta` — metadatos internos. En respuestas, además, en el nivel raíz: `tags`, `creationDate`, `modificationDate`, `related`. Si creas el albarán con OAuth y no envías `owner`, la API asigna como responsable al usuario autenticado. Si usas una apiKey, queda sin responsable salvo que envíes `owner` en el body. `content.main.customFields` es un mapa de valores indexado por el ID estable `cfi_` de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las claves deben corresponder a definiciones existentes. También se aceptan definiciones borradas para conservar albaranes históricos; un ID desconocido o con formato incorrecto produce `400 Bad Request`. ### Líneas de detalle `content.main.lines` es un array. Cada línea requiere `text`, `quantity` y `unitPrice`. Otros campos relevantes: - `discount` o `discountRate` — descuento absoluto o porcentual. - `tax` — array de **identificadores de impuesto** (`TaxId`) que aplican a la línea. Los IDs se obtienen del catálogo de **ventas** de la empresa con `GET /{companyId}/settings/taxes/sales`. Ver [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). - `lineTotal` — total de la línea; la API lo calcula si lo omites. - `document` — ID del producto del catálogo si la línea proviene de uno. - `origin` — ID del documento del que procede la línea (presupuesto o pedido de cliente), para que los documentos queden enlazados. - `account` — cuenta contable de la línea; por defecto se toma de la cuenta asociada al producto o, en su defecto, la de ventas de la empresa. **Importes y totales.** Los totales del documento (`total`, `totalBeforeTaxes`, `linesTotal`, `taxes`) se calculan a partir de las líneas. Conviene dejarlos vacíos al crear o actualizar y leerlos de la respuesta. `taxIncludedPrices: true` indica que `unitPrice` ya incluye IVA. Por defecto es `false`. ## Enlace a la aplicación Las respuestas que devuelven un albarán incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de albaranes](#lista-de-albaranes) - [Crear albarán](#crear-albarán) - [Obtener un albarán](#obtener-un-albarán) - [Actualizar albarán](#actualizar-albarán) - [Borrar albarán](#borrar-albarán) - [Actualizar etiquetas de albarán](#actualizar-etiquetas-de-albarán) - [Enviar albarán por correo](#enviar-albarán-por-correo) - [Generar el albarán en PDF](#generar-el-albarán-en-pdf) - [Adjuntos](#adjuntos) ## Lista de albaranes `GET /{companyId}/deliveryNotes` devuelve los albaranes de la empresa, paginados. **Parámetros de consulta principales:** - `search` — búsqueda aproximada en el texto del albarán (título, líneas, notas) y en el nombre de su cliente: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - `minDate`, `maxDate` — rango de fechas del documento (`YYYY-MM-DD`). - `series`, `formattedSeries` — serie de numeración. - `minNumber`, `maxNumber` — rango de número de documento. - `contact` — ID de contacto. - `hasContact` — `true`/`false` para filtrar con o sin contacto. - `minTotal`, `maxTotal` — rango de importe. - `currency` — código ISO 4217. - `country` — código país ISO 3166-1 Alpha-2. - `emails` — buscar por destinatario. - `allTheseTags`, `anyOfTheseTags` — filtros por etiquetas concretas. - `hasTags` — `true` devuelve solo albaranes con al menos una etiqueta y `false`, solo albaranes sin etiquetas. - `sortBy` — campo de orden. - `related` — recursos a expandir. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) para los parámetros comunes. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes?minDate=2026-01-01&limit=50" ``` ## Crear albarán `POST /{companyId}/deliveryNotes` crea un albarán. **Parámetros del body:** - `content.type` — siempre `"deliveryNote"`. - `content.main` — campos del documento. `contact` y `lines` son las partes esenciales. - `tags` (opcional). **Notas:** - `language` (opcional): idioma en que se imprime y se envía el documento, como código ISO 639-1 (`es`, `ca`, `en`...). Si se omite, el del contacto al asignarlo y, si no tiene, el idioma base de la plantilla. Solo tiene efecto si la plantilla tiene ese idioma. - Si `contact` apunta a un ID existente, sus datos se toman del contacto. - `series` y `docNumber` se asignan automáticamente si no los envías, según la numeración configurada para albaranes. - La respuesta es el albarán creado completo, en el mismo formato que [Obtener un albarán](#obtener-un-albarán). ###### Ejemplo de request JSON ```json { "content": { "type": "deliveryNote", "main": { "docNumber": { "series": "AL" }, "baseState": "pending", "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "currency": "EUR", "lines": [ { "quantity": 1, "unitPrice": 50, "tax": ["S_IVA_21"], "text": "Descripción del producto 1" } ] } } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@deliveryNote.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes" ``` ## Obtener un albarán `GET /{companyId}/deliveryNotes/{id}` devuelve un albarán por ID. **Parámetros:** - `related` (opcional) — recursos relacionados a expandir, por ejemplo `invoices` para ver si el albarán está vinculado a alguna factura. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c?related=invoices" ``` ## Actualizar albarán `PUT /{companyId}/deliveryNotes/{id}` sustituye el contenido del albarán. Las líneas se reemplazan por las que envíes y los campos opcionales que no envíes se eliminan. Los campos obligatorios que no envíes conservan su valor, igual que `docNumber` y `meta`, que no cambian. Lo que FacturaDirecta rellena al crear (`account`, `dueDate`, `fiscalPosition`, `theme`…) se rellena igual al actualizar si no lo envías. Si no envías `exchangeRate`, se conserva el anterior mientras no cambien ni la moneda ni la fecha; si cambia alguna de las dos, se toma el tipo de cambio de la nueva fecha. Para cambiar el estado del documento, envía el contacto y las líneas inalterados junto con el nuevo `baseState` (y, si aplica, `deliveredDate`/`deliveredTo`, `deliveryReceipt` o `rejectedDate`/`rejectedBy`). **Restricciones:** - Si el albarán está vinculado a una factura ya emitida, las modificaciones pueden producir efectos secundarios. Antes de actualizar, consulta su `related`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@deliveryNote.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c" ``` ## Borrar albarán `DELETE /{companyId}/deliveryNotes/{id}` elimina un albarán. **Restricciones.** No se puede borrar un albarán vinculado a una factura emitida. La API devuelve `409 Conflict` con el detalle del documento que bloquea el borrado. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c" ``` ## Actualizar etiquetas de albarán `PUT /{companyId}/deliveryNotes/{id}/tags` reemplaza el conjunto de etiquetas sin tocar el resto del contenido. ###### Ejemplo de request JSON ```json { "tags": ["q2-2026", "logística"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["q2-2026","logística"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/tags" ``` ## Enviar albarán por correo `PUT /{companyId}/deliveryNotes/{id}/send` envía el albarán por correo electrónico. Los destinatarios, asunto y cuerpo se toman de la plantilla configurada para albaranes; los campos del body permiten sobreescribirlos puntualmente. **Parámetros del body** (todos opcionales): - `to`, `cc`, `bcc` — listas de destinatarios. - `from` — remitente; debe ser una dirección autorizada en la empresa. - `subject` — asunto. - `html` — cuerpo del email en HTML. Si no envías body, se usa enteramente la plantilla del albarán. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"to":["recepcion@acme.es"],"subject":"Albarán 2026/AL/0012"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/send" ``` ## Generar el albarán en PDF `PUT /{companyId}/deliveryNotes/{id}/pdf` genera una URL temporal con el albarán en PDF. **Parámetros del body:** - `mode` (opcional) — `"attachment"` (URL para descargar como adjunto, por defecto), `"inline"` (visualización en línea) o `"print"` (URL HTML que carga el PDF y lo envía a imprimir). La URL devuelta es válida solo durante el periodo indicado en la respuesta. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"mode":"inline"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/pdf" ``` ## Adjuntos Un albarán puede tener varios adjuntos vinculados (PDFs firmados por el cliente, documentación de logística, fotos de la entrega...). Los adjuntos **no se suben en el body del albarán**: primero se crean con `POST /uploads` y después se vinculan con `POST /deliveryNotes/{id}/attachments` enviando los `uploadIds`. ### Listar adjuntos `GET /{companyId}/deliveryNotes/{id}/attachments` devuelve los adjuntos vinculados al albarán. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/attachments" ``` ### Vincular adjuntos `POST /{companyId}/deliveryNotes/{id}/attachments` añade adjuntos al albarán a partir de uploads previos. **Parámetros del body:** - `uploadIds` — array de identificadores `upl_` obtenidos con `POST /uploads`. Mínimo uno. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"uploadIds":["upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b","upl_7a9b0c53-2e4d-4b8a-a3f5-0d2e6f8a4b7c"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/attachments" ``` ### Eliminar un adjunto `DELETE /{companyId}/deliveryNotes/{id}/attachments/{attachmentIndex}` elimina un adjunto del albarán. `attachmentIndex` es la posición en el array (basada en cero). ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/deliveryNotes/den_3a7b9c14-2f5e-4a7b-b0c8-1d2e3f4a5b6c/attachments/0" ``` ## Versiones `GET /{companyId}/deliveryNotes/{id}/versions` lista las versiones de el albarán: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/deliveryNotes/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/deliveryNotes/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` — falta `contact` o `lines` con `quantity` / `unitPrice` inválidos. - `400 ValidationError` — `baseState` con valor no admitido (debe ser `pending`, `delivered`, `closed` o `rejected`). - `409 Conflict` — borrado o modificación incompatible con una factura ya emitida vinculada al albarán. - `409 Conflict` — `series` o `docNumber` duplicado en la empresa. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general de respuestas de error. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/deliveryNotes` | `getDeliveryNotes` | `deliveryNotes:read` | Lista de albaranes | | GET | `/{companyId}/deliveryNotes/{id}` | `getDeliveryNote` | `deliveryNotes:read` | Obtener un albarán | | GET | `/{companyId}/deliveryNotes/{id}/attachments` | `getDeliveryNoteAttachments` | `deliveryNotes:read` | Listar adjuntos de un albarán | | GET | `/{companyId}/deliveryNotes/{id}/versions` | `getDeliveryNoteVersions` | `deliveryNotes:read` | Versiones de el albarán | | GET | `/{companyId}/deliveryNotes/{id}/versions/{versionId}` | `getDeliveryNoteVersion` | `deliveryNotes:read` | Versión de el albarán | | POST | `/{companyId}/deliveryNotes` | `createDeliveryNote` | `deliveryNotes:write` | Crear albarán | | POST | `/{companyId}/deliveryNotes/{id}/attachments` | `addDeliveryNoteAttachments` | `deliveryNotes:write` | Vincular adjuntos a un albarán | | PUT | `/{companyId}/deliveryNotes/{id}` | `updateDeliveryNote` | `deliveryNotes:write` | Actualizar albarán | | PUT | `/{companyId}/deliveryNotes/{id}/pdf` | `createDeliveryNotePDF` | `deliveryNotes:read` | Generar el albarán en formato PDF | | PUT | `/{companyId}/deliveryNotes/{id}/send` | `sendDeliveryNote` | `deliveryNotes:write` | Enviar albarán por correo electrónico | | PUT | `/{companyId}/deliveryNotes/{id}/tags` | `updateDeliveryNoteTags` | `deliveryNotes:write` | Actualizar etiquetas de albarán | | DELETE | `/{companyId}/deliveryNotes/{id}` | `deleteDeliveryNote` | `deliveryNotes:write` | Borrar albarán | | DELETE | `/{companyId}/deliveryNotes/{id}/attachments/{attachmentIndex}` | `deleteDeliveryNoteAttachment` | `deliveryNotes:write` | Eliminar adjunto de un albarán | ## Scopes - **`deliveryNotes:read`** — Lectura de albaranes. - **`deliveryNotes:write`** — Modificación de albaranes. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/delivery-notes.md --- # Pedidos > Un pedido de cliente registra un encargo en firme de un cliente antes de su entrega y facturación. Un pedido de cliente registra un encargo en firme de un cliente antes de su entrega y facturación. Tiene líneas de detalle, totales y condiciones como el resto de documentos de venta, con una diferencia clave: **cada línea tiene su propio estado** (pendiente, pedida a proveedor, lista para entregar, entregada o cancelada), y el estado del pedido se calcula a partir de ellas. Cuando el pedido está listo, lo habitual es convertirlo en albarán o factura. **La conversión no es una operación de esta API**: se hace desde la interfaz. El documento resultante conserva el enlace con el pedido a través del campo `origin` de sus líneas. El pedido tiene su contrapartida en compras: la [orden de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/). Las dos se coordinan **línea a línea** y el detalle de esa coordinación está en [Flujo de pedidos y órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/guias/orders-flow/). > En los ejemplos de esta página los **UUIDs** (`cor_…`, `con_…`, `upl_…`) son > ilustrativos: sustitúyelos por los identificadores reales de tu empresa. Los > **IDs de impuestos** (`S_IVA_21`) son los del catálogo por defecto; recupera > los tuyos con `GET /{companyId}/settings/taxes/sales` y consulta > [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). ## Disponibilidad por plan Los pedidos y las órdenes de compra forman parte del **Módulo Inventario**. Están incluidos en el plan **Diamante** y se pueden contratar como módulo en **Bronce**, **Plata** y **Oro**. En el plan **Gratis** no están disponibles. Sin esa disponibilidad, **las operaciones de escritura** (crear, actualizar, etiquetar y borrar) responden `403 Forbidden` con el código `plan_limit_exceeded` y el mensaje «Los pedidos de cliente y las órdenes de compra no están disponibles en tu plan». Las operaciones de lectura no fallan: devuelven lo que haya (normalmente, una lista vacía). ## Estados ### Estado del pedido (calculado) `content.main.state` es el estado del pedido y es de **solo lectura en la práctica**: FacturaDirecta lo recalcula en cada guardado a partir del estado de las líneas, con esta prioridad: - `pending` — alguna línea está `pending` u `ordered`. - `ready` — sin líneas pendientes y alguna línea `ready`. - `delivered` — sin pendientes ni listas y alguna línea `delivered`. - `canceled` — todas las líneas están canceladas. Si envías `main.state` en un create o un update, el valor se ignora y se sustituye por el calculado. Para cambiar el estado del pedido, cambia el estado de sus líneas. Dos matices del cálculo: - Las **líneas vacías** (sin texto ni importe) no cuentan. Puedes usarlas como separadores sin alterar el estado. - Un pedido **sin líneas con contenido** queda en `pending`, que es el estado neutro mientras se edita. ### Estado de línea `content.main.lines[].state` es escribible y admite estos valores: - `pending` — pendiente: aún no se puede servir. - `ordered` — pedida a proveedor. Este estado lo fija FacturaDirecta cuando la línea queda vinculada a una orden de compra; no lo asignes a mano. - `ready` — lista para entregar. - `delivered` — entregada al cliente. - `canceled` — cancelada. Las líneas nuevas sin `state` se crean como `pending`. En una **línea vinculada a una orden de compra**, los estados `pending`, `ordered` y `ready` los gobierna la orden: si envías uno distinto del que hay guardado, el servidor restaura el suyo sin error. `delivered` y `canceled` sí los decides tú desde el pedido y la orden nunca los pisa. ## Identidad de las líneas Cada línea tiene un `id` **estable** que la identifica dentro del pedido. FacturaDirecta lo genera si no lo envías. Al **actualizar** un pedido las líneas se sustituyen por las del body, con esta regla: una línea que incluya un `id` ya existente **conserva su identidad** (y con ella los vínculos que esa línea tenga con otros documentos); una línea sin `id` se trata como línea nueva. Si actualizas pedidos por API, conserva los `id` que devolvió la API en las líneas que no quieras recrear. Los `id` deben ser únicos dentro del documento: dos líneas con el mismo `id` se rechazan con `400`. ## Estructura del pedido Forma común a los documentos de venta de FacturaDirecta: - `content.type` — siempre `"clientOrder"`. - `content.uuid` — identificador inmutable, prefijo `cor_`. - `content.main` — datos del documento (contacto, fechas, divisa, líneas, totales, plantilla). - `content.attachments` — adjuntos vinculados (ver [Adjuntos](#adjuntos)). - `content.meta` — metadatos internos. Dentro de `content.main` son obligatorios `docNumber` y `lines`. Campos con comportamiento propio: | Campo | Significado | |---|---| | `state` | Estado del pedido. Calculado; se ignora al escribir. | | `owner` | Usuario responsable del pedido. Limita la visibilidad cuando se trabaja con roles personalizados. | | `warehouse` | Almacén del documento a efectos de stock. Si no lo indicas se usa el almacén por defecto de la empresa. | | `customFields` | Valores de campos personalizados del documento. | | `counterpart` | Datos fiscales de la contraparte. | En respuestas, además, en el nivel raíz: `tags`, `creationDate`, `modificationDate` y `related`. **`related` solo transporta los objetos que pidas con el parámetro `related`** (hoy, definiciones de impuestos): no contiene los documentos en los que se haya convertido el pedido. Si creas el pedido con OAuth y no envías `owner`, la API asigna como responsable al usuario autenticado. Si usas una API key, queda sin responsable salvo que envíes `owner` en el body. ### Líneas de detalle `content.main.lines` es un array. Cada línea requiere `text`, `quantity` y `unitPrice`. Además de los campos comunes a los documentos de venta (`discount`/`discountRate`, `tax`, `document`, `account`, `lineTotal`, `origin`), las líneas de pedido añaden: - `id` — identidad estable de la línea (ver [Identidad de las líneas](#identidad-de-las-líneas)). - `state` — estado de la línea (ver [Estado de línea](#estado-de-línea)). - `provider` — ID del contacto proveedor que suministrará esa línea. Se usa para generar órdenes de compra a partir de líneas pendientes. Si la línea referencia un producto (`document`), ese producto debe tener activada la faceta de compra. - `purchaseOrder`, `purchaseOrderLine` — vínculo con la orden de compra que abastece la línea. Los gestiona FacturaDirecta: no se pueden crear ni modificar desde el pedido. Si omites `tax` en una línea, FacturaDirecta resuelve el impuesto por defecto a partir del producto, del contacto y de la posición fiscal del documento. Las líneas **con producto** deben llevar `quantity` mayor que cero: de esa cantidad se derivan el stock comprometido y el aviso de bajo mínimo. **Importes y totales.** Los totales del documento (`total`, `totalBeforeTaxes`, `linesTotal`, `taxes`) se calculan a partir de las líneas. Conviene dejarlos vacíos al crear o actualizar y leerlos de la respuesta. ## Enlace a la aplicación Las respuestas que devuelven un pedido de cliente incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de pedidos](#lista-de-pedidos) - [Crear pedido](#crear-pedido) - [Obtener un pedido](#obtener-un-pedido) - [Actualizar pedido](#actualizar-pedido) - [Borrar pedido](#borrar-pedido) - [Actualizar etiquetas](#actualizar-etiquetas) - [Enviar pedido por correo](#enviar-pedido-por-correo) - [Generar el pedido en PDF](#generar-el-pedido-en-pdf) - [Adjuntos](#adjuntos) ## Lista de pedidos `GET /{companyId}/clientOrders` devuelve los pedidos activos de la empresa, paginados. **Parámetros de consulta específicos:** - **`state`** — estado del pedido: `pending`, `ready`, `delivered` o `canceled`. Repite el parámetro para incluir varios estados. - **`search`** — búsqueda aproximada en el texto del pedido (título, líneas, notas) y en el nombre de su cliente: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - **`minDate`** / **`maxDate`** — fecha del pedido (`YYYY-MM-DD`), inclusive. - **`series`** — serie sin aplicar el formato de año (`##` o `####`). - **`formattedSeries`** — serie ya formateada. - **`minNumber`** / **`maxNumber`** — número secuencial, inclusive. - **`contact`** — cliente del pedido. Admite varios valores. - **`hasContact`** — `false` para obtener los pedidos sin contacto asociado. - **`minTotal`** / **`maxTotal`** — importe total. - **`currency`** — moneda (ISO 4217). - **`country`** — país de la dirección de facturación (ISO 3166-1 alpha-2). - **`emails`** — búsqueda parcial en los correos del documento, insensible a mayúsculas y acentos; todas las palabras deben coincidir. - **`allTheseTags`** — el pedido debe llevar todas las etiquetas indicadas. - **`anyOfTheseTags`** — basta con una de las etiquetas indicadas. - **`hasTags`** — `true` devuelve solo pedidos con al menos una etiqueta y `false`, solo pedidos sin etiquetas. - **`sortBy`** — orden de los resultados. Valores: `date`, `series`, `formattedSeries`, `number`, `total`, `currency`, `country`, `creationDate`, `modificationDate`. Prefija con `-` para orden descendente y repite el parámetro para ordenar por varios criterios. - **`related`** — datos adicionales. Único valor disponible: `taxIds`, que devuelve en `related.objects` las definiciones de los impuestos usados. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders?state=pending&state=ready&sortBy=-date&limit=50" ``` ## Crear pedido `POST /{companyId}/clientOrders` crea un pedido. **Parámetros del body:** - `content` (obligatorio) — el pedido. `content.type` debe ser `"clientOrder"` y `content.main` debe llevar `docNumber` y `lines`. - `tags` (opcional) — etiquetas iniciales del pedido. **Notas:** - `language` (opcional): idioma en que se imprime y se envía el documento, como código ISO 639-1 (`es`, `ca`, `en`...). Si se omite, el del contacto al asignarlo y, si no tiene, el idioma base de la plantilla. Solo tiene efecto si la plantilla tiene ese idioma. - Si no envías `content.uuid`, la API asigna uno con prefijo `cor_`. Si lo envías y ya existe, responde `409 Conflict`. - El `state` del pedido se calcula desde las líneas: lo que envíes en `main.state` se descarta. - Toda referencia a otro documento (`main.contact`, `lines[].document`, `lines[].provider`, `main.paymentMethod`, `main.theme`) debe existir. Si no, la respuesta es `400` indicando los IDs no encontrados. **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON Contenido mínimo (heredado del ejemplo `minimal` del openapi). Las líneas se crean en estado `pending`, así que el pedido queda `pending`: ```json { "content": { "type": "clientOrder", "main": { "docNumber": { "series": "PED" }, "contact": "con_2e3f4a18-9b7c-4d6a-8e1f-5c2d3b4a7e9f", "currency": "EUR", "lines": [ { "quantity": 1, "unitPrice": 100, "tax": ["S_IVA_21"], "text": "Descripción del artículo pedido" } ] } } } ``` Con estado y proveedor por línea (ejemplo `conEstadosDeLinea`). Todas las líneas están `ready`, así que el pedido queda `ready`: ```json { "content": { "type": "clientOrder", "main": { "docNumber": { "series": "PED" }, "contact": "con_2e3f4a18-9b7c-4d6a-8e1f-5c2d3b4a7e9f", "currency": "EUR", "lines": [ { "quantity": 2, "unitPrice": 50, "tax": ["S_IVA_21"], "text": "Artículo en stock listo para entregar", "state": "ready" }, { "quantity": 1, "unitPrice": 75, "tax": ["S_IVA_21"], "text": "Artículo suministrado por proveedor", "state": "ready", "provider": "con_7b1d2c93-4e5f-4a6b-8c7d-9e0f1a2b3c4d" } ] } } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@clientOrder.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders" ``` ## Obtener un pedido `GET /{companyId}/clientOrders/{id}` devuelve un pedido por ID. **Parámetros de consulta específicos:** - **`related`** — `taxIds` para recibir en `related.objects` las definiciones de los impuestos usados en el pedido. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8?related=taxIds" ``` ## Actualizar pedido `PUT /{companyId}/clientOrders/{id}` sustituye el contenido del pedido. Las líneas se reemplazan por las que envíes y los campos opcionales que no envíes se eliminan. Los campos obligatorios que no envíes conservan su valor, igual que `docNumber` y `meta`, que no cambian. Lo que FacturaDirecta rellena al crear (`account`, `dueDate`, `fiscalPosition`, `theme`…) se rellena igual al actualizar si no lo envías. Si no envías `exchangeRate`, se conserva el anterior mientras no cambien ni la moneda ni la fecha; si cambia alguna de las dos, se toma el tipo de cambio de la nueva fecha. **Parámetros del body:** - `content` (obligatorio) — el pedido completo. Si incluye `uuid`, debe coincidir con el de la ruta. - `syncContactData` (opcional) — con `true`, actualiza dirección (`address`, `country`, `zipcode`) y `counterpart` a partir del contacto de `main.contact`. No toca correos, posición fiscal, vencimiento ni cuenta contable. - `tags` y `tagsOperation` (opcionales) — etiquetas y operación a aplicar (`add`, `remove` o `replace`). **Notas:** - Conserva los `id` de las líneas que no quieras recrear (ver [Identidad de las líneas](#identidad-de-las-líneas)). - No puedes eliminar, cambiar de producto, cambiar de cantidad ni cambiar de proveedor una línea vinculada a una orden de compra. Tampoco puedes crear el vínculo desde aquí: se crea desde la orden. - Si la actualización cambia datos visibles del pedido, FacturaDirecta descarta el PDF anterior. La siguiente llamada a `/pdf` o `/send` genera un PDF con el contenido actualizado. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@clientOrder.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8" ``` ## Borrar pedido `DELETE /{companyId}/clientOrders/{id}` borra el pedido. El borrado es recuperable desde la interfaz; el documento queda con `archived: true` y emite el evento `client_order.archived`. **Restricciones:** - No puedes borrar un pedido con líneas vinculadas a órdenes de compra **activas**: la API responde `400` pidiendo que borres antes esas órdenes (o canceles sus líneas) para liberar los vínculos. **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/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8" ``` ## Actualizar etiquetas `PUT /{companyId}/clientOrders/{id}/tags` cambia solo las etiquetas, sin tocar el contenido del pedido. **Parámetros del body** (ambos obligatorios): - `tags` — array de etiquetas. - `tagsOperation` — `add`, `remove` o `replace`. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["urgente","q3-2026"],"tagsOperation":"add"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8/tags" ``` ## Enviar pedido por correo `PUT /{companyId}/clientOrders/{id}/send` envía el pedido por correo electrónico. Los destinatarios, el asunto y el cuerpo se toman de la plantilla del tema; los campos del body permiten sobreescribirlos puntualmente. **Parámetros del body** (todos opcionales): - `to`, `cc`, `bcc` — listas de destinatarios; sustituyen a los valores de `main.emails`, `main.emailsCc` y `main.emailsBcc` del documento. - `from` — remitente; debe ser una dirección dada de alta en la empresa. - `subject` — asunto. - `html` — cuerpo del mensaje en HTML. Debe haber al menos un destinatario. Si no se puede determinar ninguno, o si la empresa no tiene remitentes configurados, la respuesta es `409 Conflict`. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"to":["compras@acme.es"],"subject":"Tu pedido PED-2026-14"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8/send" ``` ## Generar el pedido en PDF `PUT /{companyId}/clientOrders/{id}/pdf` genera una URL temporal con el pedido en PDF. **Parámetros del body:** - `mode` — `"attachment"` (descarga como adjunto), `"inline"` (visualización en línea) o `"print"` (URL HTML que carga el PDF y lo envía a imprimir). Un valor ausente o distinto de esos tres devuelve `400`. La respuesta lleva `url`, `filename` y `availableUntil`; en modo `print` añade `urlPdf`. La URL es válida hasta la fecha de `availableUntil` (tres días). **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"mode":"inline"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8/pdf" ``` ## Adjuntos Un pedido puede tener hasta **10 adjuntos**. Los adjuntos no se suben en el body del pedido: primero se crean con `POST /uploads` y después se vinculan enviando sus `uploadIds`. ### Listar adjuntos `GET /{companyId}/clientOrders/{id}/attachments` devuelve los adjuntos vinculados al pedido. **Parámetros globales aceptados:** `accept-version`. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8/attachments" ``` ### Vincular adjuntos `POST /{companyId}/clientOrders/{id}/attachments` añade adjuntos al pedido a partir de uploads previos. **Parámetros del body:** - `uploadIds` (obligatorio) — array no vacío de identificadores `upl_` obtenidos con `POST /uploads`. Si la suma de adjuntos existentes y nuevos supera 10, la respuesta es `400` con el código `attachments_limit_exceeded`. **Parámetros globales aceptados:** `accept-version`. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"uploadIds":["upl_8a2c1234-1234-4123-8123-123456789012"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8/attachments" ``` ### Eliminar un adjunto `DELETE /{companyId}/clientOrders/{id}/attachments/{attachmentIndex}` elimina un adjunto. `attachmentIndex` es su posición en el array, empezando en cero; un valor no entero o negativo devuelve `400`. **Parámetros globales aceptados:** `accept-version`. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/clientOrders/cor_1a2b3c44-5d6e-4f70-8192-a3b4c5d6e7f8/attachments/0" ``` ## Numeración Los pedidos usan sus propias series de numeración, configurables en los ajustes de la empresa. Si no indicas `docNumber.number` al crear, se asigna el siguiente número de la serie. Las series con `##` o `####` se expanden con el año en `docNumber.formattedSeries`. ## Webhooks Los cambios en pedidos emiten `client_order.created`, `client_order.updated`, `client_order.archived` y `client_order.unarchived`. Ver [Webhooks](https://www.facturadirecta.com/desarrolladores/webhooks/). Ten en cuenta que un pedido puede emitir `client_order.updated` **sin que tú lo hayas tocado**: guardar o borrar una orden de compra vinculada propaga estados a sus líneas. Ver [Flujo de pedidos y órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/guias/orders-flow/). ## Versiones `GET /{companyId}/clientOrders/{id}/versions` lista las versiones de el pedido: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/clientOrders/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/clientOrders/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400` — el documento tiene referencias que no existen (contacto, producto, proveedor, método de pago o plantilla). - `400` — dos líneas con el mismo `id`, o un vínculo con orden de compra incompleto (documento vinculado y línea vinculada deben ir juntos). - `400` — intento de crear o modificar desde el pedido el vínculo con una orden de compra: solo puede crearse desde la propia orden. - `400` — eliminación, cambio de producto, de cantidad o de proveedor en una línea vinculada a una orden de compra. - `400` — línea con producto y `quantity` menor o igual que cero. - `400` — proveedor asignado a una línea cuyo producto no tiene activadas las preferencias de compra. - `400` — borrado de un pedido con líneas vinculadas a órdenes de compra activas. - `400` con código `attachments_limit_exceeded` — se supera el máximo de adjuntos del documento. - `403` — el plan no incluye pedidos ni órdenes de compra (ver [Disponibilidad por plan](#disponibilidad-por-plan)), o faltan scopes. - `404` — el pedido no existe en la empresa indicada. - `409` — `content.uuid` ya existe al crear, o no hay destinatario ni remitente al enviar por correo. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/clientOrders` | `getClientOrders` | `clientOrders:read` | Lista de pedidos | | GET | `/{companyId}/clientOrders/{id}` | `getClientOrder` | `clientOrders:read` | Obtener un pedido | | GET | `/{companyId}/clientOrders/{id}/attachments` | `getClientOrderAttachments` | `clientOrders:read` | Listar adjuntos de un pedido | | GET | `/{companyId}/clientOrders/{id}/versions` | `getClientOrderVersions` | `clientOrders:read` | Versiones de el pedido | | GET | `/{companyId}/clientOrders/{id}/versions/{versionId}` | `getClientOrderVersion` | `clientOrders:read` | Versión de el pedido | | POST | `/{companyId}/clientOrders` | `createClientOrder` | `clientOrders:write` | Crear pedido | | POST | `/{companyId}/clientOrders/{id}/attachments` | `addClientOrderAttachments` | `clientOrders:write` | Vincular adjuntos a un pedido | | PUT | `/{companyId}/clientOrders/{id}` | `updateClientOrder` | `clientOrders:write` | Actualizar pedido | | PUT | `/{companyId}/clientOrders/{id}/pdf` | `createClientOrderPDF` | `clientOrders:read` | Generar el pedido en formato PDF | | PUT | `/{companyId}/clientOrders/{id}/send` | `sendClientOrder` | `clientOrders:write` | Enviar pedido por correo electrónico | | PUT | `/{companyId}/clientOrders/{id}/tags` | `updateClientOrderTags` | `clientOrders:write` | Actualizar etiquetas de pedido | | DELETE | `/{companyId}/clientOrders/{id}` | `deleteClientOrder` | `clientOrders:write` | Borrar pedido | | DELETE | `/{companyId}/clientOrders/{id}/attachments/{attachmentIndex}` | `deleteClientOrderAttachment` | `clientOrders:write` | Eliminar adjunto de un pedido | ## Scopes - **`clientOrders:read`** — Lectura de pedidos. - **`clientOrders:write`** — Modificación de pedidos. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/client-orders.md --- # Facturas de compra y tickets > Una factura de compra representa un gasto: una factura que has recibido de un proveedor, o un ticket de caja (con o sin identificación fiscal del proveedor). Una factura de compra representa un gasto: una factura que has recibido de un proveedor, o un ticket de caja (con o sin identificación fiscal del proveedor). En FacturaDirecta ambos casos se modelan con el mismo recurso (`bill`), distinguidos por un campo `subtype`. **Flujos para crear facturas de compra.** Existen dos caminos: - **Construir el body manualmente** y enviarlo a [`POST /bills`](#crear-factura-de-compra). Es el flujo descrito en esta página. - **Desde la bandeja de entrada**: subes el PDF (o Facturae) a la bandeja, el sistema extrae los datos por escaneo automático, llamas a [`POST /inbox/{id}/proposeBill`](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/#proponer-factura-de-compra) para obtener un prototipo prerellenado, lo revisas y lo envías a `POST /bills` con `fromInbox: { taskId }` (ver [parámetro `fromInbox`](#crear-factura-de-compra) más abajo). Este flujo vincula la factura con su origen en la bandeja y reutiliza el PDF sin re-upload. ## Factura de compra vs ticket Por defecto, `content.main.subtype` no se envía y el documento es una **factura de compra** convencional. Para registrar un ticket de caja, envía `content.main.subtype: "ticket"`. La diferencia entre ambos: - **Factura de compra** — proveedor con identificación fiscal, número de factura del proveedor (`referenceNumber`), tratamiento fiscal estándar. - **Ticket** — gasto registrado típicamente desde un escaneo o entrada manual rápida. Suele venir sin contacto identificado y sin número de factura del proveedor. Ambos se listan, se filtran y se gestionan con los mismos endpoints. El `subtype` solo cambia la categorización a efectos de informes y plantillas. ## Estructura Forma común a los documentos de gasto de FacturaDirecta: - `content.type` — siempre `"bill"`. - `content.uuid` — identificador inmutable. - `content.main` — datos del documento (contacto, fechas, divisa, líneas, totales, modo de cálculo, fiscalidad, pagos a plazos, ...). Puede incluir `warehouse`, el almacén del documento a efectos de stock (si no lo indicas se usa el almacén por defecto de la empresa; ver [Productos](https://www.facturadirecta.com/desarrolladores/api/referencia/products/#control-de-stock)). - `content.attachments` — adjuntos vinculados (ver [Adjuntos](#adjuntos)). - `content.meta` — metadatos internos. En respuestas, además, en el nivel raíz: `tags`, `creationDate`, `modificationDate`, `related`. `content.main.customFields` es un mapa de valores indexado por el ID estable `cfi_` de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las claves deben corresponder a definiciones existentes. También se aceptan definiciones borradas para conservar compras históricas; un ID desconocido o con formato incorrecto produce `400 Bad Request`. ### Líneas de detalle `content.main.lines` es un array. Cada línea requiere `text`, `quantity` y `unitPrice`. Otros campos relevantes: - `discount` o `discountRate` — descuento absoluto o porcentual. - `origin` — ID del documento del que procede la línea (típicamente una [orden de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/)), para que los documentos queden enlazados. - `tax` — array de **identificadores de impuesto** (`TaxId`) que aplican a la línea. Los IDs se obtienen del catálogo de **compras** de la empresa con `GET /{companyId}/settings/taxes/purchases`. Pueden aplicarse varios impuestos a una misma línea, típicamente IVA + retención de IRPF. **Los impuestos autorepercutidos** (intracomunitarios, inversión del sujeto pasivo, importación de servicios) **no se envían aquí**: el sistema los añade automáticamente al agregado del documento a partir de su impuesto base. Ver [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/) para el modelo completo. > En los ejemplos de esta página: > > - Los **UUIDs** (`con_2c4f8e21-…`, `bil_4a9c2e58-…`, `ban_5e7d8a31-…`, > `upl_6f8e9b42-…`) son ilustrativos. Cada empresa tiene los suyos; > sustitúyelos por los identificadores reales que devuelve la API. > - El **ID de impuesto** `P_IVA_21_SV` es el del catálogo por defecto > para IVA 21% en compra de **servicios**. Para bienes corrientes el > ID típico es `P_IVA_21_BC`. El sufijo identifica el régimen > aplicable (servicios, bienes corrientes, bienes de inversión). > Recupera el catálogo real con `GET /{companyId}/settings/taxes/purchases`. - `lineTotal` — total de la línea; la API lo calcula si lo omites. - `account` — cuenta contable de la línea; por defecto se toma de la cuenta de gastos por defecto del proveedor o de la empresa. ### Modos de cálculo de totales: `totalProvided` vs `calcTotal` `content.main.mode` controla cómo se interpretan los totales del documento: - **`calcTotal`** (modo por defecto) — los totales (`total`, `totalBeforeTaxes`, `linesTotal`, `taxes`) se **calculan** a partir de las líneas. Conviene dejarlos vacíos al crear y leerlos de la respuesta. - **`totalProvided`** — los totales del cuerpo se **toman como dados**; no se recalculan a partir de las líneas. Útil cuando importas una factura escaneada que solo declara totales agregados, o cuando hay redondeos del proveedor que quieres respetar. `taxIncludedPrices: true` indica que `unitPrice` ya incluye IVA. Por defecto es `false`. ### Vencimientos múltiples Para dividir el pago de una factura de compra, envía `main.installments` al crearla o actualizarla. Cada elemento requiere: - `date` — fecha del vencimiento en formato `YYYY-MM-DD`. - `amount` — importe previsto para ese vencimiento. - `currency` — moneda del importe en formato ISO 4217. Por ejemplo, una factura de compra de 121 EUR puede dividirse así: ```json { "dueDate": "2026-08-15", "installments": [ { "date": "2026-07-15", "amount": 60.5, "currency": "EUR" }, { "date": "2026-08-15", "amount": 60.5, "currency": "EUR" } ] } ``` Incluye estos campos dentro de `content.main` en `POST /{companyId}/bills` o `PUT /{companyId}/bills/{billId}`. El servidor ajusta el importe del último elemento para que la suma coincida con el total calculado de la factura y establece `dueDate` en la fecha más tardía. Con un método de pago por transferencia SEPA, cada vencimiento pendiente se puede seleccionar como un pago separado al preparar una remesa. Si no necesitas dividir el pago, `dueDate` basta para representar un único vencimiento. ## Estados `state` es un campo calculado a partir de los flags del documento, sus pagos y sus vencimientos: | Estado | Cuándo | |---|---| | `draft` | `main.draft === true`. Provisional; no genera asientos contables. | | `pending` | Definitiva, con saldo pendiente que aún no ha vencido. | | `overdue` | Definitiva, con saldo pendiente vencido según sus vencimientos. | | `paid` | Saldo pendiente cero. | | `overpaid` | Los pagos superan el importe esperado. | | `voided` | `main.voided === true`. Anulada. | Cada factura de compra o ticket tiene un único estado en cada momento: los valores son excluyentes entre sí. En el filtro `state` se pueden indicar varios y se devuelven los documentos cuyo estado sea cualquiera de ellos. Al crear o actualizar, `draft: true` guarda el documento como provisional. Si se omite, se aplica la configuración por defecto de la empresa. Los borradores no aparecen en el diario, por lo que un listado que los incluya puede tener un recuento distinto al de los movimientos contables. Con `voided: true` la factura queda anulada. Solo se puede anular en empresas que soportan el registro de facturas de compra en sistemas externos (TicketBAI, VeriFactu...). En otros casos, usa el borrado. ### Fiscalidad y casos especiales Bills tiene varios campos para tratamiento fiscal específico que esta guía menciona pero no desarrolla, porque cada uno merece su propia guía: - **`ticketbai`** — datos de cumplimiento TicketBAI para empresas del País Vasco. - **`correctedBill`** — ID de la factura de compra que esta factura rectifica. - **`referenceNumber`** — número de factura asignado por el proveedor. No confundir con `docNumber` (numeración interna). - **`transactionDate`** — fecha de la transacción cuando difiere de la fecha del documento. En moneda extranjera, es la fecha del tipo de cambio. - **`depreciable`** y **`depreciationSettings`** — amortización contable para bienes inventariables. - **`installments`** — calendario de pagos a plazos. Ver [Vencimientos múltiples](#vencimientos-múltiples). - **`manualAccounting`** y **`manualTotals`** — overrides contables manuales para casos donde la lógica automática no aplica. - **`nonDeductibleInIRPF`** — gasto no deducible en IRPF. ## Enlace a la aplicación Las respuestas que devuelven una factura de compra o un ticket incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de facturas de compra](#lista-de-facturas-de-compra) - [Crear factura de compra](#crear-factura-de-compra) - [Obtener una factura de compra](#obtener-una-factura-de-compra) - [Actualizar factura de compra](#actualizar-factura-de-compra) - [Borrar factura de compra](#borrar-factura-de-compra) - [Actualizar etiquetas](#actualizar-etiquetas) - [Registrar pagos](#registrar-pagos) - [Adjuntos](#adjuntos) ## Lista de facturas de compra `GET /{companyId}/bills` devuelve las facturas de compra y tickets de la empresa, paginados. **Parámetros de consulta principales:** - `search` — búsqueda aproximada en el texto del documento (título, líneas, notas) y en el nombre de su proveedor: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - `minDate`, `maxDate` — rango por fecha del documento (`YYYY-MM-DD`). - `contact` — ID de contacto. - `hasContact` — `true`/`false` para filtrar con o sin contacto (los tickets suelen ir sin contacto). - `minTotal`, `maxTotal` — rango de importe. - `currency` — código ISO 4217. - `country` — código país ISO 3166-1 Alpha-2. - `state` — estado del documento (`draft`, `pending`, `overdue`, `paid`, `overpaid`, `voided`). Cada documento solo tiene uno; si se indican varios, el filtro incluye cualquiera de ellos. - `draft` — por defecto solo se incluyen documentos definitivos; `only` devuelve solo borradores y `all` mezcla definitivos y borradores. Los borradores no generan asientos contables, por lo que al incluirlos el recuento puede no coincidir con el diario. - `allTheseTags`, `anyOfTheseTags` y `hasTags`. En `hasTags`, `true` devuelve solo documentos con al menos una etiqueta y `false`, solo documentos sin etiquetas. - `sortBy` — admite `date`, `total`, `currency`, `country`, `creationDate` y `modificationDate`. Repite el parámetro para combinar criterios y antepone `-` para orden descendente. - `related` — recursos a expandir. - `totals` — `true` añade a la respuesta las sumas de todos los documentos que cumplen los filtros, no solo de la página. Ver [Totales del conjunto filtrado](#totales-del-conjunto-filtrado). Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) para los parámetros comunes. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills?minDate=2026-01-01&state=pending&limit=50" ``` ### Totales del conjunto filtrado Con `totals=true`, la respuesta añade `totals`: el recuento y las sumas de todos los documentos que cumplen los filtros, con una fila por moneda. Es la forma de responder «¿cuánto me queda por pagar?» sin recorrer las páginas. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills?state=pending&limit=1&totals=true" ``` ```json { "pagination": { "offset": 0, "limit": 1, "total": 2 }, "items": [ { "...": "..." } ], "totals": [ { "currency": "EUR", "count": 2, "total": 726, "totalBeforeTaxes": 600, "taxes": 126, "pending": 726, "companyCurrencyPending": 726 } ] } ``` | Campo | Significado | |---|---| | `currency` | Moneda de los documentos de la fila. | | `count` | Documentos que cumplen los filtros. | | `total`, `totalBeforeTaxes`, `taxes` | Suma del total, de la base imponible y de los impuestos (`total` menos `totalBeforeTaxes`), en la moneda de la fila. | | `pending` | Importe pendiente de pago, en positivo, en la moneda de la fila. Negativo si se ha pagado de más. | | `companyCurrencyPending` | El pendiente en la moneda de la empresa. | Para agrupar esas sumas por proveedor, cuenta de gasto o mes, o para compararlas con otro periodo, usa la [consulta analítica](https://www.facturadirecta.com/desarrolladores/api/referencia/query/). ## Crear factura de compra `POST /{companyId}/bills` crea una factura de compra o ticket. **Parámetros del body:** - `content.type` — siempre `"bill"`. - `content.main` — campos del documento (al menos `contact` o `subtype: "ticket"` y las `lines` con `text`, `quantity`, `unitPrice`). - `tags` (opcional). - `fromInbox` (opcional) — vincula la factura a un item de la [bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/). Estructura: - `taskId` (obligatorio dentro de `fromInbox`) — el `tas_*` del item de la bandeja. - `archive` (opcional, default `true`) — si `true` (por defecto), el item de la bandeja se borra tras crear la factura. El adjunto del inbox se **reutiliza** como adjunto de esta factura (sin re-upload del binario S3) y queda enlazada al inbox vía `content.main.task`. Es el flujo recomendado tras [`POST /inbox/{id}/proposeBill`](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/#proponer-factura-de-compra): el endpoint `propose*` devuelve el `content` propuesto, tú lo ajustas si quieres y lo envías aquí con `fromInbox: { taskId }`. **Notas:** - Si `contact` apunta a un ID existente con faceta de proveedor (`accounts.provider` configurada), sus datos fiscales se toman de él. - Si envías `contact` y no `country`, la factura toma el país de la dirección fiscal del contacto, y también su código postal si no envías `zipcode`. Si envías `country`, se respeta y `zipcode` no se completa. - Para registrar un ticket de caja sin proveedor identificado, omite `contact` y envía `subtype: "ticket"`. - Por defecto el modo es `calcTotal` (los totales se calculan). Para importar un documento con totales fijos del proveedor, usa `mode: "totalProvided"`. - En una moneda distinta de la de la empresa, si no envías `exchangeRate` se toma el tipo de cambio de la fecha de la factura (`transactionDate`, o `date` si no la envías). Si lo envías, se respeta. En la moneda de la empresa es siempre 1. ###### Ejemplo de request JSON ```json { "content": { "type": "bill", "main": { "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "date": "2026-04-15", "referenceNumber": "F-2026-00123", "currency": "EUR", "lines": [ { "text": "Servicios de hosting Q2 2026", "quantity": 1, "unitPrice": 240.00, "tax": ["P_IVA_21_SV"] } ] } }, "tags": ["hosting"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@bill.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills" ``` Crear desde un item de la bandeja de entrada: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{ "content": , "fromInbox": { "taskId": "tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c" } }' \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills" ``` ## Obtener una factura de compra `GET /{companyId}/bills/{id}` devuelve una factura de compra por ID. **Parámetros:** - `related` (opcional) — recursos relacionados a expandir, por ejemplo `payments` o `attachments`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e?related=payments" ``` ## Actualizar factura de compra `PUT /{companyId}/bills/{id}` sustituye el contenido de la factura de compra. Las líneas se reemplazan por las que envíes y los campos opcionales que no envíes se eliminan. Los campos obligatorios que no envíes conservan su valor, igual que `meta`, que no cambia. Lo que FacturaDirecta rellena al crear (`account`, `dueDate`, `fiscalPosition`, `country`…) se rellena igual al actualizar si no lo envías: si cambias de `contact` sin enviar `country`, la factura toma el país del nuevo contacto. Si no envías `exchangeRate`, se conserva el anterior mientras no cambien ni la moneda ni la fecha de la factura (`transactionDate`, o `date` si no la envías); si cambia alguna de las dos, se toma el tipo de cambio de la nueva fecha. **Restricciones:** - Una factura de compra anulada (`voided: true`) no se puede modificar. - Cambios en facturas de empresas con TicketBAI/VeriFactu activo pueden desencadenar la emisión de eventos de cumplimiento. - Si Batuz rechazó el alta y nunca aceptó el documento, puedes corregir `contact`, `referenceNumber`, `date` o `transactionDate` y volver a guardarlo. El servidor genera otro alta. Si Batuz lo aceptó alguna vez, los datos identificativos quedan protegidos; anula el documento y crea otro para cambiarlos. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@bill.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e" ``` ## Borrar factura de compra `DELETE /{companyId}/bills/{id}` elimina una factura de compra. **Restricciones:** - Si la factura tiene pagos asociados, se rechaza con `409 Conflict`. Elimina primero los pagos. - En empresas con TicketBAI/VeriFactu sobre compras, conviene usar `voided: true` en vez de borrado para mantener el rastro fiscal. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e" ``` ## Actualizar etiquetas `PUT /{companyId}/bills/{id}/tags` reemplaza el conjunto de etiquetas sin tocar el resto del contenido. ###### Ejemplo de request JSON ```json { "tags": ["hosting", "q2-2026"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["hosting","q2-2026"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e/tags" ``` ## Registrar pagos `POST /{companyId}/bills/{id}/payments` registra uno o varios pagos contra una factura de compra. El body es un **array** de pagos. **Parámetros de cada pago:** - `bank` (obligatorio) — ID del banco/cuenta de tesorería donde se anota el pago. - `amount` (opcional) — importe del pago en la moneda del documento. Si se omite, salda **todo el pendiente** de la factura. Solo un pago del array puede omitir `amount`, y debe ser el **último** del array. - `date` (opcional) — fecha en la que contabilizar el pago. Por defecto, la fecha actual. - `companyCurrencyAmount` (opcional) — importe a contabilizar en la moneda de la contabilidad. Solo aplica cuando la moneda de la factura difiere de la moneda contable; si no se envía, se calcula con tipo de cambio automático según `date`. - `description` (opcional) — descripción visible en Bancos → Movimientos. Sustituye al título por defecto. - `notes` (opcional) — notas adicionales del pago. La moneda de la cuenta bancaria debe coincidir con la moneda de la empresa o con la de la factura de compra. Si es una tercera moneda, la API devuelve `400` porque la petición no incluye el importe real cargado en esa cuenta. Cuando la cuenta usa la moneda de la empresa, `companyCurrencyAmount` permite registrar el importe exacto que después se compara con el extracto al conciliar. Para registrar el cobro o pago a partir de un movimiento del extracto bancario (también desde una cuenta en una tercera moneda), concilia el movimiento con `document` en [conciliar creando nuevo asiento](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/#conciliar-creando-nuevo-asiento): la API construye el asiento con el importe real del movimiento y la diferencia de cambio. **Respuesta:** la factura completa actualizada (con los pagos registrados visibles en `related.payments` cuando se consulta). ###### Ejemplo de request JSON Pago único que salda toda la factura: ```json [ { "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d" } ] ``` Dos pagos parciales seguidos del saldo final: ```json [ { "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "amount": 80.00, "date": "2026-04-20" }, { "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "amount": 80.00, "date": "2026-05-05" }, { "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d" } ] ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '[{"bank":"ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d"}]' \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e/payments" ``` ## Adjuntos Una factura de compra puede tener varios adjuntos vinculados (típicamente el PDF original del proveedor). Los adjuntos **no se suben en el body de la factura**: primero se crean con `POST /uploads` y después se vinculan con `POST /bills/{id}/attachments` enviando los `uploadIds`. ### Listar adjuntos `GET /{companyId}/bills/{id}/attachments` devuelve los adjuntos vinculados. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e/attachments" ``` ### Vincular adjuntos `POST /{companyId}/bills/{id}/attachments` añade adjuntos a partir de uploads previos. **Parámetros del body:** - `uploadIds` — array de identificadores `upl_`. Mínimo uno. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"uploadIds":["upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b","upl_7a9b0c53-2e4d-4b8a-a3f5-0d2e6f8a4b7c"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e/attachments" ``` ### Eliminar un adjunto `DELETE /{companyId}/bills/{id}/attachments/{attachmentIndex}` elimina un adjunto. `attachmentIndex` es la posición en el array (basada en cero). ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/bills/bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e/attachments/0" ``` ## Versiones `GET /{companyId}/bills/{id}/versions` lista las versiones de la factura de compra o el ticket: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/bills/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/bills/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` — falta `subtype: "ticket"` o `contact` cuando el documento no es un ticket. - `400 ValidationError` — `lines` con `quantity` o `unitPrice` inválidos. - `400 ValidationError` — `mode: "totalProvided"` sin enviar `total`. - `409 Conflict` — borrado de una factura con pagos asociados. - `409 Conflict` — modificación de una factura anulada (`voided: true`). - `409 Conflict` — `voided: true` en una empresa sin registro externo de facturas de compra. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general de respuestas de error. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/bills` | `getBills` | `bills:read` | Lista de facturas de compra o tickets | | GET | `/{companyId}/bills/{id}` | `getBill` | `bills:read` | Obtener una factura de compra o ticket | | GET | `/{companyId}/bills/{id}/attachments` | `getBillAttachments` | `bills:read` | Listar adjuntos de una factura de compra o ticket | | GET | `/{companyId}/bills/{id}/versions` | `getBillVersions` | `bills:read` | Versiones de la factura de compra o el ticket | | GET | `/{companyId}/bills/{id}/versions/{versionId}` | `getBillVersion` | `bills:read` | Versión de la factura de compra o el ticket | | POST | `/{companyId}/bills` | `createBill` | `bills:write` | Crear factura de compra o ticket | | POST | `/{companyId}/bills/{id}/attachments` | `addBillAttachments` | `bills:write` | Vincular adjuntos a una factura de compra o ticket | | POST | `/{companyId}/bills/{id}/payments` | `createBillPayments` | `bills:write` | Crear pagos para una factura de compra o ticket | | PUT | `/{companyId}/bills/{id}` | `updateBill` | `bills:write` | Actualizar factura de compra o ticket | | PUT | `/{companyId}/bills/{id}/tags` | `updateBillTags` | `bills:write` | Actualizar etiquetas de factura de compra o ticket | | DELETE | `/{companyId}/bills/{id}` | `deleteBill` | `bills:write` | Borrar factura de compra o ticket | | DELETE | `/{companyId}/bills/{id}/attachments/{attachmentIndex}` | `deleteBillAttachment` | `bills:write` | Eliminar adjunto de una factura de compra o ticket | ## Scopes - **`bills:read`** — Lectura de facturas de compra y tickets. - **`bills:write`** — Modificación de facturas de compra y tickets. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/bills.md --- # Órdenes de compra > Una orden de compra registra un encargo en firme a un proveedor antes de la recepción de la mercancía y su facturación. Una orden de compra registra un encargo en firme a un proveedor antes de la recepción de la mercancía y su facturación. Tiene líneas de detalle, totales y condiciones como el resto de documentos de compra, con una diferencia clave: **cada línea tiene su propio estado** (pendiente de recibir, recibida o cancelada), y el estado de la orden se calcula a partir de ellas. Cuando la mercancía se recibe, lo habitual es convertir la orden en factura de compra. **La conversión no es una operación de esta API**: se hace desde la interfaz. El gasto resultante conserva el enlace con la orden a través del campo `origin` de sus líneas. La orden de compra es además el lado que **manda** en la coordinación con el [pedido de cliente](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/): los vínculos línea a línea se crean desde aquí y los estados se propagan desde aquí. Ver [Flujo de pedidos y órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/guias/orders-flow/). > En los ejemplos de esta página los **UUIDs** (`pur_…`, `con_…`, `upl_…`) son > ilustrativos: sustitúyelos por los identificadores reales de tu empresa. Los > **IDs de impuestos** (`P_IVA_21_SV`) son los del catálogo por defecto; > recupera los tuyos con `GET /{companyId}/settings/taxes/purchases` y consulta > [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). ## Disponibilidad por plan Las órdenes de compra y los pedidos forman parte del **Módulo Inventario**. Están incluidos en el plan **Diamante** y se pueden contratar como módulo en **Bronce**, **Plata** y **Oro**. En el plan **Gratis** no están disponibles. Sin esa disponibilidad, **las operaciones de escritura** (crear, actualizar, etiquetar y borrar) responden `403 Forbidden` con el código `plan_limit_exceeded` y el mensaje «Los pedidos de cliente y las órdenes de compra no están disponibles en tu plan». Las operaciones de lectura no fallan: devuelven lo que haya. ## Estados ### Estado de la orden (calculado) `content.main.state` es el estado de la orden y es de **solo lectura en la práctica**: FacturaDirecta lo recalcula en cada guardado a partir del estado de las líneas, con esta prioridad: - `pending` — alguna línea está pendiente de recibir. - `received` — sin líneas pendientes y alguna línea recibida. - `canceled` — todas las líneas están canceladas. Si envías `main.state` en un create o un update, el valor se ignora y se sustituye por el calculado. Para cambiar el estado de la orden, cambia el estado de sus líneas. Como en los pedidos, las **líneas vacías** (sin texto ni importe) no cuentan, y una orden sin líneas con contenido queda en `pending`. ### Estado de línea `content.main.lines[].state` es escribible y admite estos valores: - `pending` — pendiente de recibir. - `received` — recibida. - `canceled` — cancelada. Las líneas nuevas sin `state` se crean como `pending`. Cambiar el estado de una línea vinculada a un pedido de cliente **modifica también el pedido**: ver [Flujo de pedidos y órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/guias/orders-flow/). ## Identidad de las líneas Cada línea tiene un `id` **estable** que la identifica dentro de la orden. FacturaDirecta lo genera si no lo envías. Al **actualizar** una orden las líneas se sustituyen por las del body, con esta regla: una línea que incluya un `id` ya existente **conserva su identidad** (y con ella los vínculos que esa línea tenga con otros documentos); una línea sin `id` se trata como línea nueva. Si actualizas órdenes por API, conserva los `id` que devolvió la API en las líneas que no quieras recrear. Los `id` deben ser únicos dentro del documento: dos líneas con el mismo `id` se rechazan con `400`. ## Estructura de la orden de compra - `content.type` — siempre `"purchaseOrder"`. - `content.uuid` — identificador inmutable, prefijo `pur_`. - `content.main` — datos del documento (proveedor, fechas, divisa, líneas, totales, plantilla). - `content.attachments` — adjuntos vinculados (ver [Adjuntos](#adjuntos)). - `content.meta` — metadatos internos. Dentro de `content.main` son obligatorios `docNumber` y `lines`. Campos con comportamiento propio: | Campo | Significado | |---|---| | `contact` | El **proveedor**. Los impuestos y las cuentas contables se resuelven con el catálogo de compras. | | `language` | Idioma en que se imprime y se envía la orden, como código ISO 639-1 (`es`, `ca`, `en`...). Si no lo indicas, el del proveedor y, si no tiene, el idioma base de la plantilla. Solo tiene efecto si la plantilla tiene ese idioma. | | `state` | Estado de la orden. Calculado; se ignora al escribir. | | `dueDate` | Fecha de recepción prevista de la mercancía. | | `warehouse` | Almacén del documento a efectos de stock. Si no lo indicas se usa el almacén por defecto de la empresa. | | `customFields` | Valores de campos personalizados del documento. | | `counterpart` | Datos fiscales de la contraparte. | A diferencia del pedido de cliente, la orden de compra **no tiene `owner`**. En respuestas, además, en el nivel raíz: `tags`, `creationDate`, `modificationDate` y `related`. **`related` solo transporta los objetos que pidas con el parámetro `related`** (hoy, definiciones de impuestos): no contiene los documentos en los que se haya convertido la orden. ### Líneas de detalle `content.main.lines` es un array. Cada línea requiere `text`, `quantity` y `unitPrice`. Además de los campos comunes a los documentos de compra (`discount`/`discountRate`, `tax`, `document`, `account`, `lineTotal`), las líneas de la orden añaden: - `id` — identidad estable de la línea (ver [Identidad de las líneas](#identidad-de-las-líneas)). - `state` — estado de la línea (ver [Estado de línea](#estado-de-línea)). - `client` — ID del contacto cliente para el que se compra esa línea (trazabilidad de compras bajo pedido de cliente). - `clientOrder`, `clientOrderLine` — vínculo con el pedido de cliente y con la línea concreta que origina esta línea. **Este es el lado desde el que se crea el vínculo**: al guardar la orden, FacturaDirecta completa el vínculo recíproco en el pedido y propaga el estado. Para que el vínculo se acepte, el proveedor de la línea de pedido debe coincidir con el proveedor de la orden, y el producto y la cantidad deben ser los mismos en las dos líneas. Si omites `tax` en una línea, FacturaDirecta resuelve el impuesto por defecto a partir del producto, del proveedor y de la posición fiscal del documento. Las líneas **con producto** deben llevar `quantity` mayor que cero, y ese producto debe tener activada la faceta de compra (`purchases`). **Importes y totales.** Los totales del documento (`total`, `totalBeforeTaxes`, `linesTotal`, `taxes`) se calculan a partir de las líneas. Conviene dejarlos vacíos al crear o actualizar y leerlos de la respuesta. ## Enlace a la aplicación Las respuestas que devuelven una orden de compra incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de órdenes de compra](#lista-de-órdenes-de-compra) - [Crear orden de compra](#crear-orden-de-compra) - [Obtener una orden de compra](#obtener-una-orden-de-compra) - [Actualizar orden de compra](#actualizar-orden-de-compra) - [Borrar orden de compra](#borrar-orden-de-compra) - [Actualizar etiquetas](#actualizar-etiquetas) - [Enviar la orden por correo](#enviar-la-orden-por-correo) - [Generar la orden en PDF](#generar-la-orden-en-pdf) - [Adjuntos](#adjuntos) ## Lista de órdenes de compra `GET /{companyId}/purchaseOrders` devuelve las órdenes activas de la empresa, paginadas. **Parámetros de consulta específicos:** - **`state`** — estado de la orden: `pending`, `received` o `canceled`. Repite el parámetro para incluir varios estados. - **`search`** — búsqueda aproximada en el texto de la orden (título, líneas, notas) y en el nombre de su proveedor: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - **`minDate`** / **`maxDate`** — fecha de la orden (`YYYY-MM-DD`), inclusive. - **`series`** — serie sin aplicar el formato de año (`##` o `####`). - **`formattedSeries`** — serie ya formateada. - **`minNumber`** / **`maxNumber`** — número secuencial, inclusive. - **`contact`** — proveedor de la orden. Admite varios valores. - **`hasContact`** — `false` para obtener las órdenes sin contacto asociado. - **`minTotal`** / **`maxTotal`** — importe total. - **`currency`** — moneda (ISO 4217). - **`country`** — país de la dirección fiscal del proveedor (ISO 3166-1 alpha-2). - **`emails`** — búsqueda parcial en los correos del documento, insensible a mayúsculas y acentos; todas las palabras deben coincidir. - **`allTheseTags`** — la orden debe llevar todas las etiquetas indicadas. - **`anyOfTheseTags`** — basta con una de las etiquetas indicadas. - **`hasTags`** — `true` devuelve solo órdenes con al menos una etiqueta y `false`, solo órdenes sin etiquetas. - **`sortBy`** — orden de los resultados. Valores: `date`, `series`, `formattedSeries`, `number`, `total`, `currency`, `country`, `creationDate`, `modificationDate`. Prefija con `-` para orden descendente y repite el parámetro para ordenar por varios criterios. - **`related`** — datos adicionales. Único valor disponible: `taxIds`, que devuelve en `related.objects` las definiciones de los impuestos usados. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders?state=pending&sortBy=-date&limit=50" ``` ## Crear orden de compra `POST /{companyId}/purchaseOrders` crea una orden de compra. **Parámetros del body:** - `content` (obligatorio) — la orden. `content.type` debe ser `"purchaseOrder"` y `content.main` debe llevar `docNumber` y `lines`. - `tags` (opcional) — etiquetas iniciales de la orden. **Notas:** - Si no envías `content.uuid`, la API asigna uno con prefijo `pur_`. Si lo envías y ya existe, responde `409 Conflict`. - El `state` de la orden se calcula desde las líneas: lo que envíes en `main.state` se descarta. - Para abastecer líneas de pedido, indica `clientOrder` y `clientOrderLine` en cada línea. Al guardar, esas líneas de pedido pasan a `ordered`. **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON Contenido mínimo (ejemplo `minimal` del openapi). Las líneas se crean pendientes de recibir, así que la orden queda `pending`: ```json { "content": { "type": "purchaseOrder", "main": { "docNumber": { "series": "OC" }, "contact": "con_7b1d2c93-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "currency": "EUR", "lines": [ { "quantity": 1, "unitPrice": 100, "tax": ["P_IVA_21_SV"], "text": "Descripción del artículo a comprar" } ] } } } ``` Con estado por línea (ejemplo `conEstadosDeLinea`). Todas las líneas están recibidas, así que la orden queda `received`: ```json { "content": { "type": "purchaseOrder", "main": { "docNumber": { "series": "OC" }, "contact": "con_7b1d2c93-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "currency": "EUR", "lines": [ { "quantity": 2, "unitPrice": 50, "tax": ["P_IVA_21_SV"], "text": "Artículo ya recibido del proveedor", "state": "received" }, { "quantity": 1, "unitPrice": 75, "tax": ["P_IVA_21_SV"], "text": "Otro artículo ya recibido", "state": "received" } ] } } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@purchaseOrder.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders" ``` ## Obtener una orden de compra `GET /{companyId}/purchaseOrders/{id}` devuelve una orden por ID. **Parámetros de consulta específicos:** - **`related`** — `taxIds` para recibir en `related.objects` las definiciones de los impuestos usados en la orden. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10?related=taxIds" ``` ## Actualizar orden de compra `PUT /{companyId}/purchaseOrders/{id}` sustituye el contenido de la orden. Las líneas se reemplazan por las que envíes y los campos opcionales que no envíes se eliminan. Los campos obligatorios que no envíes conservan su valor, igual que `docNumber` y `meta`, que no cambian. Lo que FacturaDirecta rellena al crear (`account`, `dueDate`, `fiscalPosition`, `theme`…) se rellena igual al actualizar si no lo envías. Si no envías `exchangeRate`, se conserva el anterior mientras no cambien ni la moneda ni la fecha; si cambia alguna de las dos, se toma el tipo de cambio de la nueva fecha. **Parámetros del body:** - `content` (obligatorio) — la orden completa. Si incluye `uuid`, debe coincidir con el de la ruta. - `syncContactData` (opcional) — con `true`, actualiza dirección (`address`, `country`, `zipcode`) y `counterpart` a partir del contacto de `main.contact`. No toca correos, posición fiscal, vencimiento ni cuenta contable. - `tags` y `tagsOperation` (opcionales) — etiquetas y operación a aplicar (`add`, `remove` o `replace`). **Notas:** - Conserva los `id` de las líneas que no quieras recrear. - Cada guardado vuelve a propagar los estados a los pedidos vinculados, que se guardan también y emiten su propio `client_order.updated`. - Si la actualización cambia datos visibles de la orden, FacturaDirecta descarta el PDF anterior. La siguiente llamada a `/pdf` o `/send` genera un PDF con el contenido actualizado. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@purchaseOrder.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10" ``` ## Borrar orden de compra `DELETE /{companyId}/purchaseOrders/{id}` borra la orden. El borrado es recuperable desde la interfaz; el documento queda con `archived: true` y emite el evento `purchase_order.archived`. Al borrarla, las líneas de pedido que abastecía vuelven a `pending` y quedan desvinculadas. Los vínculos se conservan del lado de la orden para poder rehacerlos si se recupera. **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/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10" ``` ## Actualizar etiquetas `PUT /{companyId}/purchaseOrders/{id}/tags` cambia solo las etiquetas, sin tocar el contenido de la orden. **Parámetros del body** (ambos obligatorios): - `tags` — array de etiquetas. - `tagsOperation` — `add`, `remove` o `replace`. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["reposicion"],"tagsOperation":"add"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10/tags" ``` ## Enviar la orden por correo `PUT /{companyId}/purchaseOrders/{id}/send` envía la orden al proveedor por correo electrónico. Los destinatarios, el asunto y el cuerpo se toman de la plantilla del tema; los campos del body permiten sobreescribirlos. **Parámetros del body** (todos opcionales): - `to`, `cc`, `bcc` — listas de destinatarios; sustituyen a los valores de `main.emails`, `main.emailsCc` y `main.emailsBcc` del documento. - `from` — remitente; debe ser una dirección dada de alta en la empresa. - `subject` — asunto. - `html` — cuerpo del mensaje en HTML. Debe haber al menos un destinatario. Si no se puede determinar ninguno, o si la empresa no tiene remitentes configurados, la respuesta es `409 Conflict`. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"to":["pedidos@proveedor.es"],"subject":"Orden de compra OC-2026-08"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10/send" ``` ## Generar la orden en PDF `PUT /{companyId}/purchaseOrders/{id}/pdf` genera una URL temporal con la orden en PDF. **Parámetros del body:** - `mode` — `"attachment"` (descarga como adjunto), `"inline"` (visualización en línea) o `"print"` (URL HTML que carga el PDF y lo envía a imprimir). Un valor ausente o distinto de esos tres devuelve `400`. La respuesta lleva `url`, `filename` y `availableUntil`; en modo `print` añade `urlPdf`. La URL es válida hasta la fecha de `availableUntil` (tres días). **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"mode":"inline"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10/pdf" ``` ## Adjuntos Una orden de compra puede tener hasta **10 adjuntos**. Los adjuntos no se suben en el body de la orden: primero se crean con `POST /uploads` y después se vinculan enviando sus `uploadIds`. ### Listar adjuntos `GET /{companyId}/purchaseOrders/{id}/attachments` devuelve los adjuntos vinculados a la orden. **Parámetros globales aceptados:** `accept-version`. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10/attachments" ``` ### Vincular adjuntos `POST /{companyId}/purchaseOrders/{id}/attachments` añade adjuntos a la orden a partir de uploads previos. **Parámetros del body:** - `uploadIds` (obligatorio) — array no vacío de identificadores `upl_` obtenidos con `POST /uploads`. Si la suma de adjuntos existentes y nuevos supera 10, la respuesta es `400` con el código `attachments_limit_exceeded`. **Parámetros globales aceptados:** `accept-version`. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"uploadIds":["upl_8a2c1234-1234-4123-8123-123456789012"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10/attachments" ``` ### Eliminar un adjunto `DELETE /{companyId}/purchaseOrders/{id}/attachments/{attachmentIndex}` elimina un adjunto. `attachmentIndex` es su posición en el array, empezando en cero; un valor no entero o negativo devuelve `400`. **Parámetros globales aceptados:** `accept-version`. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/purchaseOrders/pur_9f8e7d66-5c4b-4a39-8271-6e5d4c3b2a10/attachments/0" ``` ## Numeración Las órdenes de compra usan sus propias series de numeración, configurables en los ajustes de la empresa. Si no indicas `docNumber.number` al crear, se asigna el siguiente número de la serie. Las series con `##` o `####` se expanden con el año en `docNumber.formattedSeries`. ## Concurrencia Los guardados y borrados de órdenes de compra de una misma empresa **se serializan**: cada uno puede tocar los pedidos que abastece, y dos a la vez decidirían la propagación sobre una foto obsoleta. Si ya hay 20 escrituras de órdenes en curso en la misma empresa, la siguiente responde `429 Too Many Requests`. Si cargas órdenes en lote, hazlo en serie o con poca concurrencia y reintenta ante un `429`. ## Webhooks Los cambios en órdenes de compra emiten `purchase_order.created`, `purchase_order.updated`, `purchase_order.archived` y `purchase_order.unarchived`. Ver [Webhooks](https://www.facturadirecta.com/desarrolladores/webhooks/). Cada guardado o borrado que afecte a pedidos vinculados emite además un `client_order.updated` por cada pedido afectado. ## Versiones `GET /{companyId}/purchaseOrders/{id}/versions` lista las versiones de la orden de compra: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/purchaseOrders/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/purchaseOrders/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400` — el documento tiene referencias que no existen (proveedor, producto, método de pago o plantilla). - `400` — dos líneas con el mismo `id`, vínculo con pedido incompleto (documento vinculado y línea vinculada deben ir juntos) o dos líneas apuntando a la misma línea de pedido (el vínculo es 1:1). - `400` — la línea de pedido a la que apuntas no existe, ya está vinculada a otra orden de compra, tiene otro proveedor, otro producto u otra cantidad. Si necesitas comprar unidades adicionales, añade una línea de reposición independiente en vez de cambiar la cantidad de la línea vinculada. - `400` — línea con producto y `quantity` menor o igual que cero. - `400` — producto sin las preferencias de compra activadas. - `400` con código `attachments_limit_exceeded` — se supera el máximo de adjuntos del documento. - `403` — el plan no incluye pedidos ni órdenes de compra (ver [Disponibilidad por plan](#disponibilidad-por-plan)), o faltan scopes. - `404` — la orden no existe en la empresa indicada. - `409` — `content.uuid` ya existe al crear, o no hay destinatario ni remitente al enviar por correo. - `429` — ya hay 20 escrituras de órdenes en curso en la misma empresa (ver [Concurrencia](#concurrencia)). Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/purchaseOrders` | `getPurchaseOrders` | `purchaseOrders:read` | Lista de órdenes de compra | | GET | `/{companyId}/purchaseOrders/{id}` | `getPurchaseOrder` | `purchaseOrders:read` | Obtener una orden de compra | | GET | `/{companyId}/purchaseOrders/{id}/attachments` | `getPurchaseOrderAttachments` | `purchaseOrders:read` | Listar adjuntos de una orden de compra | | GET | `/{companyId}/purchaseOrders/{id}/versions` | `getPurchaseOrderVersions` | `purchaseOrders:read` | Versiones de la orden de compra | | GET | `/{companyId}/purchaseOrders/{id}/versions/{versionId}` | `getPurchaseOrderVersion` | `purchaseOrders:read` | Versión de la orden de compra | | POST | `/{companyId}/purchaseOrders` | `createPurchaseOrder` | `purchaseOrders:write` | Crear orden de compra | | POST | `/{companyId}/purchaseOrders/{id}/attachments` | `addPurchaseOrderAttachments` | `purchaseOrders:write` | Vincular adjuntos a una orden de compra | | PUT | `/{companyId}/purchaseOrders/{id}` | `updatePurchaseOrder` | `purchaseOrders:write` | Actualizar orden de compra | | PUT | `/{companyId}/purchaseOrders/{id}/pdf` | `createPurchaseOrderPDF` | `purchaseOrders:read` | Generar la orden de compra en formato PDF | | PUT | `/{companyId}/purchaseOrders/{id}/send` | `sendPurchaseOrder` | `purchaseOrders:write` | Enviar orden de compra por correo electrónico | | PUT | `/{companyId}/purchaseOrders/{id}/tags` | `updatePurchaseOrderTags` | `purchaseOrders:write` | Actualizar etiquetas de orden de compra | | DELETE | `/{companyId}/purchaseOrders/{id}` | `deletePurchaseOrder` | `purchaseOrders:write` | Borrar orden de compra | | DELETE | `/{companyId}/purchaseOrders/{id}/attachments/{attachmentIndex}` | `deletePurchaseOrderAttachment` | `purchaseOrders:write` | Eliminar adjunto de una orden de compra | ## Scopes - **`purchaseOrders:read`** — Lectura de órdenes de compra. - **`purchaseOrders:write`** — Modificación de órdenes de compra. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/purchase-orders.md --- # Bandeja de entrada > 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… 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_`. 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_`** 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`](https://www.facturadirecta.com/desarrolladores/api/referencia/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`](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/#ficha-de-la-empresa), `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](https://www.facturadirecta.com/desarrolladores/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](#lista-de-items) - [Crear item (subir documento)](#crear-item-subir-documento) - [Obtener un item](#obtener-un-item) - [Borrar un item](#borrar-un-item) - [Proponer factura de compra](#proponer-factura-de-compra) - [Proponer ticket](#proponer-ticket) - [Proponer nómina](#proponer-nómina) ## 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](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). **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_`. 2. Crear el inbox item con este endpoint, pasando el `upl_` en el campo `upload`. **Parámetros del body:** - **`upload`** (obligatorio) — `upl_` 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](#proponer-factura-de-compra) pero construye un prototipo con `subtype: "ticket"` (ver [Factura de compra vs ticket](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/#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](https://www.facturadirecta.com/desarrolladores/api/guias/idempotency/#post-inbox-es-distinto). - **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](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `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](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/inbox` | `getInboxItems` | `inbox:read` | Lista de items en la bandeja de entrada | | GET | `/{companyId}/inbox/{id}` | `getInboxItem` | `inbox:read` | Detalle de un item de la bandeja | | POST | `/{companyId}/inbox` | `createInboxItem` | `inbox:write` | Subir un documento a la bandeja de entrada | | POST | `/{companyId}/inbox/{id}/proposeBill` | `proposeBill` | `inbox:read` | Propuesta de factura de compra desde un item de la bandeja | | POST | `/{companyId}/inbox/{id}/proposePayroll` | `proposePayroll` | `inbox:read` | Propuesta de nómina desde un item de la bandeja | | POST | `/{companyId}/inbox/{id}/proposeTicket` | `proposeTicket` | `inbox:read` | Propuesta de ticket de compra desde un item de la bandeja | | DELETE | `/{companyId}/inbox/{id}` | `deleteInboxItem` | `inbox:write` | Archivar 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. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/inbox.md --- # Uploads (subida de archivos) > El recurso uploads permite subir archivos binarios a FacturaDirecta para luego vincularlos como adjuntos a documentos (facturas, gastos, presupuestos, albaranes, nóminas, facturas recurrentes) o… El recurso `uploads` permite **subir archivos binarios a FacturaDirecta** para luego vincularlos como adjuntos a documentos (facturas, gastos, presupuestos, albaranes, nóminas, facturas recurrentes) o como contenido de un [item de la bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/). Un upload es un recurso **temporal**: tiene TTL de 24 horas. Si no se vincula a ningún documento en ese plazo, el sistema lo purga automáticamente. > En los ejemplos, `upl_…` son ilustrativos. El ID real se devuelve al > crear cada upload. ## Dos modos de subida `POST /uploads` admite dos modos según el `Content-Type` del request: | Modo | Content-Type | Tamaño máximo | Cuándo usar | |---|---|---|---| | **Multipart** | `multipart/form-data` | **10 MB** | Por defecto. Subes el binario en una sola petición y recibes un upload `committed` listo para vincular. Es el modo recomendado para clientes y agentes. | | **Presigned** | `application/json` | **25 MB** | Cuando el archivo es grande o quieres evitar pasar el binario por la API. Recibes una URL pre-firmada de S3 y subes el binario directo allí; luego sellas el upload con `POST /uploads/{uploadId}/commit`. | Ambos modos requieren el scope **`files:write`**. Si en multipart se excede el límite, la API devuelve `413` con `code: "upload_too_large_for_multipart"` y un `hint` apuntando al modo presigned. ## Detección automática de Facturae Al sellar el upload (en multipart sucede automáticamente; en presigned ocurre al llamar al endpoint `commit`), el sistema **detecta si el archivo es un Facturae** válido. Si lo es, el campo `content.contentSubtype` del upload tiene valor `"facturae"`. Cuando se vincule a una factura de compra vía la bandeja de entrada, el sistema usa un extractor nativo en vez de LLM (exacto y rápido). ## Operaciones - [Crear upload (subida)](#crear-upload-subida) - [Sellar upload presigned (commit)](#sellar-upload-presigned-commit) ## Crear upload (subida) `POST /{companyId}/uploads` sube un archivo. Selecciona el modo según el `Content-Type` del request. **Parámetros globales aceptados:** `accept-version`. ### Modo multipart (recomendado, hasta 10 MB) Envía el binario en el campo `file` de un `multipart/form-data`. **Respuesta:** `{ content: PublicApiUpload }` con el upload ya en estado `committed`, listo para vincular. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -F "file=@./factura.pdf" \ "https://app.facturadirecta.com/api/$COMPANY_ID/uploads" ``` Respuesta típica: ```json { "content": { "id": "upl_4a8c2e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f", "name": "factura.pdf", "size": 245678, "contentType": "application/pdf", "contentSubtype": null, "expiresAt": "2026-05-14T10:22:31.000Z" } } ``` ### Modo presigned (hasta 25 MB) Para archivos grandes o cuando prefieres no pasar el binario por la API. **Parámetros del body** (`application/json`): - **`filename`** (obligatorio) — nombre del archivo (incluyendo extensión). - **`contentType`** (opcional) — MIME del archivo. Si no se indica, se usa `application/octet-stream`. **Respuesta:** `{ content: PublicApiPresignedUpload }` con: - `id` — el `upl_` del upload (aún no `committed`). - `signedRequest` — URL pre-firmada de S3 para hacer `PUT` con el binario. - `expiresAt` — fecha límite para completar PUT + commit. Tras subir a S3, sella el upload con [`POST /uploads/{uploadId}/commit`](#sellar-upload-presigned-commit). ###### Ejemplo de request JSON ```json { "filename": "factura.pdf", "contentType": "application/pdf" } ``` ###### Copy as cURL (presigned, flujo completo) ```shell # 1. Solicitar URL presigned curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"filename":"factura.pdf","contentType":"application/pdf"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/uploads" # Respuesta: { content: { id: "upl_...", signedRequest: "https://...s3...", ... } } # 2. PUT del binario a la URL pre-firmada curl -s -X PUT \ -H "Content-Type: application/pdf" \ --data-binary @./factura.pdf \ "" # 3. Sellar el upload (commit) curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/uploads/upl_4a8c2e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/commit" ``` ## Sellar upload presigned (commit) `POST /{companyId}/uploads/{uploadId}/commit` cierra un upload creado en modo presigned. El endpoint: 1. Verifica que el binario está en S3. 2. Comprueba que el tamaño no excede 25 MB. Si lo excede, devuelve `413` con `code: "upload_too_large_for_presigned"` y el upload pasa a estado `expired`. 3. Detecta si el archivo es un Facturae y, en su caso, marca `contentSubtype: "facturae"`. 4. Deja el upload en estado `committed`, listo para vincular a un documento. **Sin body.** Solo el `uploadId` en el path. **Respuesta:** `{ content: PublicApiUpload }` con los datos del upload ya sellado. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/uploads/upl_4a8c2e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/commit" ``` ## Vincular un upload Una vez `committed`, el upload se vincula a un documento o item de la bandeja desde la API del recurso correspondiente. Ejemplos: - **A una factura de compra:** `POST /bills/{id}/attachments` con `{ "uploadIds": ["upl_..."] }`. Ver [Adjuntos en bills](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/#adjuntos). - **A un presupuesto, albarán, nómina, factura recurrente:** misma forma en sus respectivos endpoints `/attachments`. - **A la bandeja de entrada:** `POST /inbox` con `{ "upload": "upl_..." }`. El upload se convierte en un inbox item; el sistema lo escanea para extraer datos. Ver [Crear item](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/#crear-item-subir-documento). Tras vincular, el upload **deja de ser temporal** y persiste como parte del adjunto del documento. ## Recomendaciones - **Usa multipart por defecto.** Es más simple y cubre la mayoría de PDFs de facturas y nóminas (típicamente < 10 MB). - **Cambia a presigned para archivos grandes** o cuando tu pipeline ya hace uploads directos a S3 (evita doble tráfico de red). - **Vincula pronto.** Los uploads tienen TTL de 24 h; un upload sin vincular se purga al pasar ese plazo. - **Para integraciones inbox**, el flujo natural es: `POST /uploads` (multipart) → `POST /inbox` (con el `upl_*` resultante). El sistema escanea automáticamente. - **Detecta `contentSubtype: "facturae"` en la respuesta** para identificar Facturae sin parsear el XML tú mismo. ## Errores comunes - `413 Payload Too Large` (`code: "upload_too_large_for_multipart"`) — archivo > 10 MB en modo multipart. Cambia a modo presigned. - `413 Payload Too Large` (`code: "upload_too_large_for_presigned"`) — archivo > 25 MB tras el commit. El upload pasa a `expired`. Para archivos mayores, no hay forma de subirlos por la API pública. - `400 ValidationError` — `filename` ausente en modo presigned. - `404 Not Found` — `uploadId` no existe o pertenece a otra empresa. - `409 Conflict` — `commit` sobre un upload que ya está `committed` o `expired`. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. ## Referencia exhaustiva Esta página cubre los dos modos y el flujo completo. Para el contrato exhaustivo del schema, consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | POST | `/{companyId}/uploads` | `createUpload` | `files:write` | Subir un archivo | | POST | `/{companyId}/uploads/{uploadId}/commit` | `commitUpload` | `files:write` | Sellar un upload presigned tras subirlo a S3 | ## Scopes - **`files:write`** — Subida de archivos para adjuntar a documentos. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/uploads.md --- # Contactos > Un contacto representa una persona física o empresa con la que mantienes una relación contable: clientes a los que facturas, proveedores de los que recibes gastos y empleados a los que pagas nóminas. Un contacto representa una persona física o empresa con la que mantienes una relación contable: clientes a los que facturas, proveedores de los que recibes gastos y empleados a los que pagas nóminas. En FacturaDirecta un mismo contacto puede actuar simultáneamente como cliente, proveedor y empleado. No son tipos excluyentes: son **facetas** que se activan al asociar al contacto la cuenta contable correspondiente. ## Facetas: cliente, proveedor, empleado Las facetas se controlan a través del objeto `content.main.accounts`: - `accounts.client` → habilita al contacto como cliente (cuenta 430* por defecto). - `accounts.provider` → habilita al contacto como proveedor (cuenta 400/410*). - `accounts.employee` → habilita al contacto como empleado (cuenta 465*). Cada faceta principal tiene su contraparte de anticipos (`clientCredit`, `providerCredit`, `employeeCredit`). Si indicas solo la cuenta principal con el prefijo esperado, FacturaDirecta autocompleta la de anticipos con el valor por defecto de la empresa. **Comportamiento por defecto al crear un contacto.** Si no envías `accounts`, el contacto se crea como cliente con las cuentas contables por defecto. Para crear un proveedor o un empleado debes indicarlo explícitamente. Para listar contactos por faceta, usa los filtros `isClient`, `isProvider` o `isEmployee` en `GET /contacts`. Devuelven los contactos que tienen la cuenta correspondiente asignada. Cuando el contacto actúa como cliente, `content.main.owner` identifica al usuario responsable de esa cartera. Se usa con roles personalizados para limitar la visibilidad a clientes asignados. Si creas el contacto con OAuth y no envías `owner`, la API asigna como responsable al usuario autenticado; con apiKey se deja vacío salvo que lo envíes en el body. ### Permisos por faceta Con una aplicación conectada o un rol personalizado, los permisos de clientes, proveedores y empleados se evalúan por separado: - Los listados y las consultas solo devuelven contactos con al menos una faceta que la credencial pueda leer. - Crear, actualizar, etiquetar o borrar exige permiso de escritura sobre todas las facetas del contacto. - Al actualizar también se comprueban las facetas anteriores. Quitar una cuenta de proveedor, por ejemplo, sigue requiriendo permiso para modificar proveedores. Si falta un permiso, la respuesta es `403`. Una aplicación conectada recibe `code: "connection_permission_insufficient"` y `manageUrl` cuando el usuario puede ampliar la conexión. Si la limitación procede del rol, el código es `user_role_insufficient`. Consulta [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/). ## Campos personalizados `content.main.customFields` es un mapa de valores indexado por el ID estable `cfi_` de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las claves deben corresponder a definiciones existentes. También se aceptan definiciones borradas para conservar contactos históricos; un ID desconocido o con formato incorrecto produce `400 Bad Request`. ## Enlace a la aplicación Las respuestas que devuelven un contacto incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de contactos](#lista-de-contactos) - [Crear contacto](#crear-contacto) - [Obtener un contacto](#obtener-un-contacto) - [Actualizar contacto](#actualizar-contacto) - [Borrar contacto](#borrar-contacto) - [Actualizar etiquetas de contacto](#actualizar-etiquetas-de-contacto) ## Lista de contactos `GET /{companyId}/contacts` devuelve los contactos de la empresa indicada, paginados. **Parámetros de consulta:** - `search` — búsqueda libre por nombre, apellidos o nombre comercial. - `fiscalId` — identificador fiscal exacto (sin separadores). - `vatEU` — identificador IVA intracomunitario. - `externalId` — referencia externa asignada al importar. - `phone` — teléfono exacto. - `country` — código país ISO 3166-1 Alpha-2 (`ES`, `FR`...). - `isClient`, `isProvider`, `isEmployee` — facetas (ver sección superior). - `allTheseTags`, `anyOfTheseTags` — filtros por etiquetas concretas. - `hasTags` — `true` devuelve solo contactos con al menos una etiqueta y `false`, solo contactos sin etiquetas. - `sortBy` — campo de orden. - `related` — recursos relacionados a expandir. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) para los parámetros comunes (`offset`, `limit`, `hasMore`). ###### Ejemplo de respuesta JSON ```json { "pagination": { "offset": 0, "limit": 50, "total": 124 }, "items": [ { "content": { "type": "contact", "uuid": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "main": { "title": "Acme Diseño", "name": "Acme Diseño S.L.", "email": "facturacion@acme.es", "phone": "+34 932 000 000", "address": "Carrer Aribau 123", "zipcode": "08036", "city": "Barcelona", "region": "Barcelona", "country": "ES", "fiscalId": "B12345674", "fiscalIdCountry": "ES", "accounts": { "client": "4300001", "clientCredit": "4380001" }, "currency": "EUR" } }, "tags": ["agencia", "barcelona"], "creationDate": "2025-11-14T10:22:31.000Z", "modificationDate": "2026-04-02T08:15:00.000Z" } ] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/contacts?isClient=true&country=ES&limit=50" ``` ## Crear contacto `POST /{companyId}/contacts` crea un contacto en la empresa indicada. **Parámetros del body:** - `content.type` — siempre `"contact"`. - `content.main` — campos del contacto. - `tags` (opcional) — lista de etiquetas a asociar. **Notas:** - Si omites `accounts`, el contacto se crea como cliente con las cuentas por defecto de la empresa. - `fiscalId` se valida cuando `fiscalIdCountry` es `ES` (o `country` es `ES` si no se indica `fiscalIdCountry`). - `language` (opcional) es el idioma en que se imprimen y se envían los documentos del contacto, como código ISO 639-1 (`es`, `ca`, `en`...). Las facturas, presupuestos y demás documentos nuevos lo toman al asignarles el contacto. Sin valor, cada documento sale en el idioma base de su plantilla. - La respuesta es el contacto creado completo, en el mismo formato que [Obtener un contacto](#obtener-un-contacto). **Límites del plan.** Los planes limitan cuántos **clientes** y cuántos **empleados** puede tener la empresa a la vez: | Plan | Clientes | Empleados | |---|---|---| | Gratis | 10 | 1 | | Bronce | 100 | 2 | | Plata | Sin límite | 5 | | Oro | Sin límite | 20 | | Diamante | Sin límite | Sin límite | Los límites solo bloquean **crear**: los contactos que ya existen siguen funcionando aunque la empresa baje de plan, y borrar uno libera hueco. Al superarlos, la respuesta es `403` con el código `plan_limit_exceeded` (ver [Errores](https://www.facturadirecta.com/desarrolladores/api/guias/errors/)). Cuentan solo los contactos con la faceta correspondiente: un proveedor no consume cupo de cliente ni de empleado. ###### Ejemplo de request JSON ```json { "content": { "type": "contact", "main": { "name": "Acme Diseño S.L.", "email": "facturacion@acme.es", "fiscalId": "B12345674", "address": "Carrer Aribau 123", "zipcode": "08036", "city": "Barcelona", "country": "ES" } }, "tags": ["agencia", "barcelona"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{ "content": { "type": "contact", "main": { "name": "Acme Diseño S.L.", "email": "facturacion@acme.es", "fiscalId": "B12345674", "address": "Carrer Aribau 123", "zipcode": "08036", "city": "Barcelona", "country": "ES" } }, "tags": ["agencia", "barcelona"] }' \ "https://app.facturadirecta.com/api/$COMPANY_ID/contacts" ``` ## Obtener un contacto `GET /{companyId}/contacts/{id}` devuelve un contacto por su ID. **Parámetros:** - `related` (opcional) — recursos relacionados a expandir, por ejemplo `invoices,bills`. ###### Ejemplo de respuesta JSON ```json { "content": { "type": "contact", "uuid": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "main": { "title": "Acme Diseño", "name": "Acme Diseño S.L.", "email": "facturacion@acme.es", "phone": "+34 932 000 000", "address": "Carrer Aribau 123", "zipcode": "08036", "city": "Barcelona", "region": "Barcelona", "country": "ES", "fiscalId": "B12345674", "fiscalIdCountry": "ES", "accounts": { "client": "4300001", "clientCredit": "4380001" }, "currency": "EUR" } }, "tags": ["agencia", "barcelona"], "creationDate": "2025-11-14T10:22:31.000Z", "modificationDate": "2026-04-02T08:15:00.000Z", "related": {} } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/contacts/con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3" ``` ## Actualizar contacto `PUT /{companyId}/contacts/{id}` sustituye **el contenido completo** del contacto. No es un PATCH: lo que no envíes, se borra. **Parámetros del body:** mismo formato que [Crear contacto](#crear-contacto). Para añadir una faceta a un contacto existente (por ejemplo, convertir un cliente en cliente-y-proveedor) reenvía el contacto completo con las cuentas adicionales en `accounts`. **Respuesta:** el contacto actualizado completo, en el mismo formato que [Obtener un contacto](#obtener-un-contacto). ###### Ejemplo de request JSON ```json { "content": { "type": "contact", "main": { "name": "Acme Diseño S.L.", "email": "facturacion@acme.es", "phone": "+34 932 000 000", "fiscalId": "B12345674", "address": "Carrer Aribau 123", "zipcode": "08036", "city": "Barcelona", "country": "ES", "accounts": { "client": "4300001", "clientCredit": "4380001", "provider": "4100001", "providerCredit": "4070001" } } } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{ "content": { "type": "contact", "main": { "name": "Acme Diseño S.L.", "email": "facturacion@acme.es", "phone": "+34 932 000 000", "fiscalId": "B12345674", "address": "Carrer Aribau 123", "zipcode": "08036", "city": "Barcelona", "country": "ES", "accounts": { "client": "4300001", "clientCredit": "4380001", "provider": "4100001", "providerCredit": "4070001" } } } }' \ "https://app.facturadirecta.com/api/$COMPANY_ID/contacts/con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3" ``` ## Borrar contacto `DELETE /{companyId}/contacts/{id}` elimina un contacto de la empresa. **Restricciones.** Si el contacto tiene documentos asociados (facturas, gastos, nóminas), la API rechaza el borrado con `409 Conflict` y un mensaje indicando el motivo. En ese caso, reasigna sus documentos a otro contacto antes de borrarlo. **Respuesta:** `{ "result": true }`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/contacts/con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3" ``` ## Actualizar etiquetas de contacto `PUT /{companyId}/contacts/{id}/tags` reemplaza el conjunto de etiquetas de un contacto sin tocar el resto del contenido. Útil cuando solo quieres reclasificar sin reenviar la estructura completa del contacto. **Parámetros del body:** - `tags` — lista completa de etiquetas (sustituye, no añade). ###### Ejemplo de request JSON ```json { "tags": ["agencia", "premium", "barcelona"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["agencia","premium","barcelona"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/contacts/con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3/tags" ``` ## Identificador fiscal `fiscalId` es obligatorio para emitir facturas a clientes españoles. Se valida formato (NIF, CIF o NIE) y se almacena sin separadores ni espacios. Si no indicas `fiscalIdCountry`, se asume el mismo país que la dirección fiscal del contacto. Para contactos extracomunitarios sin identificador fiscal, deja el campo vacío e informa el nombre y el domicilio completos. Si TicketBAI no está activo, sus facturas se emiten por defecto como facturas completas sin identificación del destinatario (art. 6.1.d del RD 1619/2012), no como simplificadas. Con VeriFactu se registran como F2. Para contactos intracomunitarios, usa el campo `vatEU` (formato con prefijo de país: `ESB12345674`). ## Versiones `GET /{companyId}/contacts/{id}/versions` lista las versiones de el contacto: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/contacts/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/contacts/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400 ValidationError` — `fiscalId` con formato inválido para el país indicado. - `403` con código `plan_limit_exceeded` — se ha alcanzado el máximo de clientes o de empleados del plan. - `403` con código `connection_permission_insufficient` o `user_role_insufficient` — falta permiso sobre alguna faceta del contacto. - `409 Conflict` — borrado de contacto con documentos asociados. - `409 Conflict` — `fiscalId` duplicado en la empresa. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general de respuestas de error. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/contacts` | `getContacts` | `contacts:read` | Lista de contactos | | GET | `/{companyId}/contacts/{id}` | `getContact` | `contacts:read` | Obtener un contacto | | GET | `/{companyId}/contacts/{id}/versions` | `getContactVersions` | `contacts:read` | Versiones de el contacto | | GET | `/{companyId}/contacts/{id}/versions/{versionId}` | `getContactVersion` | `contacts:read` | Versión de el contacto | | POST | `/{companyId}/contacts` | `createContact` | `contacts:write` | Crear contacto | | PUT | `/{companyId}/contacts/{id}` | `updateContact` | `contacts:write` | Actualizar contacto | | PUT | `/{companyId}/contacts/{id}/tags` | `updateContactTags` | `contacts:write` | Actualizar etiquetas de contacto | | DELETE | `/{companyId}/contacts/{id}` | `deleteContact` | `contacts:write` | Borrar contacto | ## Scopes - **`contacts:read`** — Lectura de contactos. - **`contacts:write`** — Modificación de contactos. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/contacts.md --- # Productos > Un producto es un concepto facturable preconfigurado que se reutiliza en líneas de documentos (facturas, presupuestos, albaranes, facturas de compra). Un producto es un **concepto facturable preconfigurado** que se reutiliza en líneas de documentos (facturas, presupuestos, albaranes, facturas de compra). Tiene nombre, precio por defecto, impuestos por defecto y cuentas contables asociadas. Al añadir un producto a una línea, los campos de la línea se preinicializan con los valores del producto. > En los ejemplos de esta página: > > - Los **UUIDs** (`pro_…`) son ilustrativos. Cada empresa tiene los > suyos; sustitúyelos por los identificadores reales que devuelve la API. > - Los **IDs de impuestos** (`S_IVA_21`, `P_IVA_21_BC`) y las **cuentas > contables** (`700000`, `600000`) son los del catálogo por defecto. > Recupera los reales con `GET /{companyId}/settings/taxes/{sales,purchases}`. > Ver [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). ## Facetas: venta, compra, ambas Un producto tiene dos facetas independientes y opcionales: - **`content.main.sales`** — habilita al producto en documentos de venta (facturas, presupuestos, albaranes, recurrentes). - **`content.main.purchases`** — habilita al producto en documentos de compra (facturas de compra y tickets). No son excluyentes: un producto puede tener una, otra, o ambas. Sin ninguna faceta el producto no es seleccionable en líneas (caso raro pero válido para borradores). Cada faceta tiene su propio bloque con dos campos obligatorios y varios opcionales: | Campo | Tipo | Obligatorio | Significado | |---|---|---|---| | `tax` | `TaxId[]` | sí | IDs del catálogo de impuestos correspondiente (ventas o compras). | | `account` | `AccountCode` | sí | Cuenta contable: 700* en ventas, 600* en gastos corrientes, grupo 2 en inmovilizado amortizable. | | `price` | number | no | Precio unitario por defecto. | | `discountRate` | number | no | Descuento porcentual por defecto en **base 1** (`0.10` = 10%). | | `description` | string | no | Texto que aparece en la línea del documento al incorporar el producto. | | `depreciable` | boolean | no (solo en `purchases`) | Marca el producto como **bien amortizable**: el gasto se trata como inmovilizado sujeto a amortización en vez de gasto corriente. | | `provider` | string | no (solo en `purchases`) | ID del contacto proveedor habitual. Se usa como proveedor por defecto de las líneas de pedido que incorporan el producto, para agrupar después las órdenes de compra por proveedor. | ## Estructura - `content.type` — siempre `"product"`. - `content.uuid` — identificador inmutable, prefijo `pro_`. - `content.main`: - `name` (obligatorio) — nombre del producto. - `currency` (obligatorio) — código ISO 4217 (`EUR`, `USD`...). - `title` — calculado automáticamente; no enviar al crear. - `sku` (opcional) — referencia del producto. - `externalId` (opcional) — ID externo del producto. **Debe ser único entre todos los productos** de la empresa. - `sales` — faceta de venta (ver tabla anterior). - `purchases` — faceta de compra (ver tabla anterior). - `stock` — control de existencias del producto (ver [Control de stock](#control-de-stock)). ## Control de stock El control de existencias es **opt-in por producto** y se activa con el sub-objeto `content.main.stock`: | Campo | Tipo | Obligatorio | Significado | |---|---|---|---| | `enabled` | boolean | sí | Con `true`, el producto controla existencias: los documentos configurados mueven su stock y cada movimiento queda registrado. | | `minimum` | number | no | Stock mínimo deseado. Cuando el stock proyectado cae por debajo, el producto se marca como bajo mínimo y entra en la reposición sugerida. | | `replenishLot` | number | no | Cantidad propuesta al reponer. Por defecto se propone la necesaria para volver al mínimo. | Las **magnitudes** de stock (físico, comprometido, previsto) no forman parte del producto: se consultan con [Stock de un producto](#stock-de-un-producto). `main.stock` guarda solo la configuración. Si haces un `PUT` de producto sin incluir `main.stock`, se conserva la configuración que ya tuviera el producto. ### Disponibilidad por plan El control de stock forma parte del **Módulo Inventario**: está incluido en el plan **Diamante** y se puede contratar como módulo en **Bronce**, **Plata** y **Oro**. En el plan **Gratis** no está disponible. Las tres operaciones de stock responden `403 Forbidden` con el código `plan_limit_exceeded` y el mensaje «El control de stock no está disponible en tu plan» si el plan no lo incluye. Además, los planes limitan cuántos productos pueden llevar control de stock a la vez (500 en Bronce y Plata; sin límite en Oro y Diamante). Al superarlo, la API responde `403` con el código `plan_limit_exceeded`. ## Enlace a la aplicación Las respuestas que devuelven un producto incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de productos](#lista-de-productos) - [Crear producto](#crear-producto) - [Obtener un producto](#obtener-un-producto) - [Actualizar producto](#actualizar-producto) - [Borrar producto](#borrar-producto) - [Stock de un producto](#stock-de-un-producto) - [Movimientos de stock de un producto](#movimientos-de-stock-de-un-producto) - [Ajuste manual de stock](#ajuste-manual-de-stock) ## Lista de productos `GET /{companyId}/products` devuelve los productos de la empresa, paginados. **Parámetros de consulta específicos:** - **`search`** — búsqueda aproximada en el título, la referencia y las descripciones de venta y de compra: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - **`title`** — búsqueda por título (calculado a partir del nombre). - **`sku`** — búsqueda por referencia. - **`externalId`** — búsqueda por ID externo. - **`isSales`** — `true`/`false` para filtrar productos que tienen la faceta de venta. - **`isPurchases`** — `true`/`false` para filtrar productos que tienen la faceta de compra. - **`isDepreciable`** — `true`/`false` para filtrar productos marcados como amortizables (`purchases.depreciable: true`). - **`salesDescription`** — búsqueda por descripción de venta. - **`purchasesDescription`** — búsqueda por descripción de compra. - **`sortBy`** — campo de orden. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). **Notas:** - Los productos no tienen `related` ni filtros por etiquetas: son un recurso "hoja" sin relaciones que expandir. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/products?isSales=true&limit=50" ``` ## Crear producto `POST /{companyId}/products` crea un producto. **Parámetros del body:** - `content.type` — siempre `"product"`. - `content.main.name` — obligatorio. - `content.main.currency` — obligatorio, ISO 4217. - `content.main.sales` y/o `content.main.purchases` — al menos una de las dos facetas en uso típico. Cada faceta requiere `tax` y `account`. - `tags` (opcional). **Notas:** - `title` se calcula automáticamente; no es necesario enviarlo. - Si envías `externalId`, debe ser único entre los productos de la empresa. Si choca con uno existente, la API devuelve `409 Conflict`. - La respuesta es el producto creado completo, en el mismo formato que [Obtener un producto](#obtener-un-producto). **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON Producto de solo venta (heredado del ejemplo `sales` del openapi): ```json { "content": { "type": "product", "main": { "sku": "PV001", "name": "Producto 001", "currency": "EUR", "sales": { "price": 125, "description": "Descripción del producto 001 que aparecerá en los documentos cuando se seleccione", "tax": ["S_IVA_21"], "account": "700000" } } } } ``` Producto de venta y compra (heredado del ejemplo `salesAndPurchases`): ```json { "content": { "type": "product", "main": { "sku": "PCV009", "name": "Producto 009", "currency": "EUR", "sales": { "price": 125, "description": "Descripción del producto en los documentos de venta", "tax": ["S_IVA_21"], "account": "700000" }, "purchases": { "price": 75, "description": "Descripción del producto en los documentos de compra", "tax": ["P_IVA_21_BC"], "account": "600000" } } } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@product.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/products" ``` ## Obtener un producto `GET /{companyId}/products/{id}` devuelve un producto por ID. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/products/pro_3c6b2e91-4d7a-4f1b-9e8c-2a5d7f0b1e4c" ``` ## Actualizar producto `PUT /{companyId}/products/{id}` sustituye **el contenido completo** del producto. No es un PATCH. **Notas:** - Cambiar el precio de un producto **no modifica documentos ya emitidos** que usaron ese producto. Solo afecta a las nuevas líneas creadas con el producto a partir de ahora. - Para retirar una faceta, omite el sub-objeto correspondiente en el PUT (no envíes `sales: null`; simplemente no lo incluyas). **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@product.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/products/pro_3c6b2e91-4d7a-4f1b-9e8c-2a5d7f0b1e4c" ``` ## Borrar producto `DELETE /{companyId}/products/{id}` elimina un producto. **Restricciones:** - Si el producto está referenciado por alguna línea de documento (a través del campo `document` de la línea), la API rechaza el borrado con `409 Conflict`. Para retirar un producto que ya se ha usado, quita ambas facetas (`sales` y `purchases`) para que deje de ser seleccionable. **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/products/pro_3c6b2e91-4d7a-4f1b-9e8c-2a5d7f0b1e4c" ``` ## Stock de un producto `GET /{companyId}/products/{id}/stock` devuelve las magnitudes de stock del producto, **sumadas de todos los almacenes**. Requiere que el producto tenga el control de stock activado (`main.stock.enabled: true`). Si no lo tiene, la respuesta es `400` con el mensaje «El producto no tiene el control de stock activado». **Campos de la respuesta** (todos dentro de `content`): | Campo | Significado | |---|---| | `physical` | Existencias físicas: la suma del libro de movimientos. | | `committed` | Comprometido en pedidos de cliente activos. | | `incoming` | Previsto en órdenes de compra pendientes de recibir. | | `available` | Disponible: `physical − committed`. | | `projected` | Proyectado: `available + incoming`. | `committed` e `incoming` salen de las líneas de [pedidos](https://www.facturadirecta.com/desarrolladores/api/referencia/client-orders/) y [órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/purchase-orders/); ver [Flujo de pedidos y órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/guias/orders-flow/). **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/products/pro_3c6b2e91-4d7a-4f1b-9e8c-2a5d7f0b1e4c/stock" ``` Respuesta: ```json { "content": { "physical": 5, "committed": 0, "incoming": 0, "available": 5, "projected": 5 } } ``` ## Movimientos de stock de un producto `GET /{companyId}/products/{id}/stockMovements` devuelve el libro de movimientos del producto, **del más reciente al más antiguo**, con el saldo tras cada movimiento. Requiere el control de stock activado en el producto. **Campos de cada movimiento:** | Campo | Significado | |---|---| | `id` | Identificador del movimiento. | | `date` | Fecha efectiva del movimiento. | | `quantity` | Cantidad con signo: positiva en entradas, negativa en salidas. | | `type` | `initial`, `reception`, `delivery`, `adjustment`, `count` o `transfer`. | | `warehouse` | Identificador del almacén donde se registró. | | `balanceAfter` | Saldo del producto **en ese almacén** tras el movimiento. | | `transferGroup` | Identificador compartido por las dos patas de una transferencia entre almacenes. | | `reason` | Motivo, solo en ajustes manuales (ver [Ajuste manual de stock](#ajuste-manual-de-stock)). | | `sourceDocument` | ID del documento que generó el movimiento, si lo hay. | | `note` | Nota libre del movimiento. | | `creationDate` | Fecha y hora de registro en el sistema. | Como `balanceAfter` es el saldo **del almacén** y no el del producto entero, en empresas con varios almacenes no coincide con el `physical` de [Stock de un producto](#stock-de-un-producto). **Parámetros globales aceptados:** Acepta `offset`, `limit` y el header `accept-version`. La respuesta trae `pagination` e `items`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/products/pro_3c6b2e91-4d7a-4f1b-9e8c-2a5d7f0b1e4c/stockMovements?limit=50" ``` Respuesta: ```json { "pagination": { "offset": 0, "limit": 50, "total": 1 }, "items": [ { "id": "stm_4d9a7c21-3b5e-4f80-9a1c-6d2e8f0b3a5c", "date": "2026-09-12", "quantity": 5, "type": "adjustment", "warehouse": "war_main", "reason": "correccion", "note": "alta inicial por API", "creationDate": "2026-09-12T09:14:22.000Z", "balanceAfter": 5 } ] } ``` ## Ajuste manual de stock `POST /{companyId}/products/{id}/stockAdjustments` registra un ajuste manual del stock físico con un motivo tipado. El ajuste queda como un movimiento más del libro. Requiere el control de stock activado en el producto. **Parámetros del body:** - `quantity` (obligatorio) — cantidad con signo: positiva para dar entrada, negativa para dar salida. **No puede ser cero.** - `reason` (obligatorio) — motivo del ajuste: `merma`, `rotura`, `robo`, `caducidad`, `consumo_propio`, `muestra_regalo` o `correccion`. - `note` (opcional) — nota libre. **Notas:** - El ajuste se registra siempre en el **almacén por defecto**: este endpoint no acepta almacén. Para ajustar otro almacén, usa la interfaz. - Si la empresa tiene configurado bloquear los negativos y el ajuste dejaría ese almacén por debajo de cero, la API responde `400` y **no** registra el movimiento. - La respuesta devuelve el `id` del movimiento creado, la `quantity` aplicada y el `physical` resultante del producto (suma de todos los almacenes). **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON ```json { "quantity": 5, "reason": "correccion", "note": "alta inicial por API" } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"quantity":5,"reason":"correccion","note":"alta inicial por API"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/products/pro_3c6b2e91-4d7a-4f1b-9e8c-2a5d7f0b1e4c/stockAdjustments" ``` Respuesta: ```json { "content": { "id": "stm_4d9a7c21-3b5e-4f80-9a1c-6d2e8f0b3a5c", "quantity": 5, "physical": 5 } } ``` ## Recomendaciones - **Configura ambas facetas si el producto se compra y se vende** (típico en distribución / retail): así la línea se preinicializa con los datos correctos según el documento donde se incorpora. - **Usa `externalId`** si tu producto ya existe en otro sistema (ERP, Shopify, etc.) y necesitas mapearlo de forma idempotente. La unicidad por empresa te permite usarlo como llave para upserts. - **Para activos amortizables** (equipos, mobiliario), pon `purchases.depreciable: true` y elige una cuenta del grupo 2 (inmovilizado) en `purchases.account`. La interfaz aplicará el tratamiento contable correspondiente al crear el gasto. - **No envíes `title`**: lo calcula el servidor a partir del `name`. - **Activa el control de stock solo donde aporte.** Cada producto con `stock.enabled: true` consume uno de los huecos que permite el plan, y el libro de movimientos crece con cada documento que lo mueve. - **Para inventarios iniciales**, registra la existencia de partida con un [ajuste manual](#ajuste-manual-de-stock) de motivo `correccion` en lugar de crear documentos ficticios. - **Antes de asignar un proveedor a líneas de pedido**, comprueba que el producto tiene la faceta `purchases`: sin ella, la línea se rechaza. ## Versiones `GET /{companyId}/products/{id}/versions` lista las versiones de el producto: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/products/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/products/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400 ValidationError` — falta `name`, `currency` o alguno de los campos obligatorios de las facetas (`account`, `tax`). - `400 ValidationError` — `tax` de la faceta `sales` con IDs del catálogo de compras (o viceversa). Ver [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). - `400 ValidationError` — se intenta retirar la faceta `purchases` de un producto que participa en pedidos pendientes u órdenes de compra abiertas. - `400` — ajuste de stock con `quantity` a cero, o ajuste que dejaría el almacén en negativo cuando la empresa bloquea los negativos. - `400` — operación de stock sobre un producto que no tiene el control de stock activado. - `403` con código `plan_limit_exceeded` — se ha alcanzado el máximo de productos del plan, o el máximo de productos con control de stock. - `403` — el plan no incluye el control de stock (ver [Disponibilidad por plan](#disponibilidad-por-plan)). - `409 Conflict` — borrado de un producto referenciado por líneas de documentos existentes. - `409 Conflict` — `externalId` duplicado en la empresa. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/products` | `getProducts` | `products:read` | Lista de productos | | GET | `/{companyId}/products/{id}` | `getProduct` | `products:read` | Obtener un producto | | GET | `/{companyId}/products/{id}/stock` | `getProductStock` | `products:read` | Stock de un producto | | GET | `/{companyId}/products/{id}/stockMovements` | `listProductStockMovements` | `products:read` | Movimientos de stock de un producto | | GET | `/{companyId}/products/{id}/versions` | `getProductVersions` | `products:read` | Versiones de el producto | | GET | `/{companyId}/products/{id}/versions/{versionId}` | `getProductVersion` | `products:read` | Versión de el producto | | POST | `/{companyId}/products` | `createProduct` | `products:write` | Crear producto | | POST | `/{companyId}/products/{id}/stockAdjustments` | `adjustProductStock` | `products:write` | Ajuste manual de stock | | PUT | `/{companyId}/products/{id}` | `updateProduct` | `products:write` | Actualizar producto | | DELETE | `/{companyId}/products/{id}` | `deleteProduct` | `products:write` | Borrar producto | ## Scopes - **`products:read`** — Lectura de productos. - **`products:write`** — Modificación de productos. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/products.md --- # Campos personalizados > El recurso customFields permite descubrir y gestionar las definiciones de campos personalizados de una empresa. El recurso `customFields` permite descubrir y gestionar las definiciones de campos personalizados de una empresa. Los valores se guardan en los documentos dentro de `content.main.customFields`, usando como clave el `id` de la definición, no su título visible. ```json { "content": { "main": { "customFields": { "cfi_1f6f2ec8-7c2b-4c71-9c4f-2257ed34878a": "PO-2026-0042" } } } } ``` La API valida las claves al crear o actualizar facturas, presupuestos, albaranes, pedidos de cliente, órdenes de compra, gastos, nóminas, contactos y facturas recurrentes. Una clave debe corresponder a una definición existente. Las definiciones borradas siguen siendo válidas para conservar y editar documentos históricos; una clave desconocida produce `400 Bad Request`. ## Representación Todas las operaciones que devuelven un campo usan estos atributos: | Atributo | Tipo | Descripción | | --- | --- | --- | | `id` | string | `cfi_` seguido de UUID v4 en minúsculas. | | `title` | string | Nombre visible. | | `docTypes` | string[] | Uno o más de `invoice`, `estimate`, `deliveryNote`, `clientOrder`, `purchaseOrder`, `bill`, `payroll`, `contact`. | | `archived` | boolean | `true` si el campo está borrado. | Las respuestas de recurso individual envuelven la representación en `content`; el listado la devuelve en `items`. Todas las operaciones reciben `companyId` en el path y aceptan el header `accept-version`. Las operaciones sobre un campo concreto reciben además su `id` en el path. Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/) para el uso de OAuth, API keys y versionado. ## Listar campos `GET /{companyId}/customFields` devuelve solo campos activos por defecto. Admite búsqueda aproximada por `title`, uno o varios `docTypes` con semántica OR, `archived=false|true|all`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate`, `sortBy`, `limit` y `offset`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/). ```bash curl -s \ -H "facturadirecta-api-key: $API_KEY" \ "https://app.facturadirecta.com/api/$COMPANY_ID/customFields?docTypes=invoice&archived=all" ``` ## Obtener un campo `GET /{companyId}/customFields/{id}` devuelve también campos borrados. Esto permite resolver los IDs presentes en documentos históricos. ## Crear un campo `POST /{companyId}/customFields` recibe `title`, `docTypes` y un `id` opcional. Si omites el ID, FacturaDirecta genera uno. Si lo envías, debe ser exactamente `cfi_` + UUID v4 en minúsculas. Puedes reutilizar el mismo ID al crear la misma definición en sandbox y producción. ```json { "id": "cfi_1f6f2ec8-7c2b-4c71-9c4f-2257ed34878a", "title": "Referencia de pedido", "docTypes": ["invoice", "estimate"] } ``` Un ID existente devuelve `409 Conflict`, también cuando pertenece a un campo borrado: borrar no libera el identificador. La creación puede devolver `403` si el plan no permite campos personalizados o ya se alcanzó su máximo (20 en los planes que incluyen la función). ## Actualizar un campo `PUT /{companyId}/customFields/{id}` reemplaza `title` y `docTypes`. No permite editar un campo borrado: recupéralo primero. Si retiras un tipo de documento, los valores ya guardados se conservan en su JSON, pero dejan de mostrarse en ese tipo. ## Borrar un campo `DELETE /{companyId}/customFields/{id}` realiza un borrado no destructivo. La definición queda con `archived: true` y los valores de documentos existentes se conservan. ## Recuperar un campo `POST /{companyId}/customFields/{id}/restore` no recibe body y es idempotente: si el campo ya está activo devuelve su estado actual. La recuperación requiere la función de plan para recuperar documentos borrados y cuenta contra el máximo de campos activos, por lo que puede devolver `403`. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/customFields` | `getCustomFields` | `customFields:read` | Lista de campos personalizados | | GET | `/{companyId}/customFields/{id}` | `getCustomField` | `customFields:read` | Obtener campo personalizado | | POST | `/{companyId}/customFields` | `createCustomField` | `customFields:write` | Crear campo personalizado | | POST | `/{companyId}/customFields/{id}/restore` | `restoreCustomField` | `customFields:write` | Recuperar campo personalizado | | PUT | `/{companyId}/customFields/{id}` | `updateCustomField` | `customFields:write` | Actualizar campo personalizado | | DELETE | `/{companyId}/customFields/{id}` | `deleteCustomField` | `customFields:write` | Borrar campo personalizado | ## Scopes - **`customFields:read`** — Lectura de campos personalizados. - **`customFields:write`** — Gestión de campos personalizados. Para la referencia exhaustiva de campos del body y respuesta, consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/custom-fields.md --- # Bancos y cuentas de tesorería > Un banco (bank) en FacturaDirecta es una cuenta de tesorería de la empresa: una cuenta corriente con IBAN, una cuenta de tarjeta o una caja en efectivo. Un **banco** (`bank`) en FacturaDirecta es una cuenta de tesorería de la empresa: una cuenta corriente con IBAN, una cuenta de tarjeta o una caja en efectivo. Los bancos son las contrapartidas naturales de los cobros y pagos, y son lo que se concilia contra los [extractos bancarios](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/). > En los ejemplos, `ban_…` son ilustrativos. El ID real se devuelve al > consultar la API. ## Los bancos no se crean por API Este recurso es **solo lectura**. La API pública no expone crear, modificar ni borrar bancos: ese flujo vive solo en la interfaz, donde se configura la conexión automática con el banco, los datos de SEPA y demás detalles operativos. La API expone los datos necesarios para integraciones (registrar pagos contra una cuenta, mostrar listado al usuario) y, con el parámetro `related`, el saldo contable, el saldo según la entidad, el estado del enlace y los movimientos pendientes de conciliar: lo mismo que muestra la pantalla de bancos de la aplicación. ## Estructura Cada item del listado tiene la forma `{ uuid, content, creationDate, modificationDate }`. El contenido principal está en `content.main`: | Campo | Tipo | Significado | |---|---|---| | `name` | string | Nombre de la cuenta. | | `title` | string | Título descriptivo (opcional). | | `notes` | string | Notas internas de la cuenta, si están informadas en la interfaz. | | `subtype` | enum | `iban`, `card`, `stripe` u `other`. Determina qué otros campos aplican. Un banco `stripe` es una cuenta de Stripe: no tiene IBAN y sus movimientos no traen saldo de la entidad. | | `currency` | string | Moneda (ISO 4217). | | `account` | string | Cuenta contable del banco en el plan contable. | | `iban4` | string | Últimos 4 dígitos del IBAN, prefijados por `****`. Solo si `subtype="iban"`. | | `iban` | string | IBAN completo. Solo si `subtype="iban"` **Y** las credenciales tienen el scope `banks:readIban`. | | `bic` | string | BIC/SWIFT (si está informado). | | `bankCode` | string | Código de banco usado en exportaciones a contabilidad externa (A3). | > **No hay tags** sobre bancos. A diferencia del resto de documentos, > el item devuelto no incluye el campo `tags`. ## Saldos y estado del enlace: el parámetro `related` El listado y el detalle aceptan `related`, repetible, con estos valores. Cada uno añade una clave bajo `related` en el item; sin el parámetro, la respuesta no lleva `related`. | Valor | Clave | Qué devuelve | |---|---|---| | `balance` | `related.balance` | Saldo contable del banco. | | `linkedBalance` | `related.linkedBalance` | Saldo según la entidad enlazada, o `null`. | | `link` | `related.link` | Estado de la conexión automática con el banco. | | `statements` | `related.statements` | Movimientos pendientes de conciliar y fecha del último. | Todos los importes de `related` son positivos cuando hay dinero en el banco, igual que `amount` en los [extractos](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/). ### Los dos saldos Un banco tiene dos saldos distintos, y conviene no confundirlos: - **`related.balance`, el saldo contable**: la suma de los apuntes de la cuenta contable del banco (`account`) cuyo tercero es el banco. Es el saldo de FacturaDirecta: el de la pantalla de bancos, el del panel de inicio y el de [sumas y saldos](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/). Está en todos los planes. Para sincronizarlo en un ERP o cuadrar la contabilidad, este es el dato. - **`related.linkedBalance`, el saldo según la entidad**: el que el banco informó en el último movimiento sincronizado o importado. Es un dato informativo que depende de hasta dónde ha sincronizado el proveedor: cambia con cada sincronización (cada 12 horas, o cuando un usuario la pide desde la aplicación) y puede ir por detrás del banco real. Por eso lleva la fecha y el id del movimiento del que sale y la fecha de la última sincronización correcta: con ellos tu integración decide si le sirve. La diferencia entre los dos suele ser lo que falta por conciliar: `related.statements.pending` dice cuántos movimientos son. `related.balance`: | Campo | Tipo | Significado | |---|---|---| | `balance` | number | Saldo en la moneda de la empresa. Suma todos los apuntes del banco, en cualquier moneda, por su importe convertido. | | `currency` | string | Moneda del banco (ISO 4217). | | `currencyBalance` | number | Saldo en la moneda del banco. Suma solo los apuntes en esa moneda, por su importe en divisa. En un banco en la moneda de la empresa coincide con `balance`. | | `latestEntryDate` | date | Fecha del último apunte que mueve el saldo, o `null` si no hay apuntes. | | `date` | date | Fecha de corte. Solo cuando se pasa `balanceDate`. | Sin `balanceDate`, el saldo incluye **todos los apuntes, también los de fecha futura**: es el mismo número que enseña la pantalla de bancos. Con `balanceDate=YYYY-MM-DD` es el saldo al cierre de ese día (apuntes con fecha hasta ese día inclusive), y `latestEntryDate` es la del último apunte dentro del corte. `balanceDate` solo tiene efecto con `related=balance`. `related.linkedBalance` (o `null` si no hay saldo de la entidad: banco sin movimientos, movimientos importados sin saldo, o banco `stripe`): | Campo | Tipo | Significado | |---|---|---| | `balance` | number | Saldo disponible que informó la entidad tras el movimiento. | | `bookedBalance` | number | Saldo contable que informó la entidad, cuando difiere del disponible. `null` si no lo informa. | | `currency` | string | Moneda del saldo (ISO 4217). | | `date` | date | Fecha del movimiento del que sale el saldo. | | `statement` | string | Id del movimiento del que sale el saldo (`GET /statements/{id}`). | | `syncedAt` | date-time | Última sincronización correcta con el proveedor. `null` si el banco no sincroniza (movimientos importados por fichero). | El saldo es el del último movimiento con fecha hasta hoy, esté o no conciliado o ignorado: es el último dato que dio la entidad. `related.link`: | Campo | Tipo | Significado | |---|---|---| | `status` | enum | `manual` (sin conexión; los movimientos se importan a mano), `linked` (conectado con el banco, o cuenta de Stripe conectada), `paused` (conectado, pero el plan no incluye más cuentas conectadas y la sincronización está pausada) o `unlinked` (estuvo conectado y ya no). | | `bankName` | string | Nombre de la entidad según la conexión. `null` en un banco manual. | | `accessValidUntil` | date | Hasta cuándo autorizó el usuario el acceso a los datos (PSD2). `null` si el proveedor no lo informa. | | `syncEnabled` | boolean | Si el enlace sincroniza movimientos automáticamente. | | `actionRequired` | boolean | Si la sincronización está detenida porque el usuario debe hacer algo en la aplicación (volver a autorizar el acceso, confirmar un código…). | | `message` | string | Motivo por el que está detenida, tal como lo ve el usuario. `null` si no lo está. | | `lastSyncAt` | date-time | Último intento de sincronización. `null` si nunca ha sincronizado. | | `lastSyncOk` | boolean | Si el último intento acabó bien. `null` si nunca ha sincronizado. | `status` solo no dice si el banco sincroniza: un banco de Stripe es `linked` en cuanto tiene la cuenta conectada para cobrar, aunque no sincronice movimientos. Mira `syncEnabled`. `related.link` nunca incluye credenciales ni referencias de cuenta del proveedor. `related.statements`: | Campo | Tipo | Significado | |---|---|---| | `pending` | integer | Movimientos pendientes de conciliar. Quedan fuera los ignorados y los borrados; un movimiento cuyo asiento se ha borrado vuelve a contar como pendiente. | | `latestDate` | date | Fecha del último movimiento del banco con fecha hasta hoy. `null` si no tiene movimientos. | Son solo contadores y fechas: el listado de movimientos está en [`GET /{companyId}/statements?bank={id}`](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/). ## Acceso al IBAN: dos scopes Los IBAN son datos sensibles. La API segrega el acceso en dos niveles: - **`banks:read`** — siempre presente para listar bancos. Devuelve todos los campos **excepto `iban`**. El `iban4` (últimos 4 dígitos enmascarados) sí se devuelve. - **`banks:readIban`** — scope adicional para ver el IBAN completo. Sin él, `iban` se omite del objeto. Para integraciones que solo necesitan identificar la cuenta (UI de selección, conciliación contra banco), basta con `banks:read`. Pide `banks:readIban` solo si tu integración realmente necesita el IBAN completo (por ejemplo, generación de remesas SEPA externas). > Este patrón de doble scope se aplica también en los > [métodos de pago](https://www.facturadirecta.com/desarrolladores/api/referencia/payment-methods/) (allí se enmascara el IBAN > del deudor del SEPA Direct Debit por la misma razón). ## Enlace a la aplicación Las respuestas que devuelven un banco incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de bancos](#lista-de-bancos) - [Obtener un banco](#obtener-un-banco) ## Lista de bancos `GET /{companyId}/banks` devuelve los bancos activos de la empresa. **Parámetros de consulta específicos:** - **`currency`** — filtrar por moneda (ISO 4217, exacto). - **`related`** — datos adicionales de cada banco: `balance`, `linkedBalance`, `link`, `statements` (repetible). Ver [Saldos y estado del enlace](#saldos-y-estado-del-enlace-el-parámetro-related). - **`balanceDate`** — fecha de corte del saldo contable (`YYYY-MM-DD`). Solo con `related=balance`. - **`sortBy`** — campo de orden. Valores aceptados: `title`, `currency`, `creationDate`, `modificationDate` (cada uno con su variante descendente `-title`...). **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). **Notas:** - El listado **excluye los bancos borrados**: solo aparecen las cuentas activas de la empresa. - El orden por defecto es por número total de comentarios y luego por fecha de creación descendente. Si tu integración depende del orden, indícalo explícitamente con `sortBy`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/banks?currency=EUR&sortBy=title" ``` Respuesta típica (sin scope `banks:readIban`): ```json { "pagination": { "offset": 0, "limit": 100, "total": 2 }, "items": [ { "uuid": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "content": { "type": "bank", "uuid": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "main": { "name": "Cuenta principal", "subtype": "iban", "currency": "EUR", "notes": "Cuenta para cobros principales", "account": "572000", "iban4": "****1234", "bic": "BSCHESMMXXX" } }, "creationDate": "2024-01-15T08:00:00.000Z", "modificationDate": "2024-01-15T08:00:00.000Z" } ] } ``` Con `related` cada item añade los bloques pedidos: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/banks?related=balance&related=statements" ``` ## Obtener un banco `GET /{companyId}/banks/{id}` devuelve un banco activo, con la misma forma que cada item del listado (`{ uuid, content, creationDate, modificationDate, webUrl }`) y los mismos scopes. Acepta `related` y `balanceDate`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/banks/ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d?related=balance&related=linkedBalance&related=link&related=statements" ``` Respuesta típica de un banco enlazado por PSD2: ```json { "uuid": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "content": { "type": "bank", "uuid": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "main": { "name": "Cuenta principal", "subtype": "iban", "currency": "EUR", "account": "572000", "iban4": "****1234", "bic": "BSCHESMMXXX" } }, "creationDate": "2024-01-15T08:00:00.000Z", "modificationDate": "2026-09-02T10:21:00.000Z", "webUrl": "https://3.facturadirecta.com/company/com_…/banks/ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "related": { "balance": { "balance": 12345.67, "currency": "EUR", "currencyBalance": 12345.67, "latestEntryDate": "2026-10-15" }, "linkedBalance": { "balance": 12400.12, "bookedBalance": null, "currency": "EUR", "date": "2026-10-15", "statement": "sta_8c1f2b7e-3d4a-4e5f-9a6b-7c8d9e0f1a2b", "syncedAt": "2026-10-16T06:12:41.000Z" }, "link": { "status": "linked", "bankName": "Banco Santander", "accessValidUntil": "2026-12-01", "syncEnabled": true, "actionRequired": false, "message": null, "lastSyncAt": "2026-10-16T06:12:41.000Z", "lastSyncOk": true }, "statements": { "pending": 3, "latestDate": "2026-10-15" } } } ``` Los 54,45 € de diferencia entre los dos saldos son los tres movimientos pendientes de conciliar: cuando se concilien, el saldo contable coincidirá con el de la entidad. Para el saldo contable **a una fecha** (por ejemplo, al cierre de un mes): ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/banks/ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d?related=balance&balanceDate=2026-09-30" ``` `related.balance` lleva entonces `"date": "2026-09-30"` y solo suma los apuntes con fecha hasta ese día. El mismo saldo, con el detalle de cada cuenta, lo da [sumas y saldos](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/) con `thirdParty` y `account`, pero ese informe exige el módulo de Contabilidad y el scope `accounting:read`; `related=balance` está en todos los planes. ## Recomendaciones - **Para el saldo de un banco** usa `related=balance` en el detalle o en el listado. No sumes los apuntes de `GET /journal` ni construyas el saldo a partir de los extractos: es el mismo número que ve el usuario en la pantalla de bancos, calculado con el mismo criterio. - **No uses `linkedBalance` como saldo contable.** Es el último dato que dio la entidad, con fecha; sirve para mostrar «saldo en el banco» o para detectar que la sincronización va atrasada (`syncedAt`), no para cuadrar la contabilidad. - **Cachea el listado**. Las cuentas cambian con muy baja frecuencia; no hace falta consultar el endpoint en cada operación. Refresca el cache cuando una creación de cobro/pago falle con `400` por `bank` desconocido (alguien puede haber borrado o añadido cuentas desde la interfaz). - **Para mostrar al usuario**, usa `name` (o `title` si está informado) e `iban4`. No pidas `banks:readIban` solo para mostrar la cuenta: con `iban4` ya tienes los últimos 4 dígitos. - **Para registrar pagos** (`POST /bills/{id}/payments` o `POST /payrolls/{id}/payments`), basta con el `uuid` del banco. ## Versiones `GET /{companyId}/banks/{id}/versions` lista las versiones de el banco: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/banks/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que el listado `GET /{companyId}/banks`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - El listado no devuelve `404`: si no hay bancos, recibirás `items: []`. - `404 Not Found` en `GET /banks/{id}` — el banco no existe o está borrado. - `400 Bad Request` — un valor de `related` que no existe, o un `balanceDate` que no es una fecha `YYYY-MM-DD`. - `403 Forbidden` (`code: "forbidden"`) — falta el scope `banks:read`. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. ## Referencia exhaustiva Esta página cubre los matices funcionales. Para el contrato completo del schema (incluyendo los `oneOf` por `subtype`), consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/banks` | `getBanks` | `banks:read` | Lista de bancos | | GET | `/{companyId}/banks/{id}` | `getBank` | `banks:read` | Obtener un banco | | GET | `/{companyId}/banks/{id}/versions` | `getBankVersions` | `banks:read` | Versiones de el banco | | GET | `/{companyId}/banks/{id}/versions/{versionId}` | `getBankVersion` | `banks:read` | Versión de el banco | ## Scopes - **`banks:read`** — Lectura de bancos y cuentas de tesorería, sus saldos y el estado de su enlace. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/banks.md --- # Extractos bancarios > Un extracto bancario (statement) es un movimiento individual de una cuenta bancaria sincronizada con FacturaDirecta. Un **extracto bancario** (`statement`) es un movimiento individual de una cuenta bancaria sincronizada con FacturaDirecta. Cada cargo o ingreso del banco genera un statement con su fecha, importe y concepto. El recurso se centra en la **conciliación**: vincular cada statement con el asiento contable correspondiente (cobro de factura, pago de gasto, etc.) para mantener la contabilidad alineada con el banco. > En los ejemplos de esta página, los UUIDs (`sta_…`, `tra_…`, > `inv_…`, `bil_…`, `ban_…`) son ilustrativos. Cada empresa tiene los > suyos; sustitúyelos por los identificadores reales que devuelve la > API. ## Los statements no se crean por API Los statements los **importa el sistema automáticamente** mediante la conexión con el banco o la importación de extractos en CSV. La API pública solo expone **lectura y conciliación**: no hay `POST /statements` ni `DELETE /statements/{id}`. Para crear un statement nuevo hay que pasar por los flujos de importación bancaria de la interfaz. ## Estados del statement `state` toma tres valores: - **`pending`** — recién importado, sin conciliar. Aparece en el panel de conciliación pendiente. - **`reconciled`** — vinculado a un asiento contable. Su importe ya está reflejado en la contabilidad. - **`ignored`** — marcado para no aparecer en pendientes (transferencias entre cuentas propias, comisiones que no se contabilizan, etc.). El campo `ignore` complementa: cuando un statement está marcado como ignorado, `state` pasa a `ignored` y el statement deja de contar como pendiente (por ejemplo en `related.statements.pending` de los [bancos](https://www.facturadirecta.com/desarrolladores/api/referencia/banks/)). El listado lo sigue devolviendo: filtra con `state=pending` si solo quieres los que faltan por conciliar. ## Estructura `StatementContent`: | Campo | Tipo | Significado | |---|---|---| | `uuid` | string | Identificador del statement, prefijo `sta_`. | | `date` | date | Fecha de la operación bancaria. | | `title` | string | Concepto del movimiento (lo que dice el banco). | | `amount` | number | Importe. **Positivo para ingresos, negativo para cargos**. | | `currency` | string | Moneda del movimiento (ISO 4217). | | `companyCurrencyAmount` | number | Importe convertido a la moneda de la empresa. | | `balance` | number | Saldo disponible de la cuenta tras el movimiento, según la entidad. Positivo es dinero en el banco. `null` si el movimiento no lo trae (importado sin saldo, o banco de Stripe). | | `bookedBalance` | number | Saldo contable de la cuenta tras el movimiento, según la entidad, cuando difiere del disponible. `null` si la entidad no lo informa. | | `state` | enum | `pending`/`reconciled`/`ignored`. | | `bank` | string | ID del banco propio del statement (`ban_*`). | | `ignore` | boolean | Si está marcado como ignorado. | | `matchedDocument` | string \| null | ID del asiento contable (`tra_*`) con el que está conciliado el movimiento. Es `null` cuando no está conciliado o cuando el asiento se ha borrado. | | `notes` | string | Notas libres. | | `sourceKey` | string | Identificador del statement en la fuente externa (banco). | ## Operaciones - [Lista de extractos](#lista-de-extractos) - [Obtener un extracto](#obtener-un-extracto) - [Conciliar con asiento existente](#conciliar-con-asiento-existente) - [Conciliar creando nuevo asiento](#conciliar-creando-nuevo-asiento) - [Desconciliar](#desconciliar) ## Lista de extractos `GET /{companyId}/statements` devuelve los extractos bancarios de la empresa, paginados. **Parámetros de consulta específicos:** - **`minDate`**, **`maxDate`** — rango por fecha de la operación. - **`bank`** — ID del banco propio (`ban_*`). - **`currency`** — código ISO 4217. - **`minAmount`**, **`maxAmount`** — rango de importe. - **`matchedDocument`** — ID de un asiento contable (`tra_*`). Devuelve el movimiento conciliado con ese asiento. Si el asiento está borrado o no existe, el resultado queda vacío. - **`state`** — filtrar por estado (`pending`, `reconciled`, `ignored`). - **`sortBy`** — campo de orden. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/statements?state=pending&bank=ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d&limit=50" ``` Movimiento conciliado con un asiento concreto: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/statements?matchedDocument=tra_3a8f1e29-4d7b-4c5e-9a2f-6d8b1c4e7a5f" ``` ## Obtener un extracto `GET /{companyId}/statements/{id}` devuelve un statement por ID. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/statements/sta_2c7f4e91-8d3a-4b5c-9e1f-6a2d8b4c3e5f" ``` ## Conciliar con asiento existente `PUT /{companyId}/statements/{id}/reconciliation` vincula el statement con un asiento contable (`transaction`) **que ya existe** en contabilidad. Útil cuando el cobro o pago ya está registrado en la BD (por ejemplo, porque registraste el pago de una factura con `POST /bills/{id}/payments` y luego importas el extracto bancario). **Parámetros del body:** - **`transactionId`** (obligatorio) — ID del asiento contable a vincular (`tra_*`). **Notas:** - Tras la conciliación, `state` pasa a `reconciled`. - El asiento debe tener un importe compatible con el del statement. **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON ```json { "transactionId": "tra_3a8f1e29-4d7b-4c5e-9a2f-6d8b1c4e7a5f" } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"transactionId":"tra_3a8f1e29-4d7b-4c5e-9a2f-6d8b1c4e7a5f"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/statements/sta_2c7f4e91-8d3a-4b5c-9e1f-6a2d8b4c3e5f/reconciliation" ``` ## Conciliar creando nuevo asiento `POST /{companyId}/statements/{id}/reconciliation` concilia el statement **creando un asiento contable nuevo** en la misma operación. Útil cuando el cobro o pago aún no está registrado y quieres reflejarlo en la contabilidad como parte de la conciliación. El asiento se crea con la fecha del statement y queda vinculado a él. Hay dos formas de indicar el asiento, excluyentes entre sí. ### Con el documento que cobra o paga (`document`) Envía el ID de la factura de venta, factura de compra o nómina que el movimiento cobra o paga y la API construye el asiento, igual que el pago que registra `POST /bills/{id}/payments`: - el apunte del banco lleva el importe del movimiento en la moneda del banco y su contravalor en la moneda de la empresa (los del statement); - el apunte del documento salda el documento (o el importe que indiques en `amount`); - si ambos importes no coinciden en moneda de la empresa, la diferencia va a la cuenta de diferencias positivas o negativas de cambio de la configuración contable. Es la forma recomendada, y la más sencilla cuando el banco, el documento o ambos están en una moneda distinta de la de la empresa: no hace falta calcular ningún importe. **Parámetros del body:** - **`document`** (obligatorio en este modo) — ID del documento (`inv_`, `bil_` o `par_`). Debe ser definitivo (no borrador) y tener saldo pendiente. - `amount` (opcional) — importe aplicado al documento, en la moneda del documento, para un cobro o pago parcial. Solo se admite cuando el documento y el banco están en monedas distintas; por defecto se salda el pendiente del documento. - `title` (opcional) — título del asiento. Por defecto, el concepto del statement. **Reglas:** - El sentido del movimiento debe corresponder al saldo del documento: un ingreso cobra una factura de venta (o devuelve el pago de una compra o nómina con saldo a favor); un cargo paga una compra o nómina (o devuelve el cobro de una factura de venta abonada). - Si el documento y el banco están en la **misma moneda**, el importe aplicado al documento es el del movimiento: si no coincide con el pendiente, el documento queda con saldo (cobro parcial o exceso). - Si están en **monedas distintas**, se salda el pendiente del documento salvo que indiques `amount`. El contravalor del movimiento en moneda de la empresa es el que FacturaDirecta calculó al importar el statement (`companyCurrencyAmount`). ### Con los apuntes del asiento (`lines`) Para cualquier otro caso (comisiones, transferencias entre cuentas, movimientos que saldan varios documentos), envía las líneas contables completas del asiento. **Parámetros del body:** - **`lines`** (obligatorio en este modo) — array de líneas del asiento contable. Cada línea tiene: - `account` (obligatorio) — cuenta contable. - `document` (opcional) — ID del documento al que se imputa (factura, gasto, banco propio). - `debit` o `credit` (uno de los dos) — importe en la moneda de la contabilidad de la empresa. - `currencyDebit` o `currencyCredit` — importe en la moneda original del movimiento (puede coincidir con `debit`/`credit` si la moneda es la misma). - `currency` — código ISO 4217. - `title` (opcional) — título del asiento. Por defecto, el concepto del statement. Las líneas deben **cuadrar contablemente** (total de debits = total de credits) e incluir un apunte en la cuenta del banco con `document` igual al ID del banco, en el sentido del movimiento (debe en un ingreso, haber en un cargo). **Notas:** - Tras la conciliación, `state` pasa a `reconciled`. - El asiento creado se vincula automáticamente al statement. **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON Conciliar un cargo con la factura de compra que paga (la API construye el asiento; vale igual con un banco en dólares y una factura en euros): ```json { "document": "bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e" } ``` Conciliar un ingreso enviando los apuntes (cobro de factura de venta), heredado del ejemplo `reconcileReceive` del openapi: ```json { "lines": [ { "account": "572000", "document": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "debit": 121, "credit": null, "currencyDebit": 121, "currencyCredit": null, "currency": "EUR" }, { "account": "430000", "document": "inv_8a3c2f15-7b4e-4d6a-9c1f-5e2d8b3c4a7f", "debit": null, "credit": 121, "currencyDebit": null, "currencyCredit": 121, "currency": "EUR" } ] } ``` Conciliar un cargo enviando los apuntes (pago de factura de compra), heredado del ejemplo `reconcileSend`: ```json { "lines": [ { "account": "572000", "document": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "debit": null, "credit": 121, "currencyDebit": null, "currencyCredit": 121, "currency": "EUR" }, { "account": "400000", "document": "bil_4a9c2e58-3b1f-4d6a-8e2c-7f1a9b3d5c4e", "debit": 121, "credit": null, "currencyDebit": 121, "currencyCredit": null, "currency": "EUR" } ] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@reconcile.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/statements/sta_2c7f4e91-8d3a-4b5c-9e1f-6a2d8b4c3e5f/reconciliation" ``` ## Desconciliar `DELETE /{companyId}/statements/{id}/reconciliation` rompe el vínculo entre el statement y su asiento contable. El statement vuelve a estado `pending`. **Notas:** - El asiento contable asociado **no se borra**, solo se desvincula del statement. Si el asiento fue creado por `reconcileStatementWithNewTransaction`, puede quedar huérfano; bórralo manualmente si ya no aplica. **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/statements/sta_2c7f4e91-8d3a-4b5c-9e1f-6a2d8b4c3e5f/reconciliation" ``` ## Recomendaciones - **Para conciliar pagos ya registrados** (típico: pago de factura ya hecho con `POST /bills/{id}/payments`), usa [conciliar con asiento existente](#conciliar-con-asiento-existente). El asiento ya existe; solo hay que enlazarlo. - **Para conciliar el cobro o pago de un documento que aún no está registrado**, usa [conciliar creando nuevo asiento](#conciliar-creando-nuevo-asiento) con `document`: la API construye el asiento, también cuando el banco o el documento están en otra moneda. - **Para conciliar movimientos sin documento** (transferencias varias, comisiones, devoluciones), usa la misma operación enviando las líneas contables del asiento a crear (`lines`). - **Para localizar el movimiento de un asiento conciliado**, usa `matchedDocument=`. El filtro solo devuelve una relación vigente. Si borras el asiento, el movimiento vuelve a estar pendiente, `matchedDocument` pasa a `null` en la respuesta y el filtro deja de devolverlo. - **No abusar de `ignored`**: úsalo para movimientos que realmente no deben afectar a contabilidad (transferencias entre cuentas propias, movimientos de cobertura). Para movimientos relevantes, concilia aunque sea con un asiento contable simple. ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` — líneas que no cuadran (debits ≠ credits) en `reconcileWithNewTransaction`. - `400 ValidationError` — en `reconcileWithNewTransaction` con `document`, el documento es un borrador, no tiene saldo pendiente o el sentido del movimiento (ingreso o cargo) no corresponde a su saldo. - `400 ValidationError` — `transactionId` apunta a un asiento con importe incompatible con el statement. - `404 Not Found` — `transactionId` no existe en la empresa. - `409 Conflict` — intento de conciliar un statement que ya está conciliado. Desconcilia primero. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/statements` | `getStatements` | `statements:read` | Lista de extractos bancarios | | GET | `/{companyId}/statements/{id}` | `getStatement` | `statements:read` | Obtener un extracto bancario | | POST | `/{companyId}/statements/{id}/reconciliation` | `reconcileStatementWithNewTransaction` | `statements:write` | Conciliar extracto bancario creando un nuevo asiento contable | | PUT | `/{companyId}/statements/{id}/reconciliation` | `reconcileStatement` | `statements:write` | Conciliar extracto bancario con asiento contable existente | | DELETE | `/{companyId}/statements/{id}/reconciliation` | `unreconcileStatement` | `statements:write` | Desconciliar extracto bancario | ## Scopes - **`statements:read`** — Lectura de extractos bancarios. - **`statements:write`** — Conciliación de extractos bancarios. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/statements.md --- # Métodos de pago > Un método de pago describe cómo se cobra o se paga un documento. Un método de pago describe **cómo se cobra o se paga** un documento. Puede ser una descripción manual ("Pago por transferencia") que solo se muestra al cliente o al proveedor, o un instrumento SEPA con datos bancarios reales (mandato de domiciliación, IBAN para transferencia). Los métodos de pago se asocian a documentos (facturas, presupuestos, gastos, ...) en su campo `paymentMethod` y determinan qué se imprime en la plantilla y cómo se procesa el cobro/pago en operaciones de banca. Un documento solo puede llevar un método de pago **de la empresa** (sin `contact`) o **de su propio contacto**, y en la dirección del documento: `receive` (o sin `direction`) en facturas, presupuestos, albaranes y pedidos; `send` (o sin `direction`) en gastos, órdenes de compra y nóminas. La misma regla vale para los métodos por defecto de un contacto (`receivePaymentMethod` y `sendPaymentMethod`). Si no se cumple, el alta o la actualización responde 400 con un error de validación. > En los ejemplos de esta página: > > - Los **UUIDs** (`pam_…`, `ban_…`, `con_…`) son ilustrativos. Cada > empresa tiene los suyos; sustitúyelos por los identificadores > reales que devuelve la API. > - Los **IBAN** son ficticios. Usa siempre IBANs reales (con dígito > de control válido) en producción. ## Globales vs por contacto Un método de pago puede ser: - **Global de la empresa** (sin `contact`): aparece como opción por defecto en todos los documentos. Útil para "Pago por transferencia estándar" que vale para cualquier cliente o proveedor. - **Por contacto** (con `contact`): solo aplica al cliente o proveedor indicado. Típico en mandatos SEPA, donde cada cliente firma su propio mandato de domiciliación. El filtro `includeGlobal` en la lista permite incluir o excluir los globales del resultado. ## Tipos: manual y SEPA `content.main.subtype` determina la forma del método: - **`manual`** — solo descripción textual. El sistema **no** procesa cobros/pagos automáticos: es informativo para el documento. No se permite `contact`. - **`sepaDirectDebit`** — mandato para **cobros domiciliados** (recibos SEPA). El contacto es obligatorio. Lleva ID de mandato, fecha de firma, esquema (B2B o CORE) e IBAN del deudor. - **`sepaCreditTransfer`** — **mandato para pagos por transferencia** SEPA. El contacto es obligatorio. Lleva IBAN del beneficiario. - **`online`** — **cobro online** a través de una pasarela de pago (Stripe). Lo crea FacturaDirecta automáticamente al conectar la cuenta de cobro: la API lo devuelve en lecturas y permite referenciarlo desde facturas (`main.paymentMethod`), pero **no admite crearlo** y en actualizaciones solo acepta cambiar `title` (cualquier otro cambio responde 400). No lleva contacto. Las facturas con este método de pago se pueden pagar desde el portal público de facturas. La estructura de `content.main.details` cambia según el `subtype` (un `oneOf` en el schema). Los detalles relevantes están en [Estructura](#estructura). ## Dirección: send vs receive `content.main.direction`: - **`send`** — método para **hacer pagos** (a proveedores, normalmente). - **`receive`** — método para **recibir cobros** (de clientes, normalmente). En métodos manuales suele omitirse. En SEPA es coherente con el `subtype`: `sepaDirectDebit` casi siempre es `receive` (cobras del cliente), `sepaCreditTransfer` casi siempre es `send` (pagas al proveedor). ## Estructura - `content.type` — siempre `"paymentMethod"`. - `content.uuid` — identificador inmutable, prefijo `pam_`. - `content.main`: - `title` (obligatorio) — nombre del método en los listados. - `subtype` (obligatorio) — ver [Tipos](#tipos-manual-y-sepa). - `direction` — `send` o `receive`. - `contact` (obligatorio en SEPA, prohibido en manual y online) — ID del contacto asociado. - `bank` (opcional en manual, obligatorio en online) — ID del banco propio asociado. En `online` es el banco pasarela (la cuenta de cobro conectada a Stripe). - `details` (obligatorio) — varía según el `subtype`. ### Detalles según `subtype` **`subtype: "manual"`**: `details` es un objeto vacío `{}`. **`subtype: "sepaDirectDebit"`**: `details` contiene: | Campo | Tipo | Obligatorio | Significado | |---|---|---|---| | `id` | string | sí | Identificador del mandato. | | `signatureDate` | date | sí | Fecha de firma del mandato (puede ser `null`). | | `instrument` | `"B2B"` \| `"CORE"` | sí | Esquema de adeudo SEPA. CORE es el estándar B2C; B2B es entre empresas. | | `recurrent` | boolean | sí | Si el mandato cubre cobros recurrentes (`true`) o un único cobro (`false`). | | `iban` | string | sí | IBAN de la cuenta del deudor. | | `bic` | string | no | Código BIC/SWIFT (solo necesario en cobros internacionales). | | `iban4` | string | sí (lo calcula la API) | Últimos 4 dígitos del IBAN. | **`subtype: "sepaCreditTransfer"`**: `details` contiene: | Campo | Tipo | Obligatorio | Significado | |---|---|---|---| | `iban` | string | sí | IBAN de la cuenta del beneficiario. | | `bic` | string | no | Código BIC/SWIFT (solo necesario internacional). | | `iban4` | string | sí (lo calcula la API) | Últimos 4 dígitos del IBAN. | **`subtype: "online"`** (solo lectura): `details` contiene: | Campo | Tipo | Significado | |---|---|---| | `gateways` | array | Bancos pasarela por los que se puede cobrar, como `{ "bank": "ban_…" }`. Actualmente uno, que coincide con `main.bank`. | | `managed` | boolean | Siempre `true`: lo crea y gestiona FacturaDirecta al conectar la cuenta de cobro. | **Nota sobre `iban4`**: el schema lo declara como requerido en respuesta, pero al crear se calcula automáticamente a partir del `iban`. No es necesario enviarlo en el body. ## Operaciones - [Lista de métodos de pago](#lista-de-métodos-de-pago) - [Crear método de pago](#crear-método-de-pago) - [Obtener un método de pago](#obtener-un-método-de-pago) - [Actualizar método de pago](#actualizar-método-de-pago) - [Borrar método de pago](#borrar-método-de-pago) ## Lista de métodos de pago `GET /{companyId}/paymentMethods` devuelve los métodos de pago de la empresa, paginados. **Parámetros de consulta específicos:** - **`contact`** — ID de contacto. Si se indica, solo devuelve métodos asociados a ese contacto. - **`includeGlobal`** — `true`/`false` para incluir métodos globales de la empresa (sin contacto) en el resultado. - **`sortBy`** — campo de orden. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). **Notas:** - Para ver el IBAN completo (no solo `iban4`) en la respuesta, la credencial debe tener el scope `paymentMethods:readIban`. Sin ese scope la API solo devuelve `iban4`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/paymentMethods?contact=con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3&includeGlobal=true" ``` ## Crear método de pago `POST /{companyId}/paymentMethods` crea un método de pago. **Parámetros del body:** - `content.type` — siempre `"paymentMethod"`. - `content.main.title` — obligatorio. - `content.main.subtype` — obligatorio (`manual`, `sepaDirectDebit`, `sepaCreditTransfer`). El subtipo `online` no se puede crear por API: la petición responde 400. - `content.main.contact` — obligatorio en `sepa*`, prohibido en `manual`. Si se omite en `manual`, el método es global de la empresa. - `content.main.details` — obligatorio. Estructura según `subtype` (ver [Detalles según subtype](#detalles-según-subtype)). - `tags` (opcional). **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON Método manual global (heredado del ejemplo `transfer` del openapi): ```json { "content": { "type": "paymentMethod", "main": { "subtype": "manual", "title": "Pago por transferencia", "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "details": {} } } } ``` Mandato SEPA de domiciliación para un cliente (heredado del ejemplo `directDebit`): ```json { "content": { "type": "paymentMethod", "main": { "subtype": "sepaDirectDebit", "title": "Recibo domiciliado", "direction": "receive", "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "details": { "id": "MANDATO_01", "signatureDate": "2023-04-10", "instrument": "CORE", "recurrent": true, "iban": "ES3011112222003333333333" } } } } ``` Mandato de transferencia SEPA para un proveedor (heredado del ejemplo `sepaCreditTransfer`): ```json { "content": { "type": "paymentMethod", "main": { "subtype": "sepaCreditTransfer", "title": "Transferencia SEPA", "direction": "send", "contact": "con_9d4e2a73-1f6c-4b8d-a2e5-3b7c1d4e6f8a", "details": { "iban": "ES9122223333304444444444" } } } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@paymentMethod.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/paymentMethods" ``` ## Obtener un método de pago `GET /{companyId}/paymentMethods/{id}` devuelve un método de pago por ID. **Parámetros globales aceptados:** `accept-version`. **Notas:** - Sin scope `paymentMethods:readIban`, la respuesta enmascara el IBAN completo (solo se devuelve `iban4`). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/paymentMethods/pam_4b7c1e29-2d8f-4a5e-b9c3-6d8a1e4f2c7b" ``` ## Actualizar método de pago `PUT /{companyId}/paymentMethods/{id}` sustituye **el contenido completo** del método. No es un PATCH. **Notas:** - No se puede cambiar el `subtype` de un método existente. Para cambiarlo, crea uno nuevo y borra el anterior. - Cambiar el IBAN de un mandato SEPA puede afectar a cobros/pagos pendientes vinculados. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@paymentMethod.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/paymentMethods/pam_4b7c1e29-2d8f-4a5e-b9c3-6d8a1e4f2c7b" ``` ## Borrar método de pago `DELETE /{companyId}/paymentMethods/{id}` elimina el método de pago. **Restricciones:** - Si el método está referenciado por documentos existentes, la API rechaza el borrado con `409 Conflict`. Para retirar un método ya usado, crea uno nuevo y deja el antiguo sin asignar. - El método de subtipo `online` **no se puede borrar**: se creó junto con su cuenta de cobro y desaparece con ella. Intentarlo devuelve `400`. **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/paymentMethods/pam_4b7c1e29-2d8f-4a5e-b9c3-6d8a1e4f2c7b" ``` ## Scopes especiales: `paymentMethods:readIban` Por defecto, las respuestas que incluyen un método de pago SEPA muestran solo `iban4` (los últimos 4 dígitos) por privacidad. Para recibir el `iban` completo: - Pide el scope **`paymentMethods:readIban`** en tu token OAuth o API key. - También existe el scope **`banks:readIban`** para ver el IBAN completo de los bancos propios en las consultas de bancos. Sin esos scopes, las operaciones funcionan pero los IBAN aparecen truncados. ## Recomendaciones - **Crea métodos globales** para las formas de pago estándar de la empresa ("Pago al contado", "Transferencia bancaria"). Aparecen como opciones por defecto en todos los documentos. - **Crea métodos por contacto** solo cuando hay datos específicos del cliente o proveedor (típicamente mandatos SEPA con su IBAN). - **No mezcles `subtype: manual` con `contact`**: la API rechaza esa combinación. - **Pide `paymentMethods:readIban`** solo si tu integración necesita procesar IBANs (p. ej. para generar remesas SEPA fuera de FacturaDirecta). Por defecto, opera con `iban4` para minimizar el manejo de datos bancarios sensibles. ## Versiones `GET /{companyId}/paymentMethods/{id}/versions` lista las versiones de el método de pago: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/paymentMethods/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/paymentMethods/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400 ValidationError` — `subtype: "manual"` con `contact` asignado. - `400 ValidationError` — `subtype: "sepa*"` sin `contact`. - `400 ValidationError` — `details` con estructura que no corresponde al `subtype` declarado. - `400 ValidationError` — IBAN con formato inválido. - `400 ValidationError` — creación de un método de subtipo `online`, cambio de cualquier campo suyo que no sea `title`, o borrado directo. - `409 Conflict` — borrado de un método referenciado por documentos. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/paymentMethods` | `getPaymentMethods` | `paymentMethods:read` | Lista de métodos de pago | | GET | `/{companyId}/paymentMethods/{id}` | `getPaymentMethod` | `paymentMethods:read` | Obtener un método de pago | | GET | `/{companyId}/paymentMethods/{id}/versions` | `getPaymentMethodVersions` | `paymentMethods:read` | Versiones de el método de pago | | GET | `/{companyId}/paymentMethods/{id}/versions/{versionId}` | `getPaymentMethodVersion` | `paymentMethods:read` | Versión de el método de pago | | POST | `/{companyId}/paymentMethods` | `createPaymentMethod` | `paymentMethods:write` | Crear método de pago | | PUT | `/{companyId}/paymentMethods/{id}` | `updatePaymentMethod` | `paymentMethods:write` | Actualizar método de pago | | DELETE | `/{companyId}/paymentMethods/{id}` | `deletePaymentMethod` | `paymentMethods:write` | Borrar método de pago | ## Scopes - **`paymentMethods:read`** — Lectura de métodos de pago. - **`paymentMethods:write`** — Esctritura de métodos de pago. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/payment-methods.md --- # Diario contable > El diario contable es la lista cronológica de apuntes (líneas de asiento) generados por todos los documentos contabilizables de la empresa: facturas de venta, facturas de compra, nóminas, asientos… El **diario contable** es la lista cronológica de apuntes (líneas de asiento) generados por todos los documentos contabilizables de la empresa: facturas de venta, facturas de compra, nóminas, asientos manuales, pagos/cobros, modelos de impuestos, amortizaciones, etc. Cada item del diario es un **apunte individual**: una línea de un asiento con su cuenta, debe/haber, fecha y referencia al documento que la origina. La API expone una sola operación de **lectura paginada**: la contabilidad se construye desde los documentos (facturas, nóminas, etc.), no creando apuntes sueltos. Para alterar el diario, hay que crear o modificar el documento de origen. ## Estructura de un apunte | Campo | Tipo | Significado | |---|---|---| | `id` | string | ID del documento que origina el apunte (puede ser `inv_*`, `bil_*`, `pay_*`, `tra_*`, etc.). | | `idType` | enum | Tipo del documento origen. Valores: `bill`, `invoice`, `payroll`, `payment`, `taxes`, `transaction`. | | `idTitle` | string | Título del documento origen. | | `idSummary` | string | Resumen/descripción del documento origen (disponible para facturas de venta, compras y nóminas). | | `idLineIndex` | integer | Número de orden del apunte dentro del asiento del documento. | | `idAccounting` | object | Asiento contable completo del documento origen. **Solo si se pide `related=idAccounting`.** | | `date` | date | Fecha del apunte. | | `debit` | number | Importe en el DEBE en EUR. | | `credit` | number | Importe en el HABER en EUR. | | `currency` | string | Moneda original del apunte (ISO 4217). **Solo si la empresa tiene multimoneda activo y se pide `related=currency`.** | | `currencyDebit` | number | DEBE en moneda original (mismas condiciones que `currency`). | | `currencyCredit` | number | HABER en moneda original (mismas condiciones que `currency`). | | `account` | string | Cuenta contable (6 dígitos del plan contable de la empresa). | | `accountTitle` | string | Nombre de la cuenta en el plan. | | `document` | string | Documento que actúa como subcuenta (si aplica). | | `documentType` | enum | Tipo del documento subcuenta. Valores: `bill`, `invoice`, `payroll`, `payment`, `transaction`, `bank`, `contact`, `product`. | | `documentTitle` | string | Título del documento subcuenta. | | `journalPriority` | integer | Orden del asiento dentro del día. Ver tabla más abajo. | | `tags` | string[] | Etiquetas del documento origen. **Solo si se pide `related=tags`.** | > Los campos `debit`/`credit`/`currency*` siguen la convención > contable española: positivos en la columna que corresponda; nunca > negativos para "contraapuntes". Para neutralizar un asiento se > genera otro con los lados invertidos. ## `journalPriority`: orden de asientos dentro del día Los asientos del diario tienen una prioridad numérica entre 1 y 999 que determina su orden dentro de una misma fecha. Los valores que emite FacturaDirecta son: | Prioridad | Tipo de asiento | |---|---| | **1** | Inicio de contabilidad / Apertura de periodo | | **2** | Asiento ficticio generado por un documento con fecha previa al inicio de contabilidad | | **3** | Asiento de traspaso de beneficios en la apertura | | **100** | Asiento estándar (la mayoría de movimientos) | | **970** | Amortizaciones | | **980** | Regularización de moneda extranjera | | **990** | Cierre de grupos 6 y 7 | | **999** | Cierre del periodo | El filtro `journalPriority` permite aislar uno de estos tipos (por ejemplo, `journalPriority=970` para obtener solo amortizaciones). ## Operaciones - [Lista de apuntes del diario](#lista-de-apuntes-del-diario) ## Lista de apuntes del diario `GET /{companyId}/journal` devuelve la lista de apuntes del diario paginada por la línea individual (no por asiento). **Parámetros de consulta específicos:** - **`minDate`**, **`maxDate`** — rango por fecha del apunte (`YYYY-MM-DD`, inclusivos). - **`account`** — cuenta contable. Acepta entre 1 y 6 dígitos: - entre 1 y 5 dígitos: prefijo (`6` → todos los gastos; `64` → todas las cuentas de personal). - 6 dígitos: cuenta completa (`700000` → solo apuntes de la cuenta de ventas 700000). - **`id`** — ID del documento que origina los apuntes. Devuelve todas las líneas de ese asiento, factura, gasto, nómina o remesa. - **`document`** — ID del documento que actúa como subcuenta. Filtra los apuntes que afectan al saldo de ese contacto, banco o producto. No equivale a `id`: un mismo asiento puede tener líneas imputadas a documentos distintos. - **`journalPriority`** — entero. Aísla un tipo concreto de asiento (ver tabla arriba). - **`sortBy`** — solo se acepta `date` o `-date`. El orden secundario es siempre por `journalPriority`, `id` y `line_id` (ambos en la misma dirección). - **`related`** — array. Permite añadir datos derivados al item: - `idAccounting` — incluye el asiento contable completo del documento origen en cada apunte. Útil cuando vas a procesar asientos enteros. - `tags` — incluye el array `tags` (etiquetas del documento origen). - `currency` — incluye los campos `currency`, `currencyDebit` y `currencyCredit` con el contravalor en moneda original. **Solo aplica si la empresa tiene multimoneda activo**; en caso contrario, los campos se siguen omitiendo aunque pidas `related`. **Filtros por etiquetas:** Soporta los parámetros estándar `allTheseTags`, `anyOfTheseTags` y `hasTags` (`true` para documentos con alguna etiqueta y `false` para documentos sin etiquetas) para filtrar por etiquetas del documento que origina el apunte. Ver [Etiquetas en otros documentos] para la semántica. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). > Este endpoint **no** acepta los filtros estándar > `minCreationDate`/`minModificationDate` (no son apuntes con vida > propia: dependen del documento de origen). ###### Copy as cURL Saldo de la cuenta 572000 en un rango de fechas: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/journal?account=572000&minDate=2025-01-01&maxDate=2025-12-31&sortBy=date" ``` Solo amortizaciones de un año: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/journal?journalPriority=970&minDate=2025-01-01&maxDate=2025-12-31" ``` Todos los apuntes que origina un asiento contable: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/journal?id=tra_3a8f1e29-4d7b-4c5e-9a2f-6d8b1c4e7a5f" ``` Apuntes que afectan a un contacto, con asiento completo y etiquetas: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/journal?document=con_2e3f4a18-9b7c-4d6a-8e1f-5c2d3b4a7e9f&related=idAccounting&related=tags" ``` ## Recomendaciones - **Lectura por rangos de fecha**: el diario crece sin límite. Pagina siempre con `minDate`/`maxDate` razonables, no pidas el diario entero. Para exportaciones, itera mes a mes o trimestre a trimestre. - **Para ingresos, gastos y beneficio**: usa el [resumen de resultados](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/), que da los totales ya calculados con las mismas reglas que la web. No los reconstruyas sumando apuntes: una suma propia puede incluir el asiento de regularización y no coincidir con lo que ve el usuario. - **Para el saldo de una cuenta concreta**: usa `account=<6 dígitos>` y suma `debit - credit` (o `credit - debit` según el tipo de cuenta) en el cliente. - **Para recuperar un asiento completo**: filtra por `id`. No filtres por `document`, porque ese parámetro selecciona las líneas imputadas a una subcuenta y puede dejar fuera otros apuntes del mismo asiento. - **Para vincular con documentos**: el `id` del apunte es el ID del documento origen. Puedes pedir su detalle con la operación de lectura del recurso correspondiente ([invoices](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/), [bills](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/), [payrolls](https://www.facturadirecta.com/desarrolladores/api/referencia/payrolls/), etc.). - **`related=idAccounting` es caro**: solo pídelo si vas a procesar asientos completos. Si solo necesitas apuntes individuales, no lo uses. - **Multimoneda**: si tu empresa no tiene multimoneda activado, no uses `related=currency` (no devuelve nada). Si lo tiene, los campos `currencyDebit`/`currencyCredit` son útiles para informes en la moneda original del movimiento. ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` (`code: "invalidParams"`) — valor incorrecto para `sortBy`. Solo se acepta `date` o `-date`. - `400 ValidationError` — `account` con formato distinto a 1-6 dígitos. - `403 Forbidden` — falta el scope `accounting:read`. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. ## Referencia exhaustiva Esta página cubre los matices funcionales y los casos típicos. Para el contrato completo del item (incluyendo el schema `Accounting` devuelto por `related=idAccounting`), consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/journal` | `getJournal` | `accounting:read` | Diario contable | ## Scopes - **`accounting:read`** — Lectura de datos de contabilidad (diario). Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/journal.md --- # Informes > Los informes devuelven, ya calculadas, las mismas cifras que los informes de la web. Son de solo lectura y se generan en el momento a partir de la contabilidad de la empresa. Los **informes** devuelven, ya calculadas, las mismas cifras que los informes de la web. Son de solo lectura y se generan en el momento a partir de la contabilidad de la empresa. El **resumen de resultados** responde a «cuánto he facturado, cuánto he gastado y cuánto he ganado» en un periodo, separando cada importe por su origen: facturas, gastos, nóminas, amortizaciones, diferencias de cambio y otros. Está disponible en **todos los planes** y da las mismas cifras que las tarjetas de Ingresos, Gastos y Beneficio del panel. Para ver qué documentos componen una cifra, el **detalle del resumen** lista los documentos de una categoría con el importe que aporta cada uno. Los **informes contables** (pérdidas y ganancias, balance de situación y sumas y saldos) siguen el modelo oficial del Plan General Contable y necesitan el **módulo de Contabilidad**, como en la web. > Para totales usa estos informes y no sumes apuntes del > [diario](https://www.facturadirecta.com/desarrolladores/api/referencia/journal/): los informes aplican las mismas reglas que la > web (por ejemplo, excluyen el asiento de regularización), y una suma > propia puede no coincidir con lo que ve el usuario. > Para rankings, totales y comparativas a medida (los cinco mejores > clientes, el gasto por proveedor, las provincias que más facturan frente > al año pasado) usa la [consulta analítica](https://www.facturadirecta.com/desarrolladores/api/referencia/query/): agrupa facturas, > compras, contactos o apuntes por la dimensión que elijas, con las mismas > cifras que los listados. ## Estructura del resumen de resultados La respuesta tiene `meta` y `rows`: | Campo | Tipo | Significado | |---|---|---| | `meta.report` | string | Siempre `resultsSummary`. | | `meta.currency` | string | Moneda de la empresa (ISO 4217). Todos los importes van en ella. | | `meta.columns` | object[] | Columnas del informe, ver más abajo. | | `meta.generatedAt` | date-time | Momento en que se calculó el informe. | | `rows` | object[] | Filas del informe, en orden de presentación. | Cada fila de `rows`: | Campo | Tipo | Significado | |---|---|---| | `id` | enum | Categoría (`invoices`, `currencyGains`, `otherIncome`, `bills`, `payrolls`, `depreciation`, `currencyLosses`, `otherExpenses`) o total (`income`, `expenses`, `profit`). | | `kind` | enum | `category` o `total`. | | `side` | enum | Solo en las categorías: `income` (ingresos) o `expenses` (gastos). | | `title` | string | Nombre de la fila en el idioma de la petición. | | `values` | object | Valor de cada columna, por su `id`. | Las filas aparecen en este orden: categorías de ingresos, total de ingresos, categorías de gastos, total de gastos y beneficio. Una categoría solo aparece si tiene importe en alguna columna; los tres totales aparecen siempre. Los importes son positivos cuando suman a su lado: un ingreso de 100 € vale `100` en ingresos y un gasto de 40 € vale `40` en gastos. El beneficio es el total de ingresos menos el total de gastos. ### Columnas Cada columna de `meta.columns` tiene un `id`, que es la clave de `values` en cada fila, y un `kind`: | `kind` | Campos | Valor en cada fila | |---|---|---| | `movement` | `start`, `end` | Importe del periodo, con las dos fechas incluidas. | | `difference` | `current`, `reference` | Importe de `current` menos el de `reference`. | | `varianceRatio` | `current`, `reference` | Variación como fracción: `0.2` es un 20 %. Vale `null` si la referencia es cero. | Sin opciones de comparativa hay una sola columna, `current`. Con `compare` las columnas son `current` y `reference`, más `difference` y `variance` si se piden. Con `breakdown` son `p0`, `p1`... por orden de fechas, más `total` si se pide. ### Qué importes entran El resumen suma los apuntes de las cuentas de los grupos 6 (gastos) y 7 (ingresos) del periodo, sin el asiento de regularización de fin de año. Por eso coincide con sumas y saldos de esos grupos y con el resultado de pérdidas y ganancias excluyendo la regularización. Cada importe va a una categoría: 1. Las cuentas de diferencias de cambio de la configuración contable (768000 y 668000 por defecto) van a `currencyGains` y `currencyLosses`, sea cual sea el documento que las genera. 2. El resto se reparte por el documento que genera el asiento: las facturas de venta en `invoices`, los gastos en `bills` y las nóminas en `payrolls`. 3. Lo que queda del grupo 68 va a `depreciation`. 4. Todo lo demás va a `otherIncome` u `otherExpenses`. ## Estructura de los informes contables Pérdidas y ganancias y el balance de situación tienen `meta`, `rows` y `diagnostics`. `meta` añade a lo del resumen: | Campo | Tipo | Significado | |---|---|---| | `meta.template` | object | Modelo oficial: `id`, `variant` (`pyme`, o `pymesfl` para entidades sin fines lucrativos, según la empresa) y `version`. | | `meta.detail` | enum | Nivel de detalle aplicado: `summary`, `accounts` o `full`. | | `meta.policies` | object | `includeClosure`, `excludeRevenueClearing` (solo pérdidas y ganancias) y `tags`, si se filtró por etiquetas. | Cada fila de `rows`: | Campo | Tipo | Significado | |---|---|---| | `id` | string | Identificador estable: el epígrafe del modelo (`A_1`, `PC_2_3`…). Un grupo añade su prefijo de cuenta (`A_1/700`) y una fila de detalle, su cuenta y tercero (`A_1/700/700000/con_…`). | | `parentId` | string | Fila cuyo total incluye esta. Puede faltar en `rows` si todas sus columnas son cero. | | `kind` | enum | `accounts` o `sum` (epígrafes), `group` (grupo de cuentas), `detail` (cuenta y tercero), `unregularizedResult` (resultado aún sin regularizar, en el balance) o `imbalance` (descuadre del balance). | | `level` | integer | Nivel de sangría, el de la web. | | `title` | string | Título en el idioma de la petición. | | `accountPrefix` | string | Prefijo de cuenta de un grupo. | | `account`, `accountTitle` | string | Cuenta de una fila de detalle y su título en el plan contable. | | `thirdParty` | object | Tercero de una fila de detalle: `id`, `type` y `name`. | | `values` | object | Valor de cada columna por su `id`. | Una fila cuyas columnas son todas cero no aparece, igual que en la web. En el balance, cada columna es de tipo `balanceAt`: el saldo acumulado a su `date`. Con `revenueShare`, pérdidas y ganancias añade columnas `shareOfRow` con el peso de cada fila sobre el importe neto de la cifra de negocios. Cada aviso de `diagnostics` tiene `code`, `severity` (`info` o `warning`), `message` en el idioma de la petición y, según el caso, `years` o `accounts`. Son los mismos avisos que muestra la web, por ejemplo un ejercicio anterior sin regularizar (`previousYearNotClosed`) o cuentas con saldo fuera del modelo (`unmappedBalances`). Sumas y saldos tiene `meta`, `rows`, `totals` y `pagination`. Cada fila es un subtotal (`group`, con `accountPrefix`), una cuenta (`account`) o un tercero de una cuenta (`thirdParty`), con sus importes en `amounts`: `previousBalance`, `debit`, `credit` y `balance`. `totals` es el total del informe completo, no solo de la página. ## Operaciones - [Obtener el resumen de resultados](#obtener-el-resumen-de-resultados) - [Listar los documentos de una categoría](#listar-los-documentos-de-una-categoría) - [Obtener pérdidas y ganancias](#obtener-pérdidas-y-ganancias) - [Obtener el balance de situación](#obtener-el-balance-de-situación) - [Obtener sumas y saldos](#obtener-sumas-y-saldos) ## Obtener el resumen de resultados `GET /{companyId}/reports/resultsSummary` | Parámetro | Valores | Por defecto | Significado | |---|---|---|---| | `startDate`, `endDate` | fecha `YYYY-MM-DD` | año natural en curso | Periodo del informe, con las dos fechas incluidas. Como máximo, 5 años. | | `compare` | `none`, `previousPeriod`, `previousYear` | `none` | Añade un periodo de referencia: el anterior de la misma duración o el mismo periodo del año anterior. | | `breakdown` | `none`, `month`, `quarter`, `year` | `none` | Divide el periodo en meses, trimestres o años naturales, como máximo 24 columnas. | | `difference` | booleano | `false` | Con `compare`, añade la columna `difference`. | | `variance` | booleano | `false` | Con `compare`, añade la columna `variance`. | | `total` | booleano | `false` | Con `breakdown`, añade la columna `total` con el periodo completo. | `compare` y `breakdown` no se combinan. `difference` y `variance` necesitan `compare`, y `total` necesita `breakdown`. Cualquier combinación no válida responde `400`. ### Ejemplo: ingresos, gastos y beneficio del año comparados con el anterior ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/reports/resultsSummary?startDate=2025-01-01&endDate=2025-12-31&compare=previousYear&difference=true" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```json { "meta": { "report": "resultsSummary", "currency": "EUR", "columns": [ { "id": "current", "kind": "movement", "start": "2025-01-01", "end": "2025-12-31" }, { "id": "reference", "kind": "movement", "start": "2024-01-01", "end": "2024-12-31" }, { "id": "difference", "kind": "difference", "current": "current", "reference": "reference" } ], "generatedAt": "2025-12-31T10:00:00.000Z" }, "rows": [ { "id": "invoices", "kind": "category", "side": "income", "title": "Ventas facturadas", "values": { "current": 98000, "reference": 91500, "difference": 6500 } }, { "id": "currencyGains", "kind": "category", "side": "income", "title": "Diferencias de cambio a favor", "values": { "current": 1200, "reference": 0, "difference": 1200 } }, { "id": "income", "kind": "total", "title": "Total ingresos", "values": { "current": 99200, "reference": 91500, "difference": 7700 } }, { "id": "bills", "kind": "category", "side": "expenses", "title": "Compras y gastos", "values": { "current": 41500, "reference": 39000, "difference": 2500 } }, { "id": "payrolls", "kind": "category", "side": "expenses", "title": "Nóminas", "values": { "current": 22000, "reference": 21000, "difference": 1000 } }, { "id": "expenses", "kind": "total", "title": "Total gastos", "values": { "current": 63500, "reference": 60000, "difference": 3500 } }, { "id": "profit", "kind": "total", "title": "Beneficio", "values": { "current": 35700, "reference": 31500, "difference": 4200 } } ] } ``` ### Ejemplo: evolución mensual ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/reports/resultsSummary?startDate=2025-01-01&endDate=2025-12-31&breakdown=month&total=true" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` Devuelve las columnas `p0` (enero) a `p11` (diciembre) y `total`. ## Listar los documentos de una categoría `GET /{companyId}/reports/resultsDetail` | Parámetro | Valores | Por defecto | Significado | |---|---|---|---| | `category` | una de las categorías | obligatorio | Categoría cuyos documentos se quieren ver. | | `startDate`, `endDate` | fecha `YYYY-MM-DD` | año natural en curso | Periodo, con las dos fechas incluidas. Como máximo, 5 años. | | `limit`, `offset` | ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) | 25, 0 | Paginación por desplazamiento, hasta 500 por página. | Con la categoría y las fechas de una columna del resumen, la suma de `amount` de todas las páginas es el importe de esa celda. La respuesta tiene `meta` (con `category`, `side` y `period`), `items` y `pagination`. Cada elemento de `items`: | Campo | Tipo | Significado | |---|---|---| | `id` | string | Documento que genera los apuntes (`inv_…`, `bil_…`, `par_…`, `tra_…`). | | `type` | string | Tipo del documento (`invoice`, `bill`, `payroll`, `transaction`…), o `null` si no se reconoce. | | `title` | string | Título del documento. | | `date` | date | Fecha del primer apunte del documento en el periodo. | | `amount` | number | Importe que el documento aporta a la categoría, con el signo de su lado. | Los documentos van de más antiguo a más reciente. Con el `id` puedes abrir el documento en su recurso, por ejemplo [`GET /{companyId}/invoices/{id}`](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/). ### Ejemplo ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/reports/resultsDetail?category=currencyGains&startDate=2025-01-01&endDate=2025-12-31" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```json { "meta": { "report": "resultsDetail", "currency": "EUR", "category": "currencyGains", "side": "income", "period": { "start": "2025-01-01", "end": "2025-12-31" }, "generatedAt": "2025-12-31T10:00:00.000Z" }, "items": [ { "id": "", "type": "transaction", "title": "Cobro en dólares", "date": "2025-04-01", "amount": 1200 } ], "pagination": { "limit": 25, "offset": 0, "total": 1 } } ``` ## Obtener pérdidas y ganancias `GET /{companyId}/reports/profitLoss` | Parámetro | Valores | Por defecto | Significado | |---|---|---|---| | `startDate`, `endDate` | fecha `YYYY-MM-DD` | año natural en curso | Periodo, con las dos fechas incluidas. Como máximo, 5 años. | | `compare`, `breakdown`, `difference`, `variance`, `total` | como en el resumen | | Mismas opciones de comparativa que el resumen de resultados. | | `revenueShare` | booleano | `false` | Añade tras cada columna de periodo otra (`_share`) con el peso de cada fila sobre la cifra de negocios. | | `detail` | `summary`, `accounts`, `full` | `accounts` | Nivel de detalle: solo epígrafes, con grupos de cuentas o con cada cuenta y tercero. | | `includeClosure` | booleano | `false` | Incluye la regularización y el cierre fechados el último día del periodo. | | `excludeRevenueClearing` | booleano | `false` | Excluye la regularización de ingresos y gastos en todo el periodo. | | `allTheseTags` | texto, repetible | | Solo los apuntes de documentos con todas estas etiquetas. | ### Ejemplo ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/reports/profitLoss?startDate=2025-01-01&endDate=2025-12-31&detail=summary&compare=previousYear" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```json { "meta": { "report": "profitLoss", "template": { "id": "profitLoss", "variant": "pyme", "version": 1 }, "currency": "EUR", "detail": "summary", "policies": { "includeClosure": false, "excludeRevenueClearing": false }, "columns": [ { "id": "current", "kind": "movement", "start": "2025-01-01", "end": "2025-12-31" }, { "id": "reference", "kind": "movement", "start": "2024-01-01", "end": "2024-12-31" } ], "generatedAt": "2025-12-31T10:00:00.000Z" }, "rows": [ { "id": "A_1", "parentId": "A", "kind": "accounts", "level": 2, "title": "1. Importe neto de la cifra de negocios", "values": { "current": 98000, "reference": 91500 } }, { "id": "A_4", "parentId": "A", "kind": "accounts", "level": 2, "title": "4. Aprovisionamientos", "values": { "current": -41500, "reference": -39000 } }, { "id": "A", "parentId": "C", "kind": "sum", "level": 1, "title": "A) RESULTADO DE EXPLOTACIÓN ( 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 )", "values": { "current": 56500, "reference": 52500 } }, { "id": "D", "kind": "sum", "level": 1, "title": "D) RESULTADO DEL EJERCICIO (C + 17)", "values": { "current": 56500, "reference": 52500 } } ], "diagnostics": [] } ``` Los gastos aparecen en negativo, con el signo de presentación del modelo oficial. ## Obtener el balance de situación `GET /{companyId}/reports/balanceSheet` | Parámetro | Valores | Por defecto | Significado | |---|---|---|---| | `date` | fecha `YYYY-MM-DD` | 31 de diciembre del año en curso | Fecha del saldo. | | `startDate` | fecha `YYYY-MM-DD` | 1 de enero del año de `date` | Inicio del periodo para `breakdown` y `compare=previousPeriod`. Como máximo, 5 años antes de `date`. | | `compare` | `none`, `previousPeriod`, `previousYear` | `none` | Saldo al final del periodo anterior (el día antes de `startDate`) o en la misma fecha del año anterior. | | `breakdown` | `none`, `month`, `quarter`, `year` | `none` | Saldo al final de cada mes, trimestre o año del periodo, como máximo 24 columnas. | | `difference`, `variance` | booleano | `false` | Con `compare`, añaden la diferencia y la variación. | | `detail` | `summary`, `accounts`, `full` | `accounts` | Nivel de detalle, como en pérdidas y ganancias. | | `includeClosure` | booleano | `false` | Incluye la regularización y el cierre fechados en la fecha del saldo. | | `allTheseTags` | texto, repetible | | Solo los apuntes de documentos con todas estas etiquetas. | El saldo de cada columna suma todos los apuntes hasta su fecha. El resultado del ejercicio se calcula desde la cuenta de pérdidas y ganancias, aunque el año no esté regularizado. Si el activo no coincide con el patrimonio neto y el pasivo, aparece la fila `imbalance` con la diferencia y el aviso `imbalance`. Con `detail=full` el informe resuelve el tercero de todos los documentos de la historia, y en empresas con mucho volumen puede tardar varios segundos. ## Obtener sumas y saldos `GET /{companyId}/reports/trialBalance` | Parámetro | Valores | Por defecto | Significado | |---|---|---|---| | `startDate`, `endDate` | fecha `YYYY-MM-DD` | año natural en curso | Periodo. El saldo anterior es todo lo anterior a `startDate`, con el asiento de apertura de ese día. | | `account` | cuenta o prefijo | | `43` incluye todas las cuentas de clientes y `430000` solo esa cuenta. | | `groups` | `1`, `2`, `3`, repetible | | Añade subtotales por los primeros dígitos de la cuenta. | | `thirdParty` | id de contacto, producto o banco | | Solo los apuntes de ese tercero, con el criterio del balance. | | `thirdPartyDetail` | booleano | `false` | Añade bajo cada cuenta una fila por tercero. Necesita `thirdParty`. | | `showCurrency` | booleano | `false` | Separa cada cuenta por moneda, con los importes en la moneda original en `currencyAmounts`. | | `excludeBalance0` | booleano | `false` | Omite las filas con saldo final cero. | | `includeClosure`, `excludeRevenueClearing` | booleano | `false` | Como en pérdidas y ganancias. | | `allTheseTags` | texto, repetible | | Solo los apuntes de documentos con todas estas etiquetas. | | `limit`, `offset` | ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) | 25, 0 | La paginación se aplica a las filas ya agrupadas. | > Para el saldo contable de un banco no hace falta este informe: > `GET /{companyId}/banks/{id}?related=balance` lo da con el mismo > criterio (y con `balanceDate`, a una fecha), en todos los planes y con > el scope `banks:read`. Ver [Bancos](https://www.facturadirecta.com/desarrolladores/api/referencia/banks/). `thirdPartyDetail` necesita `thirdParty` porque el detalle por tercero de una cuenta entera, como la de clientes, puede tardar decenas de segundos en empresas con miles de clientes. ### Ejemplo: saldo de un cliente ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/reports/trialBalance?startDate=2025-01-01&endDate=2025-12-31&account=430&thirdParty=&thirdPartyDetail=true" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```json { "meta": { "report": "trialBalance", "currency": "EUR", "period": { "start": "2025-01-01", "end": "2025-12-31" }, "groups": [], "thirdPartyDetail": true, "policies": { "includeClosure": false, "excludeRevenueClearing": false }, "generatedAt": "2025-12-31T10:00:00.000Z" }, "rows": [ { "id": "430000", "kind": "account", "level": 0, "title": "430000 Clientes", "account": "430000", "accountTitle": "Clientes", "amounts": { "previousBalance": 0, "debit": 1210, "credit": 0, "balance": 1210 } }, { "id": "430000/", "kind": "thirdParty", "level": 1, "title": "Cliente Uno", "account": "430000", "accountTitle": "Clientes", "thirdParty": { "id": "", "type": "contact", "name": "Cliente Uno" }, "amounts": { "previousBalance": 0, "debit": 1210, "credit": 0, "balance": 1210 } } ], "totals": { "previousBalance": 0, "debit": 1210, "credit": 0, "balance": 1210 }, "pagination": { "limit": 25, "offset": 0, "total": 2 } } ``` ## Permisos Todas las operaciones necesitan el scope `accounting:read`, el mismo que el [diario](https://www.facturadirecta.com/desarrolladores/api/referencia/journal/): los informes muestran el beneficio y el gasto en nóminas de la empresa. Pérdidas y ganancias, el balance de situación y sumas y saldos necesitan además el **módulo de Contabilidad**. Sin él responden `403` con `plan_limit_exceeded` y `hint.features: ["fullAccounting"]`, sin enviar ningún aviso a los administradores (ver [Errores](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#informes-no-incluidos-en-el-plan)). Una aplicación conectada puede saber de antemano si una empresa lo tiene con `accountingModule` en el [perfil](https://www.facturadirecta.com/desarrolladores/api/referencia/profile/). ## Errores comunes - `400 Bad Request`: fechas no válidas, también las que no existen como `2026-02-30` (nunca se sustituyen por el periodo por defecto), `startDate` posterior a la fecha final, un periodo de más de 5 años, `compare` y `breakdown` a la vez, `difference`, `variance` o `total` sin la opción que necesitan, más de 24 columnas o `thirdPartyDetail` sin `thirdParty`. - `403 Forbidden`: la credencial no tiene el scope `accounting:read`, o la empresa no tiene el módulo de Contabilidad (`plan_limit_exceeded`). - `422 Unprocessable Entity` (`report_query_timeout`): el informe ha tardado demasiado. Acota el periodo, el nivel de detalle o las columnas. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/reports/balanceSheet` | `getBalanceSheet` | `accounting:read` | Balance de situación | | GET | `/{companyId}/reports/profitLoss` | `getProfitLoss` | `accounting:read` | Pérdidas y ganancias | | GET | `/{companyId}/reports/resultsDetail` | `getResultsDetail` | `accounting:read` | Detalle del resumen de resultados | | GET | `/{companyId}/reports/resultsSummary` | `getResultsSummary` | `accounting:read` | Resumen de resultados | | GET | `/{companyId}/reports/trialBalance` | `getTrialBalance` | `accounting:read` | Sumas y saldos | ## Scopes - **`accounting:read`** — Lectura de datos de contabilidad (diario). Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/reports.md --- # Declaraciones de impuestos > Una declaración de impuestos (taxDeclaration) es un modelo de impuestos que el usuario ha generado y guardado en FacturaDirecta desde la sección Impuestos: un 303 trimestral, un 347 anual, un 130…… Una **declaración de impuestos** (`taxDeclaration`) es un **modelo de impuestos** que el usuario ha generado y guardado en FacturaDirecta desde la sección **Impuestos**: un 303 trimestral, un 347 anual, un 130… Es la misma información que ve el usuario en la web: el número de modelo, el periodo, el resultado, las casillas que se escriben en el fichero de presentación de la AEAT y, en las declaraciones informativas, los terceros declarados. El recurso es de **solo lectura**. La API no genera, rellena ni presenta modelos: el usuario los genera en la web y los presenta en la Sede de la AEAT. Lo que sí hace la API es dejar **descargar el PDF y el fichero de presentación** de un modelo ya guardado, los mismos que descargan los botones de la web. No confundas este recurso con el **catálogo de impuestos** de la empresa (los tipos de IVA, IRPF o IGIC que se aplican a las líneas de los documentos), que está en `/settings/taxes/*` y se describe en la [guía del catálogo de impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). ## Permisos - El scope es `taxDeclarations:read`. Con OAuth, el rol del usuario en la empresa necesita además el permiso de **impuestos** en lectura, el mismo que da acceso a la sección Impuestos de la web. - Una API key accede si tiene el scope. Al crearla, solo puede concederle `taxDeclarations:read` quien tiene el permiso de impuestos. - El IBAN de domiciliación o devolución que algunos modelos llevan en sus casillas sale **enmascarado** (`****1332`) salvo que el token o la API key tengan también `banks:readIban`, igual que en [bancos](https://www.facturadirecta.com/desarrolladores/api/referencia/banks/) y [métodos de pago](https://www.facturadirecta.com/desarrolladores/api/referencia/payment-methods/). - Leer los modelos no depende de los módulos del plan: si el usuario pudo generarlos, la API los devuelve. ## Dos capas: el sobre y las casillas Cada declaración tiene dos partes con compromisos de estabilidad distintos: - **El sobre** es todo lo que no son casillas: identificador, modelo, periodo, resultado, estado, origen, fichero de presentación disponible y número de adjuntos. Es el contrato de la API: sus campos no cambian de significado y solo se añaden. - **Las casillas** (`boxes`) y los **registros por tercero** (`records`) siguen el **diseño de registro de la AEAT** que indica `formVersion`. Para una `formVersion` dada el conjunto de claves no cambia nunca; cuando la AEAT publica un diseño nuevo, los modelos generados a partir de entonces llevan otra `formVersion` y pueden traer casillas nuevas o dejar de traer alguna. Trata las claves como un conjunto abierto. Los valores de `form`, `kind`, `status`, `origin` y `result.declarationType` son **enums abiertos**: tu integración debe tolerar valores que no conozca. ## Estructura de una declaración ```json { "id": "tax_7c9e6679-7425-40de-944b-e07fc1f90ae7", "form": "303", "kind": "selfAssessment", "fiscalYear": 2026, "fiscalPeriod": "3T", "periodStart": "2026-07-01", "periodEnd": "2026-09-30", "generatedAt": "2026-10-14T09:12:33.000Z", "formVersion": "303_e26v101", "result": { "amount": 1842.5, "currency": "EUR", "declarationType": "U" }, "status": "generated", "filing": null, "origin": "facturadirecta", "aeatFile": { "available": true, "filename": "modelo_303.303" }, "attachmentsCount": 1, "boxes": [ { "key": "ejercicio", "title": "Ejercicio devengo", "value": "2026" }, { "key": "periodo", "title": "Período.", "value": "3T" }, { "key": "tipoDeclaracion", "title": "Tipo Declaración", "value": "U" }, { "key": "07", "title": "Liquidación (3) - Regimen General - IVA Devengado - Base imponible 21%", "value": 12500 }, { "key": "09", "title": "Liquidación (3) - Regimen General - IVA Devengado - Cuota 21%", "value": 2625 }, { "key": "27", "title": "Liquidación (3) - Regimen General - IVA Devengado - Total cuota devengada ( [152] + [167] + [03] + [155] + [06] + [09] + [11] + [13] + [15] + [158] + [170] + [18] + [21] + [24] + [26] )", "value": 2625 }, { "key": "45", "title": "Liquidación (3) - Regimen General - IVA Deducible - Total a deducir ( [29] + [31] + [33] + [35] + [37] + [39] + [41] + [42] + [43] + [44] ) - Cuota", "value": 782.5 }, { "key": "71", "title": "Resultado - Resultado ( [69] - [70] + [109] - [112] )", "value": 1842.5 }, { "key": "iban", "title": "Domiciliación/Devolución - IBAN", "value": "****1332" } ] } ``` El ejemplo está recortado: un 303 real trae unas sesenta casillas, y en el listado solo aparecen con `related=boxes`. | Campo | Tipo | Significado | |---|---|---| | `id` | string | Identificador opaco y estable del modelo (`tax_` y un uuid v4). Sirve en el resto de operaciones y en [adjuntos](#listar-los-adjuntos-de-una-declaración) y [actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/). | | `form` | string | Número del modelo de la AEAT: `303`, `111`, `115`, `130`, `202`, `180`, `190`, `347`, `349` o `390`. Enum abierto. | | `kind` | string | `selfAssessment` en las autoliquidaciones (303, 111, 115, 130, 202), que liquidan un importe; `informative` en las declaraciones informativas (180, 190, 347, 349, 390), que solo declaran operaciones o terceros. | | `fiscalYear` | entero | Ejercicio. | | `fiscalPeriod` | string \| null | Trimestre (`1T`…`4T`), mes (`01`…`12`) o pago fraccionado del 202 (`1P`…`3P`). `null` en los modelos anuales. | | `periodStart`, `periodEnd` | date | Primer y último día del periodo declarado. En los anuales y en el 202, el ejercicio entero. | | `generatedAt` | date-time \| null | Momento en que se calcularon las casillas. Si el usuario regenera el modelo, cambia. | | `formVersion` | string | Diseño de registro de la AEAT aplicado, por ejemplo `303_e26v101`. Ver [las versiones por modelo](#versiones-del-diseño-de-registro). | | `result` | objeto | `amount`: resultado de la autoliquidación en `currency` (positivo a ingresar, negativo a compensar o devolver; `null` en las informativas). `declarationType`: código de la AEAT del tipo de declaración, cuando el modelo lo tiene. | | `status` | string | Hoy siempre `generated`: FacturaDirecta no registra la presentación. Enum abierto: llegarán otros valores (como `filed`) sin cambio de versión de la API. | | `filing` | objeto \| null | Datos de presentación (fecha, CSV, NRC, canal). Hoy nada los registra y vale siempre `null`; el campo existe para que el contrato no cambie cuando se registren. | | `origin` | string | Dónde se generó el modelo. Hoy siempre `facturadirecta`. Enum abierto. | | `aeatFile` | objeto | `available` indica si el modelo tiene fichero de presentación (el 390 no: se rellena en la Sede) y `filename` el nombre con el que lo devuelve la API. | | `attachmentsCount` | entero | Adjuntos del modelo, por ejemplo el justificante de presentación si el usuario lo adjuntó. | | `boxes` | array | Casillas del modelo (ver abajo). Siempre en el detalle; en el listado, con `related=boxes`. | | `records` | objeto \| null | Registros por tercero de las informativas (ver abajo). Solo en el detalle, con `related=records`. | ### Los códigos de `result.declarationType` Son los del diseño de registro de la AEAT y solo existen en las autoliquidaciones que lo llevan (303, 130…): `I` ingreso, `U` domiciliación del ingreso, `G` cuenta corriente tributaria (ingreso), `C` solicitud de compensación, `D` devolución, `V` cuenta corriente tributaria (devolución) y `N` sin actividad o resultado cero. ### Las casillas: `boxes` `boxes` son las casillas **tal como se escriben en el fichero de presentación de la AEAT**, en el orden del diseño de registro, también las que valen cero. Cada una lleva: - **`key`** — el número oficial de la casilla (`27`, `71`) o la clave de control del diseño de registro: `ejercicio`, `periodo`, `tipoDeclaracion`, `iban`, `bic`, las marcas de régimen del 303 (`RECC`, `prorrata`, `volOperaciones`…) o los totales de cabecera de las informativas (`numPersonas`, `importeAnual`). - **`title`** — la descripción de la casilla en el formulario. Es texto de ayuda, no contrato. - **`value`** — un número en las casillas de importe y de contadores; un código o texto en las demás. No se publican el relleno técnico del fichero (constantes, longitudes, sello), la identidad de la empresa (NIF y razón social, que ya da [`GET /settings/company`](https://www.facturadirecta.com/desarrolladores/api/referencia/settings/)) ni los registros por tercero, que van en `records`. Para interpretar una casilla concreta, usa las instrucciones del modelo de la AEAT para la `formVersion` indicada. ### Los terceros: `records` Las declaraciones informativas no liquidan nada: declaran con quién se ha operado, y su fichero lleva un registro por cada tercero. `records` los devuelve con dos listas: `fields`, los campos de cada registro (`key` y `title`, en el orden del diseño), e `items`, un objeto por tercero con el valor de cada campo bajo su `key`. | Modelo | Registros | Campos típicos | |---|---|---| | 347 | Clientes y proveedores con más de 3.005,06 € en el año | `fiscalId`, `name`, `provincia`, `pais`, `clave`, `importeAnual`, `importe1T`…`importe4T` | | 349 | Operadores intracomunitarios | `fiscalId`, `name`, `clave`, `base` | | 190 | Perceptores con retención (empleados, profesionales) | `fiscalId`, `name`, `region`, `clavePercepcion`, `subclavePercepcion`, percepciones y retenciones | | 180 | Arrendadores de inmuebles urbanos | `fiscalId`, `name`, `region`, `modalidad`, `baseRetencion`, `porcentaje`, `retencion` | Los campos exactos de cada modelo son los de su `formVersion`. Los registros llevan datos personales de terceros (NIF y nombre), los mismos que ya puedes leer con `contacts:read` y `bills:read`: trátalos con el mismo cuidado. ### Versiones del diseño de registro `formVersion` identifica el diseño de registro de la AEAT con el que se generó el modelo y, por tanto, qué casillas tiene. Se elige por la fecha del modelo (el último día de su periodo) y se queda fijo en el documento guardado. Los valores actuales, del más reciente al más antiguo: | Modelo | `formVersion` | |---|---| | 303 | `303_e26v101` (desde 2026), `303_e25v102` (2025), `303_e24v201` (desde el 4T de 2024), `303_e24v201_3T` (3T de 2024), `303_e23`, `303_e22`, `303_e21v200` (desde el 3T de 2021), `303_e21v103`, `303_e19v10_10`, `303_e18v10_20`, `303_e18v10_10`, `303_e17v20_04`, `303_e15v32` | | 390 | `390_2026`, `390_2025`, `390_2024`, `390_2023`, `390_2022`, `390_2021`, `390_2018`, `390_2017` | | 190 | `190_2025`, `190_2024`, `190_2023`, `190_2022`, `190` | | 347 | `347_e25` (desde 2025), `347` | | 202 | `202_e25` (desde 2025), `202_e23` (desde 2023), `202_e17v48` | | 130 | `130_e15v12` (desde el 2T de 2024), `130_e15v11` | | 111, 115, 180, 349 | `111`, `115`, `180`, `349` | ## Operaciones - [Listar declaraciones](#listar-declaraciones) - [Obtener una declaración](#obtener-una-declaración) - [Generar el PDF de una declaración](#generar-el-pdf-de-una-declaración) - [Generar el fichero de presentación](#generar-el-fichero-de-presentación) - [Listar los adjuntos de una declaración](#listar-los-adjuntos-de-una-declaración) ## Listar declaraciones `GET /{companyId}/taxDeclarations` devuelve los modelos guardados de la empresa con **paginación por offset** (patrón A de la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/)). Los modelos eliminados no salen. Sin ordenación explícita, van del más reciente al más antiguo por la fecha del modelo. **Respuesta:** `{ pagination, items: TaxDeclarationResponse[] }`. Cada elemento lleva `content` (la declaración, sin `boxes` salvo que los pidas), `tags`, `creationDate`, `modificationDate`, `webUrl` (la página del modelo en la web) y, con `related=journal`, `related.journal`. **Parámetros de consulta:** - `form` — número de modelo. Se repite para indicar varios (`form=303&form=390`). Un valor que no es un modelo conocido responde `400`. - `kind` — `selfAssessment` o `informative`. Se puede combinar con `form`; se devuelven los modelos que cumplen los dos. - `fiscalYear` — ejercicio. - `fiscalPeriod` — `1T`…`4T`, `01`…`12` o `1P`…`3P`. Admite varios. Los modelos anuales no tienen periodo y no salen al filtrar por él. - `minDate` / `maxDate` — fecha mínima y máxima del modelo, ambas incluidas, en formato `YYYY-MM-DD`. La fecha de un modelo es el último día de su periodo (el 30 de septiembre para un 3T, el 31 de diciembre para un anual). - `status` — hoy solo admite `generated`, que es el estado de todos los modelos guardados; el filtro existe para que puedas fijarlo desde el principio y no restringe nada. - `sortBy` — `date`, `total` (el resultado), `fiscalYear`, `creationDate` o `modificationDate`, con `-` delante para el orden descendente. Admite varios: `sortBy=-fiscalYear&sortBy=date`. A igual criterio, los resultados se ordenan por fecha de creación descendente. - `related` — `boxes` añade las casillas a cada elemento; `journal`, su asiento contable en `related.journal`. Los registros por tercero solo se piden en el detalle: `related=records` responde `400` aquí. **Parámetros globales aceptados:** - Etiquetas: `allTheseTags`, `anyOfTheseTags`, `hasTags`. - Fechas de alta y modificación: `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate`. - Paginación: `limit` (por defecto `25`, máximo `500`) y `offset`. - El header `accept-version`. Ver la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/) para el detalle. ###### Copy as cURL Modelos de IVA de 2026 con sus casillas: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/taxDeclarations?form=303&fiscalYear=2026&related=boxes" ``` Declaraciones informativas de 2025: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/taxDeclarations?kind=informative&fiscalYear=2025" ``` ## Obtener una declaración `GET /{companyId}/taxDeclarations/{id}` devuelve un modelo con sus **casillas** completas. **Parámetros de consulta:** - `related` — `records` añade los registros por tercero (solo tienen las informativas; en las demás `records` vale `null`) y `journal` el asiento contable del modelo en `related.journal`. **Parámetros globales aceptados:** el header `accept-version`. **Respuesta:** `{ content: TaxDeclaration, tags, creationDate, modificationDate, webUrl, related? }`. Un modelo eliminado responde `404`. ###### Ejemplo de respuesta Un 347 con sus declarados: ```json { "content": { "id": "tax_0b6f0f0e-1f3c-4a2d-9c4e-7a8b9c0d1e2f", "form": "347", "kind": "informative", "fiscalYear": 2025, "fiscalPeriod": null, "periodStart": "2025-01-01", "periodEnd": "2025-12-31", "generatedAt": "2026-01-20T08:00:00.000Z", "formVersion": "347_e25", "result": { "amount": null, "currency": "EUR", "declarationType": null }, "status": "generated", "filing": null, "origin": "facturadirecta", "aeatFile": { "available": true, "filename": "modelo_347.347" }, "attachmentsCount": 0, "boxes": [ { "key": "ejercicio", "title": "Ejercicio", "value": "2025" }, { "key": "contactPhone", "title": "Teléfono", "value": 600000000 }, { "key": "contactName", "title": "Apellidos y nombre de la persona con quien relacionarse", "value": "PEPA PEREZ" }, { "key": "numPersonas", "title": "Número total de personas y entidades (número de registros tipo 2)", "value": 2 }, { "key": "importeAnual", "title": "Importe total anual de las operaciones", "value": 9000 } ], "records": { "fields": [ { "key": "fiscalId", "title": "NIF del declarado" }, { "key": "name", "title": "Apellidos y nombre, razon social o denominación del declarado" }, { "key": "provincia", "title": "Código provincia" }, { "key": "pais", "title": "Código pais" }, { "key": "clave", "title": "Clave de operación (A..G)" }, { "key": "importeAnual", "title": "Importe anual de las operaciones" } ], "items": [ { "fiscalId": "12345678Z", "name": "CLIENTE UNO", "provincia": "08", "pais": "ES", "clave": "B", "importeAnual": 5000 }, { "fiscalId": "87654321X", "name": "PROVEEDOR DOS", "provincia": "28", "pais": "ES", "clave": "A", "importeAnual": 4000 } ] } }, "tags": [], "creationDate": "2026-01-20T08:00:05.000Z", "modificationDate": "2026-01-20T08:00:05.000Z", "webUrl": "https://3.facturadirecta.com/company//doc/tax_0b6f0f0e-1f3c-4a2d-9c4e-7a8b9c0d1e2f" } ``` `` es un placeholder. Las casillas y los campos de los registros están recortados. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/taxDeclarations/tax_0b6f0f0e-1f3c-4a2d-9c4e-7a8b9c0d1e2f?related=records" ``` ## Generar el PDF de una declaración `PUT /{companyId}/taxDeclarations/{id}/pdf` genera el PDF resumen del modelo, con sus casillas y valores, el mismo que imprime la web, y devuelve una URL temporal desde la que descargarlo. Funciona igual que el PDF de las [facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#generar-pdf): el cuerpo lleva `mode` (`attachment`, `inline` o `print`) y la respuesta `url`, `filename` (`modelo_303_2026_3T.pdf`), `availableUntil` y, en modo `print`, `urlPdf`. El PDF se genera solo si aún no existe o si el modelo ha cambiado desde la última generación; si no, se reutiliza el ya generado, como el PDF de las facturas. No calcula ni modifica el modelo, y cada generación (no cada descarga) deja en la [actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/) del documento la misma traza `file` (formato `pdf`) que la web. No admite la cabecera `Idempotency-Key`: repetir la llamada devuelve el mismo fichero. ###### Copy as cURL ```shell curl -s -X PUT -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"mode": "attachment"}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/taxDeclarations/tax_7c9e6679-7425-40de-944b-e07fc1f90ae7/pdf" ``` ## Generar el fichero de presentación `PUT /{companyId}/taxDeclarations/{id}/aeatFile` devuelve el **fichero de presentación de la AEAT** (el diseño de registro BOE) de un modelo ya guardado, el mismo que descarga el botón «Descargar fichero AEAT» de la web, como `{ url, filename }`. `filename` es el que indica `aeatFile.filename` en la declaración (`modelo_303.303`). El fichero se compone solo si aún no existe o si el modelo ha cambiado desde la última generación; si no, se devuelve el ya generado, que normalmente es el que la web generó al crear el modelo. Funciona como el [Facturae de las facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#generar-facturae). Se construye a partir de las casillas guardadas: no calcula ni modifica nada, y cada generación (no cada descarga) deja en la [actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/) la misma traza `file` (formato `boefile`) que la web. Presentarlo sigue siendo cosa del usuario, en la Sede de la AEAT. No admite la cabecera `Idempotency-Key`: repetir la llamada devuelve el mismo fichero. El 390 no tiene fichero (se rellena en la Sede) y responde `404`; compruébalo antes con `aeatFile.available`. ###### Copy as cURL ```shell curl -s -X PUT -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/taxDeclarations/tax_7c9e6679-7425-40de-944b-e07fc1f90ae7/aeatFile" ``` ## Listar los adjuntos de una declaración `GET /{companyId}/taxDeclarations/{id}/attachments` devuelve `{ items: Attachment[] }` con los ficheros que el usuario adjuntó al modelo desde la web, por ejemplo el justificante de presentación. La API no interpreta su contenido. Cada adjunto lleva `name`, `url`, `creationTime` y, cuando se conoce, `size`. Añadir o borrar adjuntos por la API no está disponible en este recurso. ## Recomendaciones - **Para saber si una empresa ha generado un modelo**, filtra por `form`, `fiscalYear` y `fiscalPeriod`. Un modelo que no aparece no se ha generado en FacturaDirecta; uno que aparece se ha generado, pero la API no sabe si se ha presentado (`status` es siempre `generated` y `filing` es `null`). - **Guarda `formVersion` junto a las casillas** que proceses. Es lo que te dice qué significa cada `key` y te avisa de que un modelo nuevo puede traer casillas distintas. - **Pide `related=boxes` solo cuando las necesites**: un 303 trae unas sesenta casillas y un listado con ellas crece rápido. Para los terceros de una informativa, usa el detalle con `related=records`. - **Compara `generatedAt`** si guardas copias: si el usuario regenera un modelo, el `id` no cambia pero sus casillas sí, y las [versiones](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/) no están disponibles para este recurso; la [actividad](https://www.facturadirecta.com/desarrolladores/api/referencia/activity/) del documento registra cada modificación y cada generación de fichero. ## Errores comunes - `400 ValidationError` — `form`, `kind`, `fiscalPeriod`, `status` o `sortBy` llevan un valor desconocido, `fiscalYear` no es un año, `minDate`/`maxDate` no son una fecha `YYYY-MM-DD`, `related=records` en el listado, o `mode` del PDF no es `attachment`, `inline` ni `print`. - `403 Forbidden` (`code: "token_scope_missing"`) — falta el scope `taxDeclarations:read`. - `403 Forbidden` (`code: "user_role_insufficient"`) — el rol del usuario no tiene el permiso de impuestos. - `404 Not Found` — el modelo no existe o está eliminado, o se pide el fichero de presentación de un 390. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. ## Referencia exhaustiva Esta página cubre los matices funcionales y los casos típicos. Para el contrato completo, consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/taxDeclarations` | `getTaxDeclarations` | `taxDeclarations:read` | Lista de modelos de impuestos | | GET | `/{companyId}/taxDeclarations/{id}` | `getTaxDeclaration` | `taxDeclarations:read` | Obtener un modelo de impuestos | | GET | `/{companyId}/taxDeclarations/{id}/attachments` | `getTaxDeclarationAttachments` | `taxDeclarations:read` | Listar adjuntos de un modelo de impuestos | | PUT | `/{companyId}/taxDeclarations/{id}/aeatFile` | `createTaxDeclarationAeatFile` | `taxDeclarations:read` | Generar el fichero de presentación de un modelo de impuestos | | PUT | `/{companyId}/taxDeclarations/{id}/pdf` | `createTaxDeclarationPDF` | `taxDeclarations:read` | Generar el PDF de un modelo de impuestos | ## Scopes - **`taxDeclarations:read`** — Lectura de los modelos de impuestos generados (declaraciones), sus casillas, PDF y fichero de presentación. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/tax-declarations.md --- # Nóminas > Una nómina (payroll) representa la retribución mensual a un empleado: salario bruto, deducciones, retenciones de IRPF y aportaciones de Seguridad Social, tanto del trabajador como de la empresa. Una nómina (`payroll`) representa la **retribución mensual a un empleado**: salario bruto, deducciones, retenciones de IRPF y aportaciones de Seguridad Social, tanto del trabajador como de la empresa. El recurso modela el documento contable de la nómina, no la gestión laboral completa. > En los ejemplos de esta página: > > - Los **UUIDs** (`par_…`, `con_…`, `ban_…`, `tas_…`) son ilustrativos. > Cada empresa tiene los suyos; sustitúyelos por los identificadores > reales que devuelve la API. > - El `contact` de una nómina es un contacto **con faceta `employee`**. > Si el contacto no tiene esa faceta configurada > (`accounts.employee` asignada), la creación falla. ## Flujos para crear nóminas Puedes **construir el body manualmente** y enviarlo a [`POST /payrolls`](#crear-nómina). Es el flujo descrito en esta página. También puedes crearla **desde la bandeja de entrada**: subes el PDF de la nómina a la bandeja, el sistema lo escanea y extrae los conceptos. Llamas a [`POST /inbox/{id}/proposePayroll`](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/#proponer-nómina) para obtener un prototipo prerellenado, lo revisas y lo envías a `POST /payrolls` con `fromInbox: { taskId }`. Este flujo vincula la nómina con su origen y reutiliza el PDF sin re-upload. ## Estructura - `content.type` — siempre `"payroll"`. - `content.uuid` — identificador inmutable, prefijo `par_`. - `content.main`: - `contact` — ID del empleado (contacto con faceta `employee`). - `date` — fecha de la nómina. - `dueDate` — fecha de vencimiento del pago. - `lines` — array de [líneas de nómina](#líneas-de-la-nómina) (mínimo 1). - `modelo190` — datos para el modelo 190 de retenciones IRPF (ver [modelo 190](#modelo-190)). - `paymentMethod` — método de pago. - `currency`, `total`, `totalBeforeTaxes`, etc. - `customFields` — mapa de valores indexado por el ID estable `cfi_` de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las definiciones borradas siguen siendo válidas; un ID desconocido o con formato incorrecto produce `400 Bad Request`. - `content.attachments` — adjuntos (ver [Adjuntos](#adjuntos)). - `content.accounting`, `content.books` — overrides contables específicos (uso avanzado). ### Líneas de la nómina `content.main.lines` es un array con **al menos una línea**. Cada línea representa un concepto de la nómina identificado por `type`. Tipos admitidos (7): | `type` | Significado | Cuenta contable típica | |---|---|---| | `debit` | Percepción salarial o no salarial. Con `debitType` se distingue. | 640* (salarios) | | `credit` | Deducción (penalizaciones, anticipos, otras retenciones). | varía | | `irpf_employee` | Retención de IRPF sobre percepciones dinerarias. | 475100 | | `irpf_employee_kind` | Retención de IRPF sobre percepciones en especie. | 475100 | | `irpf_employee_kind_passedon` | Retención IRPF percepciones en especie transferida al empleado. | 475100 | | `ss_employee` | Aportación a la Seguridad Social a cargo del trabajador. | 476000 | | `ss_company` | Aportación a la Seguridad Social a cargo de la empresa. | 642000 | Campos de cada línea: - `type` (obligatorio) — uno de los 7 anteriores. - `account` (obligatorio) — cuenta contable. - `lineTotal` (obligatorio) — importe. - `base` (opcional) — base imponible, relevante para IRPF y SS. - `debitType` — solo en `type: "debit"`, valores `"salary"` o `"nonSalary"`. - `label` — etiqueta interna descriptiva. - `text` — etiqueta libre. **Las cuentas SS y el cargo a empresa**: `ss_company` aparece como línea aunque no implica un cargo al empleado (es un gasto que asume la empresa). El total de la nómina se calcula sobre lo que **percibe** el empleado y las retenciones aplicadas; la línea `ss_company` queda reflejada contablemente como gasto separado. ### modelo 190 `content.main.modelo190` describe la clave fiscal para la declaración anual de retenciones (modelo 190 de Hacienda): - `clavePercepcion` (obligatorio) — 1 letra mayúscula (A-Z) según el catálogo del modelo 190. La más común es **`A`** (rendimientos del trabajo en relación de dependencia). - `subclavePercepcion` (opcional) — 2 dígitos. Consulta el catálogo oficial del modelo 190 de la AEAT para la correspondencia exacta entre claves y casos. ## Enlace a la aplicación Las respuestas que devuelven una nómina incluyen `webUrl` junto al resto de los datos. En los listados, cada elemento de `items` lleva su propio enlace. `webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta. Puedes mostrarlo como enlace en tu integración sin construir rutas internas. El navegador pedirá iniciar sesión con un usuario que tenga acceso a la empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`. ## Operaciones - [Lista de nóminas](#lista-de-nóminas) - [Crear nómina](#crear-nómina) - [Obtener una nómina](#obtener-una-nómina) - [Actualizar nómina](#actualizar-nómina) - [Borrar nómina](#borrar-nómina) - [Actualizar etiquetas](#actualizar-etiquetas) - [Registrar pagos](#registrar-pagos) - [Adjuntos](#adjuntos) ## Lista de nóminas `GET /{companyId}/payrolls` devuelve las nóminas de la empresa, paginadas. **Parámetros de consulta específicos:** - **`search`** — búsqueda aproximada en el texto de la nómina (título, líneas, notas) y en el nombre del empleado: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)). - **`minDate`**, **`maxDate`** — rango por fecha de la nómina (`YYYY-MM-DD`). - **`contact`** — ID del empleado. - **`minTotal`**, **`maxTotal`** — rango de importe total. - **`state`** — estado del pago. Es un array; admite combinar valores: - `pending` — no pagada y no vencida. - `overdue` — no pagada y vencida. - `paid` — pagada en su totalidad. - `overpaid` — pagada de más (importe registrado superior al total). - **`allTheseTags`**, **`anyOfTheseTags`** — filtros por etiquetas concretas. - **`hasTags`** — `true` devuelve solo nóminas con al menos una etiqueta y `false`, solo nóminas sin etiquetas. - **`sortBy`** — campo de orden. - **`related`** — recursos a expandir. **Parámetros globales aceptados:** Acepta además los parámetros estándar `offset`, `limit`, `minCreationDate`, `maxCreationDate`, `minModificationDate`, `maxModificationDate` y el header `accept-version`. Ver [Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls?state=pending&state=overdue&limit=50" ``` ## Crear nómina `POST /{companyId}/payrolls` crea una nómina. Soporta crear nómina y sus pagos en una sola llamada (patrón "atómico"). **Parámetros del body:** - **`content`** (obligatorio) — `PayrollWrite`. Estructura descrita en [Estructura](#estructura). - **`tags`** (opcional). - **`payments`** (opcional) — array de pagos a registrar en la misma llamada. Misma forma que el body de [`POST /payrolls/{id}/payments`](#registrar-pagos). Útil para crear nómina + pagos atómicamente y evitar dos llamadas. **No disponible en `POST /bills`** (donde los pagos requieren llamada separada). - **`fromInbox`** (opcional) — vincula la nómina con un item de la [bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/). Estructura: - `taskId` (obligatorio dentro de `fromInbox`) — el `tas_*` del item. - `archive` (opcional, default `true`) — si borrar el item de la bandeja tras crear la nómina. Es el flujo recomendado tras [`POST /inbox/{id}/proposePayroll`](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/#proponer-nómina): el `propose*` devuelve el `content` prerellenado y aquí lo envías con `fromInbox: { taskId }`. **Notas:** - `content.main.contact` debe apuntar a un contacto con faceta **`employee`** configurada (`accounts.employee` asignada). Si no, la API devuelve `400 ValidationError`. - `lines` debe tener mínimo 1. Una nómina realista tiene típicamente 4 líneas: `debit` (salario), `ss_employee`, `irpf_employee`, `ss_company` (ver ejemplo). - La respuesta es la nómina creada completa, en el mismo formato que [Obtener una nómina](#obtener-una-nómina). **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON Nómina mínima heredada del ejemplo `minimal` del openapi (salario bruto 2000 €, SS empleado 120, IRPF dinerario 240, SS empresa 600): ```json { "content": { "type": "payroll", "main": { "date": "2026-05-31", "contact": "con_4f8e2a73-1d3c-4b5e-9c7a-8d6f2a1b3e5c", "modelo190": { "clavePercepcion": "A" }, "lines": [ { "type": "debit", "label": "Percepción salarial", "account": "640000", "lineTotal": 2000 }, { "type": "ss_employee", "label": "Aportación Seg.Soc. trabajador", "account": "476000", "base": 2000, "lineTotal": 120 }, { "type": "irpf_employee", "label": "Retenciones IRPF (Percepciones dinerarias)", "account": "475100", "base": 2000, "lineTotal": 240 }, { "type": "ss_company", "label": "Aportaciones Cotización Seg.Soc. Empresa", "account": "642000", "base": 2000, "lineTotal": 600 } ] } } } ``` Crear nómina + pago en la misma llamada (atómico): ```json { "content": { "...": "como arriba" }, "payments": [ { "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d", "date": "2026-06-05" } ] } ``` Crear desde un item de la bandeja de entrada: ```json { "content": "", "fromInbox": { "taskId": "tas_3a7f9c12-2d4e-4b8a-9c1f-5d6e8f0a3b2c" } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '@payroll.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls" ``` ## Obtener una nómina `GET /{companyId}/payrolls/{id}` devuelve una nómina por ID. **Parámetros:** - `related` (opcional) — recursos relacionados a expandir, por ejemplo `payments` para ver los pagos registrados. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c?related=payments" ``` ## Actualizar nómina `PUT /{companyId}/payrolls/{id}` sustituye el contenido de la nómina. Las líneas se reemplazan por las que envíes y los campos opcionales que no envíes se eliminan. Los campos obligatorios que no envíes conservan su valor, igual que `meta`, que no cambia. Lo que FacturaDirecta rellena al crear (`account`, `dueDate`, `exchangeRate`, `fiscalPosition`…) se rellena igual al actualizar si no lo envías. **Restricciones:** - Si la nómina tiene pagos asociados, las modificaciones pueden estar limitadas. Antes de actualizar, consulta sus pagos en `related`. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '@payroll.json' \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c" ``` ## Borrar nómina `DELETE /{companyId}/payrolls/{id}` elimina una nómina. **Restricciones:** - Si la nómina tiene pagos asociados, la API rechaza el borrado con `409 Conflict`. Elimina primero los pagos. **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/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c" ``` ## Actualizar etiquetas `PUT /{companyId}/payrolls/{id}/tags` reemplaza el conjunto de etiquetas sin tocar el resto del contenido. ###### Ejemplo de request JSON ```json { "tags": ["nominas-2026", "departamento-tecnico"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"tags":["nominas-2026","departamento-tecnico"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c/tags" ``` ## Registrar pagos `POST /{companyId}/payrolls/{id}/payments` registra uno o varios pagos contra una nómina. El body es un **array** de pagos, equivalente a [Registrar pagos en bills](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/#registrar-pagos). **Parámetros de cada pago:** - `bank` (obligatorio) — ID del banco/cuenta de tesorería donde se anota el pago. - `amount` (opcional) — importe en la moneda de la nómina. Si se omite, salda **todo el pendiente**. Solo un pago del array puede omitir `amount`, y debe ser el **último**. - `date` (opcional) — fecha del pago. Por defecto la fecha de la nómina cuando se crea, la fecha actual cuando se actualiza. - `companyCurrencyAmount` (opcional) — importe contable cuando la moneda de la nómina difiere de la contable. - `description`, `notes` (opcionales). La moneda de la cuenta bancaria debe coincidir con la moneda de la empresa o con la de la nómina. Si es una tercera moneda, la API devuelve `400` porque la petición no incluye el importe real cargado en esa cuenta. Cuando la cuenta usa la moneda de la empresa, `companyCurrencyAmount` permite registrar el importe exacto que después se compara con el extracto al conciliar. Para registrar el cobro o pago a partir de un movimiento del extracto bancario (también desde una cuenta en una tercera moneda), concilia el movimiento con `document` en [conciliar creando nuevo asiento](https://www.facturadirecta.com/desarrolladores/api/referencia/statements/#conciliar-creando-nuevo-asiento): la API construye el asiento con el importe real del movimiento y la diferencia de cambio. **Respuesta:** la nómina completa actualizada con los pagos registrados en `related.payments`. **Para crear nómina y pago a la vez**, usa el campo `payments` en el body de [`POST /payrolls`](#crear-nómina) — único patrón del recurso no disponible en bills. ###### Ejemplo de request JSON Pago único que salda toda la nómina: ```json [ { "bank": "ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d" } ] ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '[{"bank":"ban_5e7d8a31-9c4b-4f6e-a1d3-2b5c7e9f1a4d"}]' \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c/payments" ``` ## Adjuntos Una nómina puede tener adjuntos vinculados (típicamente el PDF original de la nómina). Los adjuntos **no se suben en el body**: primero se crean con `POST /uploads` y después se vinculan con `POST /payrolls/{id}/attachments`. ### Listar adjuntos `GET /{companyId}/payrolls/{id}/attachments` devuelve los adjuntos vinculados. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c/attachments" ``` ### Vincular adjuntos `POST /{companyId}/payrolls/{id}/attachments` añade adjuntos a partir de uploads previos. **Parámetros del body:** - `uploadIds` — array de `upl_`. Mínimo uno. ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"uploadIds":["upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c/attachments" ``` ### Eliminar un adjunto `DELETE /{companyId}/payrolls/{id}/attachments/{attachmentIndex}` elimina un adjunto. `attachmentIndex` es la posición en el array (basada en cero). ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/payrolls/par_8c3f1e29-4a7b-4d6e-9c2a-5f8d1b3e7a4c/attachments/0" ``` ## Recomendaciones - **Para escanear nóminas en PDF**, usa el flujo desde la [bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/): el extractor de nóminas identifica automáticamente los conceptos y rellena las 4-7 líneas típicas (salario + SS empleado + IRPF + SS empresa, y casos especiales como dietas, bajas por IT, etc.). - **Crear nómina + pago en una sola llamada**: usa el campo `payments` en el body de `POST /payrolls` cuando ya conoces el pago. Evita una segunda petición y deja la nómina marcada como `paid` desde el inicio. - **`modelo190.clavePercepcion: "A"`** cubre el caso más habitual (relación laboral común). Para autónomos, becarios, miembros de consejos de administración, etc., consulta el catálogo del modelo 190 de la AEAT. - **No olvides la línea `ss_company`**: aunque no sale del salario del empleado, sí es un gasto contable de la empresa y debe quedar reflejado en la nómina. ## Versiones `GET /{companyId}/payrolls/{id}/versions` lista las versiones de la nómina: su alta, cada modificación, su eliminación y su recuperación, con quién y cuándo, aunque se haya eliminado. `GET /{companyId}/payrolls/{id}/versions/{versionId}` devuelve la copia de una versión, en el mismo formato que `GET /{companyId}/payrolls/{id}`, y los cambios respecto a la versión anterior. Sirven para ver qué se cambió y recuperar datos: ver [Versiones de documentos](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Errores comunes - `400` — `minDate` o `maxDate` es una fecha que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400). - `400 ValidationError` — `contact` sin faceta `employee` configurada. - `400 ValidationError` — `lines` vacío (debe tener al menos 1). - `400 ValidationError` — `modelo190.clavePercepcion` con valor fuera del catálogo (A-Z). - `409 Conflict` — borrado de una nómina con pagos asociados. - `409 Conflict` — actualización incompatible con pagos existentes. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/payrolls` | `getPayrolls` | `payrolls:read` | Lista de nóminas | | GET | `/{companyId}/payrolls/{id}` | `getPayroll` | `payrolls:read` | Obtener una nómina | | GET | `/{companyId}/payrolls/{id}/attachments` | `getPayrollAttachments` | `payrolls:read` | Listar adjuntos de una nómina | | GET | `/{companyId}/payrolls/{id}/versions` | `getPayrollVersions` | `payrolls:read` | Versiones de la nómina | | GET | `/{companyId}/payrolls/{id}/versions/{versionId}` | `getPayrollVersion` | `payrolls:read` | Versión de la nómina | | POST | `/{companyId}/payrolls` | `createPayroll` | `payrolls:write` | Crear nómina | | POST | `/{companyId}/payrolls/{id}/attachments` | `addPayrollAttachments` | `payrolls:write` | Vincular adjuntos a una nómina | | POST | `/{companyId}/payrolls/{id}/payments` | `createPayrollPayments` | `payrolls:write` | Crear pagos para una nómina | | PUT | `/{companyId}/payrolls/{id}` | `updatePayroll` | `payrolls:write` | Actualizar nómina | | PUT | `/{companyId}/payrolls/{id}/tags` | `updatePayrollTags` | `payrolls:write` | Actualizar etiquetas de nómina | | DELETE | `/{companyId}/payrolls/{id}` | `deletePayroll` | `payrolls:write` | Borrar nómina | | DELETE | `/{companyId}/payrolls/{id}/attachments/{attachmentIndex}` | `deletePayrollAttachment` | `payrolls:write` | Eliminar adjunto de una nómina | ## Scopes - **`payrolls:read`** — Lectura de nóminas. - **`payrolls:write`** — Modificación de nóminas. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/payrolls.md --- # Configuración de la empresa > Bajo /settings/... vive la configuración de la empresa que la API pública expone como solo lectura: la ficha de la empresa y su forma de trabajar, los impuestos disponibles, las series de… Bajo `/settings/...` vive la configuración de la empresa que la API pública expone como **solo lectura**: la ficha de la empresa y su forma de trabajar, los impuestos disponibles, las series de numeración por tipo de documento y las plantillas de impresión. La modificación de esta configuración se hace desde la interfaz, no por API. ## Las 5 operaciones | Endpoint | Cubre | |---|---| | `GET /settings/company` | Ficha de la empresa y su forma de trabajar: dirección fiscal, moneda, idioma, modo borrador, régimen fiscal, VeriFactu y TicketBAI, bloqueo contable, canales de entrada de la bandeja y módulos disponibles. | | `GET /settings/taxes/sales` | Catálogo de impuestos para documentos de venta. Documentado en la [guía de Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). | | `GET /settings/taxes/purchases` | Catálogo de impuestos para documentos de compra. Documentado en la [guía de Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). | | `GET /settings/series/{documentType}` | Series de numeración configuradas en la empresa para un tipo de documento. | | `GET /settings/themes` | Plantillas de impresión disponibles. | Las cinco requieren scope **`settings:read`**. > Para los catálogos de impuestos consulta directamente la > [guía de Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/) — cubre el modelo completo > (dos catálogos, schema, autorepercutidos, `lineAmountIsTax`...). > Esta página documenta las tres operaciones restantes (`company`, > `series` y `themes`). ## Operaciones - [Ficha de la empresa](#ficha-de-la-empresa) - [Lista de series de numeración](#lista-de-series-de-numeración) - [Lista de plantillas](#lista-de-plantillas) ## Ficha de la empresa `GET /{companyId}/settings/company` devuelve los datos de la empresa y su configuración operativa. Es la llamada recomendada justo después de [`GET /profile`](https://www.facturadirecta.com/desarrolladores/api/referencia/profile/): el perfil dice **qué empresas** hay y esta operación dice **cómo trabaja** cada una, lo que un integrador o un agente necesita saber antes de crear documentos o enviar ficheros a la [bandeja](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/). **Parámetros globales aceptados:** `accept-version`. **Respuesta:** `{ content: CompanySettings }`. Bloques de `content`: | Bloque | Qué contiene | |---|---| | `company` | Identificación y datos fiscales: `id`, `sandbox`, `name`, `surname`, `brand`, `taxCode`, `email`, `phone`, `website`, `address`, `country`, `businessType` (`EM` empresa, `AU` autónomo, `AS` asociación), `taxRegion`, `currency` y `creationDate`. | | `locale` | `timezone`, `language` y los formatos de fecha y hora con los que la empresa ve la interfaz y los PDF. La API siempre intercambia fechas en ISO 8601. | | `sales` | `newInvoiceMode` (`draft` o `final`: estado con el que nacen las facturas si no indicas `content.main.draft`), `taxCalculationMode` (`total` o `lines`) y `externalInvoicesEnabled`. | | `expenses` | `newBillMode` (igual que `newInvoiceMode`, para gastos) y `taskToExpenseDefaultDate`. | | `taxes` | `defaultSales` (`BC` bienes o `SV` servicios), `equivalenceSurcharge`, `applyVatEUToFinalConsumer` y `vatEU` (NIF-IVA intracomunitario o `null`). El catálogo de impuestos está en `/settings/taxes/*`. | | `numbering` | `zeros` y `position` del número de documento. Las series están en `/settings/series/{documentType}`. | | `accounting` | `startDate` y `lockDate`: no se pueden crear ni modificar documentos con fecha igual o anterior al bloqueo. | | `fiscal` | `verifactu` (`enabled`, `environment`, `mode`, `startDate`, `endDate`, `exemption`) y `ticketbai` (`enabled`, `environment`, `region`, `startDate`). Sin certificados. | | `email` | `from`: remitentes disponibles para enviar documentos. | | `stock` | Solo si la empresa tiene control de stock: `salesTrigger`, `purchasesTrigger`, `negativesPolicy` y `defaultWarehouse`. | | `clientPortal` | `includeLinkInEmails`. | | `inbox` | Canales de entrada de la bandeja: `email` (`active`, `address`, `autoScan`) y `googleDrive` (`linked`, `folderId`, `folderUrl`, `error`). Detalle en [Bandeja de entrada](https://www.facturadirecta.com/desarrolladores/api/referencia/inbox/#cómo-llegan-documentos-a-la-bandeja). | | `modules` | Funcionalidades disponibles según el plan: `accounting`, `inventory`, `stockControl`, `inbox`, `googleDrive`, `payrollScanning`, `multicurrency`, `clientPortal`, `recurringInvoices` y `scans` (`period` y `limit`, con `-1` ilimitados). | | `webUrl` | Página de Ajustes en la aplicación web, para que un usuario cambie esta configuración. | **Notas:** - **Es solo lectura.** Activar el buzón, enlazar Google Drive, cambiar el modo borrador o dar de alta VeriFactu se hace desde la interfaz (`webUrl`). - **`inbox.email.address` es confidencial.** Cualquier correo que llegue a esa dirección entra en la bandeja de la empresa sin verificar el remitente. Dásela al usuario de la empresa, no la publiques. - **`inbox.email.autoScan`** indica si lo que llega por correo se escanea al momento. Si es `false`, los documentos quedan en la bandeja con `status: pending` hasta que un usuario los escanee. - **`inbox.googleDrive.error`** distinto de `null` significa que la sincronización está parada (credenciales caducadas o carpeta borrada) y el usuario tiene que volver a enlazar la carpeta. - **`company.sandbox`** marca los entornos de pruebas: sus documentos no tienen validez y no se envían a la AEAT ni a las diputaciones. - Lo que `modules` marca como no disponible devuelve `403` o `409` en los endpoints que lo requieren. - Esta configuración cambia con muy poca frecuencia: cachéala por empresa y refréscala al arrancar una sesión o si una llamada falla por un motivo de configuración. ###### Ejemplo de respuesta ```json { "content": { "company": { "id": "com_4d8f0a6e-1b2c-4c3d-9e8f-0a1b2c3d4e5f", "sandbox": false, "name": "Cafés Zamora SL", "surname": null, "brand": "Cafés Zamora", "taxCode": "B12345674", "email": "hola@cafeszamora.example", "phone": "980 000 000", "website": "https://cafeszamora.example", "address": { "details": "Pza Mayor, 4", "zipcode": "49004", "city": "Zamora", "province": "Zamora", "country": "ES" }, "country": "ES", "businessType": "EM", "taxRegion": "ES_ES", "currency": "EUR", "creationDate": "2024-03-12T09:15:00.000Z" }, "locale": { "timezone": "Europe/Madrid", "language": "es", "dateFormat": "DD-MM-YYYY", "timeFormat": "HH:mm", "dateTimeFormat": "DD-MM-YYYY HH:mm" }, "sales": { "newInvoiceMode": "final", "taxCalculationMode": "total", "externalInvoicesEnabled": false }, "expenses": { "newBillMode": "final", "taskToExpenseDefaultDate": "invoiceDateOrEmpty" }, "taxes": { "defaultSales": "BC", "equivalenceSurcharge": false, "applyVatEUToFinalConsumer": false, "vatEU": "ESB12345674" }, "numbering": { "zeros": 4, "position": "prefix" }, "accounting": { "startDate": "2024-01-01", "lockDate": "2025-12-31" }, "fiscal": { "verifactu": { "enabled": true, "environment": "verifactu_production", "mode": "mode_verifactu", "startDate": "2026-01-01", "endDate": null, "exemption": null }, "ticketbai": { "enabled": false, "environment": null, "region": null, "startDate": null } }, "email": { "from": ["facturas@cafeszamora.example"] }, "clientPortal": { "includeLinkInEmails": true }, "inbox": { "email": { "active": true, "address": "cafes_zamora-a1b2c3d4e5@inbox.facturadirecta.com", "autoScan": true }, "googleDrive": { "linked": true, "folderId": "1AbCdEfGhIjKlMnOpQrStUvWxYz", "folderUrl": "https://drive.google.com/drive/folders/1AbCdEfGhIjKlMnOpQrStUvWxYz", "error": null } }, "modules": { "accounting": true, "inventory": false, "stockControl": false, "inbox": true, "googleDrive": true, "payrollScanning": true, "multicurrency": false, "clientPortal": true, "recurringInvoices": true, "scans": { "period": "total", "limit": -1 } }, "webUrl": "https://3.facturadirecta.com/company/com_4d8f0a6e-1b2c-4c3d-9e8f-0a1b2c3d4e5f/settings" } } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/settings/company" ``` ## Lista de series de numeración `GET /{companyId}/settings/series/{documentType}` devuelve las series de numeración disponibles para un tipo de documento. **Parámetro de path:** - **`documentType`** (obligatorio) — uno de: - `invoice` — facturas de venta. - `estimate` — presupuestos. - `deliveryNote` — albaranes. - `clientOrder` — pedidos de cliente. - `purchaseOrder` — órdenes de compra. **Parámetros globales aceptados:** `accept-version`. **Respuesta:** `{ items: SeriesItem[] }` sin paginar. Cada `SeriesItem` tiene: | Campo | Tipo | Significado | |---|---|---| | `serie` | string | Valor de la serie. Puede contener `##` o `####`, que se sustituye por el año de la fecha del documento en el momento de guardar. | | `reset` | `"never"` \| `"year"` | Si reinicia la numeración con el cambio de año (`year`) o si continúa desde el último número del año anterior (`never`). | | `manual` | boolean | Si la serie acepta numeración manual desde la interfaz. | | `notes` | string | Notas descriptivas. | | `theme` | string | ID efectivo de la plantilla de la serie. Si la serie no tiene una asociación específica, devuelve la predeterminada de la empresa. | | `invoiceType` | enum | Solo en series de facturas. Valores: `complete`, `complete_correction`, `simplified`, `simplified_correction`, `external`. | | `correction` | boolean | **OBSOLETO** — usar `invoiceType` en su lugar. Indica si la serie es para rectificativas. | **Notas:** - Las series se configuran desde la interfaz; este endpoint solo las expone. La API pública no crea, modifica ni borra series. - Las series de pedidos de cliente y de órdenes de compra solo existen si la empresa las ha configurado; si no, `items` es una lista vacía. - Para series de facturas, `invoiceType` distingue los regímenes fiscales aplicables. El campo `correction` queda por compatibilidad pero **no debe usarse en código nuevo**. - `##`/`####` en `serie` se sustituye por el año al guardar el documento. Ejemplo: serie `"F-##-"` con fecha 2026 produce números como `F-26-1`, `F-26-2`... - Si una serie no tiene plantilla propia, `theme` contiene la predeterminada vigente de la empresa. No presupongas que siempre será `thm_master`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/settings/series/invoice" ``` ## Lista de plantillas `GET /{companyId}/settings/themes` devuelve las plantillas de impresión configuradas en la empresa. **Parámetros globales aceptados:** `accept-version`. **Respuesta:** `{ items: ThemeItem[] }` sin paginar. Cada `ThemeItem` tiene: | Campo | Tipo | Significado | |---|---|---| | `id` | string | Identificador de la plantilla. Es lo que se usa en `content.main.theme` de los documentos. | | `title` | string | Nombre legible. | **Notas:** - Las plantillas se configuran desde la interfaz; este endpoint solo las expone. - Para aplicar una plantilla concreta a un documento, indica su `id` en `content.main.theme` al crear o actualizar el documento. - Si omites `content.main.theme` al crear un documento, FacturaDirecta usa la plantilla de su serie y, si la serie no tiene una asociación específica, la predeterminada de la empresa. `thm_master` es la alternativa final cuando no hay una selección válida. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/settings/themes" ``` ## Recomendaciones - **Empieza por `/settings/company`** al abrir una sesión con una empresa: te dice en qué modo trabaja, qué módulos tiene y por dónde puede recibir documentos, y evita llamadas que fallarían por configuración. - **Cachea el catálogo** localmente por empresa. La ficha, las series, los themes y los impuestos cambian con muy baja frecuencia; consultarlos en cada llamada gasta cuota innecesariamente. - **Refresca al fallar**: si una creación falla con `400` por `theme` o `serie` desconocido, refresca el catálogo: alguien puede haber cambiado la configuración desde la interfaz. ## Referencia exhaustiva Esta página cubre los matices funcionales de `company`, `series` y `themes`. Para impuestos, la referencia funcional está en la [guía de Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/). Para el contrato completo campo a campo, consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/settings/company` | `getCompanySettings` | `settings:read` | Ficha y forma de trabajar de la empresa | | GET | `/{companyId}/settings/series/{documentType}` | `getSeries` | `settings:read` | Lista de series de numeración | | GET | `/{companyId}/settings/taxes/purchases` | `getPurchasesTaxes` | `settings:read` | Lista de impuestos de compra disponibles | | GET | `/{companyId}/settings/taxes/sales` | `getSalesTaxes` | `settings:read` | Lista de impuestos de venta disponibles | | GET | `/{companyId}/settings/themes` | `getThemes` | `settings:read` | Lista de plantillas de documentos disponibles | ## Scopes - **`settings:read`** — Lectura de ajustes/datos de configuración. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/settings.md --- # Perfil de usuario > El recurso profile devuelve los datos básicos del usuario autenticado y las empresas disponibles para la credencial actual. El recurso `profile` devuelve los datos básicos del **usuario autenticado** y las **empresas disponibles para la credencial actual**. Es la operación que usa una integración OAuth para resolver qué `companyId` puede usar en el resto de llamadas. Cuando la petición procede de una aplicación conectada, la respuesta incluye solo las empresas que el usuario autorizó para esa conexión y añade sus permisos efectivos. ## Excepciones de este endpoint `/profile` es el único endpoint de la API pública que rompe con dos convenciones del resto del catálogo: - **No lleva `companyId` en la ruta.** El path es literalmente `/profile`: el usuario puede tener acceso a varias empresas, y este endpoint sirve precisamente para descubrirlas antes de fijar una. - **Es OAuth-only.** Las [API Keys](https://www.facturadirecta.com/desarrolladores/api/referencia/api-keys/) **no pueden llamar** a este endpoint: una API key está ya ligada a una empresa concreta, así que no tiene sentido pedir su perfil de empresas. ## Scope El scope efectivo es `profile`. No aparece como permiso seleccionable porque **se incluye automáticamente** en el cliente OAuth `facturadirecta-api` al hacer login. No hace falta pedirlo explícitamente. ## Estructura El endpoint devuelve `{ profile }` con la siguiente forma: | Campo | Tipo | Significado | |---|---|---| | `username` | string | Email del usuario autenticado (es el identificador único). | | `email` | string | Mismo valor que `username`. Se mantiene por compatibilidad. | | `firstName` | string | Nombre del usuario (del campo `given_name` del JWT). | | `lastName` | string | Apellidos del usuario (del campo `family_name` del JWT). | | `companies` | array | Empresas disponibles para la credencial actual. | | `connection` | object | Solo con una aplicación conectada. Identifica la conexión y permite abrir su gestión. | El objeto opcional `connection` contiene: | Campo | Tipo | Significado | |---|---|---| | `id` | string | Identificador de la conexión. | | `clientName` | string | Nombre de la aplicación conectada. | | `manageUrl` | string | Página de FacturaDirecta donde el usuario puede cambiar sus empresas y permisos. | Cada elemento de `companies`: | Campo | Tipo | Significado | |---|---|---| | `id` | string | ID de empresa. Es el valor que debes usar como `{companyId}` en las llamadas al resto de la API. | | `name` | string | Nombre de la empresa. Si la empresa tiene primer apellido (`surname`), aparece concatenado con un espacio. | | `taxCode` | string | CIF/NIF de la empresa. | | `brand` | string | Marca comercial, si está informada (opcional). | | `owner` | string | Email del propietario de la empresa. | | `role` | string | Rol del usuario autenticado en esa empresa concreta. | | `permissions` | object | Solo con una aplicación conectada. Permisos efectivos por recurso: `read` para lectura y `full` para lectura y escritura. | | `accountingModule` | boolean | Solo con una aplicación conectada. `true` si la empresa tiene el módulo de Contabilidad, contratado o incluido en su plan o en la prueba. Sin él no están disponibles los informes contables: pérdidas y ganancias, balance de situación y sumas y saldos. | | `manageUrl` | string | Solo con una aplicación conectada. Página para cambiar los permisos de esa empresa. | ## Operaciones - [Obtener el perfil](#obtener-el-perfil) ## Obtener el perfil `GET /profile` devuelve el perfil del usuario autenticado y sus empresas. **No lleva parámetros de path ni de consulta.** Solo el header `accept-version` para fijar la versión de la API. Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/profile" ``` Respuesta típica: ```json { "profile": { "username": "ana@ejemplo.com", "email": "ana@ejemplo.com", "firstName": "Ana", "lastName": "García López", "companies": [ { "id": "com_3a7e8d29-4f5b-4c6e-9a1d-2b8c4e7f5d3a", "name": "Estudio Diseño SL", "taxCode": "B12345674", "owner": "ana@ejemplo.com", "role": "admin" }, { "id": "com_8b1c5e94-2d6a-4f7e-9c3b-1a5d8f4e2c7b", "name": "Cooperativa Norte", "taxCode": "F87654328", "brand": "Norte", "owner": "luis@cooperativa.com", "role": "accountant" } ] } } ``` ### Respuesta de una aplicación conectada Una aplicación conectada recibe solo la selección autorizada. Los permisos ya están limitados por la concesión, el rol actual del usuario y el plan de cada empresa: ```json { "profile": { "username": "ana@ejemplo.com", "email": "ana@ejemplo.com", "firstName": "Ana", "lastName": "García López", "companies": [ { "id": "com_3a7e8d29-4f5b-4c6e-9a1d-2b8c4e7f5d3a", "name": "Estudio Diseño SL", "taxCode": "B12345674", "owner": "ana@ejemplo.com", "role": "admin", "permissions": { "contacts": "read", "invoices": "full" }, "accountingModule": false, "manageUrl": "https://app.facturadirecta.com/connections/?company=" } ], "connection": { "id": "", "clientName": "Asistente de ejemplo", "manageUrl": "https://app.facturadirecta.com/connections/" } } } ``` ## Flujo típico de integración 1. El usuario completa el login OAuth. Tu integración recibe el `access_token`. 2. Llamas a `GET /profile` para descubrir las empresas disponibles. 3. Si aparece `connection`, respetas la selección y los `permissions` de cada empresa. Los `manageUrl` permiten que el usuario amplíe el acceso sin volver a conectar. 4. Eliges una empresa entre las devueltas. 5. Usas su `id` como `{companyId}` en las llamadas siguientes a la API. ## Recomendaciones - **No cachees indefinidamente**: la lista de empresas y sus permisos pueden cambiar cuando el usuario modifica la conexión, cambia de rol o deja una empresa. Refresca al iniciar sesión y ante un `401` o `403`. - **`role` informa, no autoriza por sí solo**: la autorización efectiva combina los scopes de la credencial y los permisos del rol. En una aplicación conectada también se limita por la concesión de cada empresa. - **Usa `permissions` cuando esté presente** para adaptar las acciones de tu aplicación antes de intentar una escritura. - **Usa `accountingModule` para elegir el informe** antes de pedirlo: sin el módulo de Contabilidad, la empresa no tiene los informes contables. - **Para API Keys, no llames a `/profile`**: la API key ya conoce su empresa. Si tu integración alterna entre OAuth y API Key, detéctalo en el cliente y omite el paso. ## Errores comunes - `401 Unauthorized` (`code: "auth_*"`) — el `access_token` no es válido o ha expirado. Refresca el token vía OAuth. - `403 Forbidden` — intento de llamar con una API key. Este endpoint es OAuth-only por diseño. - Una empresa que no pertenece a la conexión no aparece en `companies`. Si se intenta usar directamente, el resto de endpoints responde `403` con `code: "company_not_in_connection"` y puede incluir `manageUrl`. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) y [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/) para los detalles del flujo OAuth. ## Referencia exhaustiva Esta página cubre el único endpoint del recurso y todos sus campos. Para el contrato JSON literal, consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/profile` | `getProfile` | — | Perfil de usuario | Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/profile.md --- # API keys > Una API key es una credencial por empresa para autenticar integraciones de máquina a máquina. Una API key es una credencial **por empresa** para autenticar integraciones de máquina a máquina. Es el método alternativo a OAuth2 cuando tu integración solo necesita acceder a una empresa concreta y no necesita actuar en nombre de un usuario humano. Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/) para el contexto general. Este recurso permite gestionar las API keys vía la propia API: crear, listar, consultar y borrar. También permite consultar los scopes de la clave que autentica la llamada actual. > En los ejemplos de esta página, los IDs y prefijos son ilustrativos. > Los reales se devuelven al crear cada API key. ## Cinco principios duros del recurso Estas reglas son distintas a las de cualquier otro recurso y conviene tenerlas claras antes de operar: 1. **La clave completa solo se ve una vez**, en la respuesta de la creación. Después FacturaDirecta solo guarda un hash y el prefijo para identificación. Si la pierdes, hay que crear otra. 2. **No hay endpoint de modificación** (`PUT`). Las API keys no se actualizan: para cambiar nombre o scopes hay que crear una nueva y borrar la antigua. Esto evita reasignar scopes a una clave en uso por descuido. 3. **No se pueden borrar API keys creadas manualmente** desde la interfaz (`managedViaApi: false`). Solo las que tu integración ha creado por API (`managedViaApi: true`) son borrables por API. Es una protección contra que un cliente automático elimine accidentalmente las credenciales que un humano configuró. 4. **Una API key no puede borrarse a sí misma.** Si autenticas con la API key `K` e intentas `DELETE /apiKeys/K`, la API rechaza con `403`. 5. **Una API key no tiene más permisos que quien la crea.** En cada petición solo cuentan sus scopes, sin rol de usuario ni aplicación conectada, así que al crearla cada scope tiene que poder usarlo, en esa empresa, quien hace la llamada. Si no, la API rechaza con `403`. ## Formato de la clave Al crear una API key, la respuesta incluye un campo `apiKey` con la clave **completa**, en formato: ``` . ``` - **Prefijo**: 6 caracteres alfanuméricos. Identifica la clave en logs, listas, y queda asociado a las llamadas que realiza. Es lo que ves después en el campo `prefix` de las respuestas. - **Sufijo**: 32 caracteres alfanuméricos. Es el secreto real. Junto con el prefijo forman la clave única. Ejemplo de clave válida: ``` aB3cD9.xY7zN2qK4mP8rT5sJ1hF6gL0vW3uE ``` Para usarla en peticiones a la API, se envía en el header `facturadirecta-api-key` (sin `Bearer`, sin esquema). Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/#api-key). ## Estructura `PublicApiKeyInfo` (lo que devuelven los GETs): | Campo | Tipo | Obligatorio | Significado | |---|---|---|---| | `id` | string | sí | Identificador único de la API key (UUID v4 sin prefijo). Se usa en los paths como `{id}`. | | `name` | string | sí | Nombre descriptivo elegido al crear. | | `prefix` | string | sí | Prefijo de 6 caracteres. | | `scopes` | string[] | sí | Scopes autorizados de la clave. | | `creationTime` | string ISO 8601 | sí | Fecha de creación. | | `createdBy` | string | no | Usuario que creó la clave. | | `managedViaApi` | boolean | sí | `true` si la clave fue creada vía API y por tanto es borrable vía API. `false` si se creó desde la interfaz. | Al crear, además del `content: PublicApiKeyInfo`, la respuesta incluye el campo `apiKey` con la clave completa. ## Operaciones - [Información de la API key actual](#información-de-la-api-key-actual) - [Lista de API keys](#lista-de-api-keys) - [Crear API key](#crear-api-key) - [Obtener una API key](#obtener-una-api-key) - [Borrar una API key](#borrar-una-api-key) ## Información de la API key actual `GET /{companyId}/apiKeyInfo` devuelve los scopes asociados a la API key que autentica la petición actual. Úsalo cuando tu integración necesita adaptar su comportamiento a los permisos reales de la clave instalada en una empresa. Este endpoint solo acepta autenticación mediante API key. Si llamas con OAuth2, devuelve `401 Unauthorized` porque no hay una API key actual que inspeccionar. **Respuesta:** ```json { "scopes": ["contacts:read", "contacts:write"] } ``` **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "facturadirecta-api-key: $API_KEY" \ "https://app.facturadirecta.com/api/$COMPANY_ID/apiKeyInfo" ``` ## Lista de API keys `GET /{companyId}/apiKeys` devuelve todas las API keys configuradas en la empresa, sin datos sensibles (solo prefijos, no claves completas). **Parámetros de consulta específicos:** ninguno. **Parámetros globales aceptados:** `accept-version`. **Notas:** - La lista incluye **todas** las API keys de la empresa, las creadas vía API y las creadas manualmente. Distínguelas por `managedViaApi`. - Sin paginación: devuelve el conjunto completo (ver patrón "sin paginación" en la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/)). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/apiKeys" ``` ## Crear API key `POST /{companyId}/apiKeys` crea una API key. **Parámetros del body:** - `name` (obligatorio) — nombre descriptivo para identificarla en la lista. - `scopes` (obligatorio, mínimo 1) — lista de scopes que la nueva clave podrá usar. **Cada scope tiene que poder usarlo quien crea la clave en la empresa:** - Si el caller es una API key, los scopes deben ser subconjunto de los suyos. - Si el caller es OAuth, cada scope tiene que estar en el token y permitirlo el rol del usuario en la empresa y, si la llamada viene de una aplicación conectada, los permisos de esa conexión en la empresa. - Con una aplicación conectada o un rol personalizado, `contacts:read` y `contacts:write` exigen ese nivel sobre clientes, proveedores y empleados (ver [Permisos por faceta](https://www.facturadirecta.com/desarrolladores/api/referencia/contacts/#permisos-por-faceta)): la API key ve todos los contactos, sin distinguir el tipo. **Respuesta:** ```json { "content": { "id": "...", "name": "...", "prefix": "...", "scopes": [...], ... }, "apiKey": "." } ``` El campo `apiKey` **solo se ve aquí**. Guárdalo en seguro antes de seguir; no hay forma de recuperarlo después. **Parámetros globales aceptados:** `accept-version`. ###### Ejemplo de request JSON ```json { "name": "Mi API key", "scopes": ["contacts:read"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"name":"Mi API key","scopes":["contacts:read"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/apiKeys" ``` ## Obtener una API key `GET /{companyId}/apiKeys/{id}` devuelve los metadatos de una API key por su ID. No incluye la clave completa (solo el prefijo). Esa información ya no existe en FacturaDirecta tras la creación. **Parámetros globales aceptados:** `accept-version`. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/apiKeys/3f8b2c14-7d4e-4a9c-b6f1-2a5e8d3c4b7f" ``` ## Borrar una API key `DELETE /{companyId}/apiKeys/{id}` elimina la API key. **Restricciones:** - La API key objetivo debe tener `managedViaApi: true`. Si fue creada desde la interfaz, devuelve `403 Forbidden` con el mensaje "No se puede eliminar una API key que no fue creada vía API. Las API keys creadas manualmente solo pueden eliminarse desde la interfaz de usuario." - Una API key no puede borrarse a sí misma. Si autenticas con la API key `K` e intentas borrarla, devuelve `403 Forbidden` con el mensaje "Una API key no puede eliminarse a sí misma". - Si el `id` no corresponde a ninguna API key existente, devuelve `403 Forbidden` (no `404`). **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/apiKeys/3f8b2c14-7d4e-4a9c-b6f1-2a5e8d3c4b7f" ``` ## Patrón de rotación Para rotar una API key sin interrumpir el servicio: 1. Crea una **nueva** API key con los mismos scopes que la antigua. 2. Actualiza tu integración para usar la nueva clave. 3. Verifica que la integración funciona con la nueva clave (deja un margen razonable). 4. Borra la antigua con `DELETE /apiKeys/{id}` (solo si fue creada vía API). No hay "rotateSecret" como en webhooks: la API key es inmutable. La rotación se hace siempre creando nueva y borrando antigua. ## Recomendaciones - **Pide los scopes mínimos.** Si tu integración solo lee contactos, no pidas `contacts:write`. Es más seguro y más fácil de auditar. - **Una API key por integración, no compartas.** Si una integración deja de usarse, borra su API key. - **Guarda la clave (`.`) en un gestor de secretos**, no en código fuente ni en logs. - **Las API keys son por empresa.** Para multi-tenant SaaS, OAuth2 con `offline_access` es el patrón correcto (no API keys una por empresa). - **Para auto-aprovisionamiento de claves desde tu sistema**, usa OAuth2 para autenticar tu sistema admin y crear API keys para cada cliente. Sigue siendo más seguro que distribuir API keys hardcodeadas. ## Errores comunes - `400 ValidationError` — `scopes` vacío o `name` ausente. - `403 Forbidden` — el `id` no existe, o `managedViaApi: false`, o intento de auto-eliminación. - `401 Unauthorized` — llamada a `GET /apiKeyInfo` autenticada con OAuth2 en lugar de API key. - `403 Forbidden` — al crear, un scope solicitado que el caller no puede usar en la empresa: fuera de los de su API key o, con OAuth, fuera de su token, de su rol o de la aplicación conectada. El mensaje nombra el scope. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) 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](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/apiKeys` | `listPublicApiKeys` | `apiKeys:read` | Lista de API keys | | GET | `/{companyId}/apiKeys/{id}` | `getPublicApiKey` | `apiKeys:read` | Obtener API key | | POST | `/{companyId}/apiKeys` | `createPublicApiKey` | `apiKeys:write` | Crear API key | | DELETE | `/{companyId}/apiKeys/{id}` | `deletePublicApiKey` | `apiKeys:write` | Eliminar API key | ## Scopes - **`apiKeys:read`** — Lectura de API keys. - **`apiKeys:write`** — Gestión de API keys (creación y eliminación). Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/api-keys.md --- # Actividad > El registro de actividad es el historial de lo que pasa en la empresa: altas, modificaciones y eliminaciones de documentos, envíos por email y su entrega, accesos de los usuarios, sincronizaciones… El **registro de actividad** es el historial de lo que pasa en la empresa: altas, modificaciones y eliminaciones de documentos, envíos por email y su entrega, accesos de los usuarios, sincronizaciones bancarias, envíos a la AEAT y a FACe, ejecuciones de tareas automáticas, cambios de ajustes… Es la misma información que muestra la página **Actividad** de la aplicación. La API expone una sola operación de **lectura con paginación por cursor**. Sirve para auditar quién hizo qué y cuándo, para ver el historial de un documento concreto o para llevar a otro sistema la actividad nueva desde la última consulta. Cada registro trae un núcleo común con forma fija (fecha, tipo, usuario, descripción, elemento afectado y origen de la petición) y un objeto `details` con algunos datos propios de su tipo. El contenido de los documentos no viaja en la actividad: qué campos cambió cada modificación, sus valores y la copia de cada momento están en las [versiones del documento](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). ## Permisos - El scope es `activity:read`. - Con OAuth, además, el rol del usuario en la empresa necesita el permiso de **actividad completo** (ver la actividad de todos), como los roles *Administrador* y *Asesor*. Los roles que solo ven su propia actividad (en los predefinidos, *Sólo lectura*, *Ventas*, *Compras* y *Ventas y compras*) reciben un `403`: la API no filtra la actividad por usuario. - Una API key accede si tiene el scope. Al crearla, solo puede concederle `activity:read` quien tiene el permiso de actividad completo. - Una aplicación conectada (servidor MCP y CLI) necesita además el permiso **Registro de actividad** en la conexión, en la empresa consultada. Solo puede concederlo quien tiene el permiso de actividad completo, y da acceso a la actividad de toda la empresa. ## Estructura de un registro ```json { "id": 482913, "timestamp": "2026-09-15T08:42:17.351Z", "type": "send", "subtype": null, "username": "ana.garcia@ejemplo.es", "description": "Enviado", "document": { "id": "inv_7c1e2f40-5a8b-4d3c-9e6f-2b1a0c9d8e7f", "type": "invoice", "title": "F2026-0123", "archived": false }, "request": { "ip": "88.12.34.56", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" }, "details": { "actionSource": "user", "to": "pedidos@clienteejemplo.es" } } ``` | Campo | Tipo | Significado | |---|---|---| | `id` | entero | Identificador del registro. | | `timestamp` | string | Fecha y hora de la actividad (ISO 8601, UTC). | | `type` | string | Tipo de actividad (ver [Tipos de actividad](#tipos-de-actividad)). | | `subtype` | string \| null | Subtipo, en los tipos que lo tienen: por ejemplo `voided` en una modificación que anula una factura, o `psd2` en una sincronización bancaria. | | `username` | string \| null | Usuario (email) que hizo la acción. Las acciones automáticas aparecen con un usuario de sistema de FacturaDirecta, como `ami@facturadirecta.com` en las tareas automáticas. | | `description` | string | Texto legible en español, el mismo que muestra la página de actividad. | | `document` | objeto \| null | Elemento al que se refiere la actividad. `null` en la actividad de la empresa, como los accesos o los cambios de ajustes. | | `request` | objeto \| null | Origen de la petición (`ip`, `userAgent`), cuando se conoce. | | `details` | objeto | Datos propios del tipo de actividad. | ### El elemento afectado: `document` - **`id`** — identificador del elemento: una factura (`inv_…`), un contacto (`con_…`), un banco, una tarea automática, un cobro… - **`type`** — tipo del documento (`invoice`, `bill`, `contact`, `product`…). Es `null` cuando el elemento no es un documento. - **`title`** — título del documento, por ejemplo el número de una factura o el nombre de un contacto. - **`archived`** — `true` si el documento está eliminado. Con el `id` puedes pedir el detalle en el recurso correspondiente ([facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/), [contactos](https://www.facturadirecta.com/desarrolladores/api/referencia/contacts/)…), con el scope de ese recurso. ### Datos del tipo: `details` `details` no copia el registro interno: es una selección de campos de cada tipo. Incluye identificadores de otros elementos, resultados (estados, contadores, errores) y datos de la acción (formato, canal, destinatarios de un envío). Un campo que el registro no tiene no aparece, y la lista puede ampliarse con campos nuevos sin aviso, así que tu integración debe ignorar los campos que no conozca. Muchos tipos devuelven un objeto vacío. Las altas y modificaciones de documentos no traen el contenido del documento: qué cambió en cada una se consulta en las [versiones del documento](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/), donde el `id` del registro de actividad es el identificador de la versión. | Tipo | Campos de `details` | |---|---| | `inserted` | `automationId` (tarea automática que creó el documento), `importSource` | | `comment_inserted`, `comment_updated`, `comment_archived` | `commentId` | | `file` | `format` (`pdf`, `facturae`, `sepa`, `boefile`) | | `portalView` | `action` (`view`: consulta, `pdf`: descarga del PDF) | | `portalPaymentFinalizeFailed` | `transactionId` (el cobro recibido), `error` (por qué no se pudo emitir la factura) | | `deliveryReceiptSigned` | `signedAt` | | `send`, `send_queued` | `actionSource`, `to`, `cc`, `bcc` | | `send_delivered`, `send_dropped`, `send_complained`, `send_failed_temporary`, `send_failed_permanent` | `recipient`, `message` | | `smtpCustomServerAutoDisabled` | `serverId`, `serverName`, `reason` | | `bankSync` | `manualSync`, `processedTransactions`, `loadedTransactions`, `updatedTransactions`, `repeatedTransactions`, `ignoredTransactions`, `autoReconciledTransactions` | | `bankSyncWarning` | `manualSync`, `message` | | `ticketbai_accepted`, `ticketbai_rejected` | `diputacionForal` | | `ticketbai_error` | `diputacionForal`, `error` | | `verifactu_send_aeat` | `status`, `invoiceIds`, `error` | | `verifactu_send_invoice` | `operation`, `status`, `errorCode`, `errorDescription`, `error` | | `verifactu_send_requerimiento` | `reference`, `totalRecords`, `totalBatches` | | `verifactu_export_records` | `startDate`, `endDate`, `totalRecords` | | `verifactu_export_events` | `startDate`, `endDate`, `totalEvents` | | `face_send_invoice` | `submissionId`, `registryCode`, `environment`, `trigger`, `sendStatus` | | `face_adopt_registration` | `submissionId`, `registryCode`, `environment`, `trigger`, `registrationOrigin` | | `face_refresh_status` | `submissionId`, `registryCode`, `environment`, `trigger`, `invoiceStatus`, `cancellationStatus` | | `face_cancel_invoice` | `submissionId`, `registryCode`, `environment`, `trigger` | | `accounting_export` | `format`, `startDate`, `endDate`, `elements` | | `automation` | `scheduledTime`, `createdDocumentId`, `warnings` | | `automationError` | `scheduledTime`, `errors` | | `automationInfo` | `reason`, `nextScheduledTime`, `createdDocumentId` | | `automationAutoDeactivated` | `reason` | | `automationReport` | `periodStart`, `periodEnd`, `summary` | | `updateSettings` | `reason` | | `archiveCompany` | `reason` | | `certificateExpirationEmail` | `validityNotAfter`, `daysUntilExpiration` | | `usageMeterReset` | `meter`, `month` | | `usageQuotaWarning` | `meter`, `period`, `month`, `year`, `threshold`, `quota`, `count` | | `usageCreditGrant`, `usageCreditPurchase`, `usageCreditChargeback`, `usageCreditAdjustment` | `amountCents`, `balanceAfterCents` | | `webhook` | `url`, `success`, `responseStatus` | | `webhook_endpoint_*`, `webhook_secret_rotated` | `endpointId`, `name`, `url`, `events`, `changedFields`, `failureCount` | | `report_subscription_created`, `report_subscription_updated`, `report_subscription_paused`, `report_subscription_resumed`, `report_subscription_deleted` | `subscriptionId`, `title`, `reportType`, `recipient`, `frequency`, `range`, `format`, `changedFields`, `source` (`subscription` o `savedQuery`), `reason` (`user`, `plan`, `permission`, `savedQueryDeleted` o `userGone`) | | `report_subscription_sent`, `report_subscription_failed` | `subscriptionId`, `title`, `reportType`, `recipient`, `frequency`, `range`, `format`, `test`, `period`, `sentFormat`, `pdfTooLarge`, `delivery` (`attached` o `link`), `error` | | `report_export` | `reportType`, `title`, `format` (`pdf`, `xlsx`, `csv` o `print`), `periodStart`, `periodEnd`, `requestedByEmail` | Las sincronizaciones bancarias del sistema anterior tienen sus propios tipos y devuelven los mismos campos que `bankSync` (más `error`) y `bankSyncWarning`. El resto de tipos devuelve un objeto vacío. ### Tipos de actividad Los valores de `type` son los del filtro `type` de la operación (la lista completa está en el openapi). Los más habituales: - **Documentos:** `inserted` (alta), `updated` (modificación, con `subtype` cuando es una acción concreta como `voided`, `unvoided`, `addAttachments` o `updateTags`), `archived` (eliminación), `unarchived` (recuperación), `comment_inserted`, `file` (generación de un PDF, Facturae o remesa). - **Envíos por email:** `send`, `send_queued`, `send_delivered`, `send_failed_temporary`, `send_failed_permanent`, `send_dropped`, `send_complained`, y `portalView` para las consultas del documento en el portal de cliente. `portalPaymentFinalizeFailed` indica que se ha cobrado por el portal una factura provisional que no se ha podido emitir. - **Usuarios y empresa:** `access` (entrada de un usuario), `updateSettings`, `leaveCompany`. - **Bancos:** `bankSync`, `bankSyncWarning` (`subtype` indica el canal: `psd2` para la conexión automática con el banco y `paymentGateway` para las pasarelas de pago). - **Fiscalidad:** `verifactu_send_aeat`, `verifactu_send_invoice`, `ticketbai_accepted`, `ticketbai_rejected`, `ticketbai_error`, `face_send_invoice`, `face_refresh_status`, `face_cancel_invoice`. - **Tareas automáticas:** `automation`, `automationError`, `automationInfo`, `automationReport`. - **Webhooks:** `webhook` (entrega), `webhook_endpoint_created` y el resto de cambios de endpoints. - **Informes:** `report_subscription_created`, `report_subscription_updated`, `report_subscription_paused`, `report_subscription_resumed` y `report_subscription_deleted` (el ciclo de vida de un envío programado, lo haga un usuario o el sistema), `report_subscription_sent` y `report_subscription_failed` (cada envío que sale por correo, también los de prueba) y `report_export` (cada exportación de un informe a un fichero). No incluyen la configuración ni los filtros del informe. Los registros antiguos pueden tener tipos que ya no se generan. Trata `type` como un valor abierto: si no lo conoces, usa `description`. ## Operaciones - [Listar la actividad](#listar-la-actividad) ## Listar la actividad `GET /{companyId}/activity` devuelve la actividad con **paginación por cursor** (ver el patrón B en la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/)). **Respuesta:** `{ items: Activity[], hasMore: boolean, nextCursor: string | null }`. Para pedir la página siguiente, pasa `nextCursor` en `cursor`; cuando es `null`, no hay más. **Parámetros de consulta:** - `limit` — número máximo de resultados, entre `1` y `100`. Por defecto, `50`. - `cursor` — el `nextCursor` de la página anterior, tal cual se recibió. Devuelve la página que va detrás de ella en el orden pedido. Para la primera página, se omite. Es un valor opaco: uno que no haya devuelto la API responde `400`. - `sortBy` — `-timestamp` (por defecto) devuelve primero lo más reciente; `timestamp`, lo más antiguo. A igual fecha, se ordena por `id`. - `type` — tipos de actividad a incluir. Se repite para indicar varios (`type=inserted&type=archived`) y se incluye la actividad de cualquiera de ellos. - `username` — usuarios (email) que registraron la actividad. Admite varios, como `type`. - `documentId` — identificador del elemento al que se refiere la actividad. Devuelve el historial de ese elemento. - `documentType` — tipos del documento afectado (`invoice`, `bill`, `contact`…). Admite varios. - `minDate` / `maxDate` — fecha y hora mínima y máxima, ambas incluidas, en formato ISO 8601 (`YYYY-MM-DDTHH:mm:ss.sssZ`). Los `:` pueden ir sin codificar; el `+` de un desfase horario se codifica como `%2B`, o llega como un espacio. Admiten también solo la fecha (`YYYY-MM-DD`), que cuenta el día entero en la zona horaria de la empresa (por defecto, `Europe/Madrid`): `minDate=2026-09-01&maxDate=2026-09-30` es todo septiembre. **Parámetros globales aceptados:** Acepta además el header `accept-version`. Ver [Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/). > Este endpoint **no** acepta `offset` ni los filtros estándar > `minCreationDate`/`maxCreationDate`/`minModificationDate`/`maxModificationDate`. ###### Ejemplo de respuesta ```json { "items": [ { "id": 482913, "timestamp": "2026-09-15T08:42:17.351Z", "type": "send", "subtype": null, "username": "ana.garcia@ejemplo.es", "description": "Enviado", "document": { "id": "inv_7c1e2f40-5a8b-4d3c-9e6f-2b1a0c9d8e7f", "type": "invoice", "title": "F2026-0123", "archived": false }, "request": { "ip": "88.12.34.56", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" }, "details": { "actionSource": "user", "to": "pedidos@clienteejemplo.es" } }, { "id": 482877, "timestamp": "2026-09-15T07:00:04.120Z", "type": "bankSync", "subtype": "psd2", "username": "ami@facturadirecta.com", "description": "Sincronización bancaria realizada", "document": { "id": "", "type": "bank", "title": "Cuenta principal", "archived": false }, "request": null, "details": { "processedTransactions": 12, "loadedTransactions": 9, "repeatedTransactions": 3, "ignoredTransactions": 0 } } ], "hasMore": true, "nextCursor": "NDgyODc3" } ``` `` es un placeholder: cada empresa tiene sus propios identificadores. ###### Copy as cURL Primera página: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/activity?limit=100" ``` Siguiente página, con el `nextCursor` de la anterior: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/activity?limit=100&cursor=NDgyODc3" ``` Historial de una factura, del más antiguo al más reciente: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/activity?documentId=inv_7c1e2f40-5a8b-4d3c-9e6f-2b1a0c9d8e7f&sortBy=timestamp" ``` Documentos eliminados en septiembre: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/activity?type=archived&minDate=2026-09-01T00:00:00.000Z&maxDate=2026-09-30T23:59:59.999Z" ``` ## Recomendaciones - **Para llevar la actividad nueva a otro sistema**, pide en orden ascendente (`sortBy=timestamp`) y guarda el `nextCursor` de la última página que procesaste. En la siguiente consulta, pásalo en `cursor`: recibes solo lo posterior, sin repetir ni saltar registros. - **Para auditar un documento**, filtra por `documentId` en lugar de recorrer toda la actividad. Para ver qué valores cambiaron y recuperar datos, usa las [versiones del documento](https://www.facturadirecta.com/desarrolladores/api/guias/document-versions/). - **Acota con `minDate`/`maxDate`** las consultas de históricos largos: la actividad de una empresa crece sin límite. Con `type`, añade también las fechas si buscas un tipo que no es reciente; si no, la consulta repasa toda la actividad posterior hasta encontrarlo. Una consulta que tarda demasiado se corta con un `422`. - **No dependas de `description` para automatizar.** Es texto para personas y puede cambiar; usa `type`, `subtype` y `details`. ## Errores comunes - `400 ValidationError` — `cursor` no es un cursor devuelto por la API, `minDate`/`maxDate` no son una fecha o una fecha y hora ISO 8601 que exista, `type` o `sortBy` tienen un valor desconocido, o `limit` está fuera de `1`–`100`. - `422 Unprocessable Entity` (`code: "activity_query_timeout"`) — la consulta ha tardado demasiado. Acota la búsqueda con `minDate` y `maxDate` o con más filtros. - `403 Forbidden` (`code: "token_scope_missing"`) — falta el scope `activity:read`. - `403 Forbidden` (`code: "user_role_insufficient"`) — el rol del usuario no tiene el permiso de actividad completo. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. ## Referencia exhaustiva Esta página cubre los matices funcionales y los casos típicos. Para el contrato completo del registro, consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el [openapi crudo](https://app.facturadirecta.com/openapi.json). ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/activity` | `getActivity` | `activity:read` | Registro de actividad de la empresa | ## Scopes - **`activity:read`** — Lectura del registro de actividad de la empresa. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/activity.md --- # Búsqueda > La búsqueda global localiza en una sola llamada cualquier elemento de la empresa a partir de un texto: un cliente por su nombre o su NIF, una factura por su número, un producto por su referencia o… La **búsqueda global** localiza en una sola llamada cualquier elemento de la empresa a partir de un texto: un cliente por su nombre o su NIF, una factura por su número, un producto por su referencia o un gasto por el texto de sus líneas. Es la misma búsqueda que ofrece la paleta de comandos de la aplicación (`Cmd + K`), con el mismo criterio de coincidencia que la búsqueda rápida de cada listado. Sirve para **encontrar** y después leer el elemento con el `GET` de su recurso, o para enlazarlo con su `webUrl`. Para listar, paginar o filtrar por fechas, estados o importes están los listados de cada recurso, que admiten el mismo texto en su parámetro `search`. ## Qué se busca Entran los tipos que tienen búsqueda rápida en su listado de la aplicación, en este orden, que es también el orden de los grupos de la respuesta: | `type` | `resource` | Dónde se busca | |---|---|---| | `contact` | `contacts` | Nombre y apellidos, nombre comercial, NIF, IVA intracomunitario, email, teléfono, personas de contacto y direcciones. | | `invoice` | `invoices` | Número, título, cliente (nombre, apellidos, NIF), dirección y texto de las líneas. | | `estimate` | `estimates` | Como las facturas. | | `deliveryNote` | `deliveryNotes` | Como las facturas. | | `clientOrder` | `clientOrders` | Como las facturas. | | `purchaseOrder` | `purchaseOrders` | Como las facturas. | | `bill` | `bills` | Referencia, título, proveedor, notas y texto de las líneas. Incluye los tickets (`displayType: ticket`). | | `payroll` | `payrolls` | Empleado, título, notas y texto de las líneas. | | `product` | `products` | Título, referencia (SKU) y descripciones de venta y de compra. | | `automation` | `recurring` | Título y texto de la plantilla de las facturas recurrentes (`GET /{companyId}/recurring/invoices`). | Los documentos archivados no se devuelven. Los pagos, los movimientos bancarios, los asientos y la bandeja de entrada no entran en la búsqueda: se consultan con sus propios recursos. ## Cómo coincide el texto - Se buscan **todas las palabras** del texto, en cualquier orden y posición, sin distinguir mayúsculas ni acentos: `acme consultoria` encuentra la factura de *Acme Soluciones SL* con una línea «Consultoría trimestral». - El **número del documento** se compara normalizado: sin ceros a la izquierda y con guiones y barras equivalentes a espacios. `F-2026-12`, `F 2026 12` y `F-2026-0012` encuentran la misma factura. En las facturas de compra y tickets se compara la referencia del proveedor. - Dentro de cada tipo, las coincidencias de número van primero; el resto, de más reciente a más antiguo. ## Permisos La búsqueda no tiene un scope propio: **cada tipo entra con el scope de lectura de su recurso** (`contacts:read`, `invoices:read`, `estimates:read`, `deliveryNotes:read`, `clientOrders:read`, `purchaseOrders:read`, `bills:read`, `payrolls:read`, `products:read`, `recurring:read`) y con las mismas reglas que su listado: - Una **API key** busca en los recursos cuyos scopes tiene. - Con **OAuth** manda además el rol del usuario en la empresa: un usuario del rol *Ventas* no recibe facturas de compra ni proveedores aunque el token lleve esos scopes. Con permisos V3 (roles personalizados, aplicaciones conectadas), en contactos solo las facetas permitidas (clientes, proveedores, empleados). - Las facturas recurrentes exigen `recurring:read` e `invoices:read`, como su listado. - Si el token no puede leer ningún tipo, la respuesta es `403` con el código `token_scope_missing`. ## Lista de resultados `GET /{companyId}/search?q=acme` **Parámetros de consulta:** - `q` — texto a buscar, de 2 a 200 caracteres. Obligatorio. - `types` — recursos a los que acotar la búsqueda (`invoices`, `contacts`…), varios separados por comas (`types=invoices,contacts`) o repitiendo el parámetro (`types=invoices&types=contacts`). Sin indicar, todos los que el token puede leer. Un valor desconocido devuelve `400`. - `limitPerType` — resultados por tipo, de 1 a 20 (por defecto 5). No hay paginación: la búsqueda devuelve los primeros resultados de cada tipo y avisa con `hasMore` de que hay más. Para verlos todos, el listado del recurso con el mismo texto en `search` (por ejemplo `GET /{companyId}/invoices?search=acme`), que sí pagina y admite el resto de filtros. **Respuesta:** ```json { "items": [ { "type": "contact", "resource": "contacts", "id": "con_8f2c1a7e-3b4d-4e5f-9a6b-7c8d9e0f1a2b", "title": "Acme Soluciones SL", "subtitle": "B12345674 · hola@acme.example", "displayType": "contact", "webUrl": "https://3.facturadirecta.com/company/com_…/contacts/con_8f2c1a7e-3b4d-4e5f-9a6b-7c8d9e0f1a2b" }, { "type": "invoice", "resource": "invoices", "id": "inv_7c9e6679-7425-40de-944b-e07fc1f90ae7", "title": "F-2026-0012", "subtitle": "Acme Soluciones SL", "snippet": "…Consultoría trimestral · Soporte técnico…", "date": "2026-03-01", "total": 121, "currency": "EUR", "displayType": "invoice", "webUrl": "https://3.facturadirecta.com/company/com_…/doc/inv_7c9e6679-7425-40de-944b-e07fc1f90ae7" } ], "groups": [ { "type": "contact", "resource": "contacts", "hasMore": false }, { "type": "invoice", "resource": "invoices", "hasMore": true } ] } ``` | Campo | Significado | |---|---| | `items[].type` | Tipo de documento (`contact`, `invoice`, `bill`…). | | `items[].resource` | Recurso de la API con el que se lee: `GET /{companyId}//{id}` (`recurring` es `GET /{companyId}/recurring/invoices/{id}`). | | `items[].title` | Lo que identifica el elemento: el número del documento, el nombre del contacto, el proveedor de la factura de compra, el empleado de la nómina, el título del producto o de la factura recurrente. | | `items[].subtitle` | Segunda línea: el contacto del documento, el NIF y el email del contacto, la referencia del producto o de la factura de compra, el título de la nómina. Puede faltar. | | `items[].snippet` | Fragmento del texto del documento alrededor de la palabra buscada que coincide (una línea, una nota…), para saber por qué ha salido. Solo cuando la coincidencia no está ya en `title` ni en `subtitle`. | | `items[].date`, `total`, `currency` | Fecha e importe del documento, cuando los tiene. | | `items[].displayType` | Tipo tal como se muestra: `ticket` en las facturas de compra que son tickets; el tipo en el resto. | | `items[].webUrl` | Ficha del elemento en la aplicación web (ver [Abrir un resultado en FacturaDirecta](https://www.facturadirecta.com/desarrolladores/api/guias/overview/#abrir-un-resultado-en-facturadirecta)). | | `groups[]` | Tipos con algún resultado, en el orden en que aparecen en `items`, y si hay más de los devueltos (`hasMore`). | ## Ejemplo ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/search?q=F-2026-12&types=invoices" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` Con el `id` del resultado se lee el documento completo: ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/invoices/inv_7c9e6679-7425-40de-944b-e07fc1f90ae7" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/search` | `search` | `bills:read`, `clientOrders:read`, `contacts:read`, `deliveryNotes:read`, `estimates:read`, `invoices:read`, `payrolls:read`, `products:read`, `purchaseOrders:read`, `recurring:read` | Búsqueda global de documentos | ## Scopes - **`bills:read`** — Lectura de facturas de compra y tickets. - **`clientOrders:read`** — Lectura de pedidos. - **`contacts:read`** — Lectura de contactos. - **`deliveryNotes:read`** — Lectura de albaranes. - **`estimates:read`** — Lectura de presupuestos. - **`invoices:read`** — Lectura de facturas. - **`payrolls:read`** — Lectura de nóminas. - **`products:read`** — Lectura de productos. - **`purchaseOrders:read`** — Lectura de órdenes de compra. - **`recurring:read`** — Lectura de documentos recurrentes. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/search.md --- # Consulta analítica > La consulta analítica agrega los datos de la empresa en una sola llamada: eliges una fuente, las medidas que quieres sumar, las dimensiones por las que agrupar, un periodo y unos filtros, y la API… La **consulta analítica** agrega los datos de la empresa en una sola llamada: eliges una fuente, las medidas que quieres sumar, las dimensiones por las que agrupar, un periodo y unos filtros, y la API devuelve una tabla ya calculada. Es la forma de responder preguntas como «¿quiénes son mis cinco mejores clientes del año?», «¿cuánto suman las facturas vencidas?» o «¿qué provincias facturan más que el año pasado?» sin recorrer los listados página a página. Las cifras son las mismas que muestran los listados y los informes de la aplicación: cada medida se define una sola vez y con el mismo criterio que la pantalla equivalente. Los importes de las facturas son los del documento (base, impuestos, total y pendiente); los del diario, los de los apuntes. Es de solo lectura. Las consultas se calculan en el momento, sobre los datos actuales, y cada una tiene un tope de 30 segundos. > Para los informes oficiales (resumen de resultados, pérdidas y > ganancias, balance, sumas y saldos) usa los [informes](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/): > aplican las reglas de presentación de cada modelo. La consulta > analítica sirve para rankings, totales y comparativas a medida. ## Fuentes Cada fuente se llama como el recurso de la API cuyo listado devolvería esos documentos; `invoiceLines` y `billLines` son las líneas de esos mismos documentos, con su producto. No hay fuentes genéricas como «ventas» o «compras»: la contabilidad es solo `journal`. | Fuente | Qué contiene | Scope | Medida por defecto | |---|---|---|---| | `invoices` | [Facturas de venta](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/) definitivas, no anuladas y no borradas, con los importes del documento. Un apunte manual de ventas no está aquí: está en `journal`. | `invoices:read` | `totalBeforeTaxes` | | `invoiceLines` | Líneas de las facturas de `invoices`, con su producto, su cantidad y su importe sin impuestos. Las líneas sin cantidad, como las de solo texto, no entran. Es la fuente de «¿qué producto vendo más?». | `invoices:read` | `totalBeforeTaxes` | | `bills` | [Facturas de compra y tickets](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/) definitivos, no anulados y no borrados, con los importes del documento. Las nóminas y las amortizaciones no están aquí: están en `journal`. | `bills:read` | `totalBeforeTaxes` | | `billLines` | Líneas de las facturas de compra y tickets de `bills`, con su producto, su cantidad, su importe sin impuestos y su cuenta de gasto. Las líneas sin cantidad no entran. | `bills:read` | `totalBeforeTaxes` | | `contacts` | [Contactos](https://www.facturadirecta.com/desarrolladores/api/referencia/contacts/) no borrados que la credencial puede ver: clientes, proveedores y empleados según sus permisos. | `contacts:read` | `count` | | `journal` | Apuntes del [diario](https://www.facturadirecta.com/desarrolladores/api/referencia/journal/) de todos los documentos y asientos, sin los asientos de mantenimiento (apertura, regularización y cierre). Es la única fuente contable. | `accounting:read` | `net` | Los borradores no entran salvo que lo pidas con el filtro `draft`, y los documentos anulados, salvo que los pidas con el filtro `state` (`voided` en `value` o entre los `values`). Los documentos borrados no entran nunca. En `invoices`, las facturas simplificadas (las de un destinatario sin identificador fiscal) entran como las demás; el filtro `simplified` las separa. Las sustitutivas de simplificadas (F3) no entran nunca: repiten las ventas de las simplificadas que sustituyen y no tienen asiento, así que contarlas sumaría dos veces la misma venta. Los documentos con total cero entran y cuentan en `count`. ## Petición `POST /{companyId}/query` con un cuerpo JSON: | Campo | Tipo | Significado | |---|---|---| | `from` | string | Fuente: `invoices`, `invoiceLines`, `bills`, `billLines`, `contacts` o `journal`. Obligatorio. | | `measures` | string[] | Hasta 10 medidas de la fuente. En las medidas de días, la agregación va como sufijo: `daysToPayment.median`. Sin indicar, la medida por defecto de la fuente. | | `dimensions` | string[] | Hasta 2 dimensiones por las que agrupar, en orden. Sin indicar, una sola fila con los totales. | | `period` | object | `start` y `end` (`YYYY-MM-DD`), las dos fechas incluidas. Por defecto, el año natural en curso. Como máximo, 5 años. En `contacts` es opcional y sin tope: sin `period` entran todos los contactos. | | `compare` | string | `previousPeriod` (el periodo anterior de la misma duración) o `previousYear` (el mismo periodo del año anterior). Añade a cada medida las columnas de referencia, diferencia y variación. | | `breakdown` | string | `month`, `quarter` o `year`: divide el periodo en tramos naturales y añade la dimensión `period`. Como máximo, 24 tramos. No se combina con `compare`. | | `filters` | object[] | Hasta 10 filtros `{ field, op, value }` o `{ field, op, values }`. Ver [Filtros](#filtros). | | `orderBy` | object[] | Hasta 4 criterios `{ field, direction }` sobre las medidas y dimensiones de la consulta. `direction` es `asc` o `desc` (por defecto). Sin indicar, la primera medida de mayor a menor. | | `limit` | integer | Filas como máximo, entre 1 y 500. Por defecto, 10. | | `limitPerGroup` | integer | Con dos dimensiones, como máximo estas filas de la segunda dentro de cada valor de la primera: las primeras según las medidas de `orderBy` (o la primera medida, de mayor a menor). «Los cinco productos más vendidos de cada cliente». No se combina con `compare`, `breakdown` ni `includeOther`. | | `includeOther` | boolean | Añade `other` con la suma de los grupos que quedan fuera de `limit`. No se aplica con `breakdown`. | | `amounts` | string | Moneda de los importes en `invoices`, `bills` y sus líneas: `document` (por defecto), cada importe en la de su documento, con la moneda como dimensión implícita si hay varias; `company`, el contravalor de cada documento en la moneda de la empresa (`meta.currency`), en un solo grupo aunque haya varias monedas. `pending` solo admite `document`; con multimoneda, `cost`, `margin` y `marginRate` solo admiten `company`. | El periodo acota por la fecha del documento en `invoices` y `bills` y por la fecha del apunte en `journal`. En `contacts` acota por la fecha de alta del contacto y es opcional: sin `period`, la consulta cuenta todos los contactos («¿cuántos clientes tengo por provincia?»); con `period`, los dados de alta en esas fechas, sin el tope de 5 años. `compare` y `breakdown` necesitan `period` también en `contacts`. ## Medidas ### Facturas de venta y de compra | Medida | Tipo | Significado | |---|---|---| | `count` | número | Documentos del conjunto filtrado. | | `contacts` | número | Contactos distintos de los documentos del grupo. Con la dimensión `fiscalId`, más de uno indica contactos duplicados con el mismo identificador fiscal. | | `totalBeforeTaxes` | importe | Suma de la base imponible de cada documento, en su moneda. Es la cifra que el listado muestra como base. | | `taxes` | importe | Suma de la diferencia entre el total y la base imponible: IVA, recargos y retenciones con su signo. | | `indirectTaxes` | importe | Suma de los impuestos sin las retenciones de IRPF: IVA, recargo de equivalencia, IGIC o IPSI. Es la columna «Impuestos» de los informes «Ventas por cliente» y «Compras por proveedor». | | `retentions` | importe | Suma de las retenciones de IRPF, en positivo. | | `gross` | importe | Base imponible más los impuestos sin descontar las retenciones (`totalBeforeTaxes` + `indirectTaxes`): el «Importe total» de los informes «Ventas por cliente» y «Compras por proveedor». | | `total` | importe | Suma del total de cada documento, impuestos incluidos. | | `pending` | importe | Importe pendiente de cobro (o de pago, en `bills`): el total menos lo cobrado. Negativo si se ha cobrado de más. | | `average` | importe | Base imponible media por documento: `totalBeforeTaxes` entre `count`. | | `daysToPayment` | días | Días entre la fecha del documento y la fecha del último cobro (o pago) que lo salda. Solo cuentan los documentos cobrados. Cobros y pagos a fondo: en todos los planes de pago, no en el Gratis. | | `daysOverdue` | días | Días entre el vencimiento y el cobro, o hasta hoy si sigue pendiente; nunca negativos. Solo cuentan los documentos con vencimiento. Cobros y pagos a fondo: en todos los planes de pago, no en el Gratis. | Las medidas de días admiten la agregación como sufijo: `.avg` (por defecto), `.median`, `.p90`, `.min`, `.max` o `.count` (cuántos documentos entran en el cálculo). Puedes pedir varias a la vez: `daysToPayment` y `daysToPayment.median`. ### Líneas de facturas de venta y de compra En `invoiceLines` y `billLines`: | Medida | Tipo | Significado | |---|---|---| | `count` | número | Líneas del conjunto filtrado. | | `documents` | número | Documentos distintos con alguna línea en el grupo. | | `contacts` | número | Contactos distintos de los documentos del grupo: por ejemplo, cuántos clientes compran un producto. | | `quantity` | número | Suma de las cantidades de las líneas, con hasta cuatro decimales. | | `totalBeforeTaxes` | importe | Suma del importe de las líneas sin impuestos, con el descuento de cada línea, en la moneda del documento. | | `averagePrice` | importe | `totalBeforeTaxes` entre `quantity`: el precio medio por unidad. | En `invoiceLines`, además, el margen de las ventas, incluido en todos los planes de pago (ver [Planes](#planes)). El coste sale del precio de compra que tiene hoy el producto, en la moneda de la empresa, así que estas medidas van siempre en esa moneda: con multimoneda, piden `amounts: "company"` para que las ventas también lo estén (si no, `400`). | Medida | Tipo | Significado | |---|---|---| | `cost` | importe | Cantidad por el precio de compra del producto (el de su faceta de compra, con su descuento). Las líneas sin producto, o con un producto sin precio de compra, no tienen coste: un grupo sin ninguna con coste da `null`. | | `margin` | importe | `totalBeforeTaxes` menos `cost`, solo de las líneas con coste. | | `marginRate` | ratio | `margin` entre la base imponible de las líneas con coste, como fracción (`0.25` es un 25 %). | ### Contactos | Medida | Tipo | Significado | |---|---|---| | `count` | número | Contactos del conjunto filtrado. | ### Diario Todos los importes van en la moneda de la empresa. | Medida | Tipo | Significado | |---|---|---| | `count` | número | Apuntes del conjunto filtrado. | | `debit` | importe | Suma del debe. | | `credit` | importe | Suma del haber. | | `balance` | importe | Debe menos haber: saldo deudor en positivo. | | `net` | importe | Importe con el signo natural de cada cuenta: haber menos debe en el grupo 7 (ingresos en positivo) y debe menos haber en el resto (gastos en positivo). Es la medida de un ranking de ventas o de gastos. | | `income` | importe | Haber menos debe de los apuntes del grupo 7, como el resumen de resultados. | | `expenses` | importe | Debe menos haber de los apuntes del grupo 6, como el resumen de resultados. | | `result` | importe | `income` menos `expenses`. | ## Dimensiones ### Facturas de venta y de compra | Dimensión | Valor | Significado | |---|---|---| | `contact` | entidad | Cliente o proveedor del documento. | | `contact.region` | entidad | Provincia de la dirección actual del contacto (la del contacto hoy, no la del documento). `id` es el código de provincia y `name` su nombre, para España. | | `contact.country` | código | País de la dirección actual del contacto (código ISO de dos letras), no el del documento. | | `fiscalId` | entidad | Identificador fiscal de la contraparte: NIF, CIF o NIE en España, NIF-IVA intracomunitario (VAT number) en la UE o el identificador fiscal de su país. `id` es el identificador normalizado (ver [Identificador fiscal](#identificador-fiscal)) y `name` el nombre de la contraparte en el documento más reciente del grupo. | | `country` | código | País del documento (ISO 3166-1 alpha-2). | | `series` | código | Serie de numeración. | | `currency` | código | Moneda del documento. | | `state` | valor | Estado del documento: `pending`, `overdue`, `paid`, `overpaid` o `voided`. | | `paymentMethod` | entidad | Método de cobro o de pago del documento. | | `agingBucket` | tramo | Antigüedad del pendiente: `notDue` (sin vencer), `1-30`, `31-60`, `61-90` y `over90` días de retraso; `settled` para los cobrados o pagados. Cobros y pagos a fondo: en todos los planes de pago, no en el Gratis. | | `day`, `week`, `month`, `quarter`, `year` | periodo | Día, semana ISO (de lunes a domingo), mes, trimestre o año natural de la fecha del documento (`2026-03-31`, `2026-W14`, `2026-03`, `2026-Q1`, `2026`). | | `account` | código | Solo en `bills`: cuenta contable de gasto de la cabecera del documento. | ### Líneas de facturas de venta y de compra En `invoiceLines` y `billLines`, además de `contact`, `contact.region`, `contact.country`, `fiscalId`, `country`, `series`, `currency` y las de tiempo, que son las del documento de la línea: | Dimensión | Valor | Significado | |---|---|---| | `product` | entidad | Producto o servicio de la línea, con `id`, `name` y `sku` (su referencia). Las líneas sin producto forman el grupo `null`. | | `account` | código | Cuenta contable de la línea: de ventas en `invoiceLines`, de gasto en `billLines`. | ### Contactos | Dimensión | Valor | Significado | |---|---|---| | `address.region` | entidad | Provincia de la dirección del contacto, con `id` (código) y `name` (nombre, para España). | | `address.country` | código | País de la dirección del contacto. | | `fiscalId` | entidad | Identificador fiscal del contacto, normalizado como en las facturas. Con `count`, un grupo de más de uno son contactos duplicados. `name` es el nombre del contacto dado de alta más recientemente. | | `isClient`, `isProvider`, `isEmployee` | booleano | El contacto tiene esa faceta. | | `createdMonth` | periodo | Mes natural de alta del contacto (`2026-03`). | ### Diario | Dimensión | Valor | Significado | |---|---|---| | `account` | entidad | Cuenta contable completa, con `id` (`700000`) y `name` (su título en el plan contable). | | `account.group`, `account.subgroup`, `account.code` | código | La primera cifra de la cuenta (`7`), las dos primeras (`70`) o las tres primeras (`700`). | | `thirdParty` | entidad | A quién se imputa el apunte: el contacto en las cuentas de clientes y proveedores, el banco en las de tesorería, el producto en las de ventas y compras cuando la línea lleva producto. Incluye `type` (`contact`, `bank` o `product`). | | `contact` | entidad | Contacto del documento que generó el asiento (la factura, la compra, la nómina, el cobro o la cabecera de un asiento manual). Si el documento no tiene contacto y el asiento tiene una sola contrapartida de cliente, proveedor o acreedor (cuentas 43, 40 o 41), ese contacto. Si no, `unassigned`. | | `category` | valor | Origen del importe, el del [resumen de resultados](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/): `invoices`, `currencyGains`, `otherIncome`, `bills`, `payrolls`, `depreciation`, `currencyLosses`, `otherExpenses`. Solo los apuntes de los grupos 6 y 7 tienen categoría. | | `documentType` | valor | Tipo del documento que generó el asiento: `invoice`, `bill`, `payroll`, `transaction` (cobros, pagos y asientos manuales) u `other`. | | `day`, `week`, `month`, `quarter`, `year` | periodo | Día, semana ISO, mes, trimestre o año natural de la fecha del apunte. | La dimensión `contact` del diario es la que responde «ventas de las cuentas 7 por cliente» incluyendo los asientos manuales, algo que `invoices` no ve. Un apunte de ventas sin contacto queda en `unassigned`. ### Identificador fiscal `fiscalId` agrupa por el identificador fiscal y no por el contacto: dos contactos duplicados con el mismo NIF suman en una sola fila. En las facturas es el identificador que el documento copió de su contacto al emitirse; los documentos más antiguos, que no lo guardan, toman el del contacto hoy, como el listado. Para agrupar, el identificador se normaliza según su país: | País del identificador | Clave | Ejemplo | |---|---|---| | España | En mayúsculas, sin espacios ni signos, sin el prefijo `ES` y con el DNI completado con ceros a la izquierda. | `ESB-12345674` y `b12345674` son `B12345674`; `1234567L` es `01234567L`. | | Resto de la UE | En mayúsculas, sin espacios ni signos y en la forma del NIF-IVA intracomunitario: con el prefijo de su país, lo lleve o no (`EL` en Grecia). | `12 345 678 901` de Francia es `FR12345678901`. | | Resto del mundo | En mayúsculas y sin espacios ni signos. | `12-3456789` es `123456789`. | El país es el del identificador del contacto o, si no lo tiene, el de su dirección. Los documentos sin identificador forman el grupo `null`, que en un negocio con muchos tickets puede ser el más grande. El filtro `fiscalId` busca la clave tal como la da la dimensión. Admite separadores y minúsculas, y un NIF español con el prefijo `ES` o un DNI sin sus ceros. Un identificador de otro país de la UE tiene que llevar su prefijo (`FR12345678901`): sin él, las mismas cifras podrían ser de otro país. ## Filtros Cada filtro tiene un campo, un operador y un valor (`value`) o una lista de valores (`values`): | Operador | Valor | Uso | |---|---|---| | `eq` | `value` | Igual a. | | `in` | `values` | Cualquiera de la lista, hasta 100 valores. | | `gte`, `lte` | `value` | Mayor o igual, menor o igual. Importes y fechas. | | `between` | `values` | Dos valores, los dos incluidos. Importes y fechas. | | `startsWith` | `value` | Empieza por. Cuentas contables: `7`, `629`. | | Fuente | Filtro | Operadores | Significado | |---|---|---|---| | `invoices`, `bills` | `contact` | `eq`, `in` | Identificador del contacto (`con_…`). | | | `contact.region` | `eq`, `in` | Código de provincia de la dirección del contacto. | | | `contact.country` | `eq`, `in` | Código ISO del país de la dirección del contacto. | | | `fiscalId` | `eq`, `in` | Identificador fiscal de la contraparte, sea cual sea el contacto. Ver [Identificador fiscal](#identificador-fiscal). | | | `country` | `eq`, `in` | Código ISO del país del documento. | | | `series` | `eq`, `in` | Serie de numeración. | | | `currency` | `eq`, `in` | Moneda del documento. | | | `state` | `eq`, `in` | `pending`, `overdue`, `paid`, `overpaid` o `voided`. Sin este filtro, los anulados no entran. | | | `paymentMethod` | `eq`, `in` | Identificador del método de pago. | | | `total`, `totalBeforeTaxes` | `gte`, `lte`, `between` | Importes del documento. | | | `dueDate` | `gte`, `lte`, `between` | Fecha de vencimiento (`YYYY-MM-DD`). | | | `tag` | `eq`, `in` | Etiquetas que el documento debe tener, todas ellas. | | | `draft` | `eq` | `true` incluye los borradores junto a los definitivos. | | `invoices` | `simplified` | `eq` | `true` deja solo las facturas simplificadas; `false` las deja fuera. Sin este filtro entran las dos. | | `bills` | `account` | `eq`, `in`, `startsWith` | Cuenta contable de gasto del documento. | | `invoiceLines`, `billLines` | `product` | `eq`, `in` | Identificador del producto (`pro_…`). | | | `hasProduct` | `eq` | `true` deja solo las líneas con producto; `false`, solo las de texto libre. | | | `account` | `eq`, `in`, `startsWith` | Cuenta contable de la línea. | | | `contact`, `contact.region`, `contact.country`, `fiscalId`, `country`, `series`, `currency`, `tag`, `draft` | | Los del documento de la línea, como en `invoices` y `bills`. | | `invoiceLines` | `simplified` | `eq` | Las líneas de facturas simplificadas, solas (`true`) o fuera (`false`). | | `contacts` | `address.region`, `address.country` | `eq`, `in` | Provincia y país de la dirección. | | | `fiscalId` | `eq`, `in` | Identificador fiscal del contacto. | | | `isClient`, `isProvider`, `isEmployee` | `eq` | Faceta del contacto. | | | `tag` | `eq`, `in` | Etiquetas que el contacto debe tener, todas ellas. | | `journal` | `account` | `eq`, `in`, `startsWith` | Cuenta contable o prefijo. | | | `thirdParty` | `eq`, `in` | Identificador del contacto, banco o producto al que se imputa el apunte. | | | `contact` | `eq`, `in` | Identificador del contacto del documento que generó el asiento. | | | `category` | `eq`, `in` | Categoría del resumen de resultados. | | | `documentType` | `eq`, `in` | Tipo del documento que generó el asiento. | ## Respuesta | Campo | Tipo | Significado | |---|---|---| | `meta.source`, `meta.basis` | string | La fuente y su base: `documents` (cifras de los documentos) o `accounting` (cifras del diario). | | `meta.period` | object | El periodo consultado, `start` y `end`. Ausente en `contacts` sin `period`. | | `meta.compare` | object | Con `compare`: `kind`, `start` y `end` del periodo de referencia. | | `meta.breakdown` | string | Con `breakdown`: la unidad de los tramos. | | `meta.currency` | string | Moneda de los importes cuando `currency` no es una dimensión de la respuesta o cuando van en la de la empresa (`amounts: "company"`). | | `meta.limit`, `meta.generatedAt` | | El límite aplicado y el momento del cálculo. | | `columns` | object[] | Qué es cada clave de las filas, en orden: `id`, `kind` (`dimension` o `measure`), `title`, `description`, `type` y, en las medidas, `measure` y `period`. | | `rows` | object[] | Los grupos, ordenados y limitados. Una clave por columna. | | `totals` | object | El valor de cada medida sobre el conjunto filtrado completo, no solo sobre las filas devueltas. Con `compare`, también el del periodo de referencia, la diferencia y la variación (`_reference`, `_difference` y `_variance`). | | `other` | object | Con `includeOther`, si hay más grupos que `limit`: `groups` (cuántos quedan fuera) y la suma de cada medida que se puede sumar; `null` en las que no (`average`, las de días). Con `compare`, también `_reference`, `_difference` y `_variance`; la referencia del resto es el total de referencia menos el de las filas, así que la tabla cuadra con `totals`. | | `truncated` | boolean | Había más grupos que `limit`. | En cada fila, el valor de una dimensión depende de su tipo: - **Entidad** (`contact`, `product`, `fiscalId`, `paymentMethod`, `thirdParty`, `account`, las provincias): `{ "id", "name" }`, más `type` en `thirdParty`, `fiscalId` y `country` (los del contacto hoy) en `contact`, `sku` en `product`, y `webUrl` cuando el elemento tiene ficha en la aplicación (ver [Abrir un resultado en FacturaDirecta](https://www.facturadirecta.com/desarrolladores/api/guias/overview/#abrir-un-resultado-en-facturadirecta)). `name` es `null` si el elemento ya no existe. - **Periodo**: las dimensiones de tiempo (`day`, `week`, `month`, `quarter`, `year`, `createdMonth`) valen un texto (`2026-W14`, `2026-03`); la dimensión `period` del desglose, `{ "id", "start", "end" }`. - El resto: un texto, un número o un booleano. Una dimensión sin valor (un documento sin país, por ejemplo) vale `null` y forma su propio grupo. Las medidas valen un número, o `null` cuando no hay dato (la media de días de un cliente sin cobros). Los importes van redondeados a céntimos y los días a una décima. Con `compare`, cada medida añade tres columnas: `_reference` (el valor en el periodo de referencia), `_difference` (periodo menos referencia) y `_variance` (la variación como fracción, `0.2` es un 20 %, o `null` si la referencia es cero). Con `breakdown`, la columna `period` identifica el tramo de cada fila. En empresas con multimoneda, `currency` es una dimensión implícita de `invoices`, `bills` y sus líneas cuando la consulta pide importes: aparece en las filas aunque no la pidas y los importes de monedas distintas nunca se mezclan. Una consulta sin importes (documentos, cantidades, días) no se separa por moneda. Con `amounts: "company"` los importes van en la moneda de la empresa y tampoco se separan. Los importes de `journal` van siempre en la moneda de la empresa. ## Operaciones - [Consultar](#consultar) ## Consultar `POST /{companyId}/query` Necesita el scope de la fuente: `invoices:read`, `bills:read`, `contacts:read` o `accounting:read`. Una API key con varios de ellos puede consultar varias fuentes, una por llamada. No admite la cabecera `Idempotency-Key`: repetir la consulta no tiene efectos. ### Ejemplo: los cinco mejores clientes del año ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "invoices", "measures": ["totalBeforeTaxes", "count"], "dimensions": ["contact"], "limit": 5, "includeOther": true }' ``` ```json { "meta": { "source": "invoices", "basis": "documents", "period": { "start": "2026-01-01", "end": "2026-12-31" }, "currency": "EUR", "limit": 5, "generatedAt": "2026-10-18T10:00:00.000Z" }, "columns": [ { "id": "contact", "kind": "dimension", "title": "Contacto", "description": "Cliente o proveedor del documento (`id`, `name`).", "type": "entity" }, { "id": "totalBeforeTaxes", "kind": "measure", "title": "Base imponible", "description": "Suma de la base imponible de cada factura, en la moneda del documento. Es la cifra que el listado muestra como «base».", "type": "money", "measure": "totalBeforeTaxes", "period": { "start": "2026-01-01", "end": "2026-12-31" } }, { "id": "count", "kind": "measure", "title": "Documentos", "description": "Número de facturas del conjunto filtrado.", "type": "count", "measure": "count", "period": { "start": "2026-01-01", "end": "2026-12-31" } } ], "rows": [ { "contact": { "id": "", "name": "Cliente Uno", "webUrl": "https://app.facturadirecta.com/company//contacts/" }, "totalBeforeTaxes": 18400, "count": 14 }, { "contact": { "id": "", "name": "Cliente Dos", "webUrl": "https://app.facturadirecta.com/company//contacts/" }, "totalBeforeTaxes": 12750, "count": 9 } ], "totals": { "totalBeforeTaxes": 48200, "count": 61 }, "other": { "groups": 23, "totalBeforeTaxes": 17050, "count": 38 }, "truncated": true } ``` La respuesta real tiene cinco filas; aquí se muestran dos. `totals` suma las 61 facturas del año y `other` las 38 de los 23 clientes que quedan fuera de las cinco primeras filas. ### Ejemplo: los mejores clientes por identificador fiscal Si el mismo cliente está dado de alta dos veces, `contact` lo reparte en dos filas. `fiscalId` lo reúne, y `contacts` dice cuántos contactos hay detrás de cada fila. ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "invoices", "measures": ["totalBeforeTaxes", "contacts"], "dimensions": ["fiscalId"], "limit": 5 }' ``` ```json { "rows": [ { "fiscalId": { "id": "B12345674", "name": "Cliente Uno SL" }, "totalBeforeTaxes": 21300, "contacts": 2 }, { "fiscalId": { "id": "FR12345678901", "name": "Client Deux SARL" }, "totalBeforeTaxes": 9800, "contacts": 1 } ], "totals": { "totalBeforeTaxes": 48200, "contacts": 37 } } ``` La primera fila reúne dos contactos con el NIF `B12345674`. Para ver cuáles son, repite la consulta con `"dimensions": ["fiscalId", "contact"]` y el filtro `{ "field": "fiscalId", "op": "eq", "value": "B12345674" }`. Para buscar todos los contactos duplicados sin mirar facturas, consulta `contacts` con la medida `count` y la dimensión `fiscalId`: los grupos de más de uno salen primero. ### Ejemplo: facturas vencidas y cuánto suman Sin dimensiones, la respuesta es una sola fila con los totales del conjunto filtrado. ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "invoices", "measures": ["count", "total", "pending"], "period": { "start": "2022-01-01", "end": "2026-12-31" }, "filters": [{ "field": "state", "op": "in", "values": ["overdue"] }] }' ``` ```json { "meta": { "source": "invoices", "basis": "documents", "period": { "start": "2022-01-01", "end": "2026-12-31" }, "currency": "EUR", "limit": 10, "generatedAt": "2026-10-18T10:00:00.000Z" }, "columns": [ { "id": "count", "kind": "measure", "title": "Documentos", "description": "Número de facturas del conjunto filtrado.", "type": "count", "measure": "count", "period": { "start": "2022-01-01", "end": "2026-12-31" } }, { "id": "total", "kind": "measure", "title": "Total", "description": "Suma del total de cada factura, impuestos incluidos, en la moneda del documento.", "type": "money", "measure": "total", "period": { "start": "2022-01-01", "end": "2026-12-31" } }, { "id": "pending", "kind": "measure", "title": "Pendiente", "description": "Importe pendiente de cobro, en la moneda del documento: el total menos lo cobrado; negativo si se ha cobrado de más.", "type": "money", "measure": "pending", "period": { "start": "2022-01-01", "end": "2026-12-31" } } ], "rows": [ { "count": 3, "total": 1815, "pending": 1815 } ], "totals": { "count": 3, "total": 1815, "pending": 1815 }, "truncated": false } ``` Para la lista de esas facturas, con sus datos, usa el [listado](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/#lista-de-facturas) con `state=overdue`; con `totals=true` el listado devuelve también estas sumas. ### Ejemplo: provincias con más facturación, frente al año anterior ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "invoices", "measures": ["totalBeforeTaxes"], "dimensions": ["contact.region"], "period": { "start": "2026-01-01", "end": "2026-06-30" }, "compare": "previousYear", "limit": 5 }' ``` ```json { "meta": { "source": "invoices", "basis": "documents", "period": { "start": "2026-01-01", "end": "2026-06-30" }, "compare": { "kind": "previousYear", "start": "2025-01-01", "end": "2025-06-30" }, "currency": "EUR", "limit": 5, "generatedAt": "2026-10-18T10:00:00.000Z" }, "columns": [ { "id": "contact.region", "kind": "dimension", "title": "Provincia del contacto", "description": "Provincia de la dirección actual del contacto (la del contacto hoy, no la del documento): `id` es el código y `name` el nombre para España.", "type": "entity" }, { "id": "totalBeforeTaxes", "kind": "measure", "title": "Base imponible", "description": "…", "type": "money", "measure": "totalBeforeTaxes", "period": { "start": "2026-01-01", "end": "2026-06-30" } }, { "id": "totalBeforeTaxes_reference", "kind": "measure", "title": "Base imponible (referencia)", "description": "La medida en el periodo de referencia (el mismo periodo del año anterior).", "type": "money", "measure": "totalBeforeTaxes", "period": { "start": "2025-01-01", "end": "2025-06-30" } }, { "id": "totalBeforeTaxes_difference", "kind": "measure", "title": "Base imponible (diferencia)", "description": "Periodo menos referencia.", "type": "money", "measure": "totalBeforeTaxes" }, { "id": "totalBeforeTaxes_variance", "kind": "measure", "title": "Base imponible (variación)", "description": "Variación sobre la referencia, como fracción (`0.2` es un 20 %); `null` si la referencia es cero.", "type": "ratio", "measure": "totalBeforeTaxes" } ], "rows": [ { "contact.region": { "id": "08", "name": "Barcelona" }, "totalBeforeTaxes": 31200, "totalBeforeTaxes_reference": 27400, "totalBeforeTaxes_difference": 3800, "totalBeforeTaxes_variance": 0.1387 }, { "contact.region": { "id": "28", "name": "Madrid" }, "totalBeforeTaxes": 12900, "totalBeforeTaxes_reference": 14100, "totalBeforeTaxes_difference": -1200, "totalBeforeTaxes_variance": -0.0851 } ], "totals": { "totalBeforeTaxes": 52300, "totalBeforeTaxes_reference": 48900, "totalBeforeTaxes_difference": 3400, "totalBeforeTaxes_variance": 0.0695 }, "truncated": false } ``` ### Ejemplo: ventas de las cuentas 7 por cliente, asientos manuales incluidos ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "journal", "measures": ["net"], "dimensions": ["contact"], "filters": [{ "field": "account", "op": "startsWith", "value": "7" }], "limit": 5 }' ``` ```json { "meta": { "source": "journal", "basis": "accounting", "period": { "start": "2026-01-01", "end": "2026-12-31" }, "currency": "EUR", "limit": 5, "generatedAt": "2026-10-18T10:00:00.000Z" }, "columns": [ { "id": "contact", "kind": "dimension", "title": "Contacto del asiento", "description": "…", "type": "entity" }, { "id": "net", "kind": "measure", "title": "Importe neto", "description": "…", "type": "money", "measure": "net", "period": { "start": "2026-01-01", "end": "2026-12-31" } } ], "rows": [ { "contact": { "id": "", "name": "Cliente Uno", "webUrl": "https://app.facturadirecta.com/company//contacts/" }, "net": 18400 }, { "contact": { "id": "unassigned", "name": null }, "net": 300 } ], "totals": { "net": 48500 }, "truncated": false } ``` ### Ejemplo: resultado del año mes a mes ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "journal", "measures": ["income", "expenses", "result"], "breakdown": "month", "limit": 12 }' ``` Cada fila lleva la dimensión `period` con el tramo: ```json { "meta": { "source": "journal", "basis": "accounting", "period": { "start": "2026-01-01", "end": "2026-12-31" }, "breakdown": "month", "currency": "EUR", "limit": 12, "generatedAt": "2026-10-18T10:00:00.000Z" }, "columns": [ { "id": "period", "kind": "dimension", "title": "Periodo", "description": "Tramo del desglose por meses: `id`, `start` y `end`.", "type": "period" }, { "id": "income", "kind": "measure", "title": "Ingresos", "description": "…", "type": "money", "measure": "income", "period": { "start": "2026-01-01", "end": "2026-12-31" } }, { "id": "expenses", "kind": "measure", "title": "Gastos", "description": "…", "type": "money", "measure": "expenses", "period": { "start": "2026-01-01", "end": "2026-12-31" } }, { "id": "result", "kind": "measure", "title": "Resultado", "description": "…", "type": "money", "measure": "result", "period": { "start": "2026-01-01", "end": "2026-12-31" } } ], "rows": [ { "period": { "id": "2026-01", "start": "2026-01-01", "end": "2026-01-31" }, "income": 8200, "expenses": 5100, "result": 3100 }, { "period": { "id": "2026-02", "start": "2026-02-01", "end": "2026-02-28" }, "income": 7600, "expenses": 5400, "result": 2200 } ], "totals": { "income": 91500, "expenses": 60300, "result": 31200 }, "truncated": false } ``` Las filas del desglose van por tramo y, dentro de cada tramo, por el orden pedido. Un tramo sin apuntes no aparece. ### Ejemplo: días medios de cobro por cliente ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "invoices", "measures": ["daysToPayment", "daysToPayment.median", "count"], "dimensions": ["contact"], "orderBy": [{ "field": "daysToPayment", "direction": "desc" }], "limit": 10 }' ``` ```json { "rows": [ { "contact": { "id": "", "name": "Cliente Uno", "webUrl": "…" }, "daysToPayment": 41.5, "daysToPayment.median": 38, "count": 14 }, { "contact": { "id": "", "name": "Cliente Dos", "webUrl": "…" }, "daysToPayment": null, "daysToPayment.median": null, "count": 2 } ], "totals": { "daysToPayment": 27.3, "daysToPayment.median": 24, "count": 61 } } ``` El segundo cliente tiene dos facturas sin cobrar: cuenta en `count`, pero no en los días, que valen `null`. ### Ejemplo: los productos que más vendes a cada cliente Las tres primeras líneas de producto de cada cliente, por unidades, sin las líneas de texto libre: ```bash curl "https://app.facturadirecta.com/api/$COMPANY_ID/query" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "from": "invoiceLines", "measures": ["quantity", "totalBeforeTaxes"], "dimensions": ["contact", "product"], "filters": [{ "field": "hasProduct", "op": "eq", "value": true }], "orderBy": [{ "field": "contact", "direction": "asc" }, { "field": "quantity", "direction": "desc" }], "limitPerGroup": 3, "limit": 100 }' ``` ```json { "rows": [ { "contact": { "id": "", "name": "Cliente Uno", "webUrl": "…" }, "product": { "id": "", "name": "Tornillo M4", "sku": "TOR-M4", "webUrl": "…" }, "quantity": 1200, "totalBeforeTaxes": 240 }, { "contact": { "id": "", "name": "Cliente Uno", "webUrl": "…" }, "product": { "id": "", "name": "Tuerca M4", "sku": "TUE-M4", "webUrl": "…" }, "quantity": 800, "totalBeforeTaxes": 96 } ], "totals": { "quantity": 5310, "totalBeforeTaxes": 1874.5 } } ``` `totals` es el de todas las líneas del filtro, no solo el de las filas que deja `limitPerGroup`. ## Qué cifras salen - Una consulta sin dimensiones sobre `invoices` da el mismo recuento y las mismas sumas que el [listado de facturas](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/) con los mismos filtros, `state` con todos los estados menos `draft` y `voided`, y `totals=true`, salvo por las sustitutivas (F3), que el listado sí incluye. Lo mismo entre `bills` y el [listado de facturas de compra](https://www.facturadirecta.com/desarrolladores/api/referencia/bills/), que no tiene sustitutivas. - `invoiceLines` agrupado por `product` da las mismas cantidades e importes que el informe «Ventas por producto» de la aplicación, y `billLines`, que «Compras por producto», salvo que este cuenta también las líneas de las compras anuladas. - Una factura anulada tiene los importes a cero: pedirla con `state` solo cambia `count` y las medidas que dependen de él (`average`, `contacts`). - `journal` con la dimensión `category` da las mismas cifras que el [resumen de resultados](https://www.facturadirecta.com/desarrolladores/api/referencia/reports/#obtener-el-resumen-de-resultados): los apuntes de los grupos 6 y 7 del periodo, sin el asiento de regularización. - La provincia (`contact.region`) es la de la dirección actual del contacto. Un cliente que cambió de provincia cuenta en la nueva para todas sus facturas. - `state` y `agingBucket` se calculan a la fecha de hoy en la zona horaria de la empresa, como el listado. - Las medidas que no se suman (`average`, `contacts` y las de días) se calculan en `totals` sobre todos los documentos, no como media o suma de las filas. ## Permisos Cada fuente necesita su scope: `invoices:read` (también para `invoiceLines`), `bills:read` (también para `billLines`), `contacts:read` o `accounting:read`. En `contacts`, la credencial solo ve las facetas (clientes, proveedores, empleados) que su permiso le da. ## Planes Todos los planes consultan sus datos. Dos grupos de nombres están incluidos en todos los planes de pago y no en el Gratis, igual en la aplicación, en la API y en el MCP: - **Cobros y pagos a fondo**: la dimensión `agingBucket` y las medidas `daysToPayment` y `daysOverdue`, en `invoices` y `bills`. Sin ellos la consulta responde `403` con `code: plan_limit_exceeded` y `hint.features: ["collectionsAnalytics"]`. - **Margen**: las medidas `cost`, `margin` y `marginRate` de `invoiceLines`. Sin él, `403` con `hint.features: ["marginAnalytics"]`. Los nombres siguen en el vocabulario y en las descripciones de `describe`, así que una aplicación puede anunciar lo que la empresa podría consultar si ampliara su plan. Un administrador de la empresa lo amplía en FacturaDirecta; volver a conectar la aplicación no lo soluciona. La contabilidad (`journal`) no depende del plan en la API: la decide el scope `accounting:read`. ## Errores comunes - `400 Bad Request`: una fuente, medida, dimensión o filtro fuera del vocabulario. El mensaje propone el nombre más parecido: «La medida 'totalbefore' no existe en 'invoices': ¿quieres decir 'totalBeforeTaxes'?». Algunos sinónimos también tienen sugerencia: `taxId`, `nif` o `vat` proponen `fiscalId`. También `compare` y `breakdown` a la vez, más de 2 dimensiones, 10 medidas o 10 filtros, un periodo de más de 5 años o de más de 24 tramos, una fecha que no existe, un operador que el filtro no admite, `limit` fuera de 1 a 500 o `limitPerGroup` sin dos dimensiones. - `403 Forbidden` (`token_scope_missing`): la credencial no tiene el scope de la fuente. - `403 Forbidden` (`plan_limit_exceeded`): la consulta pide cifras de cobros a fondo o de margen que el plan de la empresa no incluye; `hint.features` dice cuál falta (ver [Planes](#planes)). - `422 Unprocessable Entity` (`report_query_timeout`): la consulta ha tardado más de 30 segundos. Acota el periodo, las dimensiones o los filtros. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | POST | `/{companyId}/query` | `analyticsQuery` | `accounting:read`, `bills:read`, `contacts:read`, `invoices:read` | Consulta analítica | ## Scopes - **`accounting:read`** — Lectura de datos de contabilidad (diario). - **`bills:read`** — Lectura de facturas de compra y tickets. - **`contacts:read`** — Lectura de contactos. - **`invoices:read`** — Lectura de facturas. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/query.md --- # Webhooks > Los webhooks notifican en tiempo real cuando ocurren cambios en los recursos de tu empresa. Los webhooks notifican en tiempo real cuando ocurren cambios en los recursos de tu empresa. En vez de hacer polling, configuras una URL HTTPS y FacturaDirecta envía una petición `POST` con el detalle del cambio cada vez que se produce un evento al que estás suscrito. Esta página es la **referencia técnica** para desarrolladores: payloads, firma, eventos, endpoints de API y reintentos. Si lo que necesitas es crear o gestionar un endpoint desde la interfaz de FacturaDirecta, consulta [Configurar webhooks](https://help.facturadirecta.com/es/articles/15097369-configurar-webhooks). Son útiles para integraciones con ERPs, CRMs, plataformas de e-commerce, asesorías y cualquier automatización que necesite reaccionar a cambios en la cuenta de facturación sin hacer polling. Antes de configurarlos en producción, pruébalos en un [entorno sandbox](https://help.facturadirecta.com/es/articles/15157082-entorno-de-pruebas-sandbox). Los webhooks están disponibles en todos los planes, incluido **Gratis**. > En los ejemplos de esta página, los UUIDs (`whe_…`, `con_…`, `inv_…`) > y los `signing_secret` son **ilustrativos**. Cada endpoint tiene los > suyos; sustitúyelos por los valores reales que devuelve la API. ## Dos sub-recursos El recurso `webhooks` se divide en dos partes: - **Endpoints** (`/webhooks/endpoints/...`) — la configuración: URLs destino, eventos a los que se suscriben, activación, rotación del secret de firma. - **Eventos** (`/webhooks/events/...`) — la **historia de entregas**: cada cambio en un recurso genera un evento por cada endpoint suscrito. Permite inspeccionar qué se envió, su estado y reintentar entregas fallidas. Cada endpoint se suscribe a uno o más tipos de evento. Formato: `.`. La lista completa de tipos disponibles está más abajo: ## Catálogo de eventos Eventos disponibles para suscripción en `events` al crear o actualizar un endpoint: | Recurso | Eventos | |---|---| | Facturas | `invoice.created`, `invoice.updated`, `invoice.archived`, `invoice.unarchived`, `invoice.sent`, `invoice.voided`, `invoice.verifactu_sent` | | Gastos | `expense.created`, `expense.updated`, `expense.archived`, `expense.unarchived` | | Contactos | `contact.created`, `contact.updated`, `contact.archived`, `contact.unarchived` | | Presupuestos | `estimate.created`, `estimate.updated`, `estimate.archived`, `estimate.unarchived` | | Albaranes | `delivery_note.created`, `delivery_note.updated`, `delivery_note.archived`, `delivery_note.unarchived` | | Pedidos | `client_order.created`, `client_order.updated`, `client_order.archived`, `client_order.unarchived` | | Órdenes de compra | `purchase_order.created`, `purchase_order.updated`, `purchase_order.archived`, `purchase_order.unarchived` | | Transacciones | `transaction.created`, `transaction.updated`, `transaction.archived`, `transaction.unarchived` | | Productos | `product.created`, `product.updated`, `product.archived`, `product.unarchived` | | VeriFactu | `verifactu_batch.sent` | | Bandeja de entrada | `inbox.scanned` | El catálogo puede ampliarse ante cambios; no asumas un conjunto cerrado. Para suscribirte a todos los eventos de una categoría, declara cada tipo explícitamente. Notas sobre categorías concretas: - **Pedidos** (`client_order.*`) y **Órdenes de compra** (`purchase_order.*`) cubren los dos documentos del Módulo Inventario. Un `client_order.updated` puede llegar **sin que nadie haya tocado el pedido**: guardar o borrar una orden de compra vinculada propaga estados a las líneas del pedido y lo guarda. Ver [Flujo de pedidos y órdenes de compra](https://www.facturadirecta.com/desarrolladores/api/guias/orders-flow/). - **Bandeja de entrada** (`inbox.scanned`) se dispara cuando un documento subido queda procesado, tanto si los datos se extrajeron con el escáner como si el fichero era un Facturae y se leyó directamente. Para el consumidor el resultado es el mismo. Los eventos `*.archived` y `*.unarchived` conservan ese nombre por compatibilidad: corresponden a borrar un documento y a recuperarlo. ## Entornos sandbox y producción Los endpoints mantienen una separación total entre entornos: - Un endpoint creado en una empresa sandbox recibe eventos del sandbox y sus payloads llevan `livemode: false`. - Un endpoint creado en una empresa real recibe eventos de producción y sus payloads llevan `livemode: true`. Un endpoint de sandbox no recibe eventos de producción, y un endpoint de producción no recibe eventos de sandbox. ## Forma del payload entregado Cuando se dispara un evento, FacturaDirecta envía un `POST` a la URL del endpoint con este cuerpo JSON: ```json { "id": "whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d", "type": "invoice.created", "object_type": "invoice", "created": 1715616000, "livemode": true, "company_id": "com_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3", "data": { "object": { "...": "snapshot del recurso afectado" }, "previous_attributes": { "...": "diff con el estado anterior (solo en eventos *.updated)" } } } ``` Notas: - `id` (prefijo `whe_`) identifica la entrega. Es el mismo valor que el `id` del evento en `GET /webhooks/events/{id}`. - `type` coincide con uno de los tipos del catálogo. - `object_type` es el tipo de recurso afectado, con el mismo identificador que usa la API REST: `invoice`, `bill`, `contact`, `estimate`, `deliveryNote`, `clientOrder`, `purchaseOrder`, `transaction`, `product`, `verifactu_batch` e `inbox_item`. No lo uses como discriminador en tu integración: `type` ya identifica el evento sin ambigüedad y es el campo estable. - `created` es timestamp Unix en segundos. - `livemode: true` indica que es un evento real; `false` corresponde a eventos simulados (sandbox o reintentos manuales). - `data.object` contiene el snapshot completo del recurso en el momento del evento. - `data.previous_attributes` aparece **solo en eventos `*.updated`** y contiene un diff con los valores anteriores de los campos modificados. ## Firma del payload Cada petición incluye dos cabeceras HTTP para que verifiques que el payload viene realmente de FacturaDirecta: ```http Webhook-Signature: t=1715616000,v1=8f3d4e7c2b9a1f5e6d0c8b7a3e5f9d1c2b4a6e8f0d3c5b7a9e1f3d5c7b9a0e2f Webhook-Timestamp: 1715616000 ``` Algoritmo, equivalente al de Stripe: 1. Toma el `Webhook-Timestamp` y el cuerpo crudo de la petición. 2. Concatena: `signed_payload = "."`. 3. Calcula `HMAC-SHA256(secret, signed_payload)` y compáralo con el valor `v1=...` del header `Webhook-Signature`. 4. Rechaza la petición si la diferencia entre `Webhook-Timestamp` y la hora actual supera **5 minutos** (anti-replay). El `secret` es el `signing_secret` devuelto al crear el endpoint (formato `whsec_`) o tras `rotateSecret`. **Solo se muestra una vez** en la creación/rotación. Guárdalo en un lugar seguro al recibirlo. ## Política de reintentos Si tu endpoint responde con un código distinto de `2xx` o no responde, FacturaDirecta reintenta la entrega con backoff exponencial. Calendario hardcoded: | Intento | Espera desde el anterior | Tiempo total desde el primer envío | |---|---|---| | 1 | — | 0 | | 2 | 15 segundos | 15 s | | 3 | 45 segundos | 1 min | | 4 | 2 minutos | 3 min | | 5 | 5 minutos | 8 min | | 6 | 15 minutos | 23 min | | 7 | 45 minutos | 1 h 8 min | | 8 | 2 horas | 3 h 8 min | | 9 | 5 horas | 8 h 8 min | | 10 | 10 horas | 18 h 8 min | | 11 | 18 horas | 36 h 8 min | | 12 | 36 horas | ~72 horas | Tras 12 intentos sin éxito el evento queda en estado `failed`. Puedes reintentar manualmente con `POST /webhooks/events/{id}/retry`. ## Operaciones - [Endpoints](#endpoints) - [Crear endpoint](#crear-endpoint) - [Listar endpoints](#listar-endpoints) - [Obtener un endpoint](#obtener-un-endpoint) - [Actualizar endpoint](#actualizar-endpoint) - [Borrar endpoint](#borrar-endpoint) - [Activar endpoint](#activar-endpoint) - [Desactivar endpoint](#desactivar-endpoint) - [Rotar secret de firma](#rotar-secret-de-firma) - [Eventos](#eventos) - [Listar eventos](#listar-eventos) - [Obtener un evento](#obtener-un-evento) - [Reintentar un evento](#reintentar-un-evento) ## Endpoints ### Crear endpoint `POST /{companyId}/webhooks/endpoints` crea un endpoint con su lista inicial de suscripciones. **Parámetros del body:** - `name` (obligatorio) — nombre descriptivo del endpoint. - `url` (obligatorio) — URL HTTPS de destino. - `events` (obligatorio) — array de tipos de evento del [catálogo](#catálogo-de-eventos). **Respuesta:** `{ content: , signing_secret: "whsec_" }`. El `signing_secret` **solo se muestra en la creación**; guárdalo en seguro. ###### Ejemplo de request JSON ```json { "name": "Integración ERP — facturas", "url": "https://api.miempresa.com/webhooks/facturadirecta", "events": ["invoice.created", "invoice.updated", "invoice.voided"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{ "name": "Integración ERP — facturas", "url": "https://api.miempresa.com/webhooks/facturadirecta", "events": ["invoice.created", "invoice.updated", "invoice.voided"] }' \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints" ``` ### Listar endpoints `GET /{companyId}/webhooks/endpoints` devuelve los endpoints configurados en la empresa. **Respuesta:** `{ items: WebhookEndpoint[] }`. Sin paginación (ver patrón sin paginación en la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/)). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints" ``` ### Obtener un endpoint `GET /{companyId}/webhooks/endpoints/{endpointId}` devuelve un endpoint por ID. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d" ``` ### Actualizar endpoint `PUT /{companyId}/webhooks/endpoints/{endpointId}` aplica una **actualización parcial**: los campos omitidos conservan su valor actual. A diferencia de los recursos documento, **no es un reemplazo completo**. **Parámetros del body** (todos opcionales): - `name` - `url` - `events` (la lista nueva reemplaza la anterior). ###### Ejemplo de request JSON Cambiar solo la lista de eventos a los que se suscribe: ```json { "events": ["invoice.created", "invoice.voided"] } ``` ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \ -d '{"events":["invoice.created","invoice.voided"]}' \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d" ``` ### Borrar endpoint `DELETE /{companyId}/webhooks/endpoints/{endpointId}` elimina el endpoint. Las entregas pendientes y el historial de eventos asociados quedan disponibles en `/webhooks/events` para consulta, pero **no se reintentan** tras el borrado. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d" ``` ### Activar endpoint `POST /{companyId}/webhooks/endpoints/{endpointId}/enable` reactiva un endpoint previamente desactivado. Solo afecta a eventos futuros; no reintenta entregas pasadas (para eso, ver [Reintentar un evento](#reintentar-un-evento)). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/enable" ``` ### Desactivar endpoint `POST /{companyId}/webhooks/endpoints/{endpointId}/disable` deja al endpoint sin recibir eventos nuevos. Los eventos pendientes en cola **no se enviarán**. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/disable" ``` ### Rotar secret de firma `POST /{companyId}/webhooks/endpoints/{endpointId}/rotateSecret` genera un nuevo `signing_secret` para el endpoint e invalida el anterior. **Respuesta:** `{ signing_secret: "whsec_" }`. **Solo se muestra una vez**; guárdalo en seguro antes de actualizar tu integración. Estrategia recomendada: rotar en horario de baja actividad. Mientras actualizas tu integración, los eventos firmados con el secret antiguo fallarán hasta que verifiques con el nuevo. ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/rotateSecret" ``` ## Eventos ### Listar eventos `GET /{companyId}/webhooks/events` devuelve el historial de entregas con **paginación por cursor** (ver el patrón B en la [guía de paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/)). **Parámetros de consulta:** - `endpointId` — filtrar por endpoint. - `type` — filtrar por tipo de evento (p. ej. `invoice.created`). - `status` — filtrar por estado: `pending`, `delivered`, `failed`. - `limit` — máximo `100`. - `cursor` — el `nextCursor` de la página anterior, tal cual se recibió. Omitir para la primera página. **Respuesta:** `{ items: WebhookEvent[], hasMore: boolean, nextCursor: string | null }`, de más reciente a más antiguo. Para pedir la página siguiente, pasa `nextCursor` en `cursor`; cuando es `null`, no hay más. ###### Copy as cURL Primera página: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events?status=failed&limit=100" ``` Siguiente página, con el `nextCursor` de la anterior: ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events?status=failed&limit=100&cursor=d2hlXzdjMWUyZjQwLTVhOGItNGQzYy05ZTZmLTJiMWEwYzlkOGU3Zg" ``` ### Obtener un evento `GET /{companyId}/webhooks/events/{eventId}` devuelve un evento por ID con su detalle (incluyendo el payload enviado y el resultado del último intento). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d" ``` ### Reintentar un evento `POST /{companyId}/webhooks/events/{eventId}/retry` encola una nueva entrega del evento contra el endpoint configurado. Útil para: - Eventos en estado `failed` cuando ya has solucionado el problema en tu integración. - Eventos `delivered` cuando necesitas re-procesarlos en tu sistema (la API no impide reintentar entregas exitosas). ###### Copy as cURL ```shell curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/retry" ``` ## Recomendaciones para integradores - **Verifica siempre la firma** antes de procesar el payload. No confíes en el remitente del header `Host` ni en certificados TLS; el secret HMAC es la única garantía. - **Responde rápido (2xx)** para evitar reintentos. Si necesitas procesamiento largo, encola el evento en tu lado y responde `200` inmediatamente. - **Asume entregas duplicadas.** Tu integración debe ser idempotente: el `id` del evento es estable y sirve para deduplicar. - **Trata `event.id` como llave primaria.** El campo `id` (`whe_...`) es único por entrega y persiste entre reintentos. - **Para sincronizaciones desde cero**, en vez de paginar todo el historial guarda el `nextCursor` de la última página procesada con éxito y reanuda desde ahí. - **Tras rotar el secret**, soporta brevemente ambos secrets (antiguo y nuevo) hasta que confirmes que todos los eventos en vuelo se firmaron con el nuevo. ## Errores comunes - `400 ValidationError` — `url` no es HTTPS o `events` contiene un tipo desconocido. - `404 Not Found` — el `endpointId` o `eventId` no existe en la empresa. - `409 Conflict` — intento de rotar secret en un endpoint borrado. Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato general. ## Endpoints | Método | Path | operationId | Scopes | Descripción | |---|---|---|---|---| | GET | `/{companyId}/webhooks/endpoints` | `listPublicWebhookEndpoints` | `webhooks:read` | Lista de endpoints de webhooks | | GET | `/{companyId}/webhooks/endpoints/{endpointId}` | `getPublicWebhookEndpoint` | `webhooks:read` | Obtener endpoint de webhook | | GET | `/{companyId}/webhooks/events` | `listPublicWebhookEvents` | `webhooks:read` | Lista de eventos de webhook | | GET | `/{companyId}/webhooks/events/{eventId}` | `getPublicWebhookEvent` | `webhooks:read` | Obtener evento de webhook | | POST | `/{companyId}/webhooks/endpoints` | `createPublicWebhookEndpoint` | `webhooks:write` | Crear endpoint de webhook | | POST | `/{companyId}/webhooks/endpoints/{endpointId}/disable` | `disablePublicWebhookEndpoint` | `webhooks:write` | Desactivar endpoint de webhook | | POST | `/{companyId}/webhooks/endpoints/{endpointId}/enable` | `enablePublicWebhookEndpoint` | `webhooks:write` | Activar endpoint de webhook | | POST | `/{companyId}/webhooks/endpoints/{endpointId}/rotateSecret` | `rotatePublicWebhookEndpointSecret` | `webhooks:write` | Rotar secret de firma del endpoint | | POST | `/{companyId}/webhooks/events/{eventId}/retry` | `retryPublicWebhookEvent` | `webhooks:write` | Reintentar entrega de evento | | PUT | `/{companyId}/webhooks/endpoints/{endpointId}` | `updatePublicWebhookEndpoint` | `webhooks:write` | Actualizar endpoint de webhook | | DELETE | `/{companyId}/webhooks/endpoints/{endpointId}` | `deletePublicWebhookEndpoint` | `webhooks:write` | Eliminar endpoint de webhook | ## Scopes - **`webhooks:read`** — Leer configuración y historial de webhooks. - **`webhooks:write`** — Gestionar endpoints de webhooks. Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/webhooks.md