NewStripe: a buyer with a NIF always gets a standard invoice, and the simplified threshold is deprecated
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog
Recurring Invoices

Update a recurring invoice

Scopeinvoices:write

Updates only the fields present in the body, leaving every other field of the recurring invoice template as it is.

  • Omitted vs null: an omitted field keeps its current value; a field sent as null is cleared, and only where the request schema documents the field as nullable.
  • lines: replaced as a whole, not patched line by line. The recipient survives the change, and an empty array is rejected.
  • payment_method: replaced as a whole together with payment_iban, payment_swift and payment_term_days — send them in the same request or they are dropped.
  • Schedule: frequency, day_of_month and start_date stay put unless you send them; sending a new value for any of the three moves the next generation — resending the ones already in effect changes nothing. Changing frequency recalculates it on the new grid and discards a pending skip. start_date is only editable while the template has not generated any invoice yet.

PATCH
/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}
AuthorizationBearer <token>

Keys are prefixed beel_sk_, and each one carries the scopes it was created with: a key short of the scope an operation needs is answered 403. The scope an operation requires is shown next to its title, and the full catalogue lives in the Scopes reference.

Keys are created from the BeeL dashboard. They are secret credentials: do not share them or commit them to source control.

In: header

Path Parameters

company_idstring

Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the BeeL-Active-Company header plays no part. A company you do not reach answers 403, and so does a company that does not exist, so the existence of a company in another account is never disclosed.

Formatuuid
recurring_invoice_idstring

Unique identifier (UUID) of the recurring invoice template, as returned when it is created or listed. A template of another company answers 404 with RECURRING_NOT_FOUND, exactly like one that does not exist.

Formatuuid

Header Parameters

Idempotency-Key?string

Idempotency key to prevent duplicates in sensitive operations.

  • Any unique client-generated string (e.g. an order id). A UUID also works but is not required
  • Allowed characters: letters, digits, _ and - (max 255 chars)
  • Retrying with the same key replays the first response when it was a success (2xx) or a server error (5xx): same status and body, plus the header Idempotency-Replay: true. After a 5xx, check whether the operation took effect before retrying with a new key
  • A 4xx is not stored: the key is released, so the corrected request can reuse it
  • Stored responses expire 24 hours after processing

The key is scoped per user and environment, and bound to the request body, so retrying after a network timeout replays the stored response instead of repeating the operation.

StatusCodeWhen
400INVALID_IDEMPOTENCY_KEYThe key breaks the format rules above.
409IDEMPOTENCY_KEY_PROCESSINGThe first request is still in flight. Wait for the Retry-After seconds (2) and retry with the same key.
409IDEMPOTENCY_KEY_MISMATCHThe key was already used with a different body. Use a new key.
Match^[a-zA-Z0-9_-]+$
Lengthlength <= 255
name?string

Template name. Cannot be cleared.

Match^\S.*$
Length1 <= length <= 255
frequency?string

Generation cadence: MONTHLY every month, QUARTERLY every 3 months, YEARLY every 12 months. Omitted, the current one is kept — it is never reset to MONTHLY.

Changing it reschedules the template. The next generation is recalculated on the new grid — the day_of_month dates anchored at start_date, one every cadence — so it can land months later than the one you saw before the call. If the template has already issued invoices, the landing respects that: it is never a period that was already billed, and never before today either — the first point of the new grid strictly after the last invoiced one. It also discards a pending skip: the skipped period only existed as a point of the old grid, and that grid is gone. Send the same cadence and nothing is rescheduled.

Value in"MONTHLY" | "QUARTERLY" | "YEARLY"
day_of_month?integer

Day of the month the invoices are issued. Moves the next generation immediately, on an ACTIVE template and on a PAUSED one alike: the response to this very call already carries the new next_generation. On a paused template the new day rules from the edit, not from the day you resume — resuming keeps the date this call left, so editing the day of a paused schedule is never ignored for a round.

A month that does not have that day falls back to its last day: a template on 31 issues on 28 February (29 in a leap year) and on 30 April. So 31 is how you ask for the last day of the month it generates in — there is no separate flag for it, and the accepted range stays 1–31.

The adjustment does not stick and the schedule does not drift: every generation is recalculated from the day_of_month you sent, never from the date it was adjusted to (31 Jan → 28 Feb → 31 Mar).

Range1 <= value <= 31
start_date?string

First issue date. Only changeable while the template has not generated any invoice yet: once it has issued, a different date is rejected with a 422 RECURRING_START_DATE_NOT_EDITABLE. Sending the value it already has is a no-op and succeeds, so a read-modify-write cycle never has to strip the field out of the body.

A past date is accepted and stored as sent, but it never anchors generation in the past: next_generation becomes the first date of the template's own calendar on or after today (today included) — the grid of day_of_month dates anchored at start_date, one every frequency — which on a quarterly or yearly template can be months from now. Creating and updating apply the same rule. If today's period was already invoiced in the system you are migrating from, send a start_date of tomorrow (or of the next period) so it is not invoiced twice.

Formatdate
draft_in_advance?boolean

Whether the template prepares a draft for review before emitting. The window is fixed at 5 days and both options emit on the scheduled day. Generating manually a period whose scheduled date is today or earlier emits it on the spot. Omitted, the current value is kept.

preview_days?integerDeprecated

Deprecated. Superseded by draft_in_advance. Any value greater than 0 means the same as draft_in_advance: true, and 0 the same as false. When both are sent, draft_in_advance wins. It will be removed in a future version.

Range0 <= value <= 30
end_date?string|null

Date the recurrence stops. Send null to make it open-ended.

Formatdate
max_invoices?integer|null

Total number of invoices this template will generate before ending on its own, between 2 and 600. Send null to stop capping the recurrence by number.

Editing it MOVES THE GOAL, it does not add turns: above the invoices already generated the template stays active; exactly equal ends it in this very call (with completion.reason = MAX_INVOICES_REACHED); below it the call is rejected — issued invoices are fiscal facts, and someone typing a lower number is asking to stop now, which is what ending the recurrence is for.

A recurrence ends in ONE way, and the conflict is judged on the RESULTING state, not on the body: sending max_invoices to a template that already has an end_date is rejected with RECURRING_END_MODE_CONFLICT even though the body only mentions one of them. Switching mode is a single call that says both things — the new field with a value and the old one as null. Nothing is cleared silently.

series_id?string

Series the generated invoices are numbered in. Cannot be cleared.

Formatuuid
invoice_type?string
Value in"STANDARD" | "SIMPLIFIED"
customer_id?string|null

Recipient of the generated invoices. Send null to leave the template without a recipient; omit it to keep the current one.

Formatuuid
lines?|null

Template lines, replaced as a whole (they are not patched line by line). Omit them to keep the current ones — a template with no lines invoices nothing, so an empty array is rejected.

Items1 <= items
payment_method?string
Value in"NONE" | "BANK_TRANSFER" | "CARD" | "CASH" | "CHECK" | "DIRECT_DEBIT" | "BIZUM" | "OTHER"
payment_iban?string|null
payment_swift?string|null
payment_term_days?integer|null
notes?string|null

Notes printed on the generated invoices. Send null to clear them.

send_automatically?boolean
email_configuration?|null

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X PATCH "https://app.beel.es/api/v1/companies/497f6eca-6276-4993-bfeb-53cbbbba6f08/recurring-invoices/497f6eca-6276-4993-bfeb-53cbbbba6f08" \  -H "Content-Type: application/json" \  -d '{    "frequency": "QUARTERLY",    "day_of_month": 5,    "end_date": "2027-12-31"  }'
{
  "success": true,
  "data": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "name": "string",
    "frequency": "MONTHLY",
    "day_of_month": 0,
    "start_date": "2019-08-24",
    "end_date": "2019-08-24",
    "next_generation": "2019-08-24",
    "draft_in_advance": true,
    "status": "ACTIVE",
    "is_failing": false,
    "pause": {
      "reason": "GENERATION_FAILURE",
      "since": "2019-08-24T14:15:22Z",
      "blocker": "NIF_NOT_REGISTERED"
    },
    "completion": {
      "reason": "USER",
      "at": "2019-08-24T14:15:22Z"
    },
    "series_id": "0f6582ac-1e7e-41dc-a63a-24fccd074d0c",
    "series_code": "string",
    "invoice_type": "STANDARD",
    "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
    "recipient_fiscal_name": "string",
    "recipient_nif": "string",
    "recipient_alternative_id": {
      "type": "NIF_IVA",
      "number": "string",
      "country_code": "st"
    },
    "lines": [
      {
        "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
        "order": 0,
        "description": "string",
        "quantity": 0,
        "unit": "string",
        "unit_price": 0,
        "pricing_mode": "UNIT_PRICE",
        "total_excluding_tax": 1,
        "total_including_tax": 100,
        "discount_percentage": 0,
        "tax_type": "string",
        "vat_rate": 0,
        "regime_key": "string",
        "equivalence_surcharge_rate": 0,
        "irpf_rate": 0,
        "exemption_reason": "EXENTA_ART_20",
        "exemption_reason_text": "string"
      }
    ],
    "amount": 106,
    "payment_method": "string",
    "payment_iban": "string",
    "payment_swift": "string",
    "payment_term_days": 0,
    "notes": "string",
    "send_automatically": true,
    "email_configuration": {
      "recipients": [
        "string"
      ],
      "cc": [
        "string"
      ],
      "subject": "string",
      "message": "string"
    },
    "generated_invoices": 0,
    "max_invoices": 6,
    "last_generated_at": "2019-08-24T14:15:22Z",
    "last_generated_scheduled_date": "2026-05-01",
    "source_invoice_id": "0c7d5f47-8759-4ef4-82d5-705413539f77",
    "created_at": "2019-08-24T14:15:22Z",
    "updated_at": "2019-08-24T14:15:22Z"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "INVALID_JSON_FORMAT",
    "message": "The field 'due_date' has an invalid date format: '2026-03-04fds'. Expected format: YYYY-MM-DD.",
    "details": {
      "field": "due_date",
      "invalid_value": "2026-03-04fds",
      "expected_format": "YYYY-MM-DD"
    }
  },
  "meta": {
    "timestamp": "2026-03-05T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication is required to access this resource"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The provided data is not valid",
    "details": {
      "field": "specific error message"
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  },
  "type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
  "title": "INVOICE_NO_LINES",
  "detail": "The invoice must have at least one line",
  "instance": "/v1/invoices/abc-123"
}
{
  "success": false,
  "error": {
    "code": "INVOICE_NOT_FOUND",
    "message": "Invoice not found"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "CONCURRENT_MODIFICATION",
    "message": "The resource was modified by another request; read it again and retry"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation error",
    "details": {
      "field_name": "Field is required"
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again in 60 seconds."
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Internal server error"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "UNSUPPORTED_MEDIA_TYPE",
    "message": "Unsupported media type: text/plain. Supported: application/json"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}