---
title: "Pause a recurring invoice"
method: POST
path: "/v1/recurring-invoices/{recurring_invoice_id}/pause"
tags: ["RecurringInvoices"]
deprecated: true
---

# Pause a recurring invoice

`POST /v1/recurring-invoices/{recurring_invoice_id}/pause`

## Request

- Base URL: `https://app.beel.es/api`
- URL: `https://app.beel.es/api/v1/recurring-invoices/{recurring_invoice_id}/pause`
- Auth: HTTP bearer

> **Deprecated.**

**Deprecated.** Use `PUT /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/status` with `{"status": "PAUSED"}`, which behaves identically.

Stops automatic generation, keeping the schedule configuration intact.

**Retires on 9 December 2026.** See the [migration guide](https://docs.beel.es/changelog/resources-under-the-nif) for what moved where and what changes when you switch.

## Path parameters

- `recurring_invoice_id` string, uuid, required

## Headers

- `Idempotency-Key` string

## Response `200`

Recurring invoice paused successfully

- object: The envelope every successful JSON response of the BeeL. API is wrapped in. There are no bare resources in v1 and none are planned: the payload always hangs off `data`. - A **single resource** is an object in `data`. - A **collection** hangs off a named key inside `data`, together with its `pagination`, also inside `data` — `data: {invoices: [...], pagination: {...}}`. - A collection carries `pagination` unless its operation declares itself a **closed catalogue**: a fixed, bounded list with nothing to page through. The declaration is explicit in the operation; a missing `pagination` is never something to infer. - `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a documented variant of pagination, not another envelope. Putting the array straight into `data` with `pagination` as its sibling is the shape a v2 would adopt; v1 is not being flipped to it.
  - `success` boolean, required
  - `data` object, required: The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
    - `id` string, uuid
    - `name` string
    - `frequency` 'MONTHLY' | 'QUARTERLY' | 'YEARLY': Billing cadence of the template: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. The day it falls on is `day_of_month`, the same anchor for all three. Set it when you create the template; a template created without it is `MONTHLY`. The cadence governs the step from the first invoice onwards, not where that first one lands: the first occurrence is the first `day_of_month` on or after `start_date`, found one month at a time. A yearly template starting 15 February with `day_of_month` 10 first invoices on 10 March, then every 10 March after that.
    - `day_of_month` integer: Day of the month the invoices are issued, as it was requested: this is the anchor of the schedule, not the day the last invoice happened to land on. A month that does not have that day falls back to its last day, so `next_generation` can show a different day than this one — a template on `31` generates on 28 February (29 in a leap year) and on 30 April. The adjustment never sticks and the schedule does not drift: every generation is recalculated from this value, never from the date it was adjusted to (31 Jan → 28 Feb → 31 Mar).
    - `start_date` string, date
    - `end_date` string, date, nullable
    - `next_generation` string, date, nullable: Date the next invoice is generated — the one this template is actually going to honour. `null`, always present and never omitted, when there is none. There is none in two cases, and only in those two: - the template is `COMPLETED`: the date left on it is the one that ran past its end and triggered the closure, not a generation still to come; - the template is `PAUSED` and its date is strictly in the past: resuming does not go back to it — it reschedules from today onwards, so that date will never be invoiced. An `ACTIVE` template always carries its date, **including a date already in the past**: that is the occurrence about to be generated, and it is your only notice that an invoice is on its way. A `PAUSED` template whose date is still ahead — today counts as ahead — carries it too: it is exactly the date resuming would keep. The stored schedule is never rewritten by any of this. `next_generation` is what gets published, not what gets kept: resuming a long-paused template still reschedules from its own anchor, as it always did.
    - `draft_in_advance` boolean: Whether this template prepares a draft for review before emitting. The review window is fixed at 5 days: `true` creates the draft 5 days ahead, `false` goes straight to the emission. Either way the invoice is emitted on the scheduled `day_of_month` — what changes is only whether there is a draft to look at first. Generating manually (`POST .../generate`) a period whose scheduled date is today or earlier emits the invoice on the spot: its review window has already passed.
    - `status` 'ACTIVE' | 'PAUSED' | 'COMPLETED': Lifecycle state of a recurring invoice schedule. - `ACTIVE`: invoices are generated on schedule. - `PAUSED`: automatic generation is stopped and the schedule is kept; `pause` says who stopped it. It can be resumed. - `COMPLETED`: the schedule has ended for good and generates nothing more; `completion` says why. Terminal: it cannot be resumed or paused.
    - `is_failing` boolean: Whether this template is failing right now — from the FIRST failed unattended run, not the third. The warning email only goes out on every 3rd consecutive retryable failure, so `next_generation` not moving used to be the only clue before that; this field surfaces the same signal from the very first one. `true` when either an unattended run has failed at least once and the retry streak has not been cleared by a successful generation yet (even while `status` is still `ACTIVE`, before any email fires), or `status` is `PAUSED` with `pause.reason = GENERATION_FAILURE`. Derived on every read from data already stored — it is not a new column and nothing else changes because of it.
    - `pause` RecurringInvoicePause, nullable: Why this schedule is paused. Present while `status` is `PAUSED`; absent otherwise, and also absent for schedules paused before this was recorded. `PAUSED` covers three facts that are not interchangeable: someone paused it, a plan downgrade paused it, or an unattended generation failed with something waiting will not fix. A transient failure (network, timeout, tax authority downtime) does NOT pause the schedule — the next run retries it.
      - `reason` 'USER' | 'DOWNGRADE' | 'GENERATION_FAILURE': Who stopped the schedule. `USER` a person did, from the API or the dashboard; `DOWNGRADE` the account lost the recurring-invoices feature; `GENERATION_FAILURE` an unattended run failed with something waiting will not fix — that one carries a `blocker`.
      - `since` string, date-time
      - `blocker` string: What blocked the emission, using the same codes the emission error returns (the `blockers[]` of a 422 `EMISSION_NOT_READY`). Only for `GENERATION_FAILURE`, and it may be absent when the permanent failure was not a readiness one. Resuming is rejected while this blocker is still in effect.
    - `completion` RecurringInvoiceCompletion, nullable: Why this schedule ended. Present while `status` is `COMPLETED`; absent otherwise, and also absent for schedules completed before this was recorded — that cause is not known and is not invented. `COMPLETED` covers facts that are not interchangeable: the owner ended it, the calendar ran out, or the agreed number of invoices did. It stays a **single** terminal status and `status` keeps its three values: no business rule branches on the cause, and a new status value would break every exhaustive `switch`. New causes land in `reason`, so treat it as an open set.
      - `reason` 'USER' | 'END_DATE_REACHED' | 'MAX_INVOICES_REACHED': `USER` — someone completed it (`PUT .../status` to `COMPLETED`). `END_DATE_REACHED` — the next occurrence landed past `end_date`, so there was nothing left to generate. This includes automatic generation and the common close of generating, skipping and resuming. `MAX_INVOICES_REACHED` — the `max_invoices` cap ran out: the last agreed invoice was generated (or the cap was moved down to the number already generated). When a template exhausts its cap and its `end_date` on the very same turn this is the cause reported: the cap is the fact that just happened, the date is the calendar consequence that follows.
      - `at` string, date-time: Instant the schedule was closed.
    - `series_id` string, uuid
    - `series_code` string
    - `invoice_type` 'STANDARD' | 'SIMPLIFIED': Type of the invoices a recurring template generates: `STANDARD` for an identified recipient, `SIMPLIFIED` for a recipient that is not identified.
    - `customer_id` string, uuid, nullable
    - `recipient_fiscal_name` string, nullable
    - `recipient_nif` string, nullable: Spanish Tax ID of the recipient. Mutually exclusive with `recipient_alternative_id`: a recipient is identified by exactly one of the two, never both, and `null` when the other one carries the identifier (or when there is no recipient identified at all).
    - `recipient_alternative_id` object, nullable: Alternative identifier of the recipient (NIF-IVA, passport, etc.), same shape as the customer's own `alternative_id`. Mutually exclusive with `recipient_nif`: `null` when the recipient carries a Spanish `recipient_nif` instead, or when there is no recipient identified at all.
      - `type` 'NIF_IVA' | 'PASSPORT' | 'COUNTRY_ID' | 'RESIDENCE_CERTIFICATE' | 'OTHER_DOCUMENT' | 'NOT_REGISTERED' | '02' | '03' | '04' | '05' | '06' | '07', required: Identifier type. Use descriptive names: - **NIF_IVA**: EU VAT number (intra-community) — *only for an EU member state other than Spain, with that country's VAT number structure* - **PASSPORT**: Passport — *allowed for any country* - **COUNTRY_ID**: Country of residence ID — *not allowed when `country_code = ES`* - **RESIDENCE_CERTIFICATE**: Residence certificate — *not allowed when `country_code = ES`* - **OTHER_DOCUMENT**: Other supporting document — *not allowed when `country_code = ES`* - **NOT_REGISTERED**: Not registered in AEAT — *requires `country_code = ES`* **⚠️ DEPRECATED numeric codes** (will be removed in v2): 02, 03, 04, 05, 06, 07 — use the descriptive names above instead.
      - `number` string, required: Identifier number. For `NIF_IVA`, the full EU VAT number with its country prefix (e.g. `FR40303265045`); see the VeriFactu rules on the parent schema.
      - `country_code` string: ISO 3166-1 alpha-2 code of the country that issued the document. Required except for `PASSPORT` and `NOT_REGISTERED`, where omitting it means `ES`. Constrains the allowed `type` values; see the VeriFactu rules on the parent schema.
    - `lines` InvoiceLineTemplateResponse[]
      - `id` string, uuid
      - `order` integer
      - `description` string
      - `quantity` number
      - `unit` string, nullable
      - `unit_price` number: Unit price of the template line. In both total-declared modes (`pricing_mode` other than `UNIT_PRICE`) this value is **derived and informational** (`total / quantity`, 4 decimals): re-multiplying it does not reproduce the declared total on large quantities.
      - `pricing_mode` 'UNIT_PRICE' | 'TOTAL_EXCLUDING_TAX' | 'TOTAL_INCLUDING_TAX': How the line amount was entered. - `UNIT_PRICE`: classic mode — the amount is derived from `unit_price` (`quantity × unit_price × (1 − discount / 100)`). - `TOTAL_EXCLUDING_TAX`: total-declared mode — `total_excluding_tax` is the exact taxable base and `unit_price` is derived and informational (`total / quantity`, 4 decimals). - `TOTAL_INCLUDING_TAX`: tax-inclusive total-declared mode — `total_including_tax` is what the customer paid (taxable base + VAT + equivalence surcharge) and the engine works the breakdown backwards so the rounded amounts add up to the declared total exactly. In both total-declared modes re-multiplying the derived `unit_price` does NOT reproduce the declared total on large quantities (1.00 € ÷ 300 units → 0.0033 × 300 = 0.99), which is precisely why the declared total is the source of truth.
      - `total_excluding_tax` number: Declared line total excluding taxes. Only present on lines with `pricing_mode = TOTAL_EXCLUDING_TAX`. It never includes taxes nor subtracts IRPF withholding.
      - `total_including_tax` number: Declared line total including taxes (taxable base + VAT + equivalence surcharge; IRPF withholding is never subtracted). Only present on lines with `pricing_mode = TOTAL_INCLUDING_TAX`. Every invoice this template generates reproduces it exactly.
      - `discount_percentage` number, nullable
      - `tax_type` string
      - `vat_rate` number
      - `regime_key` string
      - `equivalence_surcharge_rate` number, nullable
      - `irpf_rate` number, nullable
      - `exemption_reason` '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': Tax exemption reason code per the Spanish VAT Law (Ley 37/1992, LIVA), with the VeriFactu code each one is reported as. - `EXENTA_ART_20`: exempt, art. 20 (domestic operations such as medical, educational, cultural and financial services, or housing rentals). E1. - `EXENTA_ART_21`: exempt, art. 21 (exports of goods). E2. - `EXENTA_ART_22`: exempt, art. 22 (operations treated as exports). E3. - `EXENTA_ART_24`: exempt, art. 24 (free zones, warehouses and customs regimes). E4. - `EXENTA_ART_25`: exempt, art. 25 (intra-community supplies of goods). E5. - `EXENTA_ART_26`: exempt, art. 26 (intra-community acquisitions of goods). It exempts the buyer's acquisition, not a supply the seller invoices, so an invoice line that carries it is rejected with `EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a supply to another Member State is `EXENTA_ART_25`. - `NO_SUJETA_ART_7_9`: not subject under art. 7 (such as the transfer of a business as a going concern, art. 7.1º). N1. - `NO_SUJETA_LOCALIZACION`: not subject by the place-of-supply rules (intra-community or non-EU services, arts. 69 and 70). N2. - `ISP_ART_84_2_A` … `ISP_ART_84_2_F`: reverse charge (the invoice states «inversión del sujeto pasivo»), art. 84.Uno.2.º letters a) (supplier not established in Spain), b) (unwrought 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 allowances), e) (certain real estate supplies: in insolvency proceedings, with the exemption waived, or enforcing a security) and f) (construction or renovation works). S2. - `ISP_ART_84_2_G`: reverse charge of letter g) (silver, platinum, palladium, mobile phones, consoles, laptops and tablets). The law requires these supplies to be invoiced in a special series, so an invoice line that carries it is rejected with `REVERSE_CHARGE_CASE_NOT_SUPPORTED`. - `EXENTA_ART_140`: investment gold exemption, art. 140 bis (usually with `regime_key` `04`). E6. - `REGIMEN_ART_129` (agriculture, livestock and fishing, arts. 124 to 134 bis), `REGIMEN_ART_135` (second-hand goods, art and antiques), `REGIMEN_ART_141` (travel agencies), `REGIMEN_ART_154` (equivalence surcharge) and `REGIMEN_ART_163_DECIES` (cash basis, arts. 163 decies to 163 sexiesdecies): operations of special regimes, which VeriFactu identifies by the regime key rather than by an exemption code. An invoice line that carries one is rejected with `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU`; declare the regime with `regime_key` instead. - `OTRO`: any other provision. Requires the text in `exemption_reason_text`. E6.
      - `exemption_reason_text` string, nullable: Custom exemption text. Only used when exemption_reason is OTRO.
    - `amount` number, nullable: What the customer will be asked to transfer on the next invoice this template generates: taxable base + VAT + equivalence surcharge − IRPF withholding + disbursements (`suplidos`). It is NOT the taxable base and NOT the total with VAT: the withholding is already subtracted. The same number the next occurrence publishes as `total_to_pay`, to the cent: both come from the template's own lines through the same line pipeline the real generation uses. It is computed on the fly and stored nowhere, so it always reflects the lines the template carries right now — and it is not sortable or filterable for that reason. `null` when the template's lines cannot be priced (no lines, or lines the engine rejects). The rest of the template is still returned.
    - `payment_method` string, nullable
    - `payment_iban` string, nullable: Full IBAN of the template's own payment method, mirrored from what was sent when it was set. `null` when the template has no payment method.
    - `payment_swift` string, nullable: SWIFT/BIC code of the template's own payment method. `null` when the template has no payment method, or when it has one without a SWIFT code.
    - `payment_term_days` integer, nullable: Payment term, in days, of the template's own payment method. `null` when the template has no payment method, or when it has one without a declared term. Each generated invoice is due on its issue date plus this term. When it is `null`, the company's default `payment_term_days` (tax configuration) applies, or 30 days if the company has none.
    - `notes` string, nullable
    - `custom_fields` InvoiceCustomField[], required: The company's custom fields saved on this template, each with the label and value it was saved with; an empty array when it carries none. Every invoice the template generates copies them. The generated invoice saves each field with the label it has at that moment; a field archived after the template was saved stays listed here, but the invoices generated from then on are created without it and the template keeps generating: it is not paused for that reason. They are informational: they are not part of the VeriFactu record sent to the AEAT and do not enter its hash.
      - `key` string, required: Identifier of the custom field within the company, chosen when the field is created: lowercase letters, digits and `_`, up to 64 characters. It never changes. BeeL. gives it no meaning of its own.
      - `label` string, required: Label of the field when the invoice was saved.
      - `value` string, required: Text stored on the invoice. It is the text the `custom_fields` filter of the invoice list compares.
    - `send_automatically` boolean
    - `email_configuration` RecurringEmailConfigResponse, nullable
      - `recipients` string[]
      - `cc` string[]
      - `subject` string, nullable
      - `message` string, nullable
    - `generated_invoices` integer
    - `max_invoices` integer, nullable: Total number of invoices agreed for this template, or absent when the recurrence is not capped by number. It is the TOTAL agreed, not what is left: "remaining" is `max_invoices - generated_invoices`, a derived reading with no field of its own — two counters of the same fact drift, and the day they do nobody knows which one is true. What counts against it are invoices GENERATED, not calendar turns: a skip does not spend cap because it produces no invoice, and a `generate now` does because it produces a real fiscal invoice. When the last one is generated the template is closed in the act, with `completion.reason = MAX_INVOICES_REACHED`.
    - `last_generated_at` string, date-time, nullable
    - `last_generated_scheduled_date` string, date, nullable: Calendar date of the latest period this template has already invoiced: the highest `scheduled_date` in its generation history. `null` when it has never generated an invoice. It is NOT the date of `last_generated_at`. That field is the instant the most recent invoice was created; this one is the calendar slot that invoice (or an earlier one) was issued for. They diverge on purpose: a `generate now` creates today an invoice for a future slot, and a re-anchored schedule can generate a slot out of order, so the latest invoice created is not necessarily the one with the latest slot. Skipping a period does not move it: a skip produces no invoice, so it consumes no slot. Deleting a generated invoice does not move it either, with one exception: deleting the draft of the latest generation releases that slot (the period becomes available to generate again, and `generated_invoices` goes down by one), so this date moves back to the previous generated slot, or to `null` if there is none.
    - `source_invoice_id` string, uuid, nullable
    - `created_at` string, date-time
    - `updated_at` string, date-time
  - `meta` ResponseMeta
    - `timestamp` string, date-time
    - `request_id` string

## Other responses

- `400`: `VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from the offending property's name (in the contract's `snake_case`, nested paths joined with a dot, e.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry per property. When the rejected value is one outside an enum's vocabulary, `details` instead follows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`). Two more codes share this status for a value that passes the contract but does not fit where it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and `VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written. Other `error.code` values with this status: - `RECURRING_STATE_TRANSITION_INVALID` — the template cannot move from its current state to the requested one (e.g. a `COMPLETED` template never changes again). `error.details` carries `current_status` and `target_status`.
- `403`: Authenticated but not allowed. Fourteen causes shared by every operation, told apart by `error.code`; an operation that adds a cause of its own names it in its `403`. You can branch on them exhaustively. - `INSUFFICIENT_SCOPE` — the credential lacks a scope the operation requires; `error.details.missing_scopes` names them as a single comma-separated string (for example `"invoices:write,customers:read"`), not as an array; `required_scopes` has the same shape and lists every scope the operation needs. Retrying will not help: mint a key that holds them. - `COMPANY_READ_ONLY` — the scope is there, but your access level over that NIF only lets you read it. - `ACCOUNT_MANAGEMENT_FORBIDDEN` — the scope is there, but your role over the account, or the management relationship you hold over it, does not cover this operation. - `ACCOUNT_NOT_ACCESSIBLE` — the account is not yours to reach, which is also the answer when it does not exist, so existence is never disclosed. - `ACTIVE_COMPANY_NOT_ACCESSIBLE` — the same, for a NIF: the company in the path, or the one named by `BeeL-Active-Company`, is not one this credential may reach — and again, this is also the answer when it does not exist. - `COMPANY_ACCESS_REVOKED` — your access to the NIF was withdrawn while the request was already in flight, so the write was rejected and nothing was recorded. Retrying will not help until the access is granted again. - `LIVE_CREDENTIAL_REQUIRED` — the operation changes the real account and the call came from a test API key (`beel_sk_test_…`). Use your live key or the dashboard. - `FEATURE_NOT_AVAILABLE` — your subscription does not include the entitlement the operation needs; `error.details.feature_code` names it. This gate runs **before** the scope gate, so for such an operation you never see `INSUFFICIENT_SCOPE` first. - `NO_ACTIVE_ACCOUNT` — the credential does not resolve to an account, so no scope can be evaluated against one. Fail-closed, not a permission that can be granted to you. - `OPERATION_REQUIRES_SESSION` — the operation is available only from the web session; no API key and no OAuth2 token can perform it, whatever scopes it holds. - `OPERATION_REQUIRES_API_KEY` — the reverse: the operation needs a scope that only an API key can hold (such as `accounts:write`), so a dashboard session never satisfies it. - `API_KEY_LIVE_NOT_AVAILABLE` — the call came with a live API key (`beel_sk_live_…`) and the account has no Live mode yet. It is never degraded to Test: use your test key, or activate Live first. - `ACCOUNT_REQUIRES_FOCUS_SWITCH` — dashboard session only: you are a member of the account in the path, but it is not the account in focus. Switch focus to it and retry. - `REQUEST_BLOCKED` — the edge security filter stopped the request before the API saw its body, usually because a text field carries something that looks like markup or a script (HTML tags, `<script>`). Review the body and retry.
- `404`: The resource addressed by the path does not exist, or is not one this credential can see — the two are answered identically, so existence is never disclosed. `error.code` names the kind of resource that was missing, so a client can tell which of several ids in a path failed to resolve: - `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources). - `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`. - `CLIENT_NOT_FOUND` — the `{customer_id}`. - `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not resolve to a customer of the issuing company. - `PRODUCT_NOT_FOUND` — the `{product_id}`. - `SERIES_NOT_FOUND` — the `{series_id}`. - `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the `{delivery_id}`. - `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder has not completed onboarding, so there is no issuer to act as. - `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own. Some resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`, `MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it is documented on the operation. A `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist in this API (a typo in the route, or a resource that was never here). It says nothing about any resource. When the path exists but not with that method, the answer is `405`, not `404`.
- `default`: Any status code the operation does not list above. Every operation declares it, so a generated client always has a branch to fall into and never loses the cause of a failure it did not anticipate. This is where the transport-level answers land — `405`, `406`, `415` and `429` — together with any status a future version of the API starts returning. All of them carry the same error envelope as the codes listed explicitly, so `error.code` is what tells them apart: switching on the status code alone is not enough. See «Transport-level errors» in the API description for when each one is produced. A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active subscription. `error.details` says why and what to do: `current_status` (absent if it never had one), `required_permission` and `action`. A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a dependency of BeeL itself (the database, the email provider) was momentarily unavailable and nothing was done. Retry after those seconds. A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the operation depends on failed or did not answer in time. It is a transient condition — retry with the same `Idempotency-Key` where the operation accepts one. One exception to the envelope: a failure of the network edge that never reaches the application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not BeeL's — it may not even be JSON. Treat those as "no answer", and retry.

---

[API](https://skmtc.dev/beel/apis/beel-public-api.md) · [All operations](https://skmtc.dev/beel/apis/beel-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/beel/apis/beel-public-api/revisions/ffbc0ae31f79?raw)
