Recurring writes reject what they used to swallow
Payment detail without a method, an inactive series, a window with no occurrence and a blank name are now rejected instead of silently discarded.
Five ways a write to a recurring template used to answer 200/201 and quietly do something other than what you asked. They are now named rejections, each with its own code.
If a request of yours is in this list, it was never doing what you thought — but it did answer success, so check before the next deploy.
What breaks
- The trap:
PATCH {"payment_iban": "ES…"}with nopayment_method. It answered200and changed nothing. It now answers422 PAYMENT_DETAILS_REQUIRE_METHOD.payment_iban,payment_swiftandpayment_term_daysare detail ofpayment_method, not standalone fields: they are only accepted alongside a method that is present, non-null and notNONE. Clearing the payment method is unchanged — sendpayment_method: nullwithout mentioning the other three and they are released, as always. - A template numbering against an inactive series is rejected with
SERIES_INACTIVE, and nothing is written. It is judged on the resulting template, so an update that leaves an inactive series in place is rejected too, even when the body does not mention the series. - A
frequencyand anend_datethat are impossible together — no occurrence at all betweenstart_dateandend_date, the everyday case for a yearly template created with the year under way — are rejected withRECURRING_NO_OCCURRENCE_IN_WINDOW, hanging offend_date, the field you can move. It is notRECURRING_END_DATE_BEFORE_START: that one says the next generation falls past the end, this one says there is no first invoice and never will be. - A line priced by declared total whose derived unit price does not fit is rejected with
LINE_UNIT_PRICE_OUT_OF_RANGE, whosedetailsname the offending line and carry the limit and the derived quotient as JSON numbers — so you do not have to parse the message. - A blank
nameis rejected as empty rather than as "characters not allowed", on all five write requests. - A line whose exemption requires a specific recipient identifier (today
EXENTA_ART_25needsNIF_IVA) is rejected withEXEMPTION_REQUIRES_RECIPIENT_ID_TYPEwhen the referenced customer does not carry it — the same rejection invoice creation already gave, brought forward to the template instead of every generated invoice bouncing at the AEAT. RECURRING_ALREADY_ENDEDanswers409, not422— since 8 September, and in every place it appears: writes, generate and preview. A completed template rejects every write, including aPATCHthat only touches notes. If you branch on the status code, that code moved; if you branch onerror.code, nothing changed.
Does this affect you?
- Grep your client for a write to a recurring template that sends
payment_iban,payment_swiftorpayment_term_days. Ifpayment_methodis not in the same body, that call now answers422. - Replay a template creation against an inactive series, and one with a yearly cadence inside a window shorter than a year: both used to succeed.
- Check whether you treat
RECURRING_ALREADY_ENDEDas a422.
What else changed
- None of this changes a read. Templates already stored are untouched — what changes is which writes are accepted.
- Block replacement is unchanged. Changing
payment_methodwithout resending the detail still releases it, and aPATCHthat mentions none of the four fields still leaves them alone.
Endpoints
- POST|PUT|PATCH/v1/companies/{company_id}/recurring-invoicesNew rejections: PAYMENT_DETAILS_REQUIRE_METHOD, SERIES_INACTIVE, RECURRING_NO_OCCURRENCE_IN_WINDOW, LINE_UNIT_PRICE_OUT_OF_RANGE
Where to go next
Recurring templates: quarterly, yearly, and an invoice cap
Templates gain quarterly and yearly cadences, an end after `max_invoices`, a `completion` reason, `draft_in_advance`, and the amount of the next invoice on every row.
The deprecated flat routes now retire on 9 December
The retirement date of the deprecated flat routes moves from 10 September to **2026-12-09**. Nothing stopped answering, and you have the whole window again.