NewProvince is only required for addresses in Spain
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

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

ValueDecimalsExamples
Unit price40.0897 per label. More decimals are rounded: 0.08975 is stored as 0.0898.
Amounts (bases, taxes, totals)282.64
Percentages35.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.

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

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.

FieldYou stateBeeL. derivesDiscount
unit_pricePrice per unit before taxesBase = quantity × unit price × (1 − discount)Allowed
total_excluding_taxThe line's exact taxable baseThe unit price, as total / quantity (4 decimals, informational)Rejected
total_including_taxWhat the customer paid for the line (base + VAT + surcharge)Base and taxes, worked backwardsRejected
  • 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. 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.

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

{
  "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 1Line 2Line 3Invoice
Base12.4512.4512.4537.35
Exact VAT (21 %)2.61452.61452.61457.8435 → 7.84
VAT per line2.622.612.617.84
Exact IRPF (15 %)1.86751.86751.86755.6025 → 5.60
IRPF per line1.871.871.865.60
line_total (returned)13.2013.1913.2039.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.

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:

{
  "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
}
ComputationResult
Base10.00 / 1.262 = 7.92393…7.92
VAT7.92393… × 21 % = 1.66402…1.66
Surcharge10.00 − 7.92 − 1.660.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, and 9.5 from an individual answers 422 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 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). 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, 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 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.

Amounts that are rejected

You sendErrorStatusWhy
A negative total on anything but a corrective invoiceNEGATIVE_TOTAL_REQUIRES_RECTIFICATIVE422The 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 0LINE_INVALID_QUANTITY422A 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_ERROR422Part 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_ERROR422Part 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 discountLINE_DECLARED_TOTAL_FORBIDS_DISCOUNT422A 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 fitLINE_UNIT_PRICE_OUT_OF_RANGE422The 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 SUPLIDOINVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE422Every 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.

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.