# 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_<uuid v4>` 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=<id-asiento>`.

**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
