{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/beel-es/json-schema/beel-es-update-recurring-invoice-request-schema.json",
  "title": "UpdateRecurringInvoiceRequest",
  "description": "Request to **replace** a recurring invoice (`PUT`).\n\nSend the template complete: `end_date`, `payment_method` (with its `payment_iban`,\n`payment_swift` and `payment_term_days`), `notes` and `email_configuration` are\ncleared when you leave them out. To change a few fields and keep the rest\nuntouched, use `PATCH /v1/recurring-invoices/{recurring_invoice_id}`.\n\n`custom_fields` is not part of this request: a replacement keeps the template's custom\nfields as they are. Change them with\n`PATCH /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}`.\n",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/beel-es-openapi.json#/components/schemas/UpdateRecurringInvoiceRequest",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 255,
      "pattern": "^\\S.*$"
    },
    "frequency": {
      "type": "string",
      "enum": [
        "MONTHLY",
        "QUARTERLY",
        "YEARLY"
      ],
      "description": "Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, the current one is kept — it is\nnever reset to `MONTHLY`.\n\n**Changing it reschedules the template.** The next generation is recalculated on the new\ngrid — the `day_of_month` dates anchored at `start_date`, one every cadence — so it can\nland months later than the one you saw before the call. **If the template has already\nissued invoices, the landing respects that**: it is never a period that was already\nbilled, and never before today either — the first point of the new grid strictly after\nthe last invoiced one. It also **discards a pending skip**: the skipped period only\nexisted as a point of the old grid, and that grid is gone. Send the same cadence and\nnothing is rescheduled.\n"
    },
    "day_of_month": {
      "type": "integer",
      "minimum": 1,
      "maximum": 31,
      "description": "Day of the month the invoices are issued. A month that does not have that day falls\nback to its last day: a template on `31` issues on 28 February (29 in a leap year)\nand on 30 April. So `31` is how you ask for the last day of the month it generates\nin — there is no separate flag for it, and the accepted range stays 1–31.\n\nThe adjustment does not stick and the schedule does not drift: every generation is\nrecalculated from the `day_of_month` you sent, never from the date it was adjusted\nto (31 Jan → 28 Feb → 31 Mar).\n"
    },
    "start_date": {
      "type": "string",
      "format": "date",
      "description": "Date the subscription started. A past date is accepted and stored as sent — useful\nwhen migrating subscriptions from another system — but it never anchors generation\nin the past. `next_generation` becomes the first date of the template's own calendar\non or after today (today included): the grid of `day_of_month` dates anchored at\n`start_date`, one every `frequency`. On a quarterly or yearly template that can be\nmonths from now, not this month. Invoices are never back-dated, so the missed periods\nare not generated. If today's period was already invoiced in the system you are\nmigrating from, send a `start_date` of tomorrow (or of the next period) so it is not\ninvoiced twice.\n\nOnce the template has issued, only a *different* date is rejected with a 422\n`RECURRING_START_DATE_NOT_EDITABLE`. Resending the value it already has is a no-op,\nso this request can keep sending the template complete, as it asks above.\n"
    },
    "draft_in_advance": {
      "type": "boolean",
      "description": "Whether the template prepares a draft for review before emitting. The window is fixed at\n5 days and both options emit on the scheduled day. Generating manually a period whose\nscheduled date is today or earlier emits it on the spot. Omitted, the current value is\nkept.\n"
    },
    "preview_days": {
      "type": "integer",
      "minimum": 0,
      "maximum": 30,
      "deprecated": true,
      "description": "**Deprecated.** Superseded by `draft_in_advance`, because the review window is no longer\na number you pick: it is fixed at 5 days. Still accepted so nothing breaks — any value\ngreater than `0` means the same as `draft_in_advance: true`, and `0` the same as `false`.\nWhen both are sent, `draft_in_advance` wins. It will be removed in a future version.\n"
    },
    "end_date": {
      "type": [
        "string",
        "null"
      ],
      "format": "date"
    },
    "max_invoices": {
      "type": [
        "integer",
        "null"
      ],
      "description": "Total number of invoices this template will generate before ending on its own, between\n2 and 600. Like `end_date`, it is **cleared when you leave it out**: this is a replacement.\n\nA recurrence ends in ONE way: open-ended, on `end_date`, or after `max_invoices`. Sending\nboth with a value is rejected with `RECURRING_END_MODE_CONFLICT`. The range is not enforced\nby this schema on purpose, so the rejection carries its own code and its own message: below\n2 you do not want a recurrence but a scheduled invoice (`PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule`).\n\nLowering it below the invoices already generated is rejected: issued invoices are fiscal\nfacts and are not undone by moving the cap. Setting it exactly to the number already\ngenerated is accepted and ends the template in that same call.\n"
    },
    "series_id": {
      "type": "string",
      "format": "uuid"
    },
    "invoice_type": {
      "allOf": [
        {
          "$ref": "#/$defs/RecurringInvoiceType"
        }
      ],
      "description": "Type of the invoices the template generates. Omitted, the current type is kept.\nA change is judged on the resulting template, with the rules of the new type: its series\nmust accept it (`SERIES_INCOMPATIBLE_DOC_TYPE` otherwise), a `SIMPLIFIED` template\ncannot keep an identified recipient (`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`)\nnor lines a simplified invoice does not admit. Send the new `series_id`, and\n`customer_id: null` when moving to `SIMPLIFIED`, in the same call.\n"
    },
    "customer_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "Recipient of the generated invoices. Send `null` to leave the template without\na recipient; omit it to keep the current one.\n"
    },
    "lines": {
      "type": [
        "array",
        "null"
      ],
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/RecurringLineRequest"
      },
      "description": "Template lines, replaced as a whole. Omit them (or send `null`) to keep the\ncurrent ones — a template with no lines invoices nothing, so an empty array is\nrejected.\n"
    },
    "payment_method": {
      "allOf": [
        {
          "$ref": "#/$defs/PaymentMethod"
        }
      ],
      "nullable": true,
      "description": "Payment method. `payment_iban`, `payment_swift` and `payment_term_days` are\ndetail of this method, not standalone fields: sending any of them without a\n`payment_method` that is present, non-null and different from `NONE` is\nrejected with `422` (`PAYMENT_DETAILS_REQUIRE_METHOD`).\n"
    },
    "payment_iban": {
      "type": [
        "string",
        "null"
      ]
    },
    "payment_swift": {
      "type": [
        "string",
        "null"
      ]
    },
    "payment_term_days": {
      "type": [
        "integer",
        "null"
      ]
    },
    "notes": {
      "type": [
        "string",
        "null"
      ]
    },
    "send_automatically": {
      "type": "boolean"
    },
    "email_configuration": {
      "$ref": "#/$defs/RecurringEmailConfigRequest"
    }
  },
  "$defs": {
    "Email": {
      "type": "string",
      "format": "email",
      "minLength": 5,
      "maxLength": 255,
      "description": "Email address (minimum valid email is 5 chars, e.g. a@b.co)"
    },
    "ExemptionReason": {
      "type": "string",
      "enum": [
        "EXENTA_ART_20",
        "EXENTA_ART_21",
        "EXENTA_ART_22",
        "EXENTA_ART_24",
        "EXENTA_ART_25",
        "EXENTA_ART_26",
        "EXENTA_ART_140",
        "NO_SUJETA_ART_7_9",
        "NO_SUJETA_LOCALIZACION",
        "ISP_ART_84_2_A",
        "ISP_ART_84_2_B",
        "ISP_ART_84_2_C",
        "ISP_ART_84_2_D",
        "ISP_ART_84_2_E",
        "ISP_ART_84_2_F",
        "ISP_ART_84_2_G",
        "REGIMEN_ART_129",
        "REGIMEN_ART_135",
        "REGIMEN_ART_141",
        "REGIMEN_ART_154",
        "REGIMEN_ART_163_DECIES",
        "OTRO"
      ],
      "description": "Tax exemption reason code per the Spanish VAT Law (Ley 37/1992, LIVA), with the\nVeriFactu code each one is reported as.\n\n- `EXENTA_ART_20`: exempt, art. 20 (domestic operations such as medical, educational,\n  cultural and financial services, or housing rentals). E1.\n- `EXENTA_ART_21`: exempt, art. 21 (exports of goods). E2.\n- `EXENTA_ART_22`: exempt, art. 22 (operations treated as exports). E3.\n- `EXENTA_ART_24`: exempt, art. 24 (free zones, warehouses and customs regimes). E4.\n- `EXENTA_ART_25`: exempt, art. 25 (intra-community supplies of goods). E5.\n- `EXENTA_ART_26`: exempt, art. 26 (intra-community acquisitions of goods). It exempts the\n  buyer's acquisition, not a supply the seller invoices, so an invoice line that carries it\n  is rejected with `EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a supply to another Member State is\n  `EXENTA_ART_25`.\n- `NO_SUJETA_ART_7_9`: not subject under art. 7 (such as the transfer of a business as\n  a going concern, art. 7.1º). N1.\n- `NO_SUJETA_LOCALIZACION`: not subject by the place-of-supply rules (intra-community\n  or non-EU services, arts. 69 and 70). N2.\n- `ISP_ART_84_2_A` … `ISP_ART_84_2_F`: reverse charge (the invoice states «inversión del\n  sujeto pasivo»), art. 84.Uno.2.º letters a) (supplier not established in Spain), b) (unwrought\n  or semi-finished gold), c) (scrap, waste and recovery materials, plastic, paper, cardboard, glass and textile waste, and semi-finished non-ferrous metal products), d) (greenhouse gas emission\n  allowances), e) (certain real estate supplies: in insolvency proceedings, with the exemption\n  waived, or enforcing a security) and f) (construction or renovation works). S2.\n- `ISP_ART_84_2_G`: reverse charge of letter g) (silver, platinum, palladium, mobile phones,\n  consoles, laptops and tablets). The law requires these supplies to be invoiced in a special\n  series, so an invoice line that carries it is rejected with\n  `REVERSE_CHARGE_CASE_NOT_SUPPORTED`.\n- `EXENTA_ART_140`: investment gold exemption, art. 140 bis (usually with `regime_key`\n  `04`). E6.\n- `REGIMEN_ART_129` (agriculture,\n  livestock and fishing, arts. 124 to 134 bis), `REGIMEN_ART_135` (second-hand goods,\n  art and antiques), `REGIMEN_ART_141` (travel agencies), `REGIMEN_ART_154` (equivalence\n  surcharge) and `REGIMEN_ART_163_DECIES` (cash basis, arts. 163 decies to 163\n  sexiesdecies): operations of special regimes, which VeriFactu identifies by the regime\n  key rather than by an exemption code. An\n  invoice line that carries one is rejected with `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU`;\n  declare the regime with `regime_key` instead.\n- `OTRO`: any other provision. Requires the text in `exemption_reason_text`. E6.\n"
    },
    "PaymentMethod": {
      "type": "string",
      "description": "Payment method shown on invoices and recurring invoices.\n\n- `NONE`: no payment information is shown.\n- `BANK_TRANSFER`: bank transfer to the IBAN of the payment details; the only method\n  that requires an IBAN.\n- `CARD`: card payment.\n- `CASH`: cash payment.\n- `CHECK`: payment by cheque.\n- `DIRECT_DEBIT`: direct debit from the customer's bank account.\n- `BIZUM`: payment through Bizum.\n- `OTHER`: any other method.\n",
      "enum": [
        "NONE",
        "BANK_TRANSFER",
        "CARD",
        "CASH",
        "CHECK",
        "DIRECT_DEBIT",
        "BIZUM",
        "OTHER"
      ]
    },
    "RecurringEmailConfigRequest": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "recipients": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Email"
          }
        },
        "cc": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Email"
          }
        },
        "subject": {
          "type": [
            "string",
            "null"
          ]
        },
        "message": {
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "RecurringInvoiceType": {
      "type": "string",
      "enum": [
        "STANDARD",
        "SIMPLIFIED"
      ],
      "description": "Type of the invoices a recurring template generates: `STANDARD` for an identified recipient,\n`SIMPLIFIED` for a recipient that is not identified.\n"
    },
    "RecurringLineRequest": {
      "type": "object",
      "description": "Recurring-invoice line. Unlike invoice lines (which nest tax data under a `main_tax` object),\nrecurring lines use flat tax fields: `vat_rate`, `tax_type`, `regime_key`,\n`equivalence_surcharge_rate` and `irpf_rate`. Do not send a `main_tax` object here.\n\n**Line amount**: send **exactly one** of `unit_price`, `total_excluding_tax` or\n`total_including_tax` — same contract as an invoice line. Sending none, or more than\none, is rejected with `LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`; a declared total together\nwith an explicit `discount_percentage` is rejected with\n`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT` (the discount, if any, already lives inside\nthe total).\n\nThe mode is what the template promises, and it is re-applied on **every** generation:\na line declared as `total_including_tax: 1.00` over 300 units invoices 1,00 € on every\ngeneration, not 300 × 0,0033 = 0,99 €. Because `PATCH` replaces the lines as a whole,\nresending a line with a different amount field switches its mode.\n",
      "required": [
        "description",
        "quantity",
        "vat_rate"
      ],
      "properties": {
        "description": {
          "type": "string",
          "maxLength": 2000
        },
        "quantity": {
          "type": "number",
          "minimum": 0.01,
          "multipleOf": 0.0001
        },
        "unit": {
          "type": "string",
          "maxLength": 20
        },
        "unit_price": {
          "type": "number",
          "minimum": 0,
          "maximum": 999999.9999,
          "description": "Unit price before taxes. Supports up to 4 decimal places for micro-pricing\n(e.g. €0.0897/unit for labels, packaging); the generated invoices always round\ntheir amounts to 2 decimals. Mutually exclusive with `total_excluding_tax` and\n`total_including_tax`.\n\nThe upper bound is the same one the invoice line declares, and so is the\nacceptance of `0` (a discount granted before or simultaneously with the sale,\ne.g. a free introductory month). A price outside the range is rejected with\n`422 VALIDATION_ERROR` and the offending field in `details`.\n\nNegative prices are NOT accepted, unlike an invoice line of a corrective invoice:\na recurring template only issues `STANDARD` or `SIMPLIFIED` invoices, and a\ncorrective is created through its own endpoint over an already issued invoice —\nnever generated by a template.\n"
        },
        "total_excluding_tax": {
          "type": "number",
          "minimum": 0,
          "maximum": 99999999.99,
          "description": "Declared line total excluding taxes: this amount IS the taxable base, exactly,\nwith no recalculation. The stored `unit_price` becomes derived and informational\n(`total / quantity`, 4 decimals). Mutually exclusive with `unit_price` and\n`total_including_tax`.\n\nNegative totals are NOT accepted, for the same reason `unit_price` does not accept\nnegative prices: a template only issues `STANDARD` or `SIMPLIFIED` invoices, and only\na corrective invoice — created through its own endpoint over an already issued\ninvoice — carries a negative amount. A total outside the range is rejected with\n`422 VALIDATION_ERROR` and the offending field in `details`.\n\nThe upper bound is the same one `POST /v1/invoices` declares for a line total: a\ntemplate is a promise to issue an invoice, and it cannot accept less than the\ninvoice it will generate does. The derived `unit_price` (`total / quantity`) can\nstill exceed the `maximum` this contract accepts for `unit_price` itself — what\nactually bounds it then is the domain's price ceiling, not this field's contract.\n"
        },
        "total_including_tax": {
          "type": "number",
          "minimum": 0,
          "maximum": 99999999.99,
          "description": "Declared line total including taxes — what the customer pays. The engine works\nthe breakdown backwards so that taxable base + VAT + equivalence surcharge equals\nthis amount exactly on every generated invoice. IRPF withholding is never part of\nthe decomposition: it is a retention, not a price. Mutually exclusive with\n`unit_price` and `total_excluding_tax`.\n\nNegative totals are NOT accepted, same as `total_excluding_tax`.\n\nThe upper bound is the same one `POST /v1/invoices` declares for a line total, for\nthe same reason: the template cannot promise more than the invoice it generates\ncould ever accept. The derived `unit_price` (`total / quantity`) can still exceed\nthe `maximum` this contract accepts for `unit_price` itself — what actually bounds\nit then is the domain's price ceiling, not this field's contract.\n"
        },
        "discount_percentage": {
          "type": "number",
          "minimum": 0,
          "maximum": 100
        },
        "tax_type": {
          "type": "string",
          "description": "Tax type. Omitted, `IVA` applies."
        },
        "vat_rate": {
          "type": "number"
        },
        "regime_key": {
          "type": "string",
          "description": "VeriFactu regime key. Omitted, `01` (general regime) applies."
        },
        "equivalence_surcharge_rate": {
          "type": [
            "number",
            "null"
          ]
        },
        "irpf_rate": {
          "type": [
            "number",
            "null"
          ]
        },
        "exemption_reason": {
          "$ref": "#/$defs/ExemptionReason",
          "nullable": true
        },
        "exemption_reason_text": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "Custom exemption text. Only used when `exemption_reason` is `OTRO`.\n\nSame shape as an invoice line: the template declares WHY the operation carries no tax,\nand every invoice it generates inherits it. A 0% line without a reason is rejected on\nwrite — VeriFactu does not accept an exempt line with no explicit motive.\n"
        }
      }
    }
  }
}