# BeeL. API Documentation — VeriFactu
Every page of the VeriFactu area, in full. Index: /llms.txt
---
# Overview
How BeeL. handles VeriFactu compliance — what BeeL. does for you, what you control, and where to learn each scenario.
VERI\*FACTU is the modality of the RD 1007/2023 in which a billing system submits every billing record (*registro de facturación*) to AEAT as it is generated (article 16). BeeL. works only in that modality: for each invoice issued through it, BeeL. generates the record, computes and chains its hash and submits it. You decide **which kind of invoice** to issue and **for whom**, and BeeL. maps the rest.
This section is the *when-to-use-what* guide. The endpoint reference for the VeriFactu and Invoicing APIs lives in the [API Reference](/verifactu/getCompanyVeriFactuConfiguration). Here we explain the fiscal decisions behind each request.
## At a glance
| | |
|---|---|
| **Who the RD 1007/2023 applies to** | The taxpayers listed in its article 3.1 who use a billing system — Corporate Tax payers, self-employed people with an economic activity, and others — with the exclusions of articles 3.3 and 4. For the Basque Country and Navarre, it applies to taxpayers with their fiscal domicile in common territory (article 1.3) |
| **When** | The regulation requires adapted billing systems before **2027-01-01** for Corporate Tax payers and before **2027-07-01** for the rest (RD-ley 15/2025). VERI\*FACTU is one of the two modalities it allows |
| **What BeeL. does** | Generates, chains and submits the billing record of each invoice issued through it |
| **What you choose** | `type` (STANDARD / SIMPLIFIED / CORRECTIVE), `rectification_code` (R1–R5) for correctives, `exemption_reason` per line, recipient identification |
| **What BeeL. never does** | Recalculate the totals you sent — your numbers are the fiscal truth |
**The NIF decides, not the invoice.** Whether an issued invoice goes to AEAT is resolved at issue time from the issuing NIF: if the NIF is under the VeriFactu regime in that environment, every invoice it issues is registered; if it is not, none is. There is no per-invoice, per-series or per-connection switch. See [Auto-submit policy](/verifactu/auto-submit) and [Enabling VeriFactu for a NIF](/verifactu/enabling-verifactu).
> **Rules that apply here:** [DAT-011 · Adapted billing systems are mandatory from 1 January 2027 or 1 July 2027](/rules/dates#dat-011)
## How issuance maps to VeriFactu
```text
1. You POST /invoices or /invoices/{id}/issue
2. BeeL validates the invoice (NIF, totals, tipo factura)
3. BeeL emits the invoice locally (assigns number, freezes totals)
4. If the NIF is under the VeriFactu regime → BeeL submits a registro de facturación
5. AEAT responds (PENDING → ACCEPTED or REJECTED; a later cancellation lands on VOIDED)
6. BeeL exposes the resulting status on the invoice (and via webhooks)
```
## Where to start
F1, F2 and R1–R5 explained.
No identified recipient on a simplified invoice, the 3,000 € cap, and what BeeL. enforces.
R1–R5 reasons, PARTIAL vs TOTAL, and worked examples for each scenario.
Subject / not subject / exempt, with the S1, S2, N1, N2, E1–E6 cheat sheet.
### Customer & territory scenarios
B2B intra-EU, B2C with OSS, exports, services to non-EU clients.
IVA, IGIC (Canary Islands), IPSI (Ceuta & Melilla) — when to use each.
Equivalence surcharge on B2B retailer lines.
The `regime_key` catalogue — OSS, criterio de caja, and more.
### Operations
The configuration ladder, the representation, and the blockers that stop issuing.
Always on, AEAT's test environment, no representation to sign.
What each VeriFactu status means, when BeeL. keeps checking, when you must act.
Reading `REJECTED` and accepted-with-errors, what to do per code, and reconciliation.
The exact conditions under which BeeL. sends an invoice to AEAT for you.
Void vs *subsanación* vs corrective invoice — pick the right operation.
When the QR exists, why the PDF waits for it, and rendering your own.
### Reference
Runnable curl + JSON for every common AEAT scenario — F1/F2, multi-IVA, intra-EU, exports, RE, ISP, correctives R1–R5.
The description, total, number and recipient BeeL. sends for your invoice — and why the total can differ.
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Compliance and responsibilities
What BeeL. does for each invoice, who signs its declaración responsable, the rules that apply to software built on the API and to the issuing business, the VERI*FACTU modality, and what BeeL. keeps for each record.
When an invoice is issued through the BeeL. API, three parties are involved: the business whose NIF is on the invoice, the software that calls the API, and BeeL. This page describes what BeeL. does for each of them and quotes the rules that apply: the RD 1007/2023 (*Reglamento de sistemas informáticos de facturación*), the Orden HAC/1177/2024, the RD 1619/2012 (*Reglamento de facturación*) and the criteria AEAT has published.
BeeL.'s own position is stated in the **VeriFactu section of its [Terms](https://beel.es/terminos#verifactu)** and in its **[*declaración responsable*](https://beel.es/declaraciones-responsables)** (the producer's signed statement that the system complies with the rules). Where this page mentions that position it links there; if this page and those texts ever differ, those texts prevail.
> **Not legal or tax advice.** This page describes how BeeL. works. It is not legal or tax advice: confirm your own obligations, and those of the businesses you invoice for, with your tax advisor.
## Who is who
| Role | Who | What it does in BeeL. |
|---|---|---|
| **Issuer** (*obligado tributario*, *expedidor*) | The business whose NIF is on the invoice | Issues its invoices through BeeL. and provides the data they contain |
| **Integrator** | Your software, calling the API | Sends data to BeeL. and shows the result |
| **Billing system** (*SIF*, *sistema informático de facturación*) | **BeeL.**, as stated in its [*declaración responsable*](https://beel.es/declaraciones-responsables) | Numbers the invoice, builds its billing record, computes and chains its hash, produces the QR and submits the record to AEAT |
| **Producer** (*productor*) of BeeL. | **Honey Solutions, S.L.** | Signs BeeL.'s *declaración responsable* |
## What BeeL. does for each invoice
The RD 1007/2023 defines a SIF as «el conjunto de hardware y software utilizado para expedir facturas» that admits, keeps and processes billing information (article 1.2). For every invoice issued through BeeL. — from the dashboard, through the API or through the Stripe integration — BeeL.:
- assigns its number from one of the issuer's series;
- generates its billing record (*registro de facturación de alta*, article 9 of the RD 1007/2023), and a cancellation record when it is voided (article 11);
- computes the record's hash and chains it to the previous record of the same issuer (article 12 of the RD 1007/2023, article 7 of the Orden HAC/1177/2024);
- produces the QR data (`qr_url`, `qr_base64`) and, when BeeL. renders the PDF, prints the QR and its legend on it;
- submits the record to AEAT.
The API does not register an invoice numbered or hashed somewhere else: see [Who generates the record, the hash and the chain](/guides/erp-integration#who-generates-the-record-the-hash-and-the-chain).
> **Rules that apply here:** [REC-001 · Each issued invoice gets a billing record built from its data](/rules/records#rec-001)
## The *declaración responsable* [#declaracion-responsable]
Article 13.1 of the RD 1007/2023 reads: «Corresponderá a la persona o entidad productora del sistema informático certificar, mediante una declaración responsable, que el sistema informático cumple con lo dispuesto en el artículo 29.2.j) de la Ley 58/2003 […] así como con lo dispuesto en este Reglamento».
For BeeL., that declaration is signed by **Honey Solutions, S.L.** It is published, with every version, at **[beel.es/declaraciones-responsables](https://beel.es/declaraciones-responsables)**, where anyone can download it.
The declaration is the producer's own. AEAT's published FAQ on VERI\*FACTU describes it as an «"auto-certificación" del propio productor del producto SIF realizada como Declaración Responsable», and states that no certification by independent bodies and no prior registration of the product is required. BeeL. is therefore **not certified, homologated or approved by AEAT** or by any other body: what exists is its producer's *declaración responsable*.
## Software built on the API [#software-built-on-the-api]
Whether software that uses BeeL. is itself part of a billing system depends on what that software does. These are the rules and criteria that apply:
- A SIF can be made of several components. Article 15.4 of the Orden HAC/1177/2024: when the system «esté formado por varios componentes […] producidos por diferentes personas o entidades, todas ellas deberán aportar las correspondientes declaraciones responsables de sus componentes».
- AEAT's FAQ for developers (version 1.3, section 5) states that every component of such a system needs a *declaración responsable*, and exempts only the components whose functions are irrelevant to the rules: «fundamentalmente, las que no afecten a la generación del RF, a su encadenamiento, a la impresión de facturas, a la generación del QR, al envío a sede electrónica, al enlace indefectible entre componentes, a la conservación inalterada ni al registro de eventos».
- AEAT's FAQ also states that when a user extends a SIF with its own means, «el responsable de la ampliación será el usuario del SIF, que como productor de la misma deberá certificarla».
Under that criterion, **printing the invoice and generating its QR are among the functions that make a component relevant**. Software that renders its own invoice document, even with the number and QR data BeeL. returns, may be treated as a component of the billing system with its own *declaración responsable*. BeeL.'s declaration covers BeeL.; it does not extend to software that others build on the API.
What BeeL. does and does not let your software do:
- the number, the billing record, the hash, the chain and the submission are always produced in BeeL., and no request accepts them from outside;
- corrections and cancellations are made through BeeL. ([corrective invoices](/verifactu/corrective-invoices), [voids](/verifactu/cancel-and-fix));
- BeeL. renders a PDF with the QR and its legend for every invoice it registers — see [QR code and the invoice PDF](/verifactu/qr-and-pdf);
- the QR data is also returned, so software can print it on a document it renders itself — see [Rendering your own PDF](/verifactu/qr-and-pdf#rendering-your-own-pdf).
Whether your architecture needs its own *declaración responsable* is a question to settle with your tax advisor before you go live, with the rules above in hand.
## The issuing business [#the-issuing-business]
A business that issues its invoices with BeeL. signs **one** document: the [representation](#representation-not-a-certificate) that lets BeeL. submit its billing records to AEAT on its behalf, once per NIF.
It does not sign a *declaración responsable*. That declaration is the software producer's, not the user's: BeeL.'s is signed by its producer, Honey Solutions, S.L., and it covers every business that invoices with BeeL. Using a billing system whose producer has signed one is what the rules ask of the user.
Signing the representation does not move the issuer's obligations to BeeL. They remain the issuer's, among them:
- those of the RD 1619/2012: the duty to issue invoices, their content (articles 6 and 7) and keeping copies of them (article 19);
- article 29.2.j of the Ley General Tributaria, which places on «los productores, comercializadores y usuarios» the duty that billing systems «garanticen la integridad, conservación, accesibilidad, legibilidad, trazabilidad e inalterabilidad de los registros».
BeeL. builds each record from the data it receives: the issuer's fiscal data, its series, the tax rates and the classification of each operation. BeeL. validates the format and some consistency rules of that data, but it cannot know whether the data is true. How responsibility is allocated between BeeL. and its users is set out in the [Terms](https://beel.es/terminos).
> **Rules that apply here:** [CNT-004 · Every invoice shows the issuer's NIF](/rules/contents#cnt-004)
## VERI\*FACTU modality [#verifactu-modality-only]
BeeL. submits billing records only in the **VERI\*FACTU** modality (article 16 of the RD 1007/2023), at the time each invoice is issued. It does not offer the "no VERI\*FACTU" modality, in which records are kept by the system and signed instead of being submitted.
VeriFactu is enabled per issuing NIF. For each NIF it includes signing the representation that authorises BeeL. to submit on the taxpayer's behalf. **Invoices a NIF issues while VeriFactu is not enabled for it generate no billing record and are not submitted**: they carry `verifactu.enabled: false` — see [Auto-submit policy](/verifactu/auto-submit).
The rules on timing:
- The RD 1007/2023, as amended by the RD-ley 15/2025, requires Corporate Tax payers to have their billing systems adapted «antes del 1 de enero de 2027», and the other taxpayers of its article 3.1 «antes del 1 de julio de 2027».
- Article 16.5 of the RD 1007/2023: the choice of VERI\*FACTU «se prolongará, al menos, hasta la finalización del año natural en el que se haya producido, de forma efectiva, el primer envío de los registros de facturación». Article 17 of the Orden HAC/1177/2024 sets the same until 31 December of that year.
Before turning VeriFactu off for a NIF, or issuing from a NIF for which it is not enabled, confirm with the taxpayer's advisor how these rules apply on that date.
BeeL. is used by several taxpayers, each with separate billing records, numbering, series and chain. Article 7.a of the RD 1007/2023 allows «un mismo sistema informático […] por parte de diversos obligados tributarios […] siempre que los registros de facturación de cada obligado tributario se encuentren diferenciados».
> **Rules that apply here:** [REC-003 · VERI*FACTU is kept until the end of the year](/rules/records#rec-003)
## Representation, not a certificate
BeeL. submits each record to AEAT **on the taxpayer's behalf**, under a representation that the NIF's holder signs once. Article 5 of the Orden HAC/1177/2024 allows the submission to be made «por el propio obligado tributario o por un tercero que actúe en su representación». No digital certificate is handed to BeeL. for the submissions, by the taxpayer or by the integrator.
The holder signs the representation document BeeL. generates, electronically and with their own certificate, once. Generating it, downloading it, uploading the signed copy and checking its status are all API calls; only the signing is done by the holder. The steps are in [Signing the VeriFactu representation](/multi-nif/companies#signing-the-verifactu-representation) and [Enabling VeriFactu for a NIF](/verifactu/enabling-verifactu). In sandbox there is no representation to sign.
> **Rules that apply here:** [REC-006 · No certificate is needed to sign records](/rules/records#rec-006) · [REC-011 · The NIF holder signs the AEAT representation first](/rules/records#rec-011)
## What BeeL. keeps for each invoice
For each invoice, BeeL. stores its billing record, its hash and AEAT's answer, and returns them through the API:
- the invoice's `verifactu` block — `submission_status`, `invoice_hash`, `qr_url`, `error_code` / `error_message` — see [Submission states](/verifactu/submission-states#reading-the-aeat-response);
- every record of the invoice, registration and cancellation, each with its own status — [List the VeriFactu records of an invoice](/invoices/listCompanyInvoiceVerifactuRecords);
- what the record sent to AEAT contains — [What AEAT receives](/verifactu/what-aeat-receives).
On keeping records, article 3 of the Orden HAC/1177/2024 exempts systems acting as VERI\*FACTU from its articles 8 (conservation of records) and 9 (event log). The duty to keep copies of the invoices themselves, in article 19 of the RD 1619/2012, belongs to the issuer; article 19.3 allows a third party to carry it out materially, with the issuer remaining responsible. BeeL.'s commitments on how long it keeps data are the ones in its [Terms](https://beel.es/terminos) and [privacy policy](https://beel.es/privacidad); this page adds none.
> **Rules that apply here:** [CON-001 · Keep copies of issued invoices for the limitation period](/rules/conservation#con-001) · [CON-002 · AEAT keeps the records, not your invoices](/rules/conservation#con-002)
## Related
- [Terms, VeriFactu section](https://beel.es/terminos#verifactu) — BeeL.'s binding position
- [Declaraciones responsables](https://beel.es/declaraciones-responsables) — every published version
- [Integrating BeeL. into your ERP](/guides/erp-integration) — who numbers, who renders, who sends
- [Multi-NIF](/multi-nif) — one integration, many issuers
- [FAQ](/faq#compliance) — the short answers
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Enabling VeriFactu for a NIF
Put a NIF under the VeriFactu regime in Live — the steps, the configuration fields that tell you where you are, the errors you can hit, and the blockers that stop issuing.
VeriFactu applies to a **NIF in an environment**, not to an invoice. Once a NIF is under the regime in Live, every invoice it issues there is registered with AEAT; until then, none is. This page is the path from "NIF created" to "invoices reach AEAT".
In **sandbox** there is nothing to enable — VeriFactu is always on there. See [Testing VeriFactu in sandbox](/verifactu/testing-in-sandbox).
## The path in Live
### Switch the NIF on in Live
A NIF only operates in Live once it is activated there, which puts it on a paid plan. That is done with the activations endpoint, described in [Switching a NIF on in Test or Live](/multi-nif/companies#switching-a-nif-on-in-test-or-live). Without it, issuing in Live is blocked (`ENV_MISMATCH`, see [below](#what-blocks-issuing)).
### Sign the AEAT representation
In Live, submitting to AEAT on the taxpayer's behalf requires a representation form signed by the NIF's holder. Generate it, have the holder sign it, and submit the signed copy — the flow is in [Signing the VeriFactu representation](/multi-nif/companies#signing-the-verifactu-representation).
If you provisioned the account, you receive a `representation.signed` webhook when the holder signs, so you don't need to poll.
> **Rules that apply here:** [REC-011 · The NIF holder signs the AEAT representation first](/rules/records#rec-011)
### Put the NIF under the regime
```bash
curl -X PUT "https://app.beel.es/api/v1/companies/{company_id}/verifactu-configuration" \
-H "Authorization: Bearer beel_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'
```
`enabled` is the only writable field and has no default. Turning it on **registers the NIF in the same call**: either the call succeeds and the NIF is registered, or it returns the reason and nothing changes. There is no half-enabled state to poll for.
### Check the NIF can issue
```bash
curl "https://app.beel.es/api/v1/companies/{company_id}/issuing-readiness" \
-H "Authorization: Bearer beel_sk_live_xxx"
```
`ready: true` with an empty `blockers` list means the next invoice goes out and reaches AEAT. See [Get issuing readiness](/companies/getCompanyIssuingReadiness).
## Reading the configuration
`GET /v1/companies/{company_id}/verifactu-configuration` ([reference](/verifactu/getCompanyVeriFactuConfiguration)) returns the NIF's state for the environment of the request. Read `status` first — it is the one field that summarises the others.
### `status`
The steps above map to a ladder. Each value names the next thing missing:
| `status` | Meaning | Next step |
|---|---|---|
| `DISABLED` | VeriFactu is not enabled for this NIF | Enable it (step 3) — after steps 1 and 2 |
| `UNSIGNED` | Enabled, but there is no signed representation for the NIF. Live only | Sign the representation (step 2) |
| `NOT_ACTIVATED` | Signed, but the NIF is not switched on in this environment | Switch it on (step 1) |
| `ACTIVE` | Ready: invoices are registered with AEAT | Nothing |
| `ERROR` | Historical — no configuration reaches it any more. Kept so older clients still parse stored values | Treat as `DISABLED` |
| `null` | VeriFactu was never configured for this NIF | Start at step 1 |
`signed`, `activated` and `pdf_generated` are the facts behind `status` — useful for a setup wizard, but decide on `status`.
### `nif_status`
Whether the NIF is registered for submission in this environment: `ACTIVATED`, `DEACTIVATED` (not registered, or deregistered), or `null` if VeriFactu was never enabled. In Live, `enabled: true` always comes with `ACTIVATED`, because enabling *is* registering. In sandbox the two can drift for a moment — see [Testing in sandbox](/verifactu/testing-in-sandbox#the-nif-registers-itself).
## When enabling fails
| Error | When | What to do |
|---|---|---|
| `422` [`VERIFACTU_REPRESENTATION_REQUIRED`](/errors/VERIFACTU_REPRESENTATION_REQUIRED) | Enabling in Live without a signed representation for the NIF | Complete step 2, then retry |
| `402` [`CHECKOUT_REQUIRED`](/errors/CHECKOUT_REQUIRED), [`PAYMENT_REQUIRED`](/errors/PAYMENT_REQUIRED) or [`PLAN_ACTIVATION_REQUIRED`](/errors/PLAN_ACTIVATION_REQUIRED) | Enabling in Live on an account that is not entitled to production — no card on file, an unpaid invoice, or a plan still to activate | Resolve the billing side (step 1), then retry |
| `422` [`ALREADY_ENABLED`](/errors/ALREADY_ENABLED) | The NIF is already under the regime in this environment | Nothing — it is on |
| `422` [`VERIFACTU_ALWAYS_ON_IN_SANDBOX`](/errors/VERIFACTU_ALWAYS_ON_IN_SANDBOX) | Sending `enabled: false` in sandbox | Nothing to do: sandbox is always on, and nothing there reaches the real AEAT |
`PUT … { "enabled": false }` in Live stops the submission of the NIF's invoices, which then generate no billing record. Under article 16.5 of the RD 1007/2023, a taxpayer that starts submitting in VERI\*FACTU stays in it at least until the end of that calendar year. Check with the taxpayer's advisor before turning it off — see [VERI\*FACTU modality](/verifactu/compliance-and-responsibilities#verifactu-modality-only).
If the registration itself is refused, the call fails, nothing is persisted, and the response says why. Fix the cause (usually the company's fiscal data) and send the same `PUT` again.
## What blocks issuing
`GET /v1/companies/{company_id}/issuing-readiness` answers "can this NIF issue right now, here?". When `ready` is `false`, `blockers` says why. Three of the reasons belong to the VeriFactu chain and are reported one at a time, in this order:
| Blocker | Meaning | Fix |
|---|---|---|
| [`ENV_MISMATCH`](/errors/ENV_MISMATCH) | The NIF is under VeriFactu but not switched on in the environment you are calling — for example a NIF active only in Live, called with a test key | Switch the NIF on in that environment ([step 1](#switch-the-nif-on-in-live)), or call with the key for the environment where it is active |
| [`NIF_NOT_REGISTERED`](/errors/NIF_NOT_REGISTERED) | Switched on, but the NIF is not registered for submission in this environment | Enable VeriFactu for the NIF ([step 3](#put-the-nif-under-the-regime)); if it is already enabled, contact support |
| [`NIF_REPRESENTATION_REQUIRED`](/errors/NIF_REPRESENTATION_REQUIRED) | Registered, but the signed representation is missing. Live only | Sign the representation ([step 2](#sign-the-aeat-representation)) |
The other blockers — `COMPANY_HAS_NO_NIF`, `SERIES_DEFAULT_NOT_FOUND`, `PROFILE_INCOMPLETE`, `COMPANY_NOT_ACTIVATED` — are not VeriFactu-specific: every blocker, with its fix, is in [Is a NIF ready to invoice?](/multi-nif#is-a-nif-ready-to-invoice).
The response also has a `verifactu` sub-block (`verifactu.ready`, `verifactu.blockers`) that answers the compliance question on its own: would this NIF pass a VeriFactu emission right now? It is useful **before** enabling, to see what is still missing.
> **Rules that apply here:** [REC-003 · VERI*FACTU is kept until the end of the year](/rules/records#rec-003)
## Related
- [Auto-submit policy](/verifactu/auto-submit) — why the NIF, not the invoice, decides
- [Testing VeriFactu in sandbox](/verifactu/testing-in-sandbox) — the same flow without the representation
- [Companies](/multi-nif/companies) — creating NIFs, activations and the representation flow
- [Handling AEAT rejections](/verifactu/handling-rejections) — issuer codes such as `4104` point back here
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Testing VeriFactu in sandbox
How VeriFactu behaves in sandbox — always on, registered against AEAT's test environment, no representation to sign — and the few ways it differs from Live.
Sandbox runs the same VeriFactu flow as Live — issue, submit, `PENDING`, AEAT's answer, QR, cancellation — against **AEAT's test environment** instead of the real one. Nothing you issue there is a real invoice, and nothing reaches the real AEAT registry.
## Access, cost and time limits
- **Access.** Sign up at [app.beel.es/signup](https://app.beel.es/signup) and create a key with the `beel_sk_test_` prefix — see [API keys](/auth/api-keys). No card is needed.
- **Cost.** Free. Only production is billed, and sandbox invoices never count towards a plan's invoices — see [Pricing](/pricing#what-counts-as-an-invoice).
- **Time limit.** None: the sandbox does not expire.
- **Limits.** The same [rate limits](/guides/rate-limits) as production, and a lower email quota — in sandbox, email only goes to your own address ([Sending email](/guides/sending-email#sandbox-only-sends-to-your-own-address)). BeeL.'s [Terms](https://beel.es/terminos) reserve the right to set usage limits on the sandbox.
## Can I build the whole integration in sandbox?
Yes. The base URL, the endpoints and the responses are the same as in production; only the key changes. You can create NIFs, issue every invoice type, correct and void, receive webhooks and follow each VeriFactu record through AEAT's test environment.
Two things can only be done in Live, because they have no test mode: **signing the representation** of a NIF, and the **billing** of a NIF switched on in Live. Going live is switching the NIF on in Live, signing its representation and swapping the key — see [Enabling VeriFactu for a NIF](/verifactu/enabling-verifactu).
## What is different from Live
| | Sandbox | Live |
|---|---|---|
| VeriFactu | **Always on.** Every invoice of the NIF is submitted | On only once you [enable it](/verifactu/enabling-verifactu) |
| Turning it off | Not possible — `enabled: false` returns `422` [`VERIFACTU_ALWAYS_ON_IN_SANDBOX`](/errors/VERIFACTU_ALWAYS_ON_IN_SANDBOX) | `PUT … { "enabled": false }` |
| Representation | **Not needed** — there is nothing to authorise | Must be signed before enabling |
| NIF registration | Done for you, asynchronously | Done in the enabling call |
| Where submissions go | AEAT's test environment | AEAT |
| QR code | Points to AEAT's test validation service | Points to AEAT's validation service |
## VeriFactu is always on
In sandbox `enabled` is a constant of the mode, not a decision: the configuration always reads `enabled: true`, and every invoice you issue there goes through the full VeriFactu cycle. That is deliberate — the point of the sandbox is to exercise the path your Live integration will take.
## The NIF registers itself
You don't enable anything in sandbox, so BeeL. registers the NIF in AEAT's test environment on its own — when the NIF is switched on in Test, and again on its first submission if that earlier attempt did not go through.
Because that registration travels asynchronously, `nif_status` can read `DEACTIVATED` or `null` for a short while after you start, even though `enabled` is already `true`. It does not block you: issuing in sandbox does not wait for it. If `nif_status` stays that way after your first invoice has been submitted, contact support.
## Where the submission goes
Submissions go to AEAT's test environment, so the whole lifecycle is real — AEAT validates the record and answers — but nothing is registered for real. The `qr_url` of a sandbox invoice points to AEAT's **test** validation service (for example `https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?…`), not the production one. Don't print a sandbox QR on a document you hand to a customer.
Timings in the test environment can be slower than in Live; a cancellation, for instance, can take a few minutes to reach `VOIDED`.
## `ENV_MISMATCH`
A NIF operates in each environment separately. If you call with a test key for a NIF that is switched on only in Live, issuing is blocked with [`ENV_MISMATCH`](/errors/ENV_MISMATCH), and `GET /v1/companies/{company_id}/issuing-readiness` lists it as a blocker. Switch the NIF on in Test with the [activations endpoint](/multi-nif/companies#switching-a-nif-on-in-test-or-live) — free and immediate — or use the key of the environment where it is active.
## A Stripe test account is not a sandbox submission
Whether an invoice goes to AEAT's test environment is decided by the NIF and environment you issue under, not by the Stripe account that paid. A payment from a Stripe test account does not, on its own, make the resulting invoice a test submission. See [Stripe / VeriFactu submission](/stripe/verifactu-submission).
> **Rules that apply here:** [LIF-003 · Test in the sandbox, never with real invoices](/rules/lifecycle#lif-003)
## Related
- [Enabling VeriFactu for a NIF](/verifactu/enabling-verifactu) — the Live path
- [Submission states](/verifactu/submission-states) — the same states in both environments
- [QR code and the invoice PDF](/verifactu/qr-and-pdf) — when the QR exists
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Invoice types
F1, F2, F3, R1–R5 — what each VeriFactu invoice type means, when BeeL. emits each one, and which API fields map to which AEAT code.
Every invoice you submit to VeriFactu carries a `tipo_factura` code. BeeL. derives that code from your invoice's `type` and (for correctives) the `rectification_code` you set. This page is the canonical map between BeeL's invoice model and the AEAT type codes.
## The full catalogue
| AEAT code | AEAT name | BeeL. request fields | Customer ID required? | Notes |
|---|---|---|---|---|
| **F1** | Factura ordinaria | `type: STANDARD` | Yes (NIF or `alternative_id`) | Default for B2B, and **any** invoice whose recipient you identify |
| **F2** | Factura simplificada | `type: SIMPLIFIED` | **Never** — BeeL. rejects an identified recipient | Total ≤ 3,000 € VAT included; whether your activity allows more than 400 € is for you and your tax advisor to check (see [Simplified vs standard](/verifactu/simplified-vs-standard)) |
| **F3** | Factura emitida en sustitución de facturas simplificadas | The [exchange of simplified invoices](/invoices/createCompanySimplifiedExchange) (a `STANDARD` invoice with `replaced_invoice_ids`) | Yes | Replaces simplified invoices the AEAT already accepted; see [Exchanging simplified invoices](/verifactu/simplified-vs-standard#exchange-with-verifactu) |
| **R1** | Rectificativa por error fundado en derecho | `type: CORRECTIVE` + `rectification_code: R1` | Yes | Most common correction |
| **R2** | Rectificativa por concurso de acreedores | `type: CORRECTIVE` + `rectification_code: R2` | Yes | Customer in formal insolvency |
| **R3** | Rectificativa por crédito incobrable | `type: CORRECTIVE` + `rectification_code: R3` | Yes | Bad debt |
| **R4** | Rectificativa por resto de causas | `type: CORRECTIVE` + `rectification_code: R4` | Yes | Catch-all not in R1–R3 |
| **R5** | Rectificativa de factura simplificada | `type: CORRECTIVE` + `rectification_code: R5` | No (like the F2, sent to AEAT without recipient) | The **only** way to correct an F2 |
`rectification_code` (R1–R5) is **why** the rectificative exists. `rectification_type` (`PARTIAL` / `TOTAL`) is **how** the correction works mathematically — see [Corrective invoices](/verifactu/corrective-invoices). The free-text `reason` is the human description.
**`PROFORMA` has no AEAT code.** The fourth value of `type` is a commercial document with no fiscal validity: it never enters VeriFactu (no QR, no AEAT submission, no `verifactu` block). See [Proformas](/guides/proformas).
## How BeeL. chooses the code
The decision happens at issuance, never on the draft:
```text
type=STANDARD → F1 (F3 when issued in exchange for simplified invoices)
type=SIMPLIFIED → F2 (no identified recipient, total ≤ 3 000 €)
type=CORRECTIVE → R{rectification_code} (R1, R2, R3, R4, R5)
```
You cannot override `tipo_factura` directly — set the right `type` and `rectification_code` and BeeL. maps them. If you try to issue a `STANDARD` invoice that fails the F1 rules (e.g. missing `recipient.nif` and no `alternative_id`), the request is rejected with a descriptive error *before* anything is sent to AEAT.
## F1 and F2
Use **F1** (`STANDARD`) whenever you identify the recipient, at any amount; **F2** (`SIMPLIFIED`) only for a consumer you don't identify, up to 3,000 € VAT included. The decision table, the rules BeeL. enforces on each path and a canonical request for each are in [Simplified vs standard](/verifactu/simplified-vs-standard).
## R1–R5 — Rectificative invoices
A corrective invoice always targets an original (via the URL `{invoice_id}`) and carries:
- `rectification_code` — `R1`–`R5` (the AEAT legal motive)
- `rectification_type` — `PARTIAL` (delta) or `TOTAL` (replace)
- `reason` — free-text human description (required)
The combination determines what BeeL. sends to AEAT (`tipo_rectificativa = S` for TOTAL, `I` for PARTIAL). Examples for every common scenario live in [Corrective invoices](/verifactu/corrective-invoices).
`R5` is reserved for correcting an F2, and `R1`–`R4` for everything else.
## Related
- [Simplified vs standard](/verifactu/simplified-vs-standard) — the F1/F2 decision tree
- [Corrective invoices](/verifactu/corrective-invoices) — every R1–R5 scenario with payloads
- [Tax classification](/verifactu/tax-classification) — the `exemption_reason` ↔ AEAT code mapping
- [Create an invoice](/invoices/createCompanyInvoice) — full request schema
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Simplified vs standard (F1 vs F2)
When BeeL. issues a simplified invoice (F2) or a standard invoice (F1), the rules of the RD 1619/2012 behind them, and what BeeL. enforces at the API boundary.
The simplified invoice (F2) is the "ticket" of the Spanish tax system: less data, but limited in amount and use case (article 4 of the RD 1619/2012). This page is the decision aid plus the exact rules BeeL. applies before the registro reaches AEAT.
> **Not legal or tax advice.** This page describes how BeeL. works. It is not legal or tax advice: confirm your own obligations, and those of the businesses you invoice for, with your tax advisor.
**A simplified invoice in BeeL. never identifies its recipient.** If the customer's NIF or a foreign identifier goes on the invoice, BeeL. issues it as a **standard** invoice (`type: STANDARD`), whatever the amount, and rejects a `SIMPLIFIED` invoice that carries one. This is a BeeL. rule: article 7.2 of the RD 1619/2012 does provide for a simplified invoice that states the recipient's NIF and address when a business recipient requires it, and BeeL. covers that case with a standard invoice. Invoices generated from Stripe apply the same rule in their own way, described [below](#on-the-f2-path).
## The decision in one table
| Situation | Type | Why |
|---|---|---|
| You identify the customer (NIF or `alternative_id`) — at **any** amount | **F1** (`type: STANDARD`) | BeeL. issues identified invoices as F1 |
| Consumer you don't identify, total **≤ 3,000 €** (VAT included) | **F2** (`type: SIMPLIFIED`) allowed | Quickest path, no NIF — but see [SIM-002 · Above 400 €, a simplified invoice needs an art. 4.2 activity](/rules/simplified#sim-002) |
| Total **> 3,000 €** | **F1** | F2 is not allowed above the cap |
| Customer is a company / self-employed person / business buyer that wants its NIF on the invoice | **F1** | BeeL. issues identified invoices as F1 (the RD 1619/2012, art. 7.2, would also allow a simplified invoice with the NIF) |
| Customer is a business in another EU country | **F1** | BeeL. rejects intra-EU supplies of goods and operations not subject in Spain on F2 — see [International customers](/verifactu/international-customers) |
| Customer asks for an identified invoice | **F1** | BeeL. issues identified invoices as F1, regardless of amount; after a ticket, [exchange it](#upgrading-an-f2-to-f1-the-canje-case) |
| You're going to apply IRPF withholding | **F1** | BeeL. rejects IRPF on F2 — see [Why no IRPF on F2](#why-no-irpf-on-f2) |
**SIM-002 · Above 400 €, a simplified invoice needs an art. 4.2 activity**
`Required` · Law · Impact: high · Responsibility: the issuing business.
Do not issue a simplified invoice above 400 €, VAT included, unless the operation is one of those listed in art. 4.2 (retail sales, hospitality, passenger transport and the rest of the list) or the invoice is a corrective.
Full rule: [SIM-002](/rules/simplified#sim-002)
> **Rules that apply here:** [SIM-001 · A simplified invoice never exceeds 3,000 €](/rules/simplified#sim-001)
## What BeeL. enforces
### On the F2 path
- **No identified recipient.** `recipient` must not carry `nif` or `alternative_id`, and must not point, through `customer_id`, at a customer that has one. Otherwise the request answers `422` [`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`](/errors/SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT) when you create the invoice, when an edit changes its type or its recipient, when you issue it — one by one, in bulk or on schedule — and when you write a recurring invoice template. Pass `recipient: {}`, or just a name for your own records
- `total` (VAT included) **must be ≤ 3,000 €** — rejected above it with `400` [`SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`](/errors/SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT), when you create the invoice, when an edit takes it over the cap (new lines, or `type` changed to `SIMPLIFIED`) and when you issue it
- IRPF lines are not allowed on F2 — [`SIMPLIFICADA_FORBIDS_IRPF`](/errors/SIMPLIFICADA_FORBIDS_IRPF); see [Why no IRPF on F2](#why-no-irpf-on-f2). The rate is **not** silently coerced to 0: send no `irpf_rate` at all, or `irpf_rate: 0`
- The equivalence surcharge is not allowed either — [`SIMPLIFICADA_FORBIDS_SURCHARGE`](/errors/SIMPLIFICADA_FORBIDS_SURCHARGE): a retailer in that regime must receive an identified F1
- Reverse charge is not allowed — [`SIMPLIFICADA_FORBIDS_ISP`](/errors/SIMPLIFICADA_FORBIDS_ISP): an anonymous recipient cannot self-assess
- Intra-EU supplies of goods (`EXENTA_ART_25`) and operations not subject by place of supply (`NO_SUJETA_LOCALIZACION`) are not allowed — [`SIMPLIFICADA_FORBIDS_CROSS_BORDER`](/errors/SIMPLIFICADA_FORBIDS_CROSS_BORDER), when you create the invoice, edit it and issue it. The OSS regime (`regime_key: "17"`) is allowed
- Apart from those, line semantics work as on F1: IVA rates, regime keys, exemption reasons, etc.
Whatever the recipient block holds, AEAT receives an F2 **without** recipient data — see [What AEAT receives](/verifactu/what-aeat-receives).
A simplified invoice issued before this check may still carry a `nif` or `alternative_id` when you read it. A draft in that state can still be edited — notes, dates, lines — but it is rejected when you issue it until it is changed to `STANDARD` or loses the identifier.
**Invoices generated from Stripe payments pick their type with the connection's [simplified threshold](/stripe/connection-settings#simplified-threshold).** Below it, a customer with fiscal data can still receive a simplified invoice, but it is issued without the NIF or `alternative_id` and without linking the customer: like every F2 in BeeL., it never identifies its recipient. The 3,000 € cap applies to both.
> **Rules that apply here:** [SIM-001 · A simplified invoice never exceeds 3,000 €](/rules/simplified#sim-001) · [SIM-004 · A simplified invoice still carries its minimum contents](/rules/simplified#sim-004) · [SIM-005 · The record of a simplified invoice carries no recipient](/rules/simplified#sim-005) · [SIM-006 · An identified customer gets a standard invoice (F1)](/rules/simplified#sim-006)
### On the F1 path
- `recipient.nif` **or** `recipient.alternative_id` is required — request rejected if both are missing
- For Spanish NIFs (DNI / NIE / CIF), the **NIF and name must match the AEAT census** when the invoice goes to AEAT — BeeL. checks it before submission (the same check as the [NIF Validation API](/nif-validation/validateNif)) and rejects a mismatch with `422 NIF_NOT_IN_CENSUS`
- For foreign customers, use [`alternative_id`](/verifactu/international-customers#identifying-foreign-customers) instead of `nif`
**3,000 € is IVA-included.** A line at 2 600 € + 21 % IVA = 3 146 € totals over the threshold → must be F1.
> **Rules that apply here:** [CNT-002 · A business customer always gets an invoice, identifying it when asked](/rules/contents#cnt-002) · [CNT-003 · A full invoice names both parties by their legal name](/rules/contents#cnt-003) · [CNT-005 · A full invoice identifies the recipient by NIF](/rules/contents#cnt-005) · [CNT-006 · A full invoice shows the address of both parties](/rules/contents#cnt-006) · [SIM-006 · An identified customer gets a standard invoice (F1)](/rules/simplified#sim-006)
## Edge cases the law handles
### Operations where F2 is not allowed
Article 4.4 of the RD 1619/2012 lists operations for which «no podrá expedirse factura simplificada», among them intra-EU supplies of goods exempt under article 25 of the VAT law and certain distance sales of goods. On top of the law, BeeL. applies its own rules. Together, BeeL. rejects these on F2, from the line data:
- Intra-EU supplies of goods exempt under art. 25 LIVA (`EXENTA_ART_25`), and operations not subject because they are located outside Spain (`NO_SUJETA_LOCALIZACION`, art. 4.4.d) → [`SIMPLIFICADA_FORBIDS_CROSS_BORDER`](/errors/SIMPLIFICADA_FORBIDS_CROSS_BORDER)
- Operations under reverse charge (*inversión del sujeto pasivo*) → [`SIMPLIFICADA_FORBIDS_ISP`](/errors/SIMPLIFICADA_FORBIDS_ISP)
- Lines carrying the equivalence surcharge → [`SIMPLIFICADA_FORBIDS_SURCHARGE`](/errors/SIMPLIFICADA_FORBIDS_SURCHARGE)
- Lines carrying IRPF withholding → [`SIMPLIFICADA_FORBIDS_IRPF`](/errors/SIMPLIFICADA_FORBIDS_IRPF)
The other cases of article 4.4 depend on the operation itself: for them, issue a `STANDARD` invoice.
> **Rules that apply here:** [SIM-003 · Some operations can never go on a simplified invoice](/rules/simplified#sim-003)
### Why no IRPF on F2
A withholding is made and reported by the payer, who is identified. A simplified invoice issued through BeeL. never identifies its recipient, so BeeL. does not accept IRPF on it. If your customer is a business that withholds from your invoice, issue an **F1**, whatever the total.
> **Rules that apply here:** [SIM-003 · Some operations can never go on a simplified invoice](/rules/simplified#sim-003)
### Exchanging simplified invoices for a full invoice [#upgrading-an-f2-to-f1-the-canje-case]
Sometimes a customer asks for an identified invoice *after* you already issued a ticket. That is not a correction — nothing in the ticket is wrong — so it is not an `R5` corrective: exchange it. [Exchange simplified invoices for a full invoice](/invoices/createCompanySimplifiedExchange) issues a `STANDARD` invoice with the lines of one or more simplified invoices and the customer's data (RD 1619/2012, art. 15.6):
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/simplified-exchanges" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Idempotency-Key: exchange-ticket-0042" \
-H "Content-Type: application/json" \
-d '{
"simplified_invoice_ids": ["{simplified_invoice_id}"],
"recipient": { "customer_id": "{customer_id}" }
}'
```
- **The full invoice** is numbered in `series_id` or in the company's default standard series, and lists the invoices it replaces in `replaced_invoice_ids`. It cannot be voided afterwards ([`EXCHANGE_INVOICE_NOT_VOIDABLE`](/errors/EXCHANGE_INVOICE_NOT_VOIDABLE)).
- **Each simplified invoice** becomes `VOIDED` with `void_cause: EXCHANGED`, in the same act. Its registration is not cancelled: the full invoice replaces it.
- **Same company, same environment.** Every simplified invoice must belong to the company in the path and to the environment (Test or Live) of your API key.
#### With VeriFactu: recorded as F3 [#exchange-with-verifactu]
When the company has VeriFactu enabled, the full invoice is recorded as `F3` (*factura emitida en sustitución de facturas simplificadas*, an invoice issued in place of simplified invoices), identifying each simplified invoice it replaces by number and issue date. The simplified invoices keep their accepted registration: they are exchanged, not cancelled, so no cancellation record is sent for them.
An `F3` can only replace simplified invoices the AEAT already has. Before issuing anything, BeeL. checks the registration of each simplified invoice in `simplified_invoice_ids`:
| The simplified invoice's registration | What happens |
|---|---|
| Accepted (`verifactu.submission_status: ACCEPTED`) | It can be exchanged |
| Still pending (`PENDING`) | `422` [`EXCHANGE_SIMPLIFIED_NOT_YET_ACCEPTED`](/errors/EXCHANGE_SIMPLIFIED_NOT_YET_ACCEPTED): wait until the AEAT accepts it and send the exchange again |
| Rejected (`REJECTED`) | `422` [`EXCHANGE_SIMPLIFIED_RECORD_REJECTED`](/errors/EXCHANGE_SIMPLIFIED_RECORD_REJECTED): fix or resubmit it first, then exchange it once it is accepted |
| None: it was issued without VeriFactu | `422` [`SIMPLIFIED_EXCHANGE_NOT_RECORDABLE`](/errors/SIMPLIFIED_EXCHANGE_NOT_RECORDABLE): it is not on file with the AEAT, so an `F3` cannot replace it |
If any of them fails, nothing is issued and the simplified invoices stay as they were. The [exchange](/invoices/createCompanySimplifiedExchange) is all or nothing.
**An exchange invoice recorded as `F3` cannot be corrected yet.** A corrective on it answers `422` [`EXCHANGE_INVOICE_NOT_CORRECTABLE`](/errors/EXCHANGE_INVOICE_NOT_CORRECTABLE), and it cannot be voided either. Check the recipient and the lines before you send the exchange; if an `F3` has an error, contact support.
#### Rejections [#exchange-errors]
| Case | Error | Status | Why |
|---|---|---|---|
| An id in `simplified_invoice_ids` that is not a simplified invoice | [`EXCHANGE_REQUIRES_SIMPLIFIED`](/errors/EXCHANGE_REQUIRES_SIMPLIFIED) | `422` | One of `simplified_invoice_ids` is not a simplified invoice. Only simplified invoices are exchanged. |
| A simplified invoice not issued, already voided, exchanged or corrected, or from another company or environment | [`SIMPLIFIED_NOT_EXCHANGEABLE`](/errors/SIMPLIFIED_NOT_EXCHANGEABLE) | `422` | One of `simplified_invoice_ids` cannot be exchanged: it is not issued, it was already voided, exchanged or corrected, or it belongs to another company or environment (Test or Live) than the request. |
| The same simplified invoice listed more than once in `simplified_invoice_ids`: list each one once | [`EXCHANGE_DUPLICATED_SIMPLIFIED`](/errors/EXCHANGE_DUPLICATED_SIMPLIFIED) | `422` | The same simplified invoice appears more than once in `simplified_invoice_ids`. The message names it. Nothing was issued or numbered. |
| With VeriFactu: a simplified invoice whose registration is still pending | [`EXCHANGE_SIMPLIFIED_NOT_YET_ACCEPTED`](/errors/EXCHANGE_SIMPLIFIED_NOT_YET_ACCEPTED) | `422` | The exchange invoice is recorded with VeriFactu as `F3`, which only replaces simplified invoices the AEAT has accepted, and the record of one of `simplified_invoice_ids` is still pending. Nothing is issued. |
| With VeriFactu: a simplified invoice whose registration was rejected | [`EXCHANGE_SIMPLIFIED_RECORD_REJECTED`](/errors/EXCHANGE_SIMPLIFIED_RECORD_REJECTED) | `422` | The exchange invoice is recorded with VeriFactu as `F3`, which only replaces simplified invoices the AEAT has accepted, and the record of one of `simplified_invoice_ids` was rejected, so that invoice is not on file with the AEAT. Nothing is issued. |
| With VeriFactu: a simplified invoice issued without VeriFactu | [`SIMPLIFIED_EXCHANGE_NOT_RECORDABLE`](/errors/SIMPLIFIED_EXCHANGE_NOT_RECORDABLE) | `422` | The exchange invoice is recorded with VeriFactu as `F3`, which only replaces simplified invoices already on file with the AEAT, and one of `simplified_invoice_ids` was issued without VeriFactu. Nothing is issued. |
| Voiding the exchange invoice afterwards | [`EXCHANGE_INVOICE_NOT_VOIDABLE`](/errors/EXCHANGE_INVOICE_NOT_VOIDABLE) | `422` | The invoice was issued in exchange for simplified invoices, which it replaced. It cannot be voided. |
| Correcting an exchange invoice recorded as `F3` | [`EXCHANGE_INVOICE_NOT_CORRECTABLE`](/errors/EXCHANGE_INVOICE_NOT_CORRECTABLE) | `422` | The invoice was issued in exchange for simplified invoices and recorded with VeriFactu as `F3`. Correcting an `F3` exchange invoice is not available yet. |
Without VeriFactu, the exchange invoice is not recorded, and it is corrected like any other invoice if it has an error.
> **Rules that apply here:** [SIM-007 · Exchanging a simplified invoice for a full one](/rules/simplified#sim-007) · [SIM-009 · A customer who asks to be identified gets an invoice that identifies it](/rules/simplified#sim-009)
## Canonical request shapes
### F2 (no identified recipient)
```json
{
"type": "SIMPLIFIED",
"recipient": {},
"lines": [
{
"description": "Computer repair service",
"quantity": 1,
"unit": "service",
"unit_price": 300,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"payment_info": { "method": "CASH" }
}
```
### F1 (Spanish customer identified)
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Comercial Martínez SL",
"nif": "B12345674",
"address": {
"street": "Avenida de la Constitución",
"number": "45",
"postal_code": "41001",
"city": "Sevilla",
"province": "Sevilla",
"country": "España"
}
},
"lines": [
{
"description": "Business strategic consulting",
"quantity": 8,
"unit": "hours",
"unit_price": 125,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"payment_info": { "method": "BANK_TRANSFER", "iban": "ES9121000418450200051332", "payment_term_days": 30 }
}
```
## Related
- [Invoice types](/verifactu/invoice-types) — the full F1/F2/R catalogue
- [Tax classification](/verifactu/tax-classification) — IVA rate, exempt operations, ISP
- [International customers](/verifactu/international-customers) — when F2 is never appropriate
- [Create an invoice](/invoices/createCompanyInvoice) — full request schema
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Corrective invoices (R1–R5)
Pick the right rectification type and code, and see the canonical request shapes BeeL. accepts for each scenario.
A corrective invoice (*factura rectificativa*) corrects a previously issued invoice. BeeL. exposes them through `POST /v1/companies/{company_id}/invoices/{invoice_id}/corrective` — the URL points to the **original** invoice, and the body describes the correction.
You decide two things:
- **`rectification_type`** — `TOTAL` (rectify everything still invoiced on the original) or `PARTIAL` (apply a delta)
- **`rectification_code`** — `R1`–`R5` (the legal motive)
Plus a `reason` in free text (10–1,000 characters) describing the situation in human language. All three are required.
The corrective comes back with `type: CORRECTIVE` and `rectified_invoice_id` pointing at the original. It goes through VeriFactu exactly like any other invoice: same `verifactu` block, same states, same QR and the same `verifactu.status.updated` webhook.
**Only invoices issued in BeeL. can be corrected.** The original is addressed by its BeeL. `invoice_id`, and an invoice issued before your integration — numbered and registered by another system — has none. Correct those with the system that issued them.
## The two axes
```text
PARTIAL (one delta registro, easy bookkeeping)
┌────────────────────────────────────────────────┐
why → │ R1 │ R2 │ R3 │ R4 │ R5 (F2 only) │
├────────────────────────────────────────────────┤
TOTAL (one registro replaces the original)
```
| `rectification_type` | AEAT `tipo_rectificativa` | What BeeL. sends to AEAT |
|---|---|---|
| `PARTIAL` | `I` (by differences) | One record with delta lines; no `importe_rectificativa` block |
| `TOTAL` | `S` (by substitution) | One record with the **new** totals + the **original** totals echoed in `importe_rectificativa` |
The original invoice's status changes after issuance:
- `PARTIAL` → original becomes `RECTIFIED` (can be corrected again — several partial correctives on the same original are fine)
- `TOTAL` → original becomes `VOIDED` (terminal, no further correctives)
A `TOTAL` rectifies what is **still invoiced** on the original: its lines and those of its live correctives (voided ones do not count), negated. After two partial correctives, a `TOTAL` takes back only what they left. If they already brought the invoice to zero, it answers `422` [`CORRECTIVE_NOTHING_LEFT_TO_RECTIFY`](/errors/CORRECTIVE_NOTHING_LEFT_TO_RECTIFY).
A corrective whose `total_to_pay` is 0 — a recipient-data correction, for example — has nothing to refund or collect, so it is issued as `PAID`, with `payment_date` equal to `issue_date`.
**A corrective is issued immediately.** There is no draft step: the request numbers the corrective, freezes it and — if the NIF is under VeriFactu — submits it, in the same call. Check the payload before you send it; a wrong corrective is fixed with another corrective against the original, not by editing it.
### What a `TOTAL` corrective does to the original
The original moves to `VOIDED`, but **no cancellation record is sent to AEAT for it**. The correction travels in the corrective's own registration — a `tipo_rectificativa` `S` record that references the original. So, on the original:
- its `verifactu.submission_status` keeps its registration's outcome (typically `ACCEPTED`) — it does not become `VOIDED`;
- [its VeriFactu records](/invoices/listCompanyInvoiceVerifactuRecords) show no `VOID` record.
- its PDF does not change: it stays the document that was delivered. The corrective has its own PDF.
That is the difference from the [void endpoint](/verifactu/cancel-and-fix#anulación-void), which does send a cancellation record. Both leave the invoice `VOIDED`; only one tells AEAT the invoice should not exist — and it is only for an invoice issued by mistake. An operation that did take place is corrected, not voided.
> **Rules that apply here:** [COR-003 · A corrective shows the difference or the amounts after the correction](/rules/corrective#cor-003) · [COR-004 · The corrective record says whether it substitutes or adds a difference](/rules/corrective#cor-004)
## Pick the reason (R1–R5)
| `rectification_code` | When to use | Legal basis |
|---|---|---|
| **R1** | Almost every common correction: wrong amount, post-issuance discount, returned goods, an operation cancelled by agreement, a wrong tax treatment | Art. 80 Uno/Dos/Seis LIVA + error fundado en derecho |
| **R2** | The customer is in formal concurso de acreedores | Art. 80 Tres LIVA |
| **R3** | Bad debt: invoice is uncollectable after the legal procedure | Art. 80 Cuatro LIVA |
| **R4** | Any other cause, such as the recipient's data recorded wrong | Resto de causas |
| **R5** | Correcting a **simplified** (F2) invoice — any reason | Art. 80 Uno/Dos LIVA for F2 |
**COR-002 · Pick the reason code: R1–R4 for standard invoices, R5 for simplified**
`Required` · Law · Impact: critical · Checked by the API: a request that breaks it is rejected with the error codes listed.
Send the `rectification_code` that matches the cause: `R1` for an error founded in law or art. 80 Uno, Dos and Seis LIVA, `R2` for insolvency, `R3` for bad debt, `R4` for the rest. A simplified invoice is always corrected with `R5`, and `R5` corrects nothing else.
Full rule: [COR-002](/rules/corrective#cor-002)
> **Rules that apply here:** [COR-002 · Pick the reason code: R1–R4 for standard invoices, R5 for simplified](/rules/corrective#cor-002) · [COR-010 · A provisional price is rectified once the final one is known](/rules/corrective#cor-010) · [COR-011 · Discounts and rebates granted after the sale go on a corrective](/rules/corrective#cor-011)
## Deadline and `circumstance_date`
A corrective must be issued within four years from when the tax accrued — the original's `operation_date`, or its `issue_date` when it has none — or, for a cause of article 80 of the VAT Act (`R1`, `R2`, `R3`, `R5`), from when the circumstance took place. Send that date as `circumstance_date` (a discount granted later, an order cancelled, the insolvency order, the bad-debt claim); without it, the four years count from the operation date.
| Case | Error | Status | Why |
|---|---|---|---|
| The four years have passed; `error.details` carries `deadline` and `counted_from` | [`CORRECTIVE_OUT_OF_TIME`](/errors/CORRECTIVE_OUT_OF_TIME) | `422` | The four-year period to issue a corrective has ended. It counts from the original's operation date (its `operation_date`, or its `issue_date` when it has none) or, for a cause of article 80 of the VAT Act, from the `circumstance_date` sent. `error.details` carries `deadline` and `counted_from`. |
| `circumstance_date` with `R4`, which covers causes other than article 80 | [`CORRECTIVE_CIRCUMSTANCE_DATE_NOT_APPLICABLE`](/errors/CORRECTIVE_CIRCUMSTANCE_DATE_NOT_APPLICABLE) | `422` | `circumstance_date` was sent with a `rectification_code` that is not a cause of article 80 of the VAT Act (`R4`). |
| `circumstance_date` before the original's operation date or after today | [`CORRECTIVE_CIRCUMSTANCE_DATE_OUT_OF_RANGE`](/errors/CORRECTIVE_CIRCUMSTANCE_DATE_OUT_OF_RANGE) | `422` | `circumstance_date` is before the original's operation date or after today. |
## Scenario 1 — TOTAL cancellation (R1)
The operation was left without effect by agreement with the customer (an order cancelled before delivery, Art. 80 Dos LIVA). Don't send `lines` for a TOTAL: it rectifies everything still invoiced on the original. Sending them is rejected with [`RECTIFICATIVA_TOTAL_CON_LINEAS`](/errors/RECTIFICATIVA_TOTAL_CON_LINEAS).
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/corrective" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rectification_type": "TOTAL",
"rectification_code": "R1",
"reason": "Order cancelled by agreement with the customer before delivery: the operation is left without effect (Art. 80 Dos LIVA).",
"circumstance_date": "2026-03-10",
"notes": "Cancellation agreed with the customer on 10/03/2026. No amount pending collection."
}'
```
The original becomes `VOIDED`; the corrective carries the matching tipo_rectificativa `S` to AEAT.
## Scenario 2 — PARTIAL adjustment for bankruptcy (R2)
The customer was declared insolvent and has not paid the VAT charged; you reduce the unpaid part. Send the delta as a `PARTIAL`, with the date of the insolvency order as `circumstance_date`. `R2` needs a recipient established in Spain, the Canary Islands, Ceuta or Melilla — or in another EU member state, for insolvency proceedings there — or it answers `422` [`CORRECTIVE_RECIPIENT_NOT_ESTABLISHED`](/errors/CORRECTIVE_RECIPIENT_NOT_ESTABLISHED).
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/corrective" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rectification_type": "PARTIAL",
"rectification_code": "R2",
"reason": "Customer declared insolvent by order of 10/02/2026 (Commercial Court No. 2 Madrid, case 123/2026): the unpaid part of the invoice is reduced under Art. 80 Tres LIVA.",
"circumstance_date": "2026-02-10",
"lines": [
{
"description": "Unpaid amount at the insolvency declaration",
"quantity": -16,
"unit": "hours",
"unit_price": 37.5,
"discount_percentage": 0,
"main_tax": {
"type": "IVA",
"percentage": 21,
"regime_key": "01"
}
}
],
"notes": "Credit communicated to the insolvency administrator."
}'
```
Lines carry negative `quantity` (or negative `unit_price`) to express the reduction. The original stays `RECTIFIED`.
A `PARTIAL` may raise any amount, but it may not take the taxable base of any rate below zero once the previous correctives are counted: that answers `422` [`CORRECTIVE_EXCEEDS_INVOICED_AMOUNT`](/errors/CORRECTIVE_EXCEEDS_INVOICED_AMOUNT), with `tax_group` (for example `IVA 21%`) and `max_reduction` in `error.details`. And a `PARTIAL` whose lines only change the withholding answers `422` [`CORRECTIVE_WITHHOLDING_ONLY`](/errors/CORRECTIVE_WITHHOLDING_ONLY): a withholding that should not have been applied is not a cause for a corrective — void the invoice and issue a new one without it.
> **Rules that apply here:** [COR-009 · An insolvency corrective (R2) needs a declaration of insolvency](/rules/corrective#cor-009)
## Scenario 3 — PARTIAL bad-debt write-off (R3)
Documented bad debt under Art. 80 Cuatro LIVA. Send a negative line for the uncollectible part, with the date of the claim as `circumstance_date`. The API checks what it can see:
| Case | Error | Status | Why |
|---|---|---|---|
| Less than six months since the original's operation date; `error.details` carries `earliest_date` (one year applies when the previous year's turnover exceeded the threshold, which is yours to apply) | [`CORRECTIVE_BAD_DEBT_TOO_EARLY`](/errors/CORRECTIVE_BAD_DEBT_TOO_EARLY) | `422` | A bad-debt corrective (`R3`) was requested less than six months after the original's operation date. `error.details` carries `earliest_date` and `counted_from`. One year applies instead when the previous year's turnover exceeded 6,010,121.04 €, which is the issuer's to apply. |
| An operation with a small base and no `recipient_is_business: true` declaring the recipient acted as a business or professional | [`CORRECTIVE_BAD_DEBT_BASE_TOO_LOW`](/errors/CORRECTIVE_BAD_DEBT_BASE_TOO_LOW) | `422` | A bad-debt corrective (`R3`) on an operation with a taxable base of 50 € or less, without `recipient_is_business`. The law allows that reduction only when the recipient acted as a business or professional. |
| A recipient not established in Spain, the Canary Islands, Ceuta or Melilla | [`CORRECTIVE_RECIPIENT_NOT_ESTABLISHED`](/errors/CORRECTIVE_RECIPIENT_NOT_ESTABLISHED) | `422` | `R2` (insolvency) or `R3` (bad debt) was used with a recipient not established in Spain, the Canary Islands, Ceuta or Melilla. The base is only reduced for those causes with such a recipient; `R2` also accepts a recipient in another EU member state, for insolvency proceedings there. |
The other conditions — the claim in court or by notarial demand, filing with the AEAT — are yours to meet.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/corrective" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rectification_type": "PARTIAL",
"rectification_code": "R3",
"reason": "Bad debt under Art. 80 Cuatro LIVA: more than six months since the tax accrued without collection, claimed by notarial demand.",
"circumstance_date": "2026-05-04",
"lines": [
{
"description": "Bad debt adjustment - unpaid amount",
"quantity": -40,
"unit": "hours",
"unit_price": 37.5,
"discount_percentage": 0,
"main_tax": {
"type": "IVA",
"percentage": 21,
"regime_key": "01"
}
}
],
"notes": "Notarial demand of 04/05/2026 (Protocol 456/2026). Operation of 15/10/2025."
}'
```
> **Rules that apply here:** [COR-008 · A bad-debt corrective (R3) needs the legal conditions first](/rules/corrective#cor-008) · [COR-020 · A bad-debt corrective waits six months and needs a business recipient under 50 €](/rules/corrective#cor-020)
## Scenario 4 — Correcting the recipient's data (R4)
The invoice recorded its recipient with a wrong name, tax ID or address. Fix the customer first, then send a `PARTIAL` corrective with `rectification_code: R4`, **no `lines`** and the corrected `recipient` — for a registered customer, the same `customer_id`:
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/corrective" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rectification_type": "PARTIAL",
"rectification_code": "R4",
"reason": "The customer'\''s tax ID was mistyped on the invoice",
"recipient": { "customer_id": "{customer_id}" }
}'
```
The corrective carries the corrected recipient and leaves the amounts unchanged: its lines negate what is still invoiced and repeat it, so every rate nets to zero and it is issued as `PAID`. The original becomes `RECTIFIED`.
| Case | Error | Status | Why |
|---|---|---|---|
| Another customer than the one the invoice went to — that is not a data error: correct the invoice in full (`TOTAL`) and issue a new one to the right customer | [`CORRECTIVE_RECIPIENT_IS_ANOTHER_PERSON`](/errors/CORRECTIVE_RECIPIENT_IS_ANOTHER_PERSON) | `422` | A recipient-data corrective (`PARTIAL`, `R4`, no `lines`) names a customer other than the one the invoice went to. It corrects the data of the same recipient; an invoice issued to another person is not a data error. |
| The same name, tax ID and address the invoice already recorded | [`CORRECTIVE_RECIPIENT_UNCHANGED`](/errors/CORRECTIVE_RECIPIENT_UNCHANGED) | `422` | A recipient-data corrective carries the same name, tax ID and address the invoice already recorded: there is nothing to correct. |
| A `recipient` in any other corrective | [`CORRECTIVE_RECIPIENT_NOT_ACCEPTED`](/errors/CORRECTIVE_RECIPIENT_NOT_ACCEPTED) | `422` | The request to create a corrective invoice carries a `recipient` (a `customer_id` or inline data) outside a recipient-data correction. A corrective goes to the recipient of the invoice it corrects, with that invoice's data; `recipient` is accepted only to correct that recipient's name, tax ID or address, with `rectification_type` `PARTIAL`, `rectification_code` `R4` and no `lines`. Nothing is created. |
## Scenario 5 — R5: correcting a simplified invoice [#scenario-5--r5-cancelling-an-f2-to-issue-a-proper-f1]
A simplified (F2) invoice is always rectified with `R5`, whatever the cause. A customer returned one of the two items on the ticket:
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/{f2_invoice_id}/corrective" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rectification_type": "PARTIAL",
"rectification_code": "R5",
"reason": "The customer returned one of the two items on the ticket",
"lines": [
{
"description": "Return - T-shirt size M",
"quantity": -1,
"unit_price": 16.53,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
]
}'
```
**A customer who asks for a full invoice is not a correction.** To give them an invoice with their details instead of the ticket, use the [exchange of simplified invoices](/verifactu/simplified-vs-standard#upgrading-an-f2-to-f1-the-canje-case), not an `R5` corrective: nothing is being rectified.
> **Rules that apply here:** [SIM-007 · Exchanging a simplified invoice for a full one](/rules/simplified#sim-007)
## Validation rules BeeL. applies
| Rule | What happens if you break it |
|---|---|
| [COR-002 · Pick the reason code: R1–R4 for standard invoices, R5 for simplified](/rules/corrective#cor-002) | `422` [`RECTIFICATIVA_R5_ONLY_SIMPLIFICADA`](/errors/RECTIFICATIVA_R5_ONLY_SIMPLIFICADA) or [`RECTIFICATIVA_R1R4_NOT_SIMPLIFICADA`](/errors/RECTIFICATIVA_R1R4_NOT_SIMPLIFICADA) |
| Original must exist and belong to your account | `404` — the URL `{invoice_id}` resolves to nothing |
| [COR-007 · A wrong corrective is fixed against the original, not corrected itself](/rules/corrective#cor-007) | `422` [`CORRECTIVE_NOT_RECTIFIABLE`](/errors/CORRECTIVE_NOT_RECTIFIABLE) |
| [COR-017 · A corrective keeps the recipient, except to correct the recipient's data](/rules/corrective#cor-017) — the corrective takes the recipient of the original; send `recipient` only to correct its data ([Scenario 4](#scenario-4--correcting-the-recipients-data-r4)) | `422` [`CORRECTIVE_RECIPIENT_NOT_ACCEPTED`](/errors/CORRECTIVE_RECIPIENT_NOT_ACCEPTED), and nothing is created |
| [COR-022 · An invoice whose record AEAT rejected is fixed before it is corrected](/rules/corrective#cor-022) | `422` [`CORRECTIVE_ORIGINAL_RECORD_REJECTED`](/errors/CORRECTIVE_ORIGINAL_RECORD_REJECTED) — fix and resubmit the original first |
| [COR-023 · A corrective never rectifies more than was invoiced](/rules/corrective#cor-023) | `422` [`CORRECTIVE_EXCEEDS_INVOICED_AMOUNT`](/errors/CORRECTIVE_EXCEEDS_INVOICED_AMOUNT) or, on a `TOTAL`, [`CORRECTIVE_NOTHING_LEFT_TO_RECTIFY`](/errors/CORRECTIVE_NOTHING_LEFT_TO_RECTIFY) |
| [COR-024 · A corrective does not change only the withholding](/rules/corrective#cor-024) | `422` [`CORRECTIVE_WITHHOLDING_ONLY`](/errors/CORRECTIVE_WITHHOLDING_ONLY) |
| [SIM-007 · Exchanging a simplified invoice for a full one](/rules/simplified#sim-007) — an invoice issued in [exchange for simplified invoices](/verifactu/simplified-vs-standard#exchange-with-verifactu) and recorded with VeriFactu as `F3` cannot be corrected yet | `422` [`EXCHANGE_INVOICE_NOT_CORRECTABLE`](/errors/EXCHANGE_INVOICE_NOT_CORRECTABLE) — contact support |
| The original cannot be a proforma | `422` [`PROFORMA_CORRECTIVE_FORBIDDEN`](/errors/PROFORMA_CORRECTIVE_FORBIDDEN) |
| Original must be `ISSUED`, `SENT`, `PAID` or `RECTIFIED` | `422` [`INVOICE_NOT_CORRECTIBLE_IN_CURRENT_STATUS`](/errors/INVOICE_NOT_CORRECTIBLE_IN_CURRENT_STATUS): a `DRAFT` must be issued first, and a `VOIDED` invoice — including one closed by a `TOTAL` corrective — closes the chain |
| [COR-021 · An identical corrective a moment after another is taken as a double submission](/rules/corrective#cor-021) | `422` [`CORRECTIVE_RECENT_DUPLICATE`](/errors/CORRECTIVE_RECENT_DUPLICATE) — same original, same `rectification_type`, `rectification_code` and `reason`, and the same totals, moments after the first; the message names the corrective that already exists. It guards against a double submit; wait a few minutes, or change the reason or amounts, if you really mean a second one |
| For `PARTIAL`, line totals can be negative or positive — anything goes mathematically | — |
| For `PARTIAL`, `lines` are **required** | `422` [`RECTIFICATIVA_PARCIAL_SIN_LINEAS`](/errors/RECTIFICATIVA_PARCIAL_SIN_LINEAS) |
| For `TOTAL`, `lines` must be **omitted**: it rectifies the whole original | `422` [`RECTIFICATIVA_TOTAL_CON_LINEAS`](/errors/RECTIFICATIVA_TOTAL_CON_LINEAS) |
> **Rules that apply here:** [COR-003 · A corrective shows the difference or the amounts after the correction](/rules/corrective#cor-003)
## Which series numbers the corrective
Omit `series_id` and the corrective is numbered in your company's **default series for corrective invoices**, never in the original's series: corrective invoices go in a series of their own. If the company has none, it is created on first use (code `R`, or the next free one). An explicit `series_id` must be a corrective series. See [Series and numbering](/guides/series-and-numbering#correctives-and-proformas).
## Finding the correctives of an invoice
List invoices filtered by the original: `GET /v1/companies/{company_id}/invoices?rectified_invoice_id={invoice_id}` returns every corrective issued against it, `TOTAL` or `PARTIAL`.
> **Rules that apply here:** [COR-001 · Wrong data on an issued invoice is fixed with a corrective](/rules/corrective#cor-001) · [COR-005 · A corrective identifies the invoice it rectifies](/rules/corrective#cor-005) · [COR-017 · A corrective keeps the recipient, except to correct the recipient's data](/rules/corrective#cor-017) · [COR-019 · Insolvency and bad-debt correctives need a recipient established in Spain](/rules/corrective#cor-019) · [COR-023 · A corrective never rectifies more than was invoiced](/rules/corrective#cor-023)
## Related
- [Invoice types](/verifactu/invoice-types) — R1–R5 in the broader catalogue
- [Cancel vs amend](/verifactu/cancel-and-fix) — when to use the void endpoint instead
- [Submission states](/verifactu/submission-states) — what happens after BeeL. submits the corrective
- [Create a corrective invoice](/invoices/createCompanyCorrectiveInvoice) — full request schema
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Tax classification per line
Set `exemption_reason` on each line correctly, with the full mapping from BeeL. API values to AEAT codes (S1/S2/N1/N2/E1–E6).
Every line carries an optional `exemption_reason` that tells AEAT how the operation is classified for IVA purposes. The BeeL. API uses **descriptive enum values** (`EXENTA_ART_25`, `NO_SUJETA_LOCALIZACION`, `ISP_ART_84_2_A`, etc.); BeeL. translates those to the AEAT codes (E5, N2, S2) before submission.
If you leave `exemption_reason` unset, BeeL. classifies the line as **subject and not exempt** (*sujeta y no exenta*, S1) with the IVA rate you set in `main_tax.percentage`.
## Allowed `main_tax.percentage` values
`main_tax.percentage` is a plain number (0–100) cross-validated against `main_tax.type`. For `type: "IVA"` the rates in play are:
| Value | Rate name | Notes |
|---|---|---|
| `0` | Exempt | Use together with an `exemption_reason` (see below) |
| `4` | Super-reduced | Basic food, books, medicines |
| `10` | Reduced | Hospitality, transport, most foods |
| `21` | Standard | Default for goods and services |
| `5` | Temporary | Only on operations dated 2022-07-01 to 2024-09-30 |
| `2`, `7.5` | Temporary | Only on operations dated 2024-10-01 to 2024-12-31 |
The rates accepted on any date are 4, 10 or 21. The temporary ones no longer apply to new operations, but they stay accepted for corrective invoices and late-filed invoices of their period. The date judged is the invoice's `operation_date`, or the issue date when there is none: a temporary rate outside its period is rejected with `422` [`VAT_RATE_NOT_ACCEPTED_ON_DATE`](/errors/VAT_RATE_NOT_ACCEPTED_ON_DATE), so send the `operation_date` of the period. [List tax types](/tax-configuration/listTaxTypes) publishes each rate with its `valid_from` / `valid_until`. Each temporary rate has its own surcharge pair (see [Equivalence surcharge](/verifactu/equivalence-surcharge)). For IGIC/IPSI lines, use `main_tax.type: "IGIC"` (0, 3, 5, 7, 9.5, 15 or 20) or `"IPSI"` (0.5, 1, 2, 4, 8 or 10) — each type is validated against its own set of rates.
> **Rules that apply here:** [TAX-001 · Apply the VAT rate in force when the operation took place](/rules/taxes#tax-001) · [TAX-014 · Only the VAT rates AEAT accepts on the operation date](/rules/taxes#tax-014)
## The four families
```text
Operation is …
│
┌─────────────────────┼─────────────────────┐
subject to IVA not subject to IVA exempt from IVA
(sujeta) (no sujeta) (exenta)
│ │ │
┌────┴────┐ │ │
S1 S2 N1 or N2 E1, E2, E3, E4, E5, E6
normal ISP (location / (legal exemption)
general rules)
```
> **Rules that apply here:** [CNT-001 · Every operation of the business is invoiced, exempt ones included](/rules/contents#cnt-001)
## Mapping table: BeeL. API value ↔ AEAT code
| AEAT code | BeeL `exemption_reason` value | When to use |
|---|---|---|
| **S1** (default) | *omit the field* | Default — subject to IVA, normal seller-collects |
| **S2** | `ISP_ART_84_2_A` … `ISP_ART_84_2_F` | ISP — buyer self-assesses the IVA (the letter of Art. 84.Uno.2.º LIVA that applies) |
| **N1** | `NO_SUJETA_ART_7_9` | Not subject by general rules (internal transfers, etc.) |
| **N2** | `NO_SUJETA_LOCALIZACION` | Operation localised outside Spain (intra-EU services, services to non-EU) |
| **E1** | `EXENTA_ART_20` | Educational, medical, social, insurance, residential rental |
| **E2** | `EXENTA_ART_21` | Exports of goods to non-EU territories |
| **E3** | `EXENTA_ART_22` | International transport, customs zone operations |
| **E4** | `EXENTA_ART_24` | Bonded warehouse operations |
| **E5** | `EXENTA_ART_25` | Intra-EU sales of goods to a business with valid VIES VAT-ID |
| **E6** | `EXENTA_ART_140` | Investment gold (Art. 140 bis LIVA), usually with `regime_key: "04"` |
| **E6** | `OTRO` | Any other case not covered above. Requires `exemption_reason_text` |
| ❌ rejected | `EXENTA_ART_26` | Art. 26 exempts the buyer's intra-EU acquisition, not a supply you invoice — see below |
| ❌ rejected | `ISP_ART_84_2_G` | The law requires these supplies in a special series — see below |
| ❌ rejected | `REGIMEN_ART_129` / `REGIMEN_ART_135` / `REGIMEN_ART_141` / `REGIMEN_ART_154` / `REGIMEN_ART_163_DECIES` | Rejected by the API — see below |
Only `EXENTA_ART_140` and `OTRO` map to **E6**; `ISP_*` maps to S2 and `NO_SUJETA_*` to N1/N2.
**Special-regime values are rejected, not reported as E6.** The `REGIMEN_ART_*` values describe special regimes (agriculture compensation, used goods, travel agencies, equivalence surcharge, cash basis), not exemptions under arts. 20–26 LIVA, and AEAT models them with the regime key rather than as an exempt operation. A line carrying one of them is rejected — whether or not the NIF is under VeriFactu — with [`EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU`](/errors/EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU) when you create or update the invoice. Drop the `exemption_reason` and set the matching `main_tax.regime_key` instead — see [Regime keys](/verifactu/regime-keys).
**Two values of the enum are rejected on an invoice line.** `EXENTA_ART_26` exempts the *acquisition* the buyer declares, not a supply the seller invoices: a line carrying it answers `422` [`EXEMPTION_NOT_FOR_ISSUED_INVOICE`](/errors/EXEMPTION_NOT_FOR_ISSUED_INVOICE), and a supply of goods to another Member State is `EXENTA_ART_25`. `ISP_ART_84_2_G` (silver, platinum, palladium, mobile phones, consoles, laptops and tablets) must be invoiced in a special series the API does not have: `422` [`REVERSE_CHARGE_CASE_NOT_SUPPORTED`](/errors/REVERSE_CHARGE_CASE_NOT_SUPPORTED). A corrective invoice of an original that already carried one of them keeps it.
When in doubt between `NO_SUJETA_LOCALIZACION` (N2) and `EXENTA_ART_25` (E5) for intra-EU operations, the rule is simple: **goods → E5**, **services → N2**. See [International customers](/verifactu/international-customers).
> **Rules that apply here:** [CNT-010 · An exempt operation states why it is exempt](/rules/contents#cnt-010)
## How you set it in the API
The `exemption_reason` lives **directly on each line**. There's also an optional `exemption_reason_text` to add free-text context.
### B2B intra-EU services (N2)
```json
{
"lines": [
{
"description": "Consulting services to EU business",
"quantity": 1,
"unit": "service",
"unit_price": 1000,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_LOCALIZACION",
"exemption_reason_text": "Operation not subject to Spanish VAT under Art. 69 LIVA"
}
]
}
```
### B2B intra-EU goods (E5)
```json
{
"lines": [
{
"description": "Hardware supply to EU customer",
"quantity": 1,
"unit": "unit",
"unit_price": 1000,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "EXENTA_ART_25"
}
]
}
```
### Reverse charge (*inversión del sujeto pasivo*, S2) [#inversión-del-sujeto-pasivo-s2]
```json
{
"lines": [
{
"description": "Construction services subject to reverse charge",
"quantity": 1,
"unit": "project",
"unit_price": 1000,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "ISP_ART_84_2_F"
}
]
}
```
Pick the letter of Art. 84.Uno.2.º LIVA that applies: `_A` (supplier not established in Spain), `_B` (unwrought or semi-finished gold), `_C` (scrap, waste and recovery materials), `_D` (greenhouse gas emission allowances), `_E` (certain real estate supplies) or `_F` (construction or renovation works). `_G` is rejected (see above).
> **Rules that apply here:** [CNT-012 · A reverse-charge invoice carries the mention «inversión del sujeto pasivo»](/rules/contents#cnt-012) · [TAX-002 · Reverse-charge operations are invoiced without charging VAT](/rules/taxes#tax-002)
### Exempt educational service (E1)
```json
{
"lines": [
{
"description": "Training course",
"quantity": 1,
"unit": "course",
"unit_price": 500,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "EXENTA_ART_20"
}
]
}
```
> **Rules that apply here:** [CNT-010 · An exempt operation states why it is exempt](/rules/contents#cnt-010)
## Rules BeeL. enforces
| Rule | What happens if you break it |
|---|---|
| If `exemption_reason` is set, `main_tax.percentage` must be `0` and `equivalence_surcharge_rate` must not be set | Rejected at issuance |
| `EXENTA_ART_25` (E5) requires the customer to have an `alternative_id` of type `NIF_IVA` | Rejected — E5 is reserved for intra-EU B2B with VAT-ID |
| `EXENTA_ART_21` (E2) and `EXENTA_ART_22` (E3) cannot use `regime_key: "01"` — exports go under `"02"`, which BeeL. doesn't set for you | Rejected — [`EXEMPTION_INCOMPATIBLE_WITH_REGIME`](/errors/EXEMPTION_INCOMPATIBLE_WITH_REGIME) |
| `regime_key: "02"` requires `EXENTA_ART_21` or `EXENTA_ART_22` on the line | Rejected — [`REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION`](/errors/REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION) |
| `ISP_*` cannot pair with `equivalence_surcharge_rate` | Rejected — [`ISP_INCOMPATIBLE_WITH_SURCHARGE`](/errors/ISP_INCOMPATIBLE_WITH_SURCHARGE): the buyer self-assesses the VAT in their own regime, so the Spanish retail surcharge doesn't apply |
| Default (no `exemption_reason`) requires `main_tax.percentage > 0` | Rejected — [`EXEMPT_ZERO_RATE_REQUIRES_REASON`](/errors/EXEMPT_ZERO_RATE_REQUIRES_REASON): a 0 % line must declare one of `EXENTA_ART_20..25`, `EXENTA_ART_140`, `NO_SUJETA_ART_7_9`, `NO_SUJETA_LOCALIZACION`, `ISP_ART_84_2_*` or `OTRO` |
| `OTRO` requires a free-text `exemption_reason_text` | Rejected |
> **Rules that apply here:** [TAX-003 · Reverse-charge lines carry no tax and never go on a simplified invoice](/rules/taxes#tax-003) · [TAX-005 · Exempt and non-subject lines carry no VAT rate](/rules/taxes#tax-005)
## Decision tree
```text
Where is the customer?
├─ In Spain
│ ├─ Operation is subject to IVA?
│ │ ├─ Yes, normal seller-collects-IVA → omit exemption_reason (S1)
│ │ ├─ Yes, ISP applies → ISP_ART_84_2_A … _F (S2)
│ │ └─ Yes, but exempted by law → EXENTA_ART_20 ... 24, EXENTA_ART_140 (E1–E4, E6)
│ └─ Not subject (out-of-scope op) → NO_SUJETA_ART_7_9 (N1)
│
├─ EU country (intra-community)
│ ├─ Customer is B2B with VIES VAT-ID
│ │ ├─ Selling goods → EXENTA_ART_25 (E5)
│ │ └─ Selling services → NO_SUJETA_LOCALIZACION (N2)
│ └─ Customer is B2C (no VIES)
│ ├─ Under 10 000 € OSS threshold → omit (S1) — apply Spanish IVA
│ └─ Over OSS threshold → main_tax.regime_key="17" + destination rate (no exemption_reason)
│
└─ Non-EU
├─ Selling goods (export) → EXENTA_ART_21 + main_tax.regime_key="02"
└─ Selling services → NO_SUJETA_LOCALIZACION
```
The full payload examples for each path live in [International customers](/verifactu/international-customers).
> **Rules that apply here:** [TAX-018 · An issued invoice does not use the intra-EU acquisition exemption](/rules/taxes#tax-018)
## Related
- [International customers](/verifactu/international-customers) — every B2B/B2C scenario in full
- [Regime keys](/verifactu/regime-keys) — when `main_tax.regime_key` is required alongside the classification
- [Equivalence surcharge](/verifactu/equivalence-surcharge) — incompatible with ISP and exempt lines
- [Create an invoice](/invoices/createCompanyInvoice) — full line schema
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Regime keys
The catalogue of VeriFactu regime codes BeeL. supports, when each one is required, and how it pairs with `exemption_reason`.
`main_tax.regime_key` (AEAT's *clave_regimen*) declares the **tax regime** that applies to the operation. Most lines run under the general regime (`"01"`) — that's the default if you don't set anything. The other codes appear in specific scenarios; this page is the canonical catalogue.
For the `exemption_reason` values referenced here, see [Tax classification per line](/verifactu/tax-classification).
## The catalogue
These are the values of the `RegimeKey` enum in the spec. Each is a two-character string. `"03"`, `"06"` and `"14"` are in the enum but rejected on a line (see [Keys that are not accepted](#keys-that-are-not-accepted)); [List tax types](/tax-configuration/listTaxTypes) only offers the keys that are accepted.
| Code | AEAT name | Where it applies |
|---|---|---|
| **`"01"`** | Régimen general | Default for every line |
| **`"02"`** | Exportación | Exports and related operations exempt under arts. 21–22 LIVA (pairs with `exemption_reason: EXENTA_ART_21` or `EXENTA_ART_22`); IVA and IGIC lines, not IPSI |
| **`"03"`** | Bienes usados, arte, antigüedades, colección | REBU regime — **not accepted** |
| **`"04"`** | Oro de inversión | Investment gold regime |
| **`"05"`** | Agencias de viajes | Travel agencies regime; the PDF carries its mention |
| **`"06"`** | Grupo de entidades IVA / IGIC | Entity group regime — **not accepted** |
| **`"07"`** | Criterio de caja | Cash-basis IVA; the PDF carries its mention |
| **`"08"`** | Operaciones sujetas a IPSI / IVA o IGIC | An operation subject to another indirect tax (IPSI or IGIC on an IVA line; IPSI or IVA on an IGIC line). Not the general regime of IGIC, which is `"01"` |
| **`"09"`** | Facturación de servicios por agencias mediadoras | Mediating agencies (travel) |
| **`"10"`** | Cobros por cuenta de terceros | Third-party honorarium collection |
| **`"11"`** | Arrendamiento de local de negocio | Business premises rental |
| **`"14"`** | IVA pendiente de devengo en certificaciones de obra | Public works certifications — **not accepted** |
| **`"15"`** | IVA pendiente de devengo en operaciones de tracto sucesivo | Successive-tract operations |
| **`"17"`** | OSS / IOSS | EU one-stop-shop for B2C distance sales |
| **`"18"`** | Recargo de equivalencia | Lines carrying `equivalence_surcharge_rate` |
| **`"19"`** | REAGYP / art. 25 Ley 19/1994 | Agriculture, livestock, fishing (or Canarias special regime) |
| **`"20"`** | Régimen simplificado | Modules / simplified regime |
> **Rules that apply here:** [CNT-015 · A travel-agency invoice carries the mention «régimen especial de las agencias de viajes»](/rules/contents#cnt-015)
## Where it goes in the request
`regime_key` lives **inside** `main_tax`, not at the line root:
```json
{
"lines": [
{
"description": "...",
"quantity": 1,
"unit_price": 100,
"main_tax": {
"type": "IVA",
"percentage": 21,
"regime_key": "01"
}
}
]
}
```
## High-signal cases
### `"02"` — Exports of goods (non-EU)
Mandatory for exports and the operations assimilated to them. Pair it with `exemption_reason: EXENTA_ART_21` (E2 — goods leaving the EU) or `EXENTA_ART_22` (E3 — operations assimilated to exports, such as international transport). The rule works both ways: regime `"02"` needs one of those two reasons, and those two reasons cannot go under the general regime `"01"`. Full payload example in [International customers > Exports of goods](/verifactu/international-customers#exports-of-goods-non-eu).
### `"03"` — REBU (used goods, art, antiques)
Not accepted: a line with `regime_key: "03"` is rejected with `422` [`REGIME_KEY_NOT_SUPPORTED`](/errors/REGIME_KEY_NOT_SUPPORTED). Under the used-goods regime the invoice must not show the tax separately (RD 1619/2012, art. 16.2.c), and a BeeL. invoice always does. See [Keys that are not accepted](#keys-that-are-not-accepted).
> **Rules that apply here:** [CNT-014 · A used-goods, art or antiques invoice carries its regime mention](/rules/contents#cnt-014)
### `"07"` — Cash-basis IVA (*criterio de caja*) [#07--cash-basis-iva-criterio-de-caja]
If you've opted into the cash-basis regime (Art. 163 decies LIVA), every issued invoice carries `regime_key: "07"`. The IVA accrues when payment is received, not when the invoice is issued. The invoice PDF carries the mention «Régimen especial del criterio de caja». A `"07"` line cannot carry reverse charge or a not-subject reason, and of the exemptions only `EXENTA_ART_20` or `OTRO`.
> **Rules that apply here:** [CNT-013 · A cash-basis invoice carries the mention «régimen especial del criterio de caja»](/rules/contents#cnt-013)
### `"17"` — OSS / IOSS
For B2C distance sales of goods or services to EU consumers above the 10,000 € annual threshold. The line keeps the **destination country's IVA rate** — `regime_key: "17"` widens the accepted rates beyond the Spanish menu, so 19 % (Germany) or 22 % (Italy) are valid here. Do **not** set `percentage: 0`, and do not add an `exemption_reason`: BeeL. classifies the line as N2 from the regime key alone, and any exemption reason would force the rate to 0 % and drop the destination IVA from the breakdown. You still settle it via Modelo 369. Full payload in [International customers > B2C OSS](/verifactu/international-customers#b2c-intra-eu--above-the-oss-threshold).
> **Rules that apply here:** [TAX-007 · Sales declared through OSS use regime key 17](/rules/taxes#tax-007)
### `"18"` — Equivalence surcharge [#18--recargo-de-equivalencia]
Set on every line that carries `equivalence_surcharge_rate`. See [Equivalence surcharge](/verifactu/equivalence-surcharge).
### `"19"` — REAGYP / Canary Islands [#19--reagyp--canarias]
Two unrelated cases share this code: **REAGYP** (the special regime for agriculture, livestock and fishing) and **Art. 25 Ley 19/1994** for the Canary Islands Special Zone (*Zona Especial Canaria*). Pick `"19"` when either applies; if unsure, talk to your tax advisor.
## Keys that are not accepted
A line with one of these keys is rejected with `422` [`REGIME_KEY_NOT_SUPPORTED`](/errors/REGIME_KEY_NOT_SUPPORTED) when you create, edit or issue the invoice, and so is a default key set to one of them in the tax configuration:
- **`"03"` (used goods, art, antiques):** under this regime the invoice must not show the tax separately (RD 1619/2012, art. 16.2.c), and a BeeL. invoice always does.
- **`"06"` (group of entities):** AEAT requires a cost-based taxable base the invoice does not carry.
- **`"14"` (VAT pending in public works certifications):** AEAT requires an operation date after the issue date and a public-administration recipient.
A corrective invoice of an original that already carried one of these keys keeps it.
## What AEAT requires with each key
Checked on IVA and IGIC lines when you create, edit or issue the invoice, before it is numbered:
| You send | Error | Status | Why |
|---|---|---|---|
| `"04"` on a line with neither reverse charge (`ISP_ART_84_2_*`) nor an exemption; `"08"` without `exemption_reason: NO_SUJETA_LOCALIZACION` at 0 %; `"10"` without `NO_SUJETA_ART_7_9`; `"11"` with reverse charge; `"07"` with reverse charge, a not-subject reason or an exemption other than `EXENTA_ART_20` / `OTRO` | [`REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED`](/errors/REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED) | `422` | The line's `exemption_reason` (or its absence) is not one AEAT accepts with its regime key: `04` needs reverse charge or an exemption; `08` only `NO_SUJETA_LOCALIZACION` at 0 %; `10` only `NO_SUJETA_ART_7_9`; `11` no reverse charge; `07` no reverse charge, no not-subject reason and only `EXENTA_ART_20` or `OTRO` as exemptions. |
| `"11"` (IVA) on a subject line at a rate other than 21 % | [`REGIME_KEY_REQUIRES_VAT_RATE`](/errors/REGIME_KEY_REQUIRES_VAT_RATE) | `422` | A subject IVA line under `regime_key` `11` carries a rate AEAT does not accept with that key; it only accepts 21 %. |
| `"10"` on a simplified or corrective invoice | [`REGIME_KEY_REQUIRES_STANDARD_INVOICE`](/errors/REGIME_KEY_REQUIRES_STANDARD_INVOICE) | `422` | `regime_key` `10` is used on an invoice that is not a full (`STANDARD`) invoice; AEAT rejects it on a simplified or corrective invoice. |
| `"10"` with a recipient identified by an `alternative_id` instead of a `nif` | [`REGIME_KEY_REQUIRES_RECIPIENT_NIF`](/errors/REGIME_KEY_REQUIRES_RECIPIENT_NIF) | `422` | `regime_key` `10` is used with a recipient identified by an `alternative_id`; AEAT requires a Spanish `nif` with that key. |
## Cross-field validations BeeL. applies
| Combination | Status |
|---|---|
| `regime_key: "17"` with any `exemption_reason` | ⚠️ Accepted, but it forces the line to 0 % and loses the destination IVA — leave it unset |
| `regime_key: "02"` without `exemption_reason` `EXENTA_ART_21` or `EXENTA_ART_22` | ❌ Rejected — [`REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION`](/errors/REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION): regime 02 needs E2 or E3 |
| `exemption_reason: EXENTA_ART_21` or `EXENTA_ART_22` with `regime_key: "01"` | ❌ Rejected — [`EXEMPTION_INCOMPATIBLE_WITH_REGIME`](/errors/EXEMPTION_INCOMPATIBLE_WITH_REGIME): exports use regime 02 |
| `regime_key: "18"` without `equivalence_surcharge_rate` on the line | ❌ Rejected — [`REGIME_REQUIRES_SURCHARGE`](/errors/REGIME_REQUIRES_SURCHARGE) |
| `equivalence_surcharge_rate` on a line whose `regime_key` is **not** `"18"` | ❌ Rejected — [`SURCHARGE_REQUIRES_REGIME`](/errors/SURCHARGE_REQUIRES_REGIME): the surcharge is only compatible with regime 18 |
> **Rules that apply here:** [TAX-015 · Regime key 08 means not subject to the line's tax](/rules/taxes#tax-015) · [TAX-016 · Regime keys 03, 06 and 14 are not accepted](/rules/taxes#tax-016) · [TAX-017 · Special regime keys carry the classification AEAT requires](/rules/taxes#tax-017)
## Related
- [Tax classification](/verifactu/tax-classification) — the `exemption_reason` enum and its AEAT mapping (SSoT)
- [International customers](/verifactu/international-customers) — B2B/B2C/extracomunitario scenarios with payloads
- [Equivalence surcharge](/verifactu/equivalence-surcharge) — the dedicated guide for `regime_key: "18"`
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Equivalence surcharge (recargo de equivalencia)
When to add `equivalence_surcharge_rate` on B2B lines, the surcharge values BeeL. accepts and their periods, and how the line is sent to AEAT.
Recargo de equivalencia is a special IVA regime for retailers (minoristas) who sell unmodified goods. The supplier (you) adds the surcharge to the standard IVA on every line sold to a retailer who has opted into the regime; the retailer then doesn't file their own IVA returns.
## When you add the surcharge
You add it **only if the buyer has declared themselves a retailer under the equivalence surcharge regime**. The buyer must inform you in writing — you don't infer it from anything in their fiscal data.
If the buyer hasn't declared the regime, you invoice them with normal IVA and no surcharge.
> **Rules that apply here:** [SUR-001 · The surcharge rate matches the VAT rate of its line](/rules/surcharge#sur-001)
## Accepted surcharge values
A request's `equivalence_surcharge_rate` is one of `0`, `0.26`, `0.5`, `0.62`, `1`, `1.4`, `1.75` or `5.2`, each paired to a specific IVA rate and, for the temporary rates, to a period:
| Value | Pairs with IVA rate | Accepted on operations dated |
|---|---|---|
| `0` | — | Any: surcharge disabled on that line |
| `0.5` | 4 % | Any |
| `1.4` | 10 % | Any |
| `5.2` | 21 % | Any |
| `1.75` | 21 % | Any (tobacco products) |
| `0.5` | 5 % | 2022-07-01 to 2022-12-31 |
| `0.62` | 5 % | 2023-01-01 to 2024-09-30 |
| `0.26` | 2 % | 2024-10-01 to 2024-12-31 |
| `1` | 7.5 % | 2024-10-01 to 2024-12-31 |
Pairing is strict: a line with `main_tax.percentage: 10` must use `1.4`, never `0.5` or `5.2`. A combination outside the table is rejected with [`INVALID_IVA_SURCHARGE_PAIR`](/errors/INVALID_IVA_SURCHARGE_PAIR), and a pair outside its period with `422` [`SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE`](/errors/SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE). The date judged is the invoice's `operation_date`, or the issue date when there is none. [List tax types](/tax-configuration/listTaxTypes) publishes every pair with its `valid_from` / `valid_until`; the same surcharge can appear twice (`0.5` with 4 % and with 5 %), so key them by surcharge and VAT rate together.
When a line omits `equivalence_surcharge_rate` and the company is in the regime, the surcharge is derived from the line's VAT **on the operation date**: with VAT at 5 %, `0.5` up to 2022-12-31 and `0.62` from 2023-01-01.
The 5 % surcharge is `0.62`: `0.625` is not accepted in a request. An invoice issued with `0.625` keeps it when you read it. A draft saved with `0.625` that could not be recalculated is rejected at issue with `422` [`SURCHARGE_RATE_REQUIRES_RECALCULATION`](/errors/SURCHARGE_RATE_REQUIRES_RECALCULATION): edit its lines and issue it again.
The regime goes with it: a line with a surcharge must carry `main_tax.regime_key: "18"`, and a line under regime `"18"` must carry a surcharge (see [below](#whats-incompatible-with-the-surcharge)).
> **Rules that apply here:** [SUR-001 · The surcharge rate matches the VAT rate of its line](/rules/surcharge#sur-001)
## How to set it in the API
Add `equivalence_surcharge_rate` to the line and set `main_tax.regime_key: "18"`:
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Tienda de Electrónica López",
"nif": "B12345674",
"address": {
"street": "Calle Comercio",
"number": "56",
"postal_code": "08001",
"city": "Barcelona",
"province": "Barcelona",
"country": "España"
}
},
"lines": [
{
"description": "Producto vendido a minorista en RE",
"quantity": 1,
"unit": "unit",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "18" },
"equivalence_surcharge_rate": 5.2
}
]
}'
```
BeeL. submits the right `clave_regimen: "18"` and AEAT codes to VeriFactu on your behalf.
## What's incompatible with the surcharge
| Setting | Reason |
|---|---|
| `exemption_reason: ISP_ART_84_2_*` | ISP shifts liability to the buyer; surcharge doesn't apply |
| Any `exemption_reason: EXENTA_*` / `NO_SUJETA_*` | Exempt and not-subject lines don't carry IVA → no surcharge |
| Any `main_tax.regime_key` other than `"18"` | The surcharge is only compatible with regime 18 ([`SURCHARGE_REQUIRES_REGIME`](/errors/SURCHARGE_REQUIRES_REGIME)); the reverse also holds, regime 18 without a rate is rejected ([`REGIME_REQUIRES_SURCHARGE`](/errors/REGIME_REQUIRES_SURCHARGE)) |
| Lines on a simplified (`type: SIMPLIFIED`) invoice | F2 is for B2C; retailers buy under F1 |
BeeL. rejects these combinations at issuance with a descriptive error.
For the `exemption_reason` values referenced here, see [Tax classification per line](/verifactu/tax-classification).
## The surcharge is per-line, not per-invoice
You can mix lines with and without the surcharge in the same invoice. Common case: the retailer buys some goods (surcharge applies) and you also charge them a transport fee that is a separate service (no surcharge, regime general).
```json
{
"lines": [
{
"description": "Mercancía minorista",
"quantity": 1,
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "18" },
"equivalence_surcharge_rate": 5.2
},
{
"description": "Transporte",
"quantity": 1,
"unit_price": 30,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
]
}
```
> **Rules that apply here:** [SUR-002 · Supplies with the surcharge go on separate invoices](/rules/surcharge#sur-002)
## Related
- [Tax classification](/verifactu/tax-classification) — the `exemption_reason` enum (SSoT)
- [Regime keys](/verifactu/regime-keys) — `"18"` and friends (SSoT)
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Disbursements (suplidos)
Bill back amounts you paid in the client's name with `line_type: SUPLIDO`: they stay out of the taxable base, VAT and VeriFactu, and add to `total_to_pay`.
A **disbursement** (*suplido*) is money you pay to a third party **in your client's name and on their behalf**, then bill back at cost. The classic cases are court fees, notary and registry charges, official gazette fees, or a tax you settle for the client. Under **art. 78.Tres.3 LIVA**, a disbursement is not part of your taxable base: you do not add VAT to it, and it does not reach VeriFactu. You are only passing the cost through.
A disbursement is not the same as a re-billed expense. If you buy something for your own activity (a plane ticket, materials) and re-invoice it, that is part of **your** taxable base and carries VAT as a normal line. It only qualifies as a disbursement when the original invoice is issued in **the client's name**, you paid it on their behalf, and you bill the exact amount with no markup.
## The three conditions
For a line to be a valid disbursement, all three must hold:
1. **Issued in the client's name.** The third party's invoice names the client as the recipient, not you.
2. **Paid on their behalf.** You advanced the money for them, under an express or implied mandate.
3. **Billed at cost.** The amount you pass through equals the amount you paid, with no margin.
If any condition fails, it is a normal line (`line_type: NORMAL`) and follows the usual VAT rules.
## How BeeL. models it
A disbursement is a **line type**, set per line with `line_type`. Every line defaults to `NORMAL`; set it to `SUPLIDO` to exclude that line from the taxable base, VAT and VeriFactu.
| Field | Type | Notes |
|---|---|---|
| `line_type` | `NORMAL` \| `SUPLIDO` | Defaults to `NORMAL`. Set `SUPLIDO` for a disbursement. |
| `source_invoice_reference` | string (≤ 50) | Reference of the original third-party invoice issued in the client's name. **Required when `line_type: SUPLIDO`.** |
| `source_invoice_ids` | array of UUID | Optional. Ids of your own issued BeeL. invoices that make up the disbursement. Their sum is the disbursement amount. For audit traceability only. |
A `SUPLIDO` line ignores the tax fields (`main_tax`, `equivalence_surcharge_rate`, `irpf_rate`, `exemption_reason`): it never contributes VAT, surcharge or withholding.
### Totals
Two totals capture the effect of disbursements:
| Field | Meaning |
|---|---|
| `total_disbursements` | Sum of all `SUPLIDO` lines. Excluded from `taxable_base`, `total_vat` and VeriFactu. Defaults to `0`. |
| `total_to_pay` | `invoice_total` + `total_disbursements`. The amount printed on the PDF and actually charged to the client. |
When an invoice has no disbursements, `total_disbursements` is `0` and `total_to_pay` equals `invoice_total`.
`invoice_total` is still the fiscal total (base + VAT + surcharge − IRPF). Disbursements never reach VeriFactu or the tax breakdowns. (The total AEAT registers also leaves IRPF out, so it is not `invoice_total` either — see [What AEAT receives](/verifactu/what-aeat-receives#the-total).) The disbursement rides on top in `total_to_pay`, which is what the client pays.
## Example
A consultancy bills 1 000 € of advisory work (21 % VAT) and passes through a 150 € registry fee it paid in the client's name.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": { "legal_name": "Cliente SL", "nif": "B12345674", "address": { "street": "Calle Mayor", "number": "1", "postal_code": "28013", "city": "Madrid", "province": "Madrid", "country": "España" } },
"lines": [
{
"description": "Asesoramiento mercantil",
"quantity": 10,
"unit": "hours",
"unit_price": 100,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
},
{
"description": "Tasa del Registro Mercantil",
"quantity": 1,
"unit": "unit",
"unit_price": 150,
"line_type": "SUPLIDO",
"source_invoice_reference": "BORME-2026-4471"
}
],
"options": { "issue_directly": true }
}'
```
Resulting totals:
```json
{
"totals": {
"taxable_base": 1000,
"total_vat": 210,
"total_irpf": 0,
"invoice_total": 1210,
"total_disbursements": 150,
"total_to_pay": 1360
}
}
```
The disbursement line adds nothing to `taxable_base` (still 1 000 €) or `total_vat` (still 210 €). The client pays `total_to_pay` = 1 360 €, and only the 1 210 € `invoice_total` is reported to VeriFactu.
## Gotchas
- **Adding VAT to the disbursement.** Do not set `main_tax` on a `SUPLIDO` line expecting VAT. Disbursements are outside the VAT base by definition.
- **Omitting `source_invoice_reference`.** It is required. The reference ties the pass-through amount to the original invoice issued in the client's name.
- **Adding a markup.** Any amount above cost turns the whole thing into a taxable service. Use a `NORMAL` line instead.
See [Tax classification per line](/verifactu/tax-classification) for how normal lines are classified, and the [Glossary](/guides/glossary) for the Spanish/English term mapping.
> **Rules that apply here:** [TAX-008 · Disbursements go as SUPLIDO lines, without tax](/rules/taxes#tax-008)
## Related
- [What AEAT receives](/verifactu/what-aeat-receives) — why disbursements are not sent
- [Tax classification per line](/verifactu/tax-classification) — classifying the taxed lines
- [Amounts and rounding](/guides/amounts-and-rounding) — how totals are computed
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# International customers
Every cross-border scenario — intra-EU B2B, B2C with OSS, exports, services to non-EU — with the exact request shape BeeL. accepts.
International invoices need the right combination of `exemption_reason`, `main_tax.regime_key`, and `alternative_id`. The wrong combination is almost always rejected by AEAT — and the right one is rarely obvious. This page walks every common scenario.
## Identifying foreign customers
You can't use `recipient.nif` for non-Spanish customers — that field is reserved for Spanish NIFs. Use `alternative_id` instead:
```json
{
"recipient": {
"legal_name": "Tech Europe GmbH",
"alternative_id": {
"type": "NIF_IVA",
"number": "DE123456789",
"country_code": "DE"
}
}
}
```
| `alternative_id.type` | Code | Meaning | Allowed countries |
|---|---|---|---|
| **`NIF_IVA`** | `02` | EU VAT number (intra-community, VIES) | EU Member States other than ES |
| **`PASSPORT`** | `03` | Passport | Any country (including ES) |
| **`COUNTRY_ID`** | `04` | National ID in country of residence | Non-ES only |
| **`RESIDENCE_CERTIFICATE`** | `05` | Residence certificate | Non-ES only |
| **`OTHER_DOCUMENT`** | `06` | Any other identifier | Non-ES only |
| **`NOT_REGISTERED`** | `07` | Customer not registered in AEAT census | **ES only** |
The country these rules refer to is `alternative_id.country_code`, not the country of the address. It is required, except for `PASSPORT` and `NOT_REGISTERED`, which take `ES` when you omit it; any other type without it answers `422` [`ALTERNATIVE_ID_COUNTRY_REQUIRED`](/errors/ALTERNATIVE_ID_COUNTRY_REQUIRED), with `error.details` naming `alternative_id.country_code`. Breaking the rules answers `422` [`ALTERNATIVE_ID_SPAIN_INVALID_TYPE`](/errors/ALTERNATIVE_ID_SPAIN_INVALID_TYPE) (a type other than `PASSPORT` or `NOT_REGISTERED` with `ES`) or `422` [`ALTERNATIVE_ID_REQUIRES_SPAIN`](/errors/ALTERNATIVE_ID_REQUIRES_SPAIN) (`NOT_REGISTERED` with any other country).
A `NIF_IVA` is accepted only with the country of another EU Member State and in that State's EU VAT number format: the country prefix (`EL` for Greece) followed by the national number, without spaces — `DE123456789`, `FR40303265045`, `EL094014201`. Lowercase letters are accepted and stored in uppercase. A country outside the EU answers `422` [`ALTERNATIVE_ID_VAT_REQUIRES_EU_COUNTRY`](/errors/ALTERNATIVE_ID_VAT_REQUIRES_EU_COUNTRY); a number without that structure, `422` [`ALTERNATIVE_ID_VAT_INVALID_FORMAT`](/errors/ALTERNATIVE_ID_VAT_INVALID_FORMAT). Both are checked when you save the customer or send the invoice, and again when you issue, before a number is used. See [CNT-021 · A recipient without a Spanish NIF is identified by an alternative id](/rules/contents#cnt-021).
The numeric codes (`"02"`, `"03"`, …, `"07"`) are also accepted for backward compatibility but are deprecated. Prefer the descriptive names.
**Well-formed is not the same as registered.** BeeL. checks the structure of a `NIF_IVA`, not whether it is active in **VIES**. A number that has the right format but is not in the VIES census is refused by VeriFactu after the invoice is issued, and the invoice ends up `REJECTED`. Check the number in VIES before you invoice; if it is not there, identify the customer with `COUNTRY_ID` or `OTHER_DOCUMENT` and treat the operation as B2C.
**Give a foreign address its `country_code`.** `address.country_code` (ISO 3166-1 alpha-2)
is the field that decides the country of the address — not `alternative_id.country_code`.
`country` is accepted too, but only as a real ISO code (`"BE"`) or the official name of the
country in Spanish, English or Catalan (`"Bélgica"`, `"Belgium"`); anything else, such as
`"UK"`, is rejected with `422` [`COUNTRY_CODE_REQUIRED`](/errors/COUNTRY_CODE_REQUIRED) (the code is `GB`), and a
`country` that names a different country than `country_code` with
`422` [`COUNTRY_CODE_MISMATCH`](/errors/COUNTRY_CODE_MISMATCH). With neither field the address is Spanish: a
Belgian postal code such as `1000` is then rejected with
`422` [`POSTAL_CODE_INVALID_ES`](/errors/POSTAL_CODE_INVALID_ES). Whatever you send, the response returns the
code and the Spanish name derived from it (`"country": "Bélgica"`, `"country_code": "BE"`).
> **Rules that apply here:** [CNT-005 · A full invoice identifies the recipient by NIF](/rules/contents#cnt-005)
## B2B intra-EU — goods (E5)
EU-to-EU sales of goods between two businesses with valid VIES VAT-IDs are **exempt under Art. 25 LIVA**. The buyer self-assesses IVA in their country.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Acme NV",
"alternative_id": { "type": "NIF_IVA", "number": "BE0404621642", "country_code": "BE" },
"address": { "street": "Hauptstraße", "number": "456", "postal_code": "1000", "city": "Brussels", "province": "Brussels", "country": "Bélgica", "country_code": "BE" }
},
"lines": [
{
"description": "Hardware supply",
"quantity": 1,
"unit": "unit",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "EXENTA_ART_25"
}
]
}'
```
> **Rules that apply here:** [TAX-004 · Intra-EU supplies of goods are exempt only with the buyer's EU VAT number](/rules/taxes#tax-004)
## B2B intra-EU — services (N2)
Services to an EU business follow the **general localisation rule** (Art. 69 LIVA): they're deemed supplied where the customer is, so the operation is **not subject** to Spanish IVA. Use `NO_SUJETA_LOCALIZACION` (N2), not `EXENTA_ART_25`.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Acme NV",
"alternative_id": { "type": "NIF_IVA", "number": "BE0404621642", "country_code": "BE" },
"address": { "street": "Hauptstraße", "number": "456", "postal_code": "1000", "city": "Brussels", "province": "Brussels", "country": "Bélgica", "country_code": "BE" }
},
"lines": [
{
"description": "B2B consulting service",
"quantity": 1,
"unit": "service",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_LOCALIZACION"
}
]
}'
```
> **Rules that apply here:** [TAX-006 · Services to a business abroad are not subject to Spanish VAT](/rules/taxes#tax-006)
## B2C intra-EU — below the OSS threshold
When you sell goods or services to an EU consumer and your **total annual cross-border B2C sales are under 10,000 €**, the operation tributes **in origin**. The invoice carries Spanish IVA just like a domestic B2C sale.
Identify the consumer with `PASSPORT`, `COUNTRY_ID`, or `OTHER_DOCUMENT`. Omit `exemption_reason` (default S1).
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Hans Müller",
"alternative_id": { "type": "PASSPORT", "number": "F8624KW3J6", "country_code": "DE" },
"address": { "street": "Müllerstraße", "number": "47", "postal_code": "80469", "city": "München", "province": "Bayern", "country": "Alemania", "country_code": "DE" }
},
"lines": [
{
"description": "Digital subscription",
"quantity": 1,
"unit": "month",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
]
}'
```
## B2C intra-EU — above the OSS threshold
Above 10,000 €/year aggregated cross-border B2C sales (previous or current year), the **destination country's IVA** applies; the **OSS (One-Stop Shop)** regime is the optional way to declare it from Spain instead of registering in each country. From AEAT's perspective the operation is **not subject** to Spanish IVA, and `main_tax.regime_key: "17"` alone is what classifies it as N2. Keep the destination rate on the line (19 % for Germany below) and set **no `exemption_reason`** — adding one forces the rate to 0 % and drops the destination IVA from the breakdown.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Hans Müller",
"alternative_id": { "type": "PASSPORT", "number": "F8624KW3J6", "country_code": "DE" },
"address": { "street": "Müllerstraße", "number": "47", "postal_code": "80469", "city": "München", "province": "Bayern", "country": "Alemania", "country_code": "DE" }
},
"lines": [
{
"description": "Digital subscription — OSS",
"quantity": 1,
"unit": "month",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 19, "regime_key": "17" }
}
]
}'
```
**Whether sales are above the 10,000 € threshold, and whether to declare them through OSS, are the issuer's to decide.** Once you've registered for OSS, set `regime_key: "17"` on every cross-border B2C line.
> **Rules that apply here:** [TAX-007 · Sales declared through OSS use regime key 17](/rules/taxes#tax-007)
## Exports of goods (non-EU)
Goods leaving the EU customs territory are **exempt under Art. 21 LIVA** regardless of who the buyer is. Pair `EXENTA_ART_21` with `regime_key: "02"`.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Acme Inc.",
"alternative_id": { "type": "PASSPORT", "number": "M76543210", "country_code": "US" },
"address": { "street": "5th Avenue", "number": "725", "postal_code": "10022", "city": "New York", "province": "NY", "country": "Estados Unidos", "country_code": "US" }
},
"lines": [
{
"description": "Export of goods",
"quantity": 1,
"unit": "unit",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "02" },
"exemption_reason": "EXENTA_ART_21"
}
]
}'
```
## Services to non-EU customers
By the general localisation rule, services to non-EU customers are **not subject** to Spanish IVA. Use `NO_SUJETA_LOCALIZACION`. The customer can be B2B or B2C — same classification.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Acme Inc.",
"alternative_id": { "type": "PASSPORT", "number": "M76543210", "country_code": "US" },
"address": { "street": "5th Avenue", "number": "725", "postal_code": "10022", "city": "New York", "province": "NY", "country": "Estados Unidos", "country_code": "US" }
},
"lines": [
{
"description": "Professional services to US client",
"quantity": 1,
"unit": "project",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_LOCALIZACION"
}
]
}'
```
> **Rules that apply here:** [TAX-006 · Services to a business abroad are not subject to Spanish VAT](/rules/taxes#tax-006)
## Cheat sheet (international scenarios only)
This page is about **cross-border** scenarios. ISP (`ISP_ART_84_2_*`, S2) is a **domestic Spanish** mechanism for reverse charge on specific operations (construction, gold, etc.) — it does **not** apply to intra-EU or non-EU buyers. For ISP, see [Tax classification > ISP](/verifactu/tax-classification#inversi%C3%B3n-del-sujeto-pasivo-s2).
| Customer | Selling | `exemption_reason` | `main_tax.regime_key` | `alternative_id.type` |
|---|---|---|---|---|
| EU B2B (VIES) | **Goods** | `EXENTA_ART_25` (E5) | `01` | `NIF_IVA` |
| EU B2B (VIES) | **Services** | `NO_SUJETA_LOCALIZACION` (N2) | `01` | `NIF_IVA` |
| EU B2C (under OSS) | Anything | *omit* (S1) | `01` | `PASSPORT` / `COUNTRY_ID` / `OTHER_DOCUMENT` |
| EU B2C (OSS) | Anything | — (regime `17` alone → N2) | `17` | `PASSPORT` / `COUNTRY_ID` / `OTHER_DOCUMENT` |
| Non-EU | Goods | `EXENTA_ART_21` (E2) | `02` | `PASSPORT` / `COUNTRY_ID` / `OTHER_DOCUMENT` |
| Non-EU | Services | `NO_SUJETA_LOCALIZACION` (N2) | `01` | `PASSPORT` / `COUNTRY_ID` / `OTHER_DOCUMENT` |
**Common confusion**: intra-EU B2B services are `NO_SUJETA_LOCALIZACION` (N2, Art. 69 LIVA), **never** ISP/S2 (Art. 84 LIVA). ISP is reserved for domestic Spanish operations — never for cross-border.
## Related
- [Tax classification](/verifactu/tax-classification) — full `exemption_reason` ↔ AEAT mapping
- [Regime keys](/verifactu/regime-keys) — `02`, `17`, and the rest
- [NIF Validation API](/nif-validation/validateNif) — how BeeL. verifies tax IDs against AEAT
- [Create an invoice](/invoices/createCompanyInvoice) — full request schema
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Territorial taxes (IVA, IGIC, IPSI)
When to use IVA, IGIC (Canary Islands), or IPSI (Ceuta & Melilla) — and which `main_tax.type` value to send.
Spain has three territorial IVA-equivalent taxes. The right one depends on **where the operation is localised**, not on the issuer's address. Most lines run on IVA; switch to IGIC or IPSI when the operation happens in those territories.
This page is the canonical reference for `main_tax.type` values and the rate menus per tax.
## The three taxes
| `main_tax.type` | Tax | Territory | AEAT `impuesto` code |
|---|---|---|---|
| **`IVA`** | Value added tax (*Impuesto sobre el Valor Añadido*) | Peninsula + Balearic Islands | `01` |
| **`IPSI`** | Production, services and import tax (*Impuesto sobre la Producción, Servicios e Importación*) | Ceuta & Melilla | `02` |
| **`IGIC`** | Canary Islands general indirect tax (*Impuesto General Indirecto Canario*) | Canary Islands | `03` |
| **`OTHER`** | Other taxes | Rare — reserved for non-Spanish taxes carried on the invoice | `05` |
`IVA` is what almost every line uses, but it is not implicit: `main_tax` requires both `type` and `percentage`. Only `regime_key` may be omitted (it falls back to `"01"`).
## How you set it in the API
`main_tax.type`, `main_tax.percentage`, and `main_tax.regime_key` are sibling fields on each line:
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Importaciones Atlántico SL",
"nif": "B12345674",
"address": {
"street": "Avenida Marítima",
"number": "200",
"postal_code": "35001",
"city": "Las Palmas de Gran Canaria",
"province": "Las Palmas",
"country": "España"
}
},
"lines": [
{
"description": "Industrial air conditioning systems",
"quantity": 3,
"unit": "unit",
"unit_price": 3500,
"discount_percentage": 0,
"main_tax": { "type": "IGIC", "percentage": 7, "regime_key": "01" }
}
]
}'
```
## Rate menus per tax
### IGIC (Canary Islands)
| `main_tax.percentage` | Use |
|---|---|
| `0` | Zero rate (essentials, intra-canary exports) |
| `3` | Reduced rate (some food, transport) |
| `5` | Special reduced rate (temporary energy/utility measures) |
| `7` | General rate |
| `9.5` | Increased rate (vehicles, mid-tier goods) |
| `15` | Special increased rate (luxury) |
| `20` | Tobacco products |
### IPSI (Ceuta & Melilla)
Rates are set per municipality and per product category. The API accepts 0.5, 1, 2, 4, 8 or 10 % — any other percentage on an `IPSI` line is rejected. Check the tax bylaw (*Ordenanza Fiscal*) of the specific city.
| `main_tax.percentage` | Typical use |
|---|---|
| `0.5` | Super-reduced (social housing, games, advertising) |
| `1` | Reduced (taxis, bars, electricity) |
| `2` | Intermediate (restaurants, hospitality) |
| `4` | Medium (renovations, real estate) |
| `8` | High (telecommunications, electronic services) |
| `10` | General (construction) |
```json
{
"lines": [
{
"description": "Servicio en Ceuta",
"quantity": 1,
"unit": "service",
"unit_price": 200,
"discount_percentage": 0,
"main_tax": { "type": "IPSI", "percentage": 4, "regime_key": "01" }
}
]
}
```
### IVA
| `main_tax.percentage` | Use |
|---|---|
| `0` | Exempt or out-of-scope (set `exemption_reason` — see [Tax classification](/verifactu/tax-classification)) |
| `4` | Super-reduced rate — basic foodstuffs, books |
| `5` | Temporary: only on operations dated 2022-07-01 to 2024-09-30 |
| `2`, `7.5` | Temporary: only on operations dated 2024-10-01 to 2024-12-31 |
| `10` | Reduced rate — hospitality, transport |
| `21` | General rate |
A temporary rate outside its period answers `422` [`VAT_RATE_NOT_ACCEPTED_ON_DATE`](/errors/VAT_RATE_NOT_ACCEPTED_ON_DATE); the date judged is the invoice's `operation_date`, or the issue date when there is none.
## Mixing taxes on the same invoice
Different lines on the same invoice can carry different taxes — for example, a service partly delivered in the peninsula (IVA) and partly in the Canary Islands (IGIC):
```json
{
"lines": [
{ "description": "Consultoría en Madrid", "quantity": 1, "unit_price": 1000, "discount_percentage": 0,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" } },
{ "description": "Consultoría en Tenerife", "quantity": 1, "unit_price": 1000, "discount_percentage": 0,
"main_tax": { "type": "IGIC", "percentage": 7, "regime_key": "01" } }
]
}
```
BeeL. groups the totals per `main_tax.type` in the response and submits each line under the correct AEAT `impuesto` code.
## When to pick which
The decision tracks the **place of supply** of the operation, not the issuer's tax address:
- Goods physically delivered or services consumed in the Canary Islands → **IGIC**
- Goods or services consumed in Ceuta or Melilla → **IPSI**
- Everything else inside Spain → **IVA**
If you're a self-employed person on the peninsula invoicing a Canary client for a service used in the Canaries, IGIC applies — not IVA. Likewise, a Canary issuer invoicing a peninsular client for a service consumed in the peninsula uses IVA.
BeeL. doesn't auto-detect the place of supply — it trusts what you set. When the customer's address is in the Canaries, IPSI territory, or another EU country, double-check the line classification before issuing.
## Mixed-territory regime (`main_tax.regime_key: "08"`)
`"08"` declares an operation subject to **another** indirect tax: IPSI or IGIC on an IVA line, IPSI or IVA on an IGIC line. It is rare, and it is **not** the general regime of IGIC — an ordinary IGIC line uses `"01"`. AEAT only accepts it on a line with `exemption_reason: NO_SUJETA_LOCALIZACION` at 0 %; otherwise the line is rejected with `422` [`REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED`](/errors/REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED). See [Regime keys](/verifactu/regime-keys).
## Related
- [Tax classification](/verifactu/tax-classification) — applies the same way regardless of IVA / IGIC / IPSI (SSoT)
- [Regime keys](/verifactu/regime-keys) — `"08"` and what AEAT requires with each key (SSoT)
- [Create an invoice](/invoices/createCompanyInvoice) — full line schema
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Auto-submit policy
Whether an issued invoice reaches AEAT is decided by the taxpayer, not by the request — what that means when you issue, and how to read the result.
Whether an invoice reaches AEAT is a fact of the **taxpayer**, resolved when the
invoice is issued. If the NIF is under the VeriFactu regime, every invoice it
issues is registered; if it is not, none is. There is nothing to set per
invoice, and no request can opt one document in or out.
BeeL. works this way because the VERI\*FACTU modality is chosen by the taxpayer,
not per document: under article 16.1 of the RD 1007/2023 it covers «todos los
registros de facturación generados». A per-invoice switch allowed a mixed
ledger — two invoices from the same NIF on the same day, one registered and one
not.
## What decides it
Two conditions, both read at issue time:
### The NIF's VeriFactu configuration is `enabled`
Read it with `GET /v1/companies/{company_id}/verifactu-configuration`. The only
writable field is `enabled`, through the matching `PUT` — everything else
(`status`, `signed`, `activated`, `nif_status`, `nif_registered_at`,
`pdf_generated`) is resolved for you and is read-only.
Turning it off stops the submission of every invoice that NIF issues from then
on: those invoices generate no billing record. It does not delete the
configuration or the registration. Before you turn it off, read
[the rules on timing](/verifactu/compliance-and-responsibilities#verifactu-modality-only):
a taxpayer that starts submitting in VERI\*FACTU stays in it at least until the
end of that calendar year (article 16.5 of the RD 1007/2023).
### The NIF is registered, and `nif_status` says so
`nif_status` is `ACTIVATED` once the tax ID is registered and ready to submit.
Its other values, and why it can lag behind `enabled` in sandbox, are in
[Enabling VeriFactu for a NIF](/verifactu/enabling-verifactu#nif_status).
A proforma is never submitted, whatever the NIF's regime: it is not an invoice
yet. It gets a fiscal record when you convert it.
## How it reads on the invoice
The invoice resource carries a nested `verifactu` object. `enabled` tells you
which side of the axis the invoice is on, and it is decided once, at issue time:
```json
{
"id": "...",
"status": "ISSUED",
"verifactu": {
"enabled": true,
"submission_status": "PENDING",
"registration_number": "094365f5-9637-40e9-851a-4e964a939b0b",
"registered_at": "2026-09-19T09:27:44Z",
"invoice_hash": "1863E77D540083E4259F4AF44322E16B962CB5D72C2F949AB2B30FA83E93FF84",
"qr_url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B27534239&numserie=S-2026-0001&fecha=19-09-2026&importe=121.00",
"qr_base64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADI...",
"skip_reason": null
}
}
```
The hash, the QR and the BeeL. `registration_number` are already there while
AEAT has not answered — they belong to the record as submitted, not to AEAT's
verdict. See [QR code and the invoice PDF](/verifactu/qr-and-pdf).
An invoice from a NIF outside the regime has `enabled: false` and no
`submission_status` at all — it is outside the axis, not failing on it:
```json
{
"id": "...",
"status": "ISSUED",
"verifactu": {
"enabled": false
}
}
```
To select those over the API, filter with `?verifactu_enabled=false`. No value
of `verifactu_status` matches them, because they have no submission to describe.
See [Submission states](/verifactu/submission-states) for what
`submission_status` does next, including `NOT_SUBMITTED` — an invoice that
*should* have reached AEAT and has no live record.
**`skip_reason` is historical.** Invoices issued while VeriFactu was a
per-invoice decision can carry a `skip_reason` (`CONNECTION_DISABLED`,
`CONFIG_MISSING`, `CONFIG_DISABLED`, `NIF_NOT_REGISTERED`) naming which switch
was off at the time. Nothing writes it any more: there is no per-invoice
omission left to explain, so on invoices issued today it is always `null`.
Keep reading it if you display it for old documents; do not build new logic on
it.
## Issuing a wave you don't want registered
There is no way to exclude individual invoices. The NIF's `enabled` flag is
all-or-nothing for that taxpayer, and it is not a tool for leaving invoices
unregistered: see [the rules on timing](/verifactu/compliance-and-responsibilities#verifactu-modality-only).
**A submission is not undoable as a batch.** If a NIF is under the regime, its
invoices are registered as they are issued. Use a sandbox NIF for test
issuance rather than disabling a live one.
## There's no public "submit now" endpoint
The public API does not expose a way to trigger a submission by hand. What to do
depends on what you are looking at:
The regime is read when you issue, so fix the configuration first and then call
`POST /invoices/{id}/issue`. If the NIF is under the regime by then, the
invoice is issued with `submission_status: PENDING`.
It was issued by a NIF outside the regime, and that cannot be changed after the
fact — the invoice is a closed fiscal document. Write to
[it@beel.es](mailto:it@beel.es) with the invoice IDs or a date range if you
believe it should have been registered.
This one *was* expected to reach AEAT. Right after issuing it can still be in
flight, so re-read the invoice first. If it persists, it will not clear on its
own — open a ticket at [it@beel.es](mailto:it@beel.es) with the invoice IDs.
> **Rules that apply here:** [REC-002 · The record goes to AEAT as soon as the invoice is issued](/rules/records#rec-002)
## Related
- [Enabling VeriFactu for a NIF](/verifactu/enabling-verifactu) — getting a NIF under the regime, step by step
- [Submission states](/verifactu/submission-states) — what happens after the submission
- [Stripe / VeriFactu submission](/stripe/verifactu-submission) — the same rule applied to Stripe-generated invoices
- [Get a NIF's VeriFactu configuration](/verifactu/getCompanyVeriFactuConfiguration) — read a NIF's configuration
- [Update a NIF's VeriFactu configuration](/verifactu/updateCompanyVeriFactuConfiguration) — flip `enabled`, the only writable field
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Submission states
Every state a VeriFactu submission can be in, what triggers transitions, and when you need to act.
A VeriFactu submission has its own lifecycle, separate from the invoice's commercial state (`DRAFT` → `ISSUED` → `PAID` …). The invoice carries a `verifactu` object on the response that holds the submission state and AEAT response — this page is the canonical map.
## Invoice status (commercial) vs VeriFactu status (fiscal)
These are two different state machines on the same invoice:
```text
COMMERCIAL (status) FISCAL (verifactu.submission_status)
───────────── ─────────────────────
DRAFT (none — verifactu.enabled = false)
↓ issue ↓ submission sent
ISSUED ── submission ──> PENDING
↓ send ↓ AEAT answers (or the record never gets there)
SENT ACCEPTED or REJECTED
↓ pay
PAID
↓ correct / void ↓ on void (cancellation record)
RECTIFIED / VOIDED VOIDED
```
Subscribe to `verifactu.status.updated` to be notified of fiscal-status changes — see [Webhook events](/webhooks/events).
## The fiscal states
The invoice response exposes a nested `verifactu` object. Its `submission_status` field carries the current state. The vocabulary has exactly five values:
| verifactu.submission_status | What it means | Terminal | What to do |
|---|---|---|---|
| **`PENDING`** | Submitted; AEAT has not answered yet. A temporary AEAT server error also stays PENDING while BeeL. retries it. | No — retried automatically | Wait — usually seconds. BeeL. keeps asking AEAT on its own. |
| **`ACCEPTED`** | Accepted by AEAT. With an error_code alongside, it was accepted with errors: registered, but AEAT flagged some data. | Yes | Nothing — unless error_code is set: then review the flagged data and, if it is wrong, issue a corrective. |
| **`VOIDED`** | A cancellation record (registro de anulación) was accepted by AEAT. | Yes | Nothing. |
| **`REJECTED`** | Not in AEAT's registry: AEAT refused it, the submission was refused before reaching AEAT, or BeeL. gave up waiting (including when the retries after a temporary AEAT error ran out). | Yes | Read error_code (AEAT's code, or null when AEAT gave none). Fix the data with a corrective (void only an invoice that should never have been issued); for a cause outside the invoice, contact BeeL. |
| **`NOT_SUBMITTED`** | Issued with VeriFactu enabled but with no live record: AEAT does not know the invoice exists. | No — but not retried | Right after issuing it can still be in flight, so re-read it. If it persists, contact BeeL. with the invoice ID. |
If there is no submission to describe yet, the `submission_status` is absent: drafts, scheduled invoices, proformas, and invoices whose `verifactu.enabled` is `false` (those are outside this axis entirely) — see [Auto-submit policy](/verifactu/auto-submit). An **issued** invoice with `verifactu.enabled: true` always reports a state: if no live record exists — the registration fell through and AEAT does not know the invoice exists — it reports `NOT_SUBMITTED`, which is transient right after issuing while the submission is still in flight.
**The list filter uses the very same vocabulary.** `GET /invoices?verifactu_status=...` accepts `PENDING`, `ACCEPTED`, `VOIDED`, `REJECTED` and `NOT_SUBMITTED` — a value read from an invoice can be fed straight back into the filter. Only invoices with VeriFactu enabled can match; to select the ones outside the axis (no VeriFactu record at all) use `?verifactu_enabled=false` instead. There is no `NO_VERIFACTU` value: sending it returns `422`.
### What moves on its own, and what does not
While a record is `PENDING`, BeeL. keeps asking AEAT for the outcome, on a backoff. A temporary AEAT server error keeps the record `PENDING`, without `error_code` or `error_message`, while BeeL. retries it; it becomes `REJECTED` only if the retries run out. Once AEAT gives a real verdict, BeeL. stops asking, and **nothing resubmits the record for you**. What to do then is in [Handling AEAT rejections](/verifactu/handling-rejections).
### Is `ACCEPTED` final?
**Yes.** Once a record reads `ACCEPTED` — with or without an `error_code` — BeeL. does not ask AEAT about it again, and nothing in BeeL. can move it back to `PENDING` or on to `REJECTED`. There is no late rejection to wait for, so there is no later `verifactu.status.updated` turning an accepted invoice into a rejected one.
What can still happen to an accepted invoice is something **you** do, and it arrives as its own record with its own status:
- you **void** it: a cancellation record is sent, and the invoice's `submission_status` becomes `VOIDED` once AEAT accepts it — see [Voiding](#voiding);
- you issue a **corrective**: the corrective has its own registration, and the original stays `ACCEPTED`.
What this means for reconciliation: you never need to re-check accepted invoices. The window worth sweeping is the one where a state can still change or still needs you — invoices `PENDING`, `REJECTED` or `NOT_SUBMITTED`. A sweep over the last few days, run daily, covers an outage of your own webhook endpoint; see [Reconcile, do not only listen](/verifactu/handling-rejections#reconcile-do-not-only-listen) and pace it with [Rate limits](/guides/rate-limits#pacing-a-polling-or-reconciliation-job).
> **Rules that apply here:** [REC-008 · Follow submission_status and fix what AEAT rejects](/rules/records#rec-008)
## Reading the AEAT response
Every submission stores the AEAT response on the invoice resource under the `verifactu` object. The relevant fields:
```json
{
"id": "...",
"invoice_number": "INV-2026/00042",
"status": "ISSUED",
"verifactu": {
"enabled": true,
"submission_status": "ACCEPTED",
"registration_number": "094365f5-9637-40e9-851a-4e964a939b0b",
"registered_at": "2026-05-18T10:24:36Z",
"invoice_hash": "1863E77D540083E4259F4AF44322E16B962CB5D72C2F949AB2B30FA83E93FF84",
"qr_url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B27534239&numserie=INV-2026%2F00042&fecha=18-05-2026&importe=121.00",
"qr_base64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADI...",
"skip_reason": null
}
}
```
The block is part of the invoice resource, so
[its fields are documented with it](/invoices/getCompanyInvoice). Several of them are routinely
misread:
- **`enabled: false` is not a failure.** It means VeriFactu never applied to this
invoice: it was issued by a NIF outside the VeriFactu regime. There is no
`submission_status` to inspect in that case.
- **`registration_number` is a BeeL. identifier**, not a code issued by AEAT. It
identifies the submitted record, and stays `null` when the record never got that far.
- **`registered_at` is when the record was submitted**, not when AEAT accepted it.
- **`invoice_hash`, `qr_url` and `qr_base64` are there from `PENDING`**, and their
presence does not mean AEAT accepted anything — see
[QR code and the invoice PDF](/verifactu/qr-and-pdf#the-qr-exists-from-pending).
- **`invoice_hash` fingerprints *this* invoice, not the chain.** It is the hash of this
record as submitted. The link to the previous record is kept on the submission
record and is **not published on the invoice resource**, so chain integrity is not
something you can verify from the API.
- **`error_code` / `error_message` are single values, not arrays**, and they are also
populated on submissions AEAT *accepted with errors* — their presence does not by
itself mean the invoice was rejected. Read `submission_status` first. `error_code`
is AEAT's code when AEAT gave one; `error_message` is **human-readable text only** —
its wording and language are not stable, so show it, log it, never parse it. When
there is nothing to report, both are absent from the block rather than `null`.
- **Right after issuing, the block is almost empty.** The issue response, and a read in
the next few seconds, carry only `enabled` and `submission_status: NOT_SUBMITTED`. The
hash, QR, `registration_number` and `registered_at` appear together with `PENDING`.
## What triggers each transition
| Event | Effect on `verifactu.submission_status` |
|---|---|
| You issue an invoice from a NIF under the regime | `NOT_SUBMITTED` for a few seconds, while the record is built → `PENDING` |
| AEAT accepts the record | `PENDING` → `ACCEPTED` (with an `error_code` if accepted with errors) |
| AEAT rejects the record | `PENDING` → `REJECTED`, with `error_code` and `error_message` |
| AEAT answers with a temporary server-side error | Stays `PENDING`, with no `error_code` or `error_message`, while BeeL. retries; → `REJECTED` only if the retries run out |
| The submission is refused before it reaches AEAT | `NOT_SUBMITTED` → `REJECTED`, no `error_code`, an explanatory `error_message` |
| AEAT already holds a different invoice with the same number and issue date | `NOT_SUBMITTED` → `REJECTED`, no `error_code`, an `error_message` that says the number is taken ([what to do](/verifactu/handling-rejections#the-number-is-already-taken-at-aeat)) |
| BeeL. stops waiting for an answer that never comes | `PENDING` → `REJECTED`; `error_message` says the submission was abandoned |
| You issue a corrective on an accepted invoice | New corrective enters its own `PENDING` cycle; the original stays `ACCEPTED` (the commercial `status` flips to `RECTIFIED` / `VOIDED`) |
| You void an invoice whose registration AEAT accepted | A cancellation record is sent; see [Voiding](#voiding) below |
The `verifactu` block summarises the registration, so after a void its hash, number and date are still the registration record's. Each record — registration and cancellation — carries its own `submission_status` in [List the VeriFactu records of an invoice](/invoices/listCompanyInvoiceVerifactuRecords); see [Did AEAT accept the cancellation?](/verifactu/cancel-and-fix#did-aeat-accept-the-cancellation).
**`REJECTED` is not one situation.** It means the invoice is not in AEAT's registry right now — because AEAT refused it, because the submission was refused before reaching AEAT, or because BeeL. gave up waiting. An `error_code` means AEAT gave a coded answer; no code means it did not — either the record never reached AEAT, or it was abandoned while waiting. How to tell them apart and what to do with each is in [Handling AEAT rejections](/verifactu/handling-rejections).
### Voiding
The `verifactu` block summarises the registration, so after a void its hash, number and date are still the registration record's. What `submission_status` shows depends on the cancellation:
| Situation | `status` | `verifactu.submission_status` |
|---|---|---|
| Cancellation sent, AEAT has not answered yet | `VOIDED` | Unchanged — still the registration's (typically `ACCEPTED`). It never goes back to `PENDING` |
| AEAT accepted the cancellation | `VOIDED` | `VOIDED` |
| AEAT rejected the cancellation | `VOIDED` | `REJECTED`, with the cancellation's `error_code` / `error_message` |
| You voided while the registration was still `PENDING` | `VOIDED` | The registration's status. The cancellation is sent only after AEAT accepts the registration |
| The registration never reached AEAT, or AEAT rejected it | `VOIDED` | Unchanged (`REJECTED`), or absent if the invoice never had a record (`NOT_SUBMITTED` before the void). **Nothing is sent to AEAT** — there is no record there to cancel |
The invoice's own `status` flips to `VOIDED` immediately in every case; see [Did AEAT accept the cancellation?](/verifactu/cancel-and-fix#did-aeat-accept-the-cancellation).
## Recovering from a rejection
There is no public endpoint to retry a submission. Wait on a temporary AEAT error, fix the invoice's own data with a new fiscal document, and ask support for anything outside the invoice — each case, per AEAT code, in [Handling AEAT rejections](/verifactu/handling-rejections).
## AEAT error codes
`error_code` carries the code AEAT returned, unchanged. The codes you are most likely to meet — grouped by what you should do about each — are listed in [Handling AEAT rejections](/verifactu/handling-rejections#aeat-codes-by-what-to-do). The complete list is in the [AEAT VeriFactu documentation](https://sede.agenciatributaria.gob.es/Sede/iva/sistemas-emision-facturas/sistemas-informaticos-facturacion-sif-veri-factu.html).
## Webhooks for fiscal status changes
Subscribe to `verifactu.status.updated` to be notified each time the public status changes:
```json
{
"type": "verifactu.status.updated",
"data": {
"invoice_id": "...",
"invoice_number": "INV-2026/00042",
"verifactu_registration_id": "...",
"previous_status": "PENDING",
"new_status": "ACCEPTED",
"qr_url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B27534239&numserie=INV-2026%2F00042&fecha=18-05-2026&importe=121.00",
"qr_base64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADI...",
"invoice_hash": "1863E77D540083E4259F4AF44322E16B962CB5D72C2F949AB2B30FA83E93FF84"
}
}
```
**Four of the five values travel by webhook.** `previous_status` and `new_status` carry `PENDING`, `ACCEPTED`, `VOIDED` or `REJECTED`. `NOT_SUBMITTED` never does: it describes an invoice with no record at all, so there is no record whose status could change. The first notification for a record has no `previous_status`.
**A webhook is sent when the public status changes, and only then.** A submission refused before it reached AEAT, or one BeeL. stopped waiting for, arrives as `REJECTED` like any other rejection. A temporary AEAT error keeps the record `PENDING`, so it sends nothing. Your endpoint can still miss deliveries, so reconcile periodically by listing with `?verifactu_status=REJECTED` and `?verifactu_status=NOT_SUBMITTED`; see [Reconciliation](/verifactu/handling-rejections#reconcile-do-not-only-listen).
See [Webhook events](/webhook-events/onVeriFactuStatusUpdated) for the full payload.
## Related
- [Auto-submit policy](/verifactu/auto-submit) — when BeeL. submits in the first place
- [Handling AEAT rejections](/verifactu/handling-rejections) — reading `REJECTED`, per-code actions, reconciliation
- [QR code and the invoice PDF](/verifactu/qr-and-pdf) — when the QR and the final PDF exist
- [What AEAT receives](/verifactu/what-aeat-receives) — why the registered total can differ from `total`
- [Cancel vs amend](/verifactu/cancel-and-fix) — what to do when AEAT rejects
- [Corrective invoices](/verifactu/corrective-invoices) — issuing R1–R5
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Handling AEAT rejections
How to read a REJECTED invoice (and an accepted one that carries an error code), what to do for each family of AEAT codes, and how to make sure you never miss one.
A registered invoice is immutable, so a rejection is resolved with a new fiscal document or, when the cause lies outside the invoice, by contacting BeeL. This page tells you which, from what the invoice says.
For the full state machine, see [Submission states](/verifactu/submission-states). For choosing between a corrective and a void, see [Cancel vs amend](/verifactu/cancel-and-fix).
## Read the invoice first
Three fields on the invoice's `verifactu` block tell you where you are: `submission_status`, `error_code` and `error_message`.
| `submission_status` | `error_code` | What happened | Go to |
|---|---|---|---|
| `REJECTED` | set | AEAT answered with a coded rejection | [AEAT codes by what to do](#aeat-codes-by-what-to-do) |
| `REJECTED` | absent | AEAT gave no coded answer: the submission was refused **before** it reached AEAT, BeeL. stopped waiting for an answer that never came, or AEAT already holds another invoice with this number. `error_message` says which | [The number is already taken](#the-number-is-already-taken-at-aeat), otherwise [When to contact support](#when-to-contact-support) |
| `ACCEPTED` | set | **Accepted with errors**: the invoice is registered, but AEAT flagged some of its data | [Accepted with errors](#accepted-with-errors) |
| `ACCEPTED` | absent | Registered cleanly | Nothing to do |
| `NOT_SUBMITTED` | — | The invoice should have gone to AEAT and has no record at all | [When to contact support](#when-to-contact-support) |
**`error_message` is for humans.** Show it, log it, attach it to a support request — never parse it or branch on its wording. Its language and phrasing are not stable, and it is not always AEAT's text: when AEAT gave no answer, BeeL. writes its own explanation there. Branch on `submission_status` and `error_code` only.
To see each record separately — the registration and, if you voided, the cancellation — use [List the VeriFactu records of an invoice](/invoices/listCompanyInvoiceVerifactuRecords). Each record carries its own `submission_status` and, read the same way as on the invoice, its own `error_message` and `error_code`.
## A temporary AEAT error stays `PENDING`
When AEAT answers with a temporary server-side error, the invoice is not rejected: it stays `PENDING`, with no `error_code` or `error_message`, while BeeL. retries it, and no `verifactu.status.updated` is sent. It moves to `ACCEPTED` or `REJECTED` when AEAT gives a real answer, or to `REJECTED` if the retries run out:
```text
PENDING → (AEAT server-side error; BeeL. retries, still PENDING) → ACCEPTED or REJECTED
```
So a `REJECTED` invoice does not move on its own: it is safe to act on it.
## Accepted with errors
`ACCEPTED` with an `error_code` means AEAT **registered** the invoice but reported a problem with some of its data. The invoice is valid and in AEAT's registry; you do not need to resubmit it, and it will not change on its own.
A typical cause is the recipient: AEAT could not match the recipient's NIF and name against its census. That can happen when the customer's NIF could not be checked against the census at the time the customer was saved — BeeL. lets the customer through rather than block you, and AEAT flags it later.
What to do:
1. Read `error_message` to see what AEAT flagged.
2. Check that data — usually the recipient's NIF and legal name — against the customer's real details.
3. If it was right, nothing else is needed. If it was wrong, fix it in the customer's data so the following invoices carry it right. A corrective does not change it on the invoice already recorded: it keeps the recipient of the invoice it corrects ([COR-017 · A corrective keeps the recipient, except to correct the recipient's data](/rules/corrective#cor-017)). See [REC-009 · Check the data AEAT accepted with errors](/rules/records#rec-009).
## AEAT codes by what to do
`error_code` carries AEAT's code unchanged. These are the codes you are most likely to meet, grouped by who has to act. AEAT has many more; the complete list is in the [AEAT VeriFactu documentation](https://sede.agenciatributaria.gob.es/Sede/iva/sistemas-emision-facturas/sistemas-informaticos-facturacion-sif-veri-factu.html).
### Recipient data — fix the invoice
| Code | Meaning |
|---|---|
| `1109` / `1110` | A NIF on the invoice is not in the AEAT census |
| `1123` | The NIF format is incorrect |
| `1193` | The recipient's NIF is not identified, or equals the issuer's |
| `1149` | Under regime key `14`, the recipient's NIF must be in the census and start with P, Q, S or V |
**What to do:** check the recipient against the census with the [NIF validation API](/nif-validation/validateNif) and fix the customer record. A corrective is not the way: the rejected invoice is not in AEAT's registry, and correcting it answers [`CORRECTIVE_ORIGINAL_RECORD_REJECTED`](/errors/CORRECTIVE_ORIGINAL_RECORD_REJECTED). Void it — that sends nothing to AEAT; if it was already sent or paid, the void needs `issued_in_error: true` — and issue a new invoice with the corrected recipient.
> **Rules that apply here:** [CNT-020 · A Spanish recipient's NIF is in the AEAT census](/rules/contents#cnt-020)
### The issuer's own setup — fix the NIF, then contact support
| Code | Meaning |
|---|---|
| `4104` | The issuing NIF is not identified in the AEAT census |
| `4107` | The issuer's NIF is not identified in the AEAT census |
| `4109` | The issuer's NIF format is incorrect |
| `4112` | The submission is not authorised for this NIF |
**What to do:** the invoice's data is fine; the taxpayer's situation with AEAT is not. Resolve it — register the NIF with AEAT, or [sign the representation](/verifactu/enabling-verifactu) — and then contact BeeL. with the invoice. Nothing in the invoice needs correcting, so do not issue a corrective for these.
### Suspended access — the taxpayer must talk to AEAT
| Code | Meaning |
|---|---|
| `4141` | AEAT has suspended submission access |
**What to do:** only AEAT can lift it. The taxpayer must contact AEAT; once it is lifted, contact BeeL. with the affected invoices.
### Records — usually nothing to fix in the data
| Code | Meaning | What to do |
|---|---|---|
| `3000` | Duplicate: AEAT already has a record for this invoice | Do **not** reissue — the invoice may already be registered. Contact support to reconcile it. A number AEAT holds for a **different** invoice does not end here: see [The number is already taken at AEAT](#the-number-is-already-taken-at-aeat) |
| `3001` | The record has already been cancelled | Seen on a cancellation: AEAT already considers the invoice voided. Nothing to redo |
| `3002` | The record to modify or cancel does not exist in AEAT | Seen on a cancellation: there is nothing registered to cancel. Contact support if you expected there to be |
| `3003` | No permission to update this record | Contact support |
### Invoice content — fix the invoice
| Code | Meaning |
|---|---|
| `1100` | A field has an incorrect value or type |
| `1106` | The invoice type is not allowed |
| `1108` | The issuer does not match the obligated party |
| `1124` | The tax rate is not one of the allowed values |
| `1112` / `1133` / `1145` / `1152` | The issue date is in the future, too old, badly formatted, or earlier than the start of VeriFactu |
**What to do:** issue a corrective with the right data. BeeL. validates most of these before submitting, so they are rare; if you cannot see what is wrong, contact support with the invoice ID.
### Record format — contact support
| Code | Meaning |
|---|---|
| `4102` / `4103` / `4119` | The record does not match AEAT's schema, could not be read, or contains characters in the wrong encoding |
**What to do:** these point at how the record was built, not at your data. Contact support with the invoice ID.
### Temporary AEAT errors — wait
| Code | Meaning |
|---|---|
| `4108` / `4111` / `4128` | Temporary AEAT technical error |
| `3500` / `3501` | Temporary AEAT database error |
| `4134` / `4139` | The AEAT service is not available |
**What to do:** nothing. While BeeL. retries, the invoice stays `PENDING` and these codes are not published on it. If the retries run out, the invoice becomes `REJECTED` without an AEAT verdict: contact support.
## Reconcile, do not only listen
`verifactu.status.updated` tells you each time the public status of a record changes, including a submission refused before it reached AEAT or one BeeL. stopped waiting for; `NOT_SUBMITTED` never produces one, because there is no record to change. Treat the webhook as the fast path and a periodic sweep as the safety net:
```bash
# Invoices AEAT does not have
curl "https://app.beel.es/api/v1/companies/{company_id}/invoices?verifactu_status=REJECTED" \
-H "Authorization: Bearer $BEEL_API_KEY"
# Invoices that should have gone to AEAT and have no record
curl "https://app.beel.es/api/v1/companies/{company_id}/invoices?verifactu_status=NOT_SUBMITTED" \
-H "Authorization: Bearer $BEEL_API_KEY"
```
Run it on a schedule (daily is plenty for most volumes), compare with what you already know, and handle anything new as above. There is no need to sweep `ACCEPTED` invoices: an accepted record never turns into a rejected one — see [Is `ACCEPTED` final?](/verifactu/submission-states#is-accepted-final). Keep the job within your [rate limits](/guides/rate-limits#pacing-a-polling-or-reconciliation-job).
`NOT_SUBMITTED` right after issuing is normal while the submission is in flight; only act on invoices that stay there.
> **Rules that apply here:** [REC-008 · Follow submission_status and fix what AEAT rejects](/rules/records#rec-008)
## The number is already taken at AEAT
AEAT identifies an invoice by the issuer's NIF, its number and its issue date. If it already
holds a record with this invoice's number and date that is **not this invoice** — typically one
issued with the software you used before, under the same NIF — BeeL. does not take that record
as this invoice's. The invoice ends `REJECTED`, with no `error_code` and an `error_message` that
says the number is taken, and it is not registered. Retrying does not help: it meets the same
record. See [NUM-002 · An issued number is never reused, even when the invoice is voided](/rules/numbering#num-002).
**What to do:** issue the invoice again with a number AEAT does not hold yet, from another series
or a series that [continues the sequence of your previous software](/guides/series-and-numbering#continuing-a-sequence-from-another-system).
The rejected invoice is not in AEAT's registry, so voiding it sends nothing to AEAT — see
[Cancel vs amend](/verifactu/cancel-and-fix#decision-matrix).
## Fixing the invoice
When the rejection is about the invoice's data, the fix is always a **new fiscal document**: a corrective or, for an invoice that should never have been issued, a void. A registered invoice is never edited. Which one fits each situation, including an invoice AEAT never registered, is in the [decision matrix of Cancel vs amend](/verifactu/cancel-and-fix#decision-matrix).
## When to contact support
Write to [it@beel.es](mailto:it@beel.es) with the invoice ID(s) and the `error_code` / `error_message` you see when:
- the invoice is `REJECTED` with no `error_code` — the record never reached AEAT, or BeeL. gave up waiting — unless `error_message` says [the number is already taken](#the-number-is-already-taken-at-aeat);
- the invoice stays `NOT_SUBMITTED`;
- the cause was **outside** the invoice (issuer not in the census, representation not signed, suspended access) and you have fixed it;
- the code is a duplicate (`3000`), a record-format code, or you cannot tell what is wrong.
When a registration that never reached AEAT — one refused before it got there, or one BeeL. stopped waiting for — is sent again, the new attempt **replaces** the undelivered one. The invoice keeps its number. In [its VeriFactu records](/invoices/listCompanyInvoiceVerifactuRecords), a registration refused before reaching AEAT is listed as `REJECTED` until the invoice is sent again; the new attempt then takes its place, since the undelivered one was never a record at AEAT.
> **Rules that apply here:** [REC-010 · Subsanación is only for errors that need no corrective](/rules/records#rec-010) · [COR-022 · An invoice whose record AEAT rejected is fixed before it is corrected](/rules/corrective#cor-022)
## Related
- [Submission states](/verifactu/submission-states) — every state and transition
- [Cancel vs amend](/verifactu/cancel-and-fix) — corrective vs void vs *subsanación*
- [Corrective invoices](/verifactu/corrective-invoices) — R1–R5 with payloads
- [Enabling VeriFactu for a NIF](/verifactu/enabling-verifactu) — census, representation and blockers
- [Webhook events](/webhook-events/onVeriFactuStatusUpdated) — the `verifactu.status.updated` payload
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Cancel vs amend
Decide between cancelling an invoice (void) and issuing a corrective invoice, two operations with different fiscal weight.
The AEAT VeriFactu spec exposes three distinct fix operations: **void** (*anulación*), ***subsanación*** (replacing a record with a corrected one) and **corrective invoice** (*factura rectificativa*). They look similar but mean very different things. Pick the wrong one and you either misreport or burn an invoice number.
## The 30-second decision
Void only what should never have been issued; correct everything else. Every situation, one row each, is in the table [Void or correct](/rules/void#void-or-correct).
| Operation | Use when | Fiscal weight |
|---|---|---|
| **Void** | [VOI-001 · Void only an invoice that should never have been issued](/rules/void#voi-001) | A cancellation record is sent to AEAT, linked to the original |
| **Corrective invoice** | [COR-001 · Wrong data on an issued invoice is fixed with a corrective](/rules/corrective#cor-001) | A new invoice, with its own record, corrects the original |
| ***Subsanación*** | [REC-010 · Subsanación is only for errors that need no corrective](/rules/records#rec-010) | The record is replaced by a corrected one; no new invoice |
## Void (*anulación*) [#anulación-void]
To void an invoice, call [`POST …/invoices/{invoice_id}/void`](/invoices/voidCompanyInvoice) with a `reason`: the invoice becomes `VOIDED` at once. A void is only for an invoice issued by mistake — the operation never took place, it was a test, or it is an accidental duplicate. Once the invoice has been sent or paid, confirm it with `issued_in_error: true`. The request rules and which invoices can be voided are in [Invoice lifecycle](/guides/invoice-lifecycle#voiding-and-correcting); this section covers what reaches AEAT.
What happens:
1. The invoice's commercial status moves to `VOIDED`.
2. BeeL. submits a **cancellation record** (*registro de anulación*) to AEAT, separate from the original registration record (*registro de alta*).
3. AEAT processes the cancellation and BeeL. updates the invoice's `verifactu.submission_status` to reflect the cancellation response. The cancellation is also a record of its own — see [Did AEAT accept the cancellation?](#did-aeat-accept-the-cancellation) below.
What you give up:
- The number stays used: a voided invoice keeps it, and the next invoice takes a new one (see [Series and numbering](/guides/series-and-numbering#the-number-is-assigned-when-you-issue)).
- The original registration record stays in AEAT's records, with the cancellation linked to it.
### Did AEAT accept the cancellation?
The invoice's `verifactu` block summarises the **registration**: once you void, its `submission_status` moves with the cancellation, but `invoice_hash`, `registration_number` and `registered_at` keep describing the registration record. To see each record with its own status, list the invoice's VeriFactu records with [List the VeriFactu records of an invoice](/invoices/listCompanyInvoiceVerifactuRecords):
```bash
curl "https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/verifactu-records" \
-H "Authorization: Bearer $BEEL_API_KEY"
```
```json
{
"success": true,
"data": {
"records": [
{
"id": "2b1605c5-cdc4-45c1-b007-5261342947ec",
"operation": "REGISTRATION",
"submission_status": "ACCEPTED",
"invoice_hash": "1863E77D540083E4259F4AF44322E16B962CB5D72C2F949AB2B30FA83E93FF84",
"registration_number": "094365f5-9637-40e9-851a-4e964a939b0b",
"registered_at": "2026-09-19T09:27:44.490799Z",
"qr_url": "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=B27534239&numserie=S-2026-0001&fecha=19-09-2026&importe=121.00"
},
{
"id": "582ed3b9-51e1-48a0-b725-137ae5b2efb1",
"operation": "VOID",
"submission_status": "PENDING",
"registration_number": "e2e5f3db-999a-4c9f-afd5-7a7a059ca384",
"registered_at": "2026-09-19T09:28:15.523675Z"
}
]
},
"meta": { "timestamp": "2026-09-19T09:28:16Z", "request_id": "ac4654e8df3bb20e339c4086fadc6725" }
}
```
Records come ordered by `registered_at`, the registration first. The `VOID` record answers the question: `PENDING` while AEAT has not replied, `VOIDED` once it accepts the cancellation, `REJECTED` if it refuses it, with `error_message` giving the reason and `error_code` carrying AEAT's code when AEAT gave one. `invoice_hash` and `qr_url` only travel on `REGISTRATION` records. `registration_number` is BeeL.'s identifier (a UUID) for the record, not an AEAT code, and `registered_at` is when the record was created. Each record's `id` is the `verifactu_registration_id` you receive in `verifactu.status.updated`, so a webhook can be matched to the exact record it reports on.
You don't have to poll for it. `verifactu.status.updated` is sent for the cancellation record too, and its `operation` says which record changed: `REGISTRATION` for the invoice's registration, `VOID` for its cancellation. A `VOID` event with `new_status: VOIDED` is AEAT accepting the cancellation; with `REJECTED`, `error_code` and `error_message` say why.
```json
{
"type": "verifactu.status.updated",
"data": {
"invoice_id": "550e8400-e29b-41d4-a716-446655440000",
"invoice_number": "2026-0042",
"verifactu_registration_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"operation": "VOID",
"previous_status": "PENDING",
"new_status": "VOIDED"
}
}
```
**Voiding while the registration is still `PENDING`.** You can void an invoice before AEAT has answered the registration record. The cancellation is sent once the registration is accepted, so until then the list shows only the `REGISTRATION` record; the `VOID` record appears when the cancellation goes out, and reaches `VOIDED` when AEAT accepts it. In sandbox this takes a few minutes. If AEAT ends up rejecting the registration instead, no cancellation is sent at all.
**Voiding an invoice that never reached AEAT sends nothing.** If the registration was refused before reaching AEAT, abandoned, or rejected by AEAT, there is no record there to cancel: the invoice becomes `VOIDED` in BeeL., no `VOID` record appears, and `verifactu.submission_status` keeps the registration's outcome. See [Handling AEAT rejections](/verifactu/handling-rejections#fixing-the-invoice).
> **Rules that apply here:** [VOI-001 · Void only an invoice that should never have been issued](/rules/void#voi-001) · [VOI-004 · A sent or paid invoice is voided only confirming it was issued by mistake](/rules/void#voi-004) · [VOI-003 · A void adds a cancellation record; the original stays](/rules/void#voi-003)
## Corrective invoice (*rectificativa*) [#rectificativa-corrective]
A new invoice is issued, referencing the original, with its own AEAT `tipo_factura` (`R1`–`R5`) and its own registration record (no cancellation record). The original is **never erased** from AEAT. Request shapes, codes and every scenario are in [Corrective invoices](/verifactu/corrective-invoices).
Use a corrective invoice for:
- Wrong amount / IVA rate
- Wrong customer NIF, name or address, when the invoice went to the right customer (`R4`, no lines — see [Correcting the recipient's data](/verifactu/corrective-invoices#scenario-4--correcting-the-recipients-data-r4))
- Post-issuance discount or quantity adjustment
- Bad debt write-off
- Insolvency proceedings (*concurso de acreedores*) adjustment
A customer who asks for a full invoice instead of a ticket is not a correction: that is the [exchange of simplified invoices](/verifactu/simplified-vs-standard#upgrading-an-f2-to-f1-the-canje-case). And a withholding that should not have been applied is not one either: void the invoice and issue a new one without it.
> **Rules that apply here:** [COR-001 · Wrong data on an issued invoice is fixed with a corrective](/rules/corrective#cor-001)
## *Subsanación* [#subsanación]
*Subsanación* is the AEAT mechanism that **replaces a registration record with a corrected one** (AEAT's FAQ: «sustitución del "registro de alta" por otro registro subsanado»). It never changes what the invoice says, so it only applies when the error needs no corrective invoice: for example a rejection caused by an issuing NIF that was not yet in the census.
*Subsanación* is **never** the way to fix a typo in the description, a wrong amount, a wrong recipient NIF or a wrong series. An error in the invoice's amounts or content is corrected with a **corrective invoice** (`POST /invoices/{id}/corrective`), which produces a new fiscal document; a void is only for an invoice that should never have been issued. A corrective invoice keeps the recipient of the invoice it corrects, and changes only that recipient's data ([COR-017 · A corrective keeps the recipient, except to correct the recipient's data](/rules/corrective#cor-017)): an invoice issued to the wrong party is corrected in full with a `TOTAL` corrective and invoiced again to the right one.
When the cause was outside the invoice and you have resolved it, contact BeeL. with the invoice — see [Handling AEAT rejections](/verifactu/handling-rejections#when-to-contact-support).
## Decision matrix
The situations and what to do with each are in [Void or correct](/rules/void#void-or-correct), a table built from the rules that decide it. Two cases that depend on what AEAT answered:
- **AEAT never registered the invoice** (`REJECTED`): voiding it sends nothing to AEAT — see [Void](#anulación-void).
- **Rejected because the issuing NIF was not in the census yet**: nothing in the invoice is wrong, so it is a *subsanación* — contact [it@beel.es](mailto:it@beel.es) once the NIF is registered.
> **Rules that apply here:** [VOI-005 · A corrected invoice, or a total corrective, is not voided](/rules/void#voi-005) · [COR-013 · A corrective is only for the causes the law lists](/rules/corrective#cor-013) · [COR-024 · A corrective does not change only the withholding](/rules/corrective#cor-024) · [REC-010 · Subsanación is only for errors that need no corrective](/rules/records#rec-010)
## Related
- [Corrective invoices](/verifactu/corrective-invoices) — R1–R5 with full examples
- [Submission states](/verifactu/submission-states) — how cancellation fits in the lifecycle
- [Auto-submit policy](/verifactu/auto-submit) — applies to cancellation records too
- [Void an issued invoice](/invoices/voidCompanyInvoice)
- [Create a corrective invoice](/invoices/createCompanyCorrectiveInvoice)
- [List the VeriFactu records of an invoice](/invoices/listCompanyInvoiceVerifactuRecords) — registration and cancellation, each with its own status
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# QR code and the invoice PDF
When the VeriFactu QR code data is available, how the PDF relates to it, and what applies to any document that carries the QR.
An invoice issued with a billing system carries a QR code that lets its recipient check it with AEAT (article 6.5 of the RD 1619/2012, and articles 20 and 21 of the Orden HAC/1177/2024). BeeL. prints that QR on the PDF it generates — once, centred at the top of the first page, at the size [QRC-003 · The QR measures between 30 mm and 40 mm](/rules/qr#qrc-003) sets, with «QR tributario:» above it and the legend below, both centred too — and also returns the QR data on the invoice.
## The QR exists from `PENDING`
The QR is produced when the record is submitted, not when AEAT accepts it. As soon as an invoice reads `submission_status: PENDING`, its `verifactu` block already carries:
- `qr_url` — the AEAT validation link the QR encodes;
- `qr_base64` — the QR as a base64-encoded PNG, ready to embed;
- `invoice_hash` — the fingerprint of the record.
They stay on the invoice whatever AEAT answers — including after a rejection. Their presence says the record was built, not that AEAT accepted it: read `submission_status` for that.
Before you render a document of the invoice, make sure the QR data is there: read the invoice until `verifactu.qr_url` is present, or wait for the `invoice.pdf.generated` webhook — see [QRC-002 · Wait for the QR before you distribute the PDF](/rules/qr#qrc-002).
In sandbox the QR points to AEAT's **test** validation service; see [Testing in sandbox](/verifactu/testing-in-sandbox#where-the-submission-goes).
## The PDF and the QR
For an invoice under VeriFactu, BeeL. renders the PDF with the QR once the QR data exists. The `invoice.pdf.generated` webhook tells you when it is ready, and [downloading the PDF](/invoices/getCompanyInvoicePdf) waits for it rather than making you poll. An invoice outside VeriFactu (`verifactu.enabled: false`) never waits: its PDF has no QR.
The PDF is generated **once**, with the QR, and is never modified afterwards. Voiding the invoice or issuing a corrective for it does not change it and does not send `invoice.pdf.generated` again: the new status is in `status` and `verifactu.submission_status`, not in the document. Emailing the invoice before its PDF exists answers `202`, and the email goes out when the PDF is stored — see [Sending email](/guides/sending-email#sending-before-the-pdf-exists).
> **Rules that apply here:** [QRC-002 · Wait for the QR before you distribute the PDF](/rules/qr#qrc-002)
## If the submission never reaches AEAT
When the registration never produces a record — the submission was refused before reaching AEAT, or the invoice stays `NOT_SUBMITTED` — there is no QR data to print. An invoice whose registration was refused before reaching AEAT, or that was voided without being registered, has no PDF: downloading it, previewing it or emailing it with the PDF answers `400` [`INVOICE_NOT_REGISTERED_NO_PDF`](/errors/INVOICE_NOT_REGISTERED_NO_PDF). The fix is to get the invoice registered, not to render around it: see [Handling AEAT rejections](/verifactu/handling-rejections#when-to-contact-support).
## Rendering your own PDF
Under the criteria AEAT has published, software that **prints the invoice or generates its QR** can be a component of the billing system that needs its own *declaración responsable* (the producer's signed statement of compliance), even when BeeL. generates the record and the QR data. The rules and AEAT's wording are in [Software built on the API](/verifactu/compliance-and-responsibilities#software-built-on-the-api).
The API returns the QR data so that it is available to your software:
```json
{
"verifactu": {
"enabled": true,
"submission_status": "PENDING",
"qr_url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B27534239&numserie=S-2026-0001&fecha=19-09-2026&importe=121.00",
"qr_base64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADI..."
}
}
```
- **`qr_base64`** is the QR as a PNG image.
- **`qr_url`** is the link the QR encodes.
The URL comes from the registered record: its `importe` is the total AEAT registers, which can differ from your invoice's total — see [What AEAT receives](/verifactu/what-aeat-receives#the-total). The number registered with AEAT is the `invoice_number` BeeL. assigned.
Any document that carries the QR is subject to these rules:
- [QRC-003 · The QR measures between 30 mm and 40 mm](/rules/qr#qrc-003)
- [QRC-004 · The QR follows ISO/IEC 18004 with error correction M](/rules/qr#qrc-004)
- [QRC-005 · Use qr_url exactly as returned](/rules/qr#qrc-005)
- [QRC-007 · «QR tributario:» goes just above the QR](/rules/qr#qrc-007)
- [QRC-008 · The QR goes once, at the start of the first page](/rules/qr#qrc-008)
- [QRC-009 · Keep a blank margin around the QR](/rules/qr#qrc-009)
- the legend goes next to it — see [The legend next to the QR](#the-legend-next-to-the-qr).
The QR data is available from `PENDING`, without waiting for `ACCEPTED`. The `verifactu.status.updated` webhook for the registration carries `qr_url` and `qr_base64` too.
> **Rules that apply here:** [QRC-001 · Every invoice carries the tax QR code](/rules/qr#qrc-001) · [QRC-010 · A structured e-invoice carries the QR URL as a field](/rules/qr#qrc-010)
## The legend next to the QR
**QRC-006 · The VeriFactu legend goes just below the QR**
`Required` · Law · Impact: medium · Responsibility: your integration.
Just below the QR, print «Factura verificable en la sede electrónica de la AEAT» or «VERI*FACTU», preferably centred on it. The Orden asks for a type and size clearly visible and similar to the rest of the invoice data; AEAT's specification, for one equal to or larger than it. Print it only on invoices whose record goes to AEAT.
Full rule: [QRC-006](/rules/qr#qrc-006)
> **Rules that apply here:** [QRC-006 · The VeriFactu legend goes just below the QR](/rules/qr#qrc-006) · [QRC-001 · Every invoice carries the tax QR code](/rules/qr#qrc-001)
## Related
- [Submission states](/verifactu/submission-states) — what `PENDING`, `ACCEPTED` and `REJECTED` mean
- [What AEAT receives](/verifactu/what-aeat-receives) — why the QR's amount can differ from `invoice_total`
- [Download the invoice PDF](/invoices/getCompanyInvoicePdf)
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# What AEAT receives from your invoice
How BeeL. turns your invoice into the record AEAT registers — the description, the total, the number, the recipient and the late-submission flag — and why some of them differ from what you see on the invoice.
AEAT does not receive your invoice as you sent it: it receives a billing record (*registro de facturación*) built from it, with AEAT's own fields and rules. Most of it is a straight copy. A few fields are derived, and those are the ones that surprise integrators reconciling against AEAT.
## The description
AEAT requires a description of the operation for the whole invoice. BeeL. takes it from, in order:
1. the invoice's `notes`, if set;
2. otherwise, the description of the **first line** that has one — skipping disbursements (*suplidos*), which AEAT never receives;
3. otherwise, a fixed generic description.
Long text is cut to AEAT's maximum length (500 characters), counted as AEAT counts it: in UTF-16 code units, so an emoji or any other character outside the Basic Multilingual Plane takes two. If the text AEAT shows for an invoice matters to you, put it in `notes`.
The recipient's name is not cut: AEAT takes at most 120 of those units, and a longer `legal_name` is rejected when the invoice is issued, before it is numbered ([`FIELD_TOO_LONG`](/errors/FIELD_TOO_LONG)).
## The total
The total AEAT receives is **not** your invoice's `totals.invoice_total`, nor its `totals.total_to_pay`. It is the sum, over the lines AEAT receives, of each line's **base + VAT + equivalence surcharge**:
- **IRPF is not included.** Withholding is declared separately; it is not part of the operation AEAT registers.
- **Disbursements are not included.** They are not part of the taxable operation, so AEAT never receives them.
- **OSS lines (`regime_key: "17"`) contribute their base only.** The destination country's VAT is settled through the OSS return, not in Spain.
A worked example:
```text
Line 1 base 1 000.00 VAT 21 % 210.00 → 1 210.00
Line 2 base 200.00 VAT 21 % 42.00 surcharge 5.2 % 10.40 → 252.40
Disbursement (court fee paid for the client) 50.00 → not sent
IRPF 15 % on line 1 -150.00 → not sent
totals.invoice_total: 1 210.00 + 252.40 − 150.00 = 1 312.40
totals.total_to_pay: 1 312.40 + 50.00 = 1 362.40
Total AEAT receives: 1 210.00 + 252.40 = 1 462.40
```
So when you compare an invoice with its AEAT record — or with the `importe` in its QR URL — expect them to differ whenever the invoice has IRPF, disbursement or OSS lines. Both are correct for what they measure.
> **Rules that apply here:** [TAX-009 · IRPF withholding is not part of the total AEAT receives](/rules/taxes#tax-009) · [QRC-005 · Use qr_url exactly as returned](/rules/qr#qrc-005)
## The invoice number
The number is sent **whole**, exactly as it appears on the invoice (for example `F-2026/00042`), including the series prefix. AEAT identifies the record by that number, the issuing NIF and the issue date — which is why a used number can never be reused, even after a void.
## The recipient
| Invoice | Recipient sent to AEAT |
|---|---|
| Standard (F1), exchange invoice (F3) and correctives R1–R4 | Name plus NIF, or plus `alternative_id` for a foreign customer |
| Simplified (F2) and its corrective R5 | **None** |
BeeL. sends no recipient data on these types. On top of that, BeeL. does not accept an identified recipient on a simplified invoice you create: it issues that invoice as a standard one — see [Simplified vs standard](/verifactu/simplified-vs-standard). For standard invoices, AEAT checks the recipient's NIF and name against its census; a mismatch is what usually produces an [accepted-with-errors](/verifactu/handling-rejections#accepted-with-errors) result.
## Dates and late submission
The record carries the invoice's issue date and, when you set one, its `operation_date` — always the **original** dates. BeeL. never re-dates an invoice to make a submission fit.
Normally the registration goes out the same day the invoice is issued. When it goes out on a **different day**, BeeL. flags the record as a late submission automatically, in the field AEAT's record format provides for it. Article 16.4 of the Orden HAC/1177/2024 requires a system that could not submit because of a technical incident to submit «en cuanto sea posible», in order, and to indicate it in that field. You cannot set the flag; BeeL. sets it from the dates.
> **Rules that apply here:** [DAT-002 · The issue date is the day the billing record is generated](/rules/dates#dat-002) · [REC-007 · Keep invoicing when AEAT is unreachable](/rules/records#rec-007) · [SIM-005 · The record of a simplified invoice carries no recipient](/rules/simplified#sim-005) · [REC-001 · Each issued invoice gets a billing record built from its data](/rules/records#rec-001)
## Related
- [Tax classification](/verifactu/tax-classification) — how each line's `exemption_reason` becomes an AEAT code
- [Invoice types](/verifactu/invoice-types) — how `type` and `rectification_code` become F1, F2, F3, R1–R5
- [Disbursements](/verifactu/suplidos) — why they stay off the record
- [QR code and the invoice PDF](/verifactu/qr-and-pdf) — the other thing built from the record
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---
# Examples cookbook
Runnable curl + JSON for every common VeriFactu scenario, mapped to BeeL's public API.
Every scenario below maps a real AEAT case to the exact JSON BeeL's public API expects. Field names and enum values come straight from the OpenAPI spec — copy, change the recipient, and ship. For the *why* behind each combination, follow the cross-links into the concept pages.
All examples target `https://app.beel.es/api/v1/companies/{company_id}/invoices` and assume your account has VeriFactu enabled. Sandbox keys (`beel_sk_test_*`) submit to AEAT pre-production and don't consume quota.
None of them send an issue date: BeeL. sets `issue_date` to the day the invoice is actually issued, when its billing record is generated. To record an operation that happened earlier, send `operation_date` (today or a past date). To issue on a future date, create a draft and use `PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule`.
## Quick reference
| Scenario | `type` | `exemption_reason` | `regime_key` | Surcharge | Recipient |
|---|---|---|---|---|---|
| [F2 simplified — under the cap, no recipient](#f2-simplified--under-the-cap-no-recipient) | `SIMPLIFIED` | — | `"01"` | — | omitted |
| [F1 standard — Spanish B2B](#f1-standard--spanish-business-customer) | `STANDARD` | — | `"01"` | — | NIF + address |
| [F1 multiple VAT rates](#f1-with-multiple-vat-rates-per-line) | `STANDARD` | — | `"01"` | — | NIF + address |
| [F1 with IRPF](#f1-with-irpf-withholding) | `STANDARD` | — | `"01"` | — | NIF + address |
| [F1 equivalence surcharge](#f1-with-recargo-de-equivalencia) | `STANDARD` | — | `"18"` | `5.2` | NIF + address |
| [F1 intra-EU B2B goods (E5)](#f1-intra-eu-b2b--goods-e5) | `STANDARD` | `EXENTA_ART_25` | `"01"` | — | `NIF_IVA` |
| [F1 intra-EU B2B services (N2)](#f1-intra-eu-b2b--services-n2) | `STANDARD` | `NO_SUJETA_LOCALIZACION` | `"01"` | — | `NIF_IVA` |
| [F1 B2C OSS over threshold](#f1-intra-eu-b2c--oss-over-threshold) | `STANDARD` | — (regime key alone → N2) | `"17"` | — | `PASSPORT` |
| [F1 B2C under threshold](#f1-intra-eu-b2c--under-oss-threshold) | `STANDARD` | — | `"01"` | — | `PASSPORT` |
| [F1 export non-EU (E2)](#f1-export-of-goods-to-non-eu-e2) | `STANDARD` | `EXENTA_ART_21` | `"02"` | — | `PASSPORT` |
| [F1 services non-EU (N2)](#f1-services-to-non-eu-n2) | `STANDARD` | `NO_SUJETA_LOCALIZACION` | `"01"` | — | `PASSPORT` |
| [F1 exempt E1 (medical/edu)](#f1-exempt-operation-e1--educationalmedical) | `STANDARD` | `EXENTA_ART_20` | `"01"` | — | NIF + address |
| [F1 IGIC (Canary Islands)](#f1-with-igic-canary-islands) | `STANDARD` | — | `"01"` | — | NIF + address |
| [F1 not subject (N1)](#f1-not-subject-n1) | `STANDARD` | `NO_SUJETA_ART_7_9` | `"01"` | — | NIF + address |
| [F1 ISP — reverse charge (S2)](#f1-isp--reverse-charge-s2) | `STANDARD` | `ISP_ART_84_2_F` | `"01"` | — | NIF + address |
| [F1 REBU (used goods)](#f1-rebu-used-goods-art-antiques) | not accepted | — | `"03"` | — | — |
| [R1 corrective — partial](#r1-corrective--partial-by-differences) | (corrective) | — | `"01"` | — | inherited |
| [R5 corrective — return on F2](#r5-corrective--return-on-simplified-f2) | (corrective) | — | `"01"` | — | none, like the F2 |
| [Voiding](#voiding-anulación) | n/a | — | — | — | n/a |
The first row of every table is the canonical pattern; everything below it is a variation. Read [Tax classification per line](/verifactu/tax-classification) for the full enum of `exemption_reason` codes and how they map to AEAT's S1/S2/N1/N2/E1–E6 four-family taxonomy.
## F2 simplified — under the cap, no recipient
The bread-and-butter ticket invoice. Use `SIMPLIFIED` when the total stays under 3,000 €, the customer is a consumer, and you don't identify them. A `SIMPLIFIED` invoice with a `nif` or `alternative_id` in `recipient` is rejected ([`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`](/errors/SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT)) — identify the customer and it is an F1. Pass `recipient: {}` — the field itself is required, but every property inside it is optional for `SIMPLIFIED`, and BeeL. fills *consumidor final* automatically.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "SIMPLIFIED",
"recipient": {},
"lines": [
{
"description": "Menú del día",
"quantity": 2,
"unit": "unit",
"unit_price": 14.50,
"main_tax": { "type": "IVA", "percentage": 10, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "SIMPLIFIED",
"recipient": {},
"lines": [
{
"description": "Menú del día",
"quantity": 2,
"unit": "unit",
"unit_price": 14.50,
"main_tax": { "type": "IVA", "percentage": 10, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}
```
See [Simplified vs standard](/verifactu/simplified-vs-standard) for the 3,000 € cap, the 400 € limit that is your responsibility, and why BeeL. always issues an identified customer's invoice as an F1.
## F1 standard — Spanish business customer
The default B2B invoice between two Spanish entities. The recipient needs a Spanish NIF, which BeeL. checks against the AEAT census together with the name.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4a7d1ed4-9bc9-4f7e-b1e9-7c2a5f9b8d11" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Avenida de Europa",
"number": "18",
"postal_code": "28108",
"city": "Alcobendas",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Consultoría de arquitectura — Sprint mayo",
"quantity": 40,
"unit": "hours",
"unit_price": 75.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"payment_info": {
"method": "BANK_TRANSFER",
"iban": "ES9121000418450200051332",
"payment_term_days": 30
},
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Avenida de Europa",
"number": "18",
"postal_code": "28108",
"city": "Alcobendas",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Consultoría de arquitectura — Sprint mayo",
"quantity": 40,
"unit": "hours",
"unit_price": 75.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"payment_info": {
"method": "BANK_TRANSFER",
"iban": "ES9121000418450200051332",
"payment_term_days": 30
},
"options": { "issue_directly": true }
}
```
The `Idempotency-Key` header is optional but recommended for any `issue_directly: true` call — retrying with the same UUID returns the original response instead of duplicating the record.
## F1 with multiple VAT rates per line
One invoice can mix any combination of the [allowed VAT percentages](/verifactu/tax-classification#allowed-main_taxpercentage-values) (`0`, `4`, `5`, `10`, `21`). BeeL. builds the `vat_breakdown` automatically by grouping lines per rate.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Calle Cava Baja",
"number": "35",
"postal_code": "28005",
"city": "Madrid",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Vino reserva (caja 6 botellas)",
"quantity": 4,
"unit": "box",
"unit_price": 90.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
},
{
"description": "Servicio de catering — menú degustación",
"quantity": 25,
"unit": "menu",
"unit_price": 32.00,
"main_tax": { "type": "IVA", "percentage": 10, "regime_key": "01" }
},
{
"description": "Pan artesano",
"quantity": 50,
"unit": "unit",
"unit_price": 1.20,
"main_tax": { "type": "IVA", "percentage": 4, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Calle Cava Baja",
"number": "35",
"postal_code": "28005",
"city": "Madrid",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Vino reserva (caja 6 botellas)",
"quantity": 4,
"unit": "box",
"unit_price": 90.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
},
{
"description": "Servicio de catering — menú degustación",
"quantity": 25,
"unit": "menu",
"unit_price": 32.00,
"main_tax": { "type": "IVA", "percentage": 10, "regime_key": "01" }
},
{
"description": "Pan artesano",
"quantity": 50,
"unit": "unit",
"unit_price": 1.20,
"main_tax": { "type": "IVA", "percentage": 4, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}
```
**What changes vs. the previous example:** three lines instead of one, each with a different `main_tax.percentage`. The response `totals.vat_breakdown` carries three entries (`21`, `10`, `4`).
## F1 with IRPF withholding
Professionals subject to IRPF retention add `irpf_rate` to the line. The accepted values are `0`, `1`, `2`, `2.8`, `6`, `7`, `7.6`, `9.5`, `15`, `19` or `24` (`IrpfPercentage` enum) — for general professional activity it's almost always `15` (or `7` during the first three years of activity). The issuer here is an individual professional: a company issuer only bears `0`, `19`, `24` and `9.5`, and any other rate is rejected with `422` [`IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`](/errors/IRPF_RATE_NOT_FOR_CORPORATE_ISSUER) (see [Amounts and rounding](/guides/amounts-and-rounding#irpf)).
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Avenida de Europa",
"number": "18",
"postal_code": "28108",
"city": "Alcobendas",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Servicios de abogacía — asesoramiento mercantil",
"quantity": 12,
"unit": "hours",
"unit_price": 120.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" },
"irpf_rate": 15
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Avenida de Europa",
"number": "18",
"postal_code": "28108",
"city": "Alcobendas",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Servicios de abogacía — asesoramiento mercantil",
"quantity": 12,
"unit": "hours",
"unit_price": 120.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" },
"irpf_rate": 15
}
],
"options": { "issue_directly": true }
}
```
BeeL. **never** accepts IRPF on `SIMPLIFIED` invoices: a simplified invoice issued through BeeL. does not identify its recipient, and a withholding is made by an identified payer. If your customer withholds, issue an F1. See [Simplified vs standard](/verifactu/simplified-vs-standard).
## F1 with equivalence surcharge [#f1-with-recargo-de-equivalencia]
For retailers that have declared themselves subject to the regime, add `equivalence_surcharge_rate` to each line and set `main_tax.regime_key: "18"`.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Calle del Comercio",
"number": "56",
"postal_code": "08001",
"city": "Barcelona",
"province": "Barcelona",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Lote de auriculares inalámbricos para reventa",
"quantity": 30,
"unit": "unit",
"unit_price": 45.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "18" },
"equivalence_surcharge_rate": 5.2
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "ACCIONA SA",
"nif": "A08001851",
"address": {
"street": "Calle del Comercio",
"number": "56",
"postal_code": "08001",
"city": "Barcelona",
"province": "Barcelona",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Lote de auriculares inalámbricos para reventa",
"quantity": 30,
"unit": "unit",
"unit_price": 45.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "18" },
"equivalence_surcharge_rate": 5.2
}
],
"options": { "issue_directly": true }
}
```
The IVA-RE pairs are strict: `4 ↔ 0.5`, `10 ↔ 1.4`, `21 ↔ 5.2` (and `21 ↔ 1.75` for tobacco); the temporary rates have their own pairs, accepted only on operations of their period. Pass `0` to disable the surcharge on a specific line. See [Equivalence surcharge](/verifactu/equivalence-surcharge) for the full table.
## F1 intra-EU B2B — goods (E5)
EU-to-EU sales of goods between two businesses with valid VIES VAT-IDs are **exempt under Art. 25 LIVA**. The buyer self-assesses the IVA in their country. BeeL. checks that the German VAT-ID has the `DE` structure; whether it is active in VIES is for you to check before invoicing — see [International customers](/verifactu/international-customers#identifying-foreign-customers).
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Berliner Industrie GmbH",
"alternative_id": {
"type": "NIF_IVA",
"number": "DE123456789",
"country_code": "DE"
},
"address": {
"street": "Friedrichstraße",
"number": "200",
"postal_code": "10117",
"city": "Berlin",
"province": "Berlin",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Componentes electrónicos — pedido EU-2026-0042",
"quantity": 500,
"unit": "unit",
"unit_price": 8.40,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "EXENTA_ART_25"
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Berliner Industrie GmbH",
"alternative_id": {
"type": "NIF_IVA",
"number": "DE123456789",
"country_code": "DE"
},
"address": {
"street": "Friedrichstraße",
"number": "200",
"postal_code": "10117",
"city": "Berlin",
"province": "Berlin",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Componentes electrónicos — pedido EU-2026-0042",
"quantity": 500,
"unit": "unit",
"unit_price": 8.40,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "EXENTA_ART_25"
}
],
"options": { "issue_directly": true }
}
```
**What changes vs. the previous example:** recipient uses `alternative_id.type: "NIF_IVA"` (VIES VAT-ID) instead of `nif`; line carries `main_tax.percentage: 0` and `exemption_reason: "EXENTA_ART_25"`. See [International customers > B2B intra-EU goods](/verifactu/international-customers#b2b-intra-eu--goods-e5).
## F1 intra-EU B2B — services (N2)
Same German customer, but selling **services** instead of goods. Services to an EU business are **not subject by place of supply** (*no sujetas por localización*, Art. 69 LIVA): the place of supply is the buyer's country, so the operation is outside the scope of Spanish IVA.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Berliner Industrie GmbH",
"alternative_id": {
"type": "NIF_IVA",
"number": "DE123456789",
"country_code": "DE"
},
"address": {
"street": "Friedrichstraße",
"number": "200",
"postal_code": "10117",
"city": "Berlin",
"province": "Berlin",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Consultoría de arquitectura cloud",
"quantity": 60,
"unit": "hours",
"unit_price": 110.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_LOCALIZACION"
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Berliner Industrie GmbH",
"alternative_id": {
"type": "NIF_IVA",
"number": "DE123456789",
"country_code": "DE"
},
"address": {
"street": "Friedrichstraße",
"number": "200",
"postal_code": "10117",
"city": "Berlin",
"province": "Berlin",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Consultoría de arquitectura cloud",
"quantity": 60,
"unit": "hours",
"unit_price": 110.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_LOCALIZACION"
}
],
"options": { "issue_directly": true }
}
```
**What changes vs. the previous example:** `exemption_reason` flips from `EXENTA_ART_25` (E5, goods) to `NO_SUJETA_LOCALIZACION` (N2, services). The recipient block is identical — the bienes-vs-servicios distinction is per-line, not per-customer.
## F1 intra-EU B2C — OSS over threshold
Once your annual cross-border B2C sales to EU consumers exceed 10,000 € you must apply destination-country IVA via the One-Stop-Shop (OSS) and file Modelo 369. The line carries `regime_key: "17"` and the **destination country's rate** — 19 % for Germany below. The regime key widens the accepted rates beyond the Spanish menu, and it alone classifies the line as N2, so the line takes **no `exemption_reason`**: adding one would force the rate to 0 % and drop the destination IVA from the breakdown.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Anna Schmidt",
"alternative_id": {
"type": "PASSPORT",
"number": "C0HJ4P9DT",
"country_code": "DE"
},
"address": {
"street": "Müllerstraße",
"number": "47",
"postal_code": "80469",
"city": "München",
"province": "Bayern",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Suscripción anual plataforma SaaS",
"quantity": 1,
"unit": "year",
"unit_price": 480.00,
"main_tax": { "type": "IVA", "percentage": 19, "regime_key": "17" }
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Anna Schmidt",
"alternative_id": {
"type": "PASSPORT",
"number": "C0HJ4P9DT",
"country_code": "DE"
},
"address": {
"street": "Müllerstraße",
"number": "47",
"postal_code": "80469",
"city": "München",
"province": "Bayern",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Suscripción anual plataforma SaaS",
"quantity": 1,
"unit": "year",
"unit_price": 480.00,
"main_tax": { "type": "IVA", "percentage": 19, "regime_key": "17" }
}
],
"options": { "issue_directly": true }
}
```
The 10,000 € threshold is an **annual aggregate across all EU countries** — not per-country, not per-customer. Once you cross it (or opt in voluntarily) every cross-border B2C sale is OSS until year-end.
## F1 intra-EU B2C — under OSS threshold
Below the threshold (and not opted in), cross-border B2C sales are billed with **Spanish IVA** as if the consumer were Spanish. No `exemption_reason`, default `regime_key: "01"`, normal rate.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Anna Schmidt",
"alternative_id": {
"type": "PASSPORT",
"number": "C0HJ4P9DT",
"country_code": "DE"
},
"address": {
"street": "Müllerstraße",
"number": "47",
"postal_code": "80469",
"city": "München",
"province": "Bayern",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Curso online — fotografía digital",
"quantity": 1,
"unit": "course",
"unit_price": 149.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Anna Schmidt",
"alternative_id": {
"type": "PASSPORT",
"number": "C0HJ4P9DT",
"country_code": "DE"
},
"address": {
"street": "Müllerstraße",
"number": "47",
"postal_code": "80469",
"city": "München",
"province": "Bayern",
"country": "Alemania",
"country_code": "DE"
}
},
"lines": [
{
"description": "Curso online — fotografía digital",
"quantity": 1,
"unit": "course",
"unit_price": 149.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}
```
**What changes vs. the previous example:** same German individual, but `regime_key` drops back to `"01"`, `exemption_reason` is removed, and `percentage` becomes the standard Spanish `21`. The 10,000 €/year aggregate decides which of the two shapes to use; track it externally.
## F1 export of goods to non-EU (E2)
Goods leaving the EU are **exempt under Art. 21 LIVA**. The line needs `exemption_reason: EXENTA_ART_21`, `regime_key: "02"` (Export), and `percentage: 0`. The customer's `alternative_id.type` will typically be `PASSPORT` for individuals or `OTHER_DOCUMENT` for foreign businesses.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Northeast Imports Inc.",
"alternative_id": {
"type": "PASSPORT",
"number": "551234567",
"country_code": "US"
},
"address": {
"street": "5th Avenue",
"number": "725",
"postal_code": "10022",
"city": "New York",
"province": "NY",
"country": "Estados Unidos",
"country_code": "US"
}
},
"lines": [
{
"description": "Aceite de oliva virgen extra — palet 480 botellas",
"quantity": 1,
"unit": "pallet",
"unit_price": 3850.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "02" },
"exemption_reason": "EXENTA_ART_21"
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Northeast Imports Inc.",
"alternative_id": {
"type": "PASSPORT",
"number": "551234567",
"country_code": "US"
},
"address": {
"street": "5th Avenue",
"number": "725",
"postal_code": "10022",
"city": "New York",
"province": "NY",
"country": "Estados Unidos",
"country_code": "US"
}
},
"lines": [
{
"description": "Aceite de oliva virgen extra — palet 480 botellas",
"quantity": 1,
"unit": "pallet",
"unit_price": 3850.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "02" },
"exemption_reason": "EXENTA_ART_21"
}
],
"options": { "issue_directly": true }
}
```
See [International customers > Exports of goods](/verifactu/international-customers#exports-of-goods-non-eu) for the documentary evidence (DUA / customs declaration) you should keep alongside the invoice.
## F1 services to non-EU (N2)
Services to a customer outside the EU are **not subject by place of supply** — same N2 family as intra-EU B2B services. The recipient is non-EU but the line shape is identical to the EU service case.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Northeast Imports Inc.",
"alternative_id": {
"type": "PASSPORT",
"number": "551234567",
"country_code": "US"
},
"address": {
"street": "5th Avenue",
"number": "725",
"postal_code": "10022",
"city": "New York",
"province": "NY",
"country": "Estados Unidos",
"country_code": "US"
}
},
"lines": [
{
"description": "Diseño de identidad de marca",
"quantity": 1,
"unit": "project",
"unit_price": 6500.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_LOCALIZACION"
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Northeast Imports Inc.",
"alternative_id": {
"type": "PASSPORT",
"number": "551234567",
"country_code": "US"
},
"address": {
"street": "5th Avenue",
"number": "725",
"postal_code": "10022",
"city": "New York",
"province": "NY",
"country": "Estados Unidos",
"country_code": "US"
}
},
"lines": [
{
"description": "Diseño de identidad de marca",
"quantity": 1,
"unit": "project",
"unit_price": 6500.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_LOCALIZACION"
}
],
"options": { "issue_directly": true }
}
```
**What changes vs. the previous example:** `exemption_reason` flips from `EXENTA_ART_21` (E2, goods) to `NO_SUJETA_LOCALIZACION` (N2, services); `regime_key` drops from `"02"` (Export) back to the default `"01"`.
## F1 exempt operation (E1) — educational/medical
Operations exempt under Art. 20 LIVA (education, medical, social, insurance, residential rental). Set `exemption_reason: EXENTA_ART_20`, `percentage: 0`, and use `exemption_reason_text` to record the specific subparagraph.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Academia Cervantes SL",
"nif": "A08001851",
"address": {
"street": "Calle Princesa",
"number": "27",
"postal_code": "28008",
"city": "Madrid",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Clases particulares de matemáticas — trimestre primavera",
"quantity": 36,
"unit": "hours",
"unit_price": 28.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "EXENTA_ART_20",
"exemption_reason_text": "Operación exenta de IVA según Art. 20.Uno.10º LIVA (enseñanza)"
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Academia Cervantes SL",
"nif": "A08001851",
"address": {
"street": "Calle Princesa",
"number": "27",
"postal_code": "28008",
"city": "Madrid",
"province": "Madrid",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Clases particulares de matemáticas — trimestre primavera",
"quantity": 36,
"unit": "hours",
"unit_price": 28.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "EXENTA_ART_20",
"exemption_reason_text": "Operación exenta de IVA según Art. 20.Uno.10º LIVA (enseñanza)"
}
],
"options": { "issue_directly": true }
}
```
`exemption_reason_text` is free-text (max 500 chars). It's shown on the PDF and persisted on the VeriFactu record, but doesn't change AEAT classification.
## F1 with IGIC (Canary Islands)
Canary Islands operations use IGIC instead of IVA. Switch `main_tax.type` to `"IGIC"` and pick a valid IGIC rate (`0`, `3`, `5`, `7`, `9.5`, `15`, `20`).
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Importaciones Atlántico SL",
"nif": "A08001851",
"address": {
"street": "Calle León y Castillo",
"number": "200",
"postal_code": "35004",
"city": "Las Palmas de Gran Canaria",
"province": "Las Palmas",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Mobiliario de oficina — sillas ergonómicas",
"quantity": 10,
"unit": "unit",
"unit_price": 180.00,
"main_tax": { "type": "IGIC", "percentage": 7, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Importaciones Atlántico SL",
"nif": "A08001851",
"address": {
"street": "Calle León y Castillo",
"number": "200",
"postal_code": "35004",
"city": "Las Palmas de Gran Canaria",
"province": "Las Palmas",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Mobiliario de oficina — sillas ergonómicas",
"quantity": 10,
"unit": "unit",
"unit_price": 180.00,
"main_tax": { "type": "IGIC", "percentage": 7, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}
```
IPSI (Ceuta and Melilla) works identically: `main_tax.type: "IPSI"` with one of its rates (`0.5`, `1`, `2`, `4`, `8`, `10`). See [Territorial taxes](/verifactu/territorial-taxes) for the full picture.
## F1 not subject (N1)
Operations *not subject* to IVA under the general rules of Art. 7 LIVA — internal transfers between branches, samples for promotion, out-of-scope operations. Use `exemption_reason: NO_SUJETA_ART_7_9` and `percentage: 0`.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Distribuciones Hermanas Pérez SL",
"nif": "A08001851",
"address": {
"street": "Polígono Industrial San Isidro",
"number": "12",
"postal_code": "46980",
"city": "Paterna",
"province": "Valencia",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Muestras comerciales sin valor — catálogo 2026",
"quantity": 50,
"unit": "unit",
"unit_price": 12.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_ART_7_9",
"exemption_reason_text": "Entrega de muestras gratuitas (Art. 7.4º LIVA)"
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Distribuciones Hermanas Pérez SL",
"nif": "A08001851",
"address": {
"street": "Polígono Industrial San Isidro",
"number": "12",
"postal_code": "46980",
"city": "Paterna",
"province": "Valencia",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Muestras comerciales sin valor — catálogo 2026",
"quantity": 50,
"unit": "unit",
"unit_price": 12.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "NO_SUJETA_ART_7_9",
"exemption_reason_text": "Entrega de muestras gratuitas (Art. 7.4º LIVA)"
}
],
"options": { "issue_directly": true }
}
```
## F1 ISP — reverse charge (S2)
Reverse charge (*inversión del sujeto pasivo*): the **buyer** self-assesses the IVA instead of the seller charging it. Common cases: construction services subcontracted to a developer (`ISP_ART_84_2_F`), scrap metal deliveries (`ISP_ART_84_2_C`), and operations performed by non-established entities (`ISP_ART_84_2_A`).
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Promociones Inmobiliarias del Mediterráneo SA",
"nif": "A46789012",
"address": {
"street": "Gran Vía Marqués del Turia",
"number": "47",
"postal_code": "46005",
"city": "Valencia",
"province": "Valencia",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Ejecución de obra — instalación eléctrica edificio Torre Norte",
"quantity": 1,
"unit": "project",
"unit_price": 42000.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "ISP_ART_84_2_F",
"exemption_reason_text": "Inversión del sujeto pasivo — Art. 84.Uno.2º.f LIVA (ejecución de obra)"
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"type": "STANDARD",
"recipient": {
"legal_name": "Promociones Inmobiliarias del Mediterráneo SA",
"nif": "A46789012",
"address": {
"street": "Gran Vía Marqués del Turia",
"number": "47",
"postal_code": "46005",
"city": "Valencia",
"province": "Valencia",
"country": "España",
"country_code": "ES"
}
},
"lines": [
{
"description": "Ejecución de obra — instalación eléctrica edificio Torre Norte",
"quantity": 1,
"unit": "project",
"unit_price": 42000.00,
"main_tax": { "type": "IVA", "percentage": 0, "regime_key": "01" },
"exemption_reason": "ISP_ART_84_2_F",
"exemption_reason_text": "Inversión del sujeto pasivo — Art. 84.Uno.2º.f LIVA (ejecución de obra)"
}
],
"options": { "issue_directly": true }
}
```
ISP lines must **never** carry `equivalence_surcharge_rate` — the buyer self-assesses the full tax, so the surcharge cannot apply. BeeL. rejects the combination at validation time.
## F1 REBU (used goods, art, antiques)
Not accepted: a line with `regime_key: "03"` is rejected with `422` [`REGIME_KEY_NOT_SUPPORTED`](/errors/REGIME_KEY_NOT_SUPPORTED). Under the used-goods regime the invoice must not show the tax separately (RD 1619/2012, art. 16.2.c), and a BeeL. invoice always does. See [Regime keys](/verifactu/regime-keys#keys-that-are-not-accepted).
## R1 corrective — partial (by differences)
The most common rectification. Apply a delta to a previously issued invoice — quantities can be positive or negative. The URL points to the **original** invoice; the body describes only the change. The original status flips to `RECTIFIED`.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/a1b2c3d4-e5f6-7890-abcd-ef1234567890/corrective" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8b9c2e44-bbcd-4f23-9a01-1c3d5e7f9a02" \
-d '{
"rectification_type": "PARTIAL",
"rectification_code": "R1",
"reason": "Descuento comercial post-emisión acordado con el cliente (5 horas no facturables)",
"lines": [
{
"description": "Ajuste por horas no facturables — Sprint mayo",
"quantity": -5,
"unit": "hours",
"unit_price": 75.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}'
```
```json
{
"rectification_type": "PARTIAL",
"rectification_code": "R1",
"reason": "Descuento comercial post-emisión acordado con el cliente (5 horas no facturables)",
"lines": [
{
"description": "Ajuste por horas no facturables — Sprint mayo",
"quantity": -5,
"unit": "hours",
"unit_price": 75.00,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
],
"options": { "issue_directly": true }
}
```
The `quantity: -5` produces a negative line — that's the canonical "by differences" pattern. See [Corrective invoices](/verifactu/corrective-invoices) for when to pick `PARTIAL` vs `TOTAL` and which code (R1–R4) applies to your case.
## R5 corrective — return on simplified F2
R5 is **reserved for rectifying SIMPLIFIED invoices**, whatever the cause. The common case: the customer returns one of the items on the ticket.
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/9f8e7d6c-5b4a-3210-fedc-ba9876543210/corrective" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rectification_type": "PARTIAL",
"rectification_code": "R5",
"reason": "El cliente devolvió uno de los dos artículos del tique",
"lines": [
{
"description": "Devolución - camiseta talla M",
"quantity": -1,
"unit_price": 16.53,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
]
}'
```
```json
{
"rectification_type": "PARTIAL",
"rectification_code": "R5",
"reason": "El cliente devolvió uno de los dos artículos del tique",
"lines": [
{
"description": "Devolución - camiseta talla M",
"quantity": -1,
"unit_price": 16.53,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }
}
]
}
```
**What changes vs. the previous example:** `rectification_code` goes from `R1` to `R5`, and, like the F2, the R5 reaches AEAT without recipient data. A customer who asks for an invoice with their details after a ticket is not a correction: nothing in the ticket is wrong, so that is not an R5 but an [exchange of the simplified invoice](/verifactu/simplified-vs-standard#upgrading-an-f2-to-f1-the-canje-case).
## Voiding [#voiding-anulación]
If the operation **never happened** — duplicate emission, wrong customer, test invoice escaped to production — use the void endpoint, **not** a corrective. Voiding is for "this invoice should not exist"; corrective is for "this invoice exists but the numbers are wrong".
```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/a1b2c3d4-e5f6-7890-abcd-ef1234567890/void" \
-H "Authorization: Bearer $BEEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Duplicado de la factura F-2026-0142, emitida dos veces por error",
"issued_in_error": true
}'
```
```json
{
"reason": "Duplicado de la factura F-2026-0142, emitida dos veces por error",
"issued_in_error": true
}
```
The invoice flips to `VOIDED` and, if AEAT had accepted its registration, the cancellation record is sent automatically; if the registration never reached AEAT, nothing is sent. `issued_in_error: true` confirms the invoice was issued by mistake: it is required once the invoice has been sent or paid ([`VOID_REQUIRES_ISSUED_IN_ERROR`](/errors/VOID_REQUIRES_ISSUED_IN_ERROR)). See [Cancel and fix](/verifactu/cancel-and-fix) for the decision tree between void and corrective.
## Related
- [Tax classification per line](/verifactu/tax-classification) — the `exemption_reason` mapping
- [Corrective invoices](/verifactu/corrective-invoices) — every R1–R5 scenario
- [International customers](/verifactu/international-customers) — the cross-border cases in depth
---
Full OpenAPI spec: https://docs.beel.es/api/openapi
---