# Amounts and rounding

How BeeL. turns quantities, prices and rates into bases, taxes and totals: precision, the three ways to price a line, per-group tax rounding, IRPF, and the amounts that are rejected.

You never send a tax amount or an invoice total. You send lines — a quantity, a price, a tax rate — and BeeL. computes the rest. This page explains how, so the numbers you store match the ones on the invoice to the cent.

## Precision

| Value | Decimals | Examples |
|---|---|---|
| Unit price | 4 | `0.0897` per label. More decimals are rounded: `0.08975` is stored as `0.0898`. |
| Amounts (bases, taxes, totals) | 2 | `82.64` |
| Percentages | 3 | `5.2`, `0.62`. AEAT only takes two: on an invoice recorded with VeriFactu, a tax or surcharge rate with more decimals is rejected at issue with `422` [`INVOICE_TAX_RATE_TOO_MANY_DECIMALS`](/errors/INVOICE_TAX_RATE_TOO_MANY_DECIMALS). |

Every rounding is **half up** (`0.005` → `0.01`). All amounts are in **euros**: there is no currency field on an invoice.

> **Rules that apply here:** [TAX-012 · The tax and the billing record are expressed in euros](/rules/taxes#tax-012) · [TAX-013 · Send every amount in euros](/rules/taxes#tax-013)

## Three ways to price a line

Each line carries **exactly one** of these fields; none or more than one is rejected with `422` [`LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`](/errors/LINE_UNIT_PRICE_XOR_DECLARED_TOTAL).

| Field | You state | BeeL. derives | Discount |
|---|---|---|---|
| `unit_price` | Price per unit before taxes | Base = quantity × unit price × (1 − discount) | Allowed |
| `total_excluding_tax` | The line's exact taxable base | The unit price, as `total / quantity` (4 decimals, informational) | Rejected |
| `total_including_tax` | What the customer paid for the line (base + VAT + surcharge) | Base and taxes, worked backwards | Rejected |

- **Discounts are per line.** `discount_percentage` goes from 0 to 100 and applies only to `unit_price` lines. A declared total already includes any discount, so sending both answers `422` [`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`](/errors/LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT). There is no invoice-level discount.
- **Declared totals win over the unit price.** The unit price of a declared-total line is printed because article 6.1.f of the RD 1619/2012 lists it among the contents of an invoice, but the base is never recomputed from it: 1.00 for 300 units is a base of exactly `1.00`, even though `0.0033 × 300` is `0.99`. The response tells you which mode a line used in `pricing_mode`.

> **Rules that apply here:** [CNT-007 · Each line describes the operation, its unit price and any discount](/rules/contents#cnt-007)

## Taxes are rounded per group, not per line

Adding up rounded line taxes does not give the same result as rounding the sum. BeeL. follows the second way, the one your tax return uses:

1. Lines are grouped by tax: VAT (and IGIC, IPSI…) by type, rate and regime key; equivalence surcharge and IRPF by rate.
2. The group's tax is computed **once**, on the sum of its unrounded bases, and rounded to the cent.
3. That amount is spread back over the group's lines: each gets its exact share rounded down, and the leftover cents go to the lines with the largest remainders (ties go to the earlier line).
4. The bases of `unit_price` lines are spread the same way, so the published base of a group always equals its rounded sum.

So a line's tax is not always `round(base × rate)` on its own — but the breakdowns (`vat_breakdown`, `surcharge_breakdown`, `irpf_breakdown`) match `base × rate` for their group, and every line still satisfies `line_total = taxable_base + VAT + surcharge − IRPF`. The one exception is a group that contains lines [priced with taxes included](#lines-priced-with-taxes-included): those lines keep the cent they absorbed, and the breakdown carries it.

A line in the response carries its `taxable_base` and its `line_total`, not its own VAT or IRPF amount: the per-line split below is what those two figures imply.

### Worked example

Three identical lines, 21 % VAT and 15 % IRPF:

```json
{
  "type": "STANDARD",
  "recipient": { "customer_id": "4f244735-980b-8d9c-80e8-6331fa0b1958" },
  "lines": [
    { "description": "Support — week 1", "quantity": 1, "unit_price": 12.45, "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }, "irpf_rate": 15 },
    { "description": "Support — week 2", "quantity": 1, "unit_price": 12.45, "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }, "irpf_rate": 15 },
    { "description": "Support — week 3", "quantity": 1, "unit_price": 12.45, "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" }, "irpf_rate": 15 }
  ]
}
```

| | Line 1 | Line 2 | Line 3 | Invoice |
|---|---|---|---|---|
| Base | 12.45 | 12.45 | 12.45 | **37.35** |
| Exact VAT (21 %) | 2.6145 | 2.6145 | 2.6145 | 7.8435 → **7.84** |
| VAT per line | 2.62 | 2.61 | 2.61 | 7.84 |
| Exact IRPF (15 %) | 1.8675 | 1.8675 | 1.8675 | 5.6025 → **5.60** |
| IRPF per line | 1.87 | 1.87 | 1.86 | 5.60 |
| `line_total` (returned) | 13.20 | 13.19 | 13.20 | **39.59** |

Rounding each line on its own would have given 3 × 2.61 = **7.83** of VAT (one cent short of 21 % of 37.35) and 3 × 1.87 = **5.61** of IRPF (one cent over). Per group, the VAT is 7.84: each line gets 2.61 and the leftover cent goes to line 1, since all three remainders tie. The IRPF is 5.60: two leftover cents go to lines 1 and 2. The invoice returns `taxable_base: 37.35`, `total_vat: 7.84`, `total_irpf: 5.60` and `invoice_total: 39.59`.

> **Rules that apply here:** [CNT-008 · The tax rate and the tax amount are shown apart from the base](/rules/contents#cnt-008) · [CNT-009 · The base is broken down by rate and by kind of operation](/rules/contents#cnt-009) · [REC-012 · Tax amounts are base times rate](/rules/records#rec-012)

## Lines priced with taxes included

For `total_including_tax`, BeeL. works backwards from what the customer paid:

1. **Base** = `total / (1 + VAT + surcharge)`, rounded to the cent.
2. **VAT** = the unrounded base × the VAT rate, rounded.
3. The last tax — the surcharge if there is one, otherwise the VAT — **absorbs the leftover cent**, so that base + VAT + surcharge equals the declared total exactly.

`10.00` including 21 % VAT and a 5.2 % surcharge:

```json
{
  "description": "Retail sale",
  "quantity": 1,
  "total_including_tax": 10.00,
  "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "18" },
  "equivalence_surcharge_rate": 5.2,
  "irpf_rate": 0
}
```

| | Computation | Result |
|---|---|---|
| Base | 10.00 / 1.262 = 7.92393… | **7.92** |
| VAT | 7.92393… × 21 % = 1.66402… | **1.66** |
| Surcharge | 10.00 − 7.92 − 1.66 | **0.42** |

Computed on its own the surcharge would be 0.41, and the line would add up to 9.99. The surcharge takes the cent instead, so the total is the 10.00 you declared, and the base stays the plain rounding of the division. The derived `unit_price` is `7.92`, and the invoice returns `surcharge_breakdown: [{ "type": 5.2, "base": 7.92, "amount": 0.42 }]` — 0.42, not the 0.41 that 5.2 % of 7.92 would give.

These lines keep their own VAT and surcharge; they are not redistributed with the rest of their group, so their declared total always holds. Their IRPF is, since the withholding is outside that total.

## IRPF

- **Rates:** `0`, `1`, `2`, `2.8`, `6`, `7`, `7.6`, `9.5`, `15`, `19` or `24`.
- **Base:** the line's taxable base after discount. On a line priced by total, the final rounded base.
- **Who can bear which rate:** only individuals pay IRPF. A company whose NIF starts with `A`, `B`, `C`, `D`, `F`, `G`, `Q`, `R`, `U` or `W` only accepts `0`, `19`, `24` and `9.5` (rents in Ceuta and Melilla); a non-resident entity (`N`) `0`, `19` and `24`; the State, an Autonomous Community or a local entity (`S`, `P`) only `0`. Any other rate answers `422` [`IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`](/errors/IRPF_RATE_NOT_FOR_CORPORATE_ISSUER), and `9.5` from an individual answers `422` [`IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER`](/errors/IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER) (the Ceuta and Melilla rates of individuals are `6`, `2.8` and `7.6`). It is checked on create, on edit and again on issue; a corrective invoice keeps what its original carried. `withholding_options` in [Get the tax configuration of a company](/tax-configuration/getCompanyTaxConfiguration) lists the rates the company can use (`allowed_irpf_rates`) and the one to preselect (`suggested_irpf_rate`, `null` when it depends on facts the NIF does not show).
- **Default:** a line of a `STANDARD` invoice (or a proforma) that omits `irpf_rate` takes the company's default IRPF rate (see [Get the tax configuration of a company](/tax-configuration/getCompanyTaxConfiguration)). With a default of 15 %, a 100.00 line sent without `irpf_rate` comes back with `irpf_rate: 15` and a `line_total` of 106.00. To invoice without withholding, send `irpf_rate: 0` explicitly. A stored default the company cannot bear is not applied: the line gets `0`.
- **Simplified invoices** cannot carry IRPF: any rate other than 0 is rejected with `422` [`SIMPLIFICADA_FORBIDS_IRPF`](/errors/SIMPLIFICADA_FORBIDS_IRPF), never silently dropped. A simplified line that omits `irpf_rate` gets `0`, not the company default.
- **Recurring invoice templates** do not inherit the default either: a template line without `irpf_rate` generates invoices with no withholding. Send the rate on the template line if you need it.
- **Accepted rates** are the list above; any other value answers `422` [`VALIDATION_ERROR`](/errors/VALIDATION_ERROR) naming the line (`lines[0].irpf_rate`). The value counts, not how it is written: `15.0` is `15` and `2.80` is `2.8`.
- **Not part of the price.** IRPF is withheld from what the customer pays: it lowers `line_total` and `invoice_total`, and it is never part of a `total_including_tax`.

> **Rules that apply here:** [TAX-009 · IRPF withholding is not part of the total AEAT receives](/rules/taxes#tax-009) · [TAX-010 · Set the IRPF withholding when the customer must withhold](/rules/taxes#tax-010)

## Amounts that are rejected

| You send | Error | Status | Why |
|---|---|---|---|
| A negative total on anything but a [corrective invoice](/verifactu/corrective-invoices) | [`NEGATIVE_TOTAL_REQUIRES_RECTIFICATIVE`](/errors/NEGATIVE_TOTAL_REQUIRES_RECTIFICATIVE) | `422` | The lines add up to a negative total on an invoice that is not a corrective one. Individual negative lines (a discount, a deposit) are allowed; a negative invoice is a refund, and a refund has to reference the invoice it refunds. |
| A line with quantity `0` | [`LINE_INVALID_QUANTITY`](/errors/LINE_INVALID_QUANTITY) | `422` | A line's `quantity` is zero or missing. A negative quantity is accepted, as long as the invoice total stays positive. |
| A negative `unit_price` (`lines[0].unit_price` in `error.details`) | [`VALIDATION_ERROR`](/errors/VALIDATION_ERROR) | `422` | Part of the request is not acceptable. With `422`, the body parsed but a value breaks a rule: a missing required property, a length or range, a value outside an enum. `error.details` maps each offending property (`snake_case`, nested paths joined with a dot) to what is wrong with it. With `400`, the problem is in the URL: an unknown query parameter, a value that does not parse, such as a malformed UUID or an unknown enum value, an empty element in a list filter (`status=ISSUED,`; a parameter sent entirely empty, `status=`, is the same as omitting it) or a `sort_by` the list does not sort by. `error.details` names the parameter. |
| A discount outside 0–100 (`lines[0].discount_percentage` in `error.details`) | [`VALIDATION_ERROR`](/errors/VALIDATION_ERROR) | `422` | Part of the request is not acceptable. With `422`, the body parsed but a value breaks a rule: a missing required property, a length or range, a value outside an enum. `error.details` maps each offending property (`snake_case`, nested paths joined with a dot) to what is wrong with it. With `400`, the problem is in the URL: an unknown query parameter, a value that does not parse, such as a malformed UUID or an unknown enum value, an empty element in a list filter (`status=ISSUED,`; a parameter sent entirely empty, `status=`, is the same as omitting it) or a `sort_by` the list does not sort by. `error.details` names the parameter. |
| A declared total together with a discount | [`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`](/errors/LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT) | `422` | A line priced by a declared total (`total_excluding_tax` or `total_including_tax`) also sends a `discount_percentage` other than `0`. A declared total already includes any discount; `discount_percentage: 0` is accepted. |
| A declared total whose derived unit price does not fit | [`LINE_UNIT_PRICE_OUT_OF_RANGE`](/errors/LINE_UNIT_PRICE_OUT_OF_RANGE) | `422` | The unit price derived from a line's declared total does not fit the accepted range, typically a large total over a tiny quantity. `error.details.field` names the line, with `max` and `derived_unit_price`. |
| An invoice whose lines are all `SUPLIDO` | [`INVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE`](/errors/INVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE) | `422` | Every line of the invoice is a disbursement (`SUPLIDO`). An invoice of disbursements alone documents no operation, so it is not an invoice. |

A total of zero is accepted. An invoice whose `total_to_pay` is 0 has nothing to collect, so it is issued as `PAID`, with `payment_date` equal to `issue_date`, and registered with the AEAT like any other.

Negative amounts on a line are accepted — a negative quantity, or a negative declared total
(`total_excluding_tax`, `total_including_tax`) — as long as the invoice total stays positive.

You never send a line's result: `taxable_base` and `line_total` are output fields. If a line carries them, they are ignored and computed again from the quantity, price and rates.

> **Rules that apply here:** [CNT-019 · Only a corrective invoice totals less than zero](/rules/contents#cnt-019)

## Issued invoices are never recalculated

Once an invoice is issued its amounts are a fact: its JSON, its PDF and its VeriFactu record say the same thing, and keep saying it. If BeeL. improves the way it computes amounts, the change applies to new invoices only. Two identical lines issued months apart can therefore split a cent differently between base and tax, with the same total.

> **Rules that apply here:** [LIF-001 · An issued invoice is never edited or deleted](/rules/lifecycle#lif-001)

## Related

<Related>

- [Invoice lifecycle](/guides/invoice-lifecycle) — what each status allows, from draft to paid
- [Equivalence surcharge](/verifactu/equivalence-surcharge) — when the surcharge applies and how it pairs with VAT
- [Corrective invoices](/verifactu/corrective-invoices) — negative amounts, done the fiscal way

</Related>

---

Full OpenAPI spec: https://docs.beel.es/api/openapi