@beel_es/sdk 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +244 -0
  2. package/README.md +216 -25
  3. package/dist/index.d.mts +1480 -185
  4. package/dist/index.d.ts +1480 -185
  5. package/dist/index.js +1419 -187
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +1416 -187
  8. package/dist/index.mjs.map +1 -1
  9. package/docs/reference/README.md +233 -0
  10. package/docs/reference/account-companies.md +55 -0
  11. package/docs/reference/account-invitations.md +57 -0
  12. package/docs/reference/account-members.md +97 -0
  13. package/docs/reference/account-webhooks.md +123 -0
  14. package/docs/reference/account.md +94 -0
  15. package/docs/reference/accounts.md +72 -0
  16. package/docs/reference/catalogs.md +68 -0
  17. package/docs/reference/client.md +113 -0
  18. package/docs/reference/company-customers.md +85 -0
  19. package/docs/reference/company-invoices-schedule.md +52 -0
  20. package/docs/reference/company-invoices.md +316 -0
  21. package/docs/reference/company-payment-connections-events.md +103 -0
  22. package/docs/reference/company-payment-connections.md +87 -0
  23. package/docs/reference/company-products.md +72 -0
  24. package/docs/reference/company-recurring-invoices.md +136 -0
  25. package/docs/reference/company-series.md +94 -0
  26. package/docs/reference/company-tax-configuration.md +36 -0
  27. package/docs/reference/company-verifactu-configuration.md +41 -0
  28. package/docs/reference/company.md +94 -0
  29. package/docs/reference/configuration.md +90 -0
  30. package/docs/reference/customer-builder.md +107 -0
  31. package/docs/reference/customers.md +91 -0
  32. package/docs/reference/errors.md +72 -0
  33. package/docs/reference/invoice-builder.md +149 -0
  34. package/docs/reference/invoices.md +250 -0
  35. package/docs/reference/nif.md +31 -0
  36. package/docs/reference/products.md +111 -0
  37. package/docs/reference/series.md +81 -0
  38. package/docs/reference/types.md +222 -0
  39. package/docs/reference/webhook-verifier.md +95 -0
  40. package/llms.txt +52 -0
  41. package/package.json +10 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,244 @@
1
+ # Changelog
2
+
3
+ All notable changes to the BeeL Node.js SDK will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [2.2.0] - 2026-09-28
11
+
12
+ Contract resynced with the API as it runs in production today and types regenerated.
13
+
14
+ ### Added
15
+ - **The VeriFactu status webhook says which record changed.** The `verifactu.status.updated`
16
+ payload (`WebhookEventDataVeriFactuStatusUpdated`) carries `operation`: `REGISTRATION` for
17
+ the invoice's registration, `VOID` for the cancellation of a voided invoice. The API always
18
+ sends it. The enum is exported as `VeriFactuRecordOperation`.
19
+
20
+ ### Changed
21
+ - **`province` is optional in the `Address` you send.** It is only required for an address in
22
+ Spain (`country_code` `ES`, or no country); the API rejects a Spanish address without it with
23
+ a `422`. Code that sets it still compiles.
24
+ - Recurring invoices: `payment_term_days` documents how the due date of each generated invoice
25
+ is computed.
26
+
27
+ ## [2.1.0] - 2026-09-28
28
+
29
+ ### Fixed
30
+ - **Automatic retries now happen.** `maxRetries`, `retryDelayMs` and `maxRetryDelayMs` had no
31
+ effect: the retry hook only ran when `fetch` itself rejected, and then ignored the error, so
32
+ no request was ever retried. Retries now run in a wrapper around `fetch`, and only when
33
+ repeating the request cannot apply it twice: `429` (after `Retry-After`) for any method;
34
+ `409 IDEMPOTENCY_KEY_PROCESSING` with the same key; `5xx` and network errors for `GET` and
35
+ for requests that carry an `Idempotency-Key`. A `5xx` the API replays for a key
36
+ (`Idempotency-Replay: true`) is not retried. Every wait is capped at `maxRetryDelayMs`.
37
+ - **A retry reuses the call's `Idempotency-Key`.** The previous retry code, had it run, set a
38
+ new key on each attempt, which could create a second invoice. One call now sends one key on
39
+ every attempt.
40
+ - **`autoIdempotencyKey: false` is honoured.** It was declared but never read.
41
+ - **Typed errors keep the API's error code.** `BeeLConflictError`, `BeeLValidationError` and the
42
+ other subclasses replaced `error.code` with a generic value, so codes such as
43
+ `INVOICE_DUPLICATE_EXTERNAL_REFERENCE` were lost. They are now in the new `apiCode` field
44
+ (`code` is unchanged). `BeeLConflictError` also carries `details`.
45
+ - **Request fields the API defaults are optional in the generated types.** Fields such as a
46
+ line's `line_type` and `discount_percentage`, or `options.issue_directly`, were typed as
47
+ required because they have a default. On schemas that only travel in requests they are now
48
+ optional, as the contract says; response types are unchanged.
49
+ - **`InvoiceBuilder` can set the tax of its lines.** `addLine` never set `main_tax`, which the
50
+ API requires on every normal line. New `mainTax(tax)` sets it for the lines added after it,
51
+ and `addLine` takes an optional per-line `mainTax`.
52
+
53
+ ### Added
54
+ - `company.invoices.createOnce(body)`: creates a standard or simplified invoice once per
55
+ `external_ref` — looks it up first, and after a `409 INVOICE_DUPLICATE_EXTERNAL_REFERENCE`
56
+ or an unknown outcome returns the invoice that exists. Safe to call again after any error.
57
+ - Per-call `RequestOptions` on every `company.invoices` method: `idempotencyKey`, `signal`
58
+ and `timeoutMs` (the timeout covers the automatic retries).
59
+ - `beel.catalogs.identity()` (`GET /v1/me/identity`): the credential's `account_id`, the
60
+ starting point to list its companies.
61
+ - `BeeLErrorCodes`, `EXTERNAL_REF_UNIQUE_INVOICE_TYPES` and the types `RequestOptions`,
62
+ `CreateInvoiceOnceRequest`, `CreateInvoiceOnceParams`, `ExternalRefUniqueInvoiceType`,
63
+ `BeeLErrorCode`.
64
+ - A typed Markdown reference in `docs/reference/` (one page per resource, one anchor per
65
+ method, an index of every method) and `llms.txt`, generated from the source and the
66
+ contract with `npm run docs:reference` and shipped in the package. `npm test` fails when
67
+ it is stale or a public method lacks JSDoc.
68
+ - README: a *Common tasks* section, and the exact retry and idempotency semantics.
69
+
70
+ ## [2.0.0] - 2026-09-27
71
+
72
+ Contract resynced with the API as it runs in production today (public contract 1.9.0) and
73
+ types regenerated. The major bump is because several **generated types narrow or lose
74
+ fields**, not because any method changed name: most integrations compile unchanged.
75
+
76
+ ### Changed (BREAKING)
77
+ - **Series need a `document_type`.** Creating a series (`company.series.create`, and the
78
+ series in an account import) now requires `document_type`: each series numbers one kind
79
+ of document.
80
+ - **Equivalence surcharge and IRPF rates follow the current tables.** `EquivalenceSurchargePercentage`
81
+ is `0 | 0.26 | 0.5 | 0.62 | 1 | 1.4 | 1.75 | 5.2` (the old `0.625` is `0.62`).
82
+ `IrpfPercentage` adds `2.8`, `6`, `7.6` and `9.5` and is now a `number`, so a line's
83
+ `irpf_rate` is no longer typed as an integer.
84
+ - **`verifactu.chaining_hash` is gone from the invoice.** The API no longer returns it.
85
+ - **`accounts.provision()` returns what the API returns**: `ProvisionAccountResult`
86
+ (`account_id`, `status`, `claim_token`, `claim_url`, `claim_link_already_issued`), not a
87
+ `ManagedAccountSummary`. The old type described fields the response never carried. Call
88
+ `beel.accounts.get(accountId)` for the summary.
89
+ - **Webhook event types are usable.** Every generated event type (`InvoiceIssuedEvent`, …)
90
+ collapsed to `never`, so `if (event.type === '...')` narrowed to nothing. They are now
91
+ plain types with `type` as the literal the API sends, and a real event has `test` absent
92
+ or `false`: check `if (event.test) return;` before narrowing on `type`.
93
+ `WebhookEventEnvelope` types `type` as a string literal union instead of the enum.
94
+ - Schemas the contract dropped are no longer in `components`: `IgicPercentage`,
95
+ `IpsiPercentage`, `IrpfCategory`, `OtherTaxPercentage`, `VatCategory`.
96
+ - `phone` on company and customer updates is typed by `PhoneInput` (the input rules); the
97
+ `Phone` returned in responses no longer carries a minimum length or pattern.
98
+
99
+ ### Added
100
+ - `company.invoices.createSimplifiedExchange()` — issues a full invoice in exchange for one or
101
+ more issued simplified invoices (`POST .../invoices/simplified-exchanges`). The simplified
102
+ invoices become `VOIDED` with `void_cause: 'EXCHANGED'`; the new invoice lists them in
103
+ `replaced_invoice_ids`.
104
+ - `company.invoices.verifactuRecords(invoiceId)` — the VeriFactu records of an invoice
105
+ (registration and, if voided, cancellation), each with its own status.
106
+ - Voids take `issued_in_error` (required as `true` once the invoice was sent or paid).
107
+ - Correctives take `circumstance_date`, `recipient` and `recipient_is_business`.
108
+ - Exemption reasons `ISP_ART_84_2_B`, `_C`, `_D` and `_G`; tax rates and surcharges carry
109
+ `valid_from` / `valid_until`; tax configuration returns `withholding_options`.
110
+ - Recurring invoices return `is_failing` and `last_generated_scheduled_date`; their history
111
+ entries carry `type`, `origin`, `reason` and who requested them.
112
+ - Webhooks `invoice.pdf.generated` and `invoice.schedule_failed`: `InvoicePdfGeneratedEvent`,
113
+ `InvoiceScheduleFailedEvent` and their `WebhookEventData*` types, included in `WebhookEvent`.
114
+ `VeriFactuSubmissionStatus` now includes `PENDING`, sent when the record is submitted.
115
+ - Error codes documented by the contract for the flows above (for example
116
+ `VOID_REQUIRES_ISSUED_IN_ERROR`, `CORRECTIVE_OUT_OF_TIME`, `SIMPLIFIED_NOT_EXCHANGEABLE`,
117
+ `EXCHANGE_INVOICE_NOT_VOIDABLE`, `SERIES_DOCUMENT_TYPE_LOCKED_HAS_INVOICES`). They reach
118
+ you as `error.code` on the typed errors, as before.
119
+ - `npm run typecheck`.
120
+
121
+ ### Fixed
122
+ - The `beel.raw` example called `GET /v1/companies/{company_id}/logo`, which does not exist
123
+ (the logo is only `PUT` and `DELETE`); it now uses `GET .../invoice-customization`.
124
+
125
+ ## [1.3.0] - 2026-09-17
126
+
127
+ ### Added
128
+ - **Payment connections per NIF**: `beel.company(id).paymentConnections` — list, start an
129
+ authorization, update and disconnect. A connection is addressed by **its own UUID**, not by
130
+ the provider slug: a NIF can hold several connections of the same provider. The slug is only
131
+ sent when starting an authorization.
132
+ - **Payment events of a connection**: `...paymentConnections.events(connectionId)` — list with
133
+ the full filter set, read one, and the five recovery levers for a charge that did not invoice:
134
+ `retry`, `draft` (builds a draft invoice to review before issuing), `resolve`, `discard`
135
+ and `restore`.
136
+ - **Fiscal configuration per company**: `beel.company(id).verifactuConfiguration` and
137
+ `.taxConfiguration`. These are the canonical replacements the `beel.configuration` JSDoc
138
+ already pointed at, which did not exist until now. In VeriFactu, `enabled` is the only
139
+ writable field: whether an invoice reaches AEAT is a fact of the NIF resolved at issue time.
140
+ - **Invoice schedule**: `beel.company(id).invoices.schedule` — one `set()` and one `clear()`
141
+ replace the legacy `schedule`, `reschedule` and `unschedule` calls.
142
+ - **Invoice derivation**: `beel.company(id).invoices.derive()`, the canonical form of `duplicate`.
143
+ - **Catalogs and preferences**: `beel.catalogs.taxTypes()`, `.invoiceCustomizationOptions()`
144
+ and `.updateMe()`, on their canonical paths instead of the deprecated `/v1/configuration/*`.
145
+ - **`beel.raw`**: the typed client underneath, for any endpoint the SDK does not wrap yet. A new
146
+ route reaches the generated types as soon as the contract is synced, but its ergonomic wrapper
147
+ is written by hand — so without this you had to wait for a release to call it. Same
148
+ authentication, retries and `Idempotency-Key`, and still typed: a path, parameter or body the
149
+ contract does not have will not compile.
150
+ - `check:coverage` guard: a ratchet that fails the build when the contract grows a live
151
+ operation the SDK does not wrap. The current set is recorded in
152
+ `scripts/coverage-baseline.json`, so it does not block today, but the contract can no longer
153
+ grow unnoticed.
154
+
155
+ ### Fixed
156
+ - `beel.accounts.scope(id).companies.list()` read the collection as a bare array with the
157
+ pagination beside it. The contract wraps it (`data.companies` + `data.pagination`), so the
158
+ call did not type-check against the current API. Its public return shape is unchanged.
159
+ - CI: the auto-regenerate workflow depended on a `branch` field the backend never sends, so
160
+ only the spec sync ever ran and the generated types were never refreshed. It now regenerates
161
+ and runs the contract guards on every sync that brings changes.
162
+ - `npm publish` ran neither the tests nor the contract guards; `prepublishOnly` now does.
163
+
164
+ ### Changed
165
+ - OpenAPI contract synced with the published API (the previous copy was five weeks behind);
166
+ types regenerated.
167
+
168
+ ## [1.2.0] - 2026-08-11
169
+
170
+ ### Added
171
+ - **Accounts (multi-account provisioning)**: `beel.accounts` — list/get/provision managed
172
+ accounts, plus `beel.accounts.scope(accountId)` for members (roles and per-company grants),
173
+ invitations, account-scoped webhook subscriptions (secret rotation, deliveries, retries, test),
174
+ companies under an account, usage counters, claim tokens, ownership transfer and access level.
175
+ - **Company scope (multi-NIF)**: `beel.company(companyId)` — invoices (full lifecycle: create,
176
+ issue, void, corrective, convert proforma, status, PDF, send), customers (incl. bulk), products,
177
+ series (incl. typed defaults seeding), recurring invoices, fiscal summary and issuing readiness.
178
+ All operations address the company in the path; no session focus needed.
179
+ - Webhook event catalog expanded from the contract: `invoice.issued`, `invoice.voided`,
180
+ `invoice.email.sent`, `recurring_invoice.paused`, `account.claimed`, `company.created`,
181
+ `representation.signed` and test events, all exported and included in `WebhookEvent`.
182
+ - `check:deprecated` drift guard: fails the build if the SDK wraps an operation the OpenAPI
183
+ contract marks `deprecated: true` without a matching `@deprecated` tag (or vice versa).
184
+
185
+ ### Changed
186
+ - OpenAPI contract synced from backend `develop` (v1.7.1); types regenerated.
187
+ - Invoice lines now require `line_type` (`NORMAL` | `SUPLIDO`); `InvoiceBuilder.addLine()`
188
+ defaults to `NORMAL`.
189
+
190
+ ### Deprecated
191
+ - `VeriFactuStatusUpdatedData` and `VeriFactuStatus` type names, renamed in the contract to
192
+ `WebhookEventDataVeriFactuStatusUpdated` and `VeriFactuSubmissionStatus`. Deprecated aliases
193
+ are kept so existing imports keep compiling.
194
+ - The whole session-focus surface (`beel.invoices`, `beel.customers`, `beel.products`,
195
+ `beel.series`, `beel.configuration`, `beel.downloadPdf`), matching the contract. Use the
196
+ company-scoped equivalents on `beel.company(companyId)`.
197
+
198
+ ## [Unreleased]
199
+
200
+ ## [2.1.0] - 2026-09-28
201
+
202
+ ### Added
203
+ - Initial release of the BeeL Node.js SDK
204
+ - Full TypeScript support with auto-generated types from OpenAPI spec
205
+ - Modular architecture with separate resource classes (Invoices, Customers, Products)
206
+ - HTTP client with automatic retry logic and exponential backoff
207
+ - Idempotency support for safe request retries
208
+ - Comprehensive error handling with custom error classes
209
+ - Support for all public API endpoints:
210
+ - Invoices (create, list, retrieve, update, delete, issue, send, mark as paid, download PDF)
211
+ - Customers (CRUD operations)
212
+ - Products (CRUD operations)
213
+ - Configuration (tax settings, VeriFactu, invoice series)
214
+ - NIF validation
215
+ - Business Profiles (multi-NIF support)
216
+ - Public metrics
217
+
218
+ ### Configuration Options
219
+ - `apiKey` - Your BeeL API key (required)
220
+ - `baseUrl` - API base URL (default: `https://app.beel.es/api`)
221
+ - `timeout` - Request timeout in milliseconds (default: 30000)
222
+ - `maxRetries` - Maximum retry attempts for GET requests (default: 3)
223
+ - `retryDelay` - Initial retry delay in milliseconds (default: 1000)
224
+
225
+ ### Error Types
226
+ - `BeelError` - Base error class
227
+ - `BeelAuthenticationError` - 401 authentication errors
228
+ - `BeelValidationError` - 400/422 validation errors
229
+ - `BeelNotFoundError` - 404 not found errors
230
+ - `BeelRateLimitError` - 429 rate limit errors
231
+ - `BeelTimeoutError` - Request timeout errors
232
+ - `BeelNetworkError` - Network/connection errors
233
+
234
+ ## [1.0.0] - 2026-02-28
235
+
236
+ ### Added
237
+ - Initial public release
238
+ - Complete SDK generated from OpenAPI specification
239
+ - TypeScript type definitions
240
+ - Comprehensive README with usage examples
241
+ - MIT License
242
+
243
+ [Unreleased]: https://github.com/beel-es/beel-node-sdk/compare/v1.0.0...HEAD
244
+ [1.0.0]: https://github.com/beel-es/beel-node-sdk/releases/tag/v1.0.0
package/README.md CHANGED
@@ -8,13 +8,17 @@ Official Node.js/TypeScript SDK for the [BeeL API](https://beel.es) — Spanish
8
8
 
9
9
  **Node.js 18+. Single dependency ([openapi-fetch](https://openapi-ts.dev/openapi-fetch/)).**
10
10
 
11
+ **Reading this from `node_modules`?** Everything you need is in Markdown next to this file:
12
+ this README for common tasks, [`docs/reference/README.md`](docs/reference/README.md) for every
13
+ resource and method (HTTP operation, parameters, return shape, errors), and
14
+ [`llms.txt`](llms.txt) as a short index. You do not need to read `dist/index.d.ts`.
15
+
11
16
  ## Features
12
17
 
13
- - **Full TypeScript types** auto-generated from OpenAPI spec
14
- - **Automatic retries** with exponential backoff on 429 and 5xx
15
- - **Auto idempotency keys** on every POST request
16
- - **Typed errors** — catch `BeeLNotFoundError` instead of checking status codes
17
- - **PDF download** as Buffer
18
+ - **Full TypeScript types** generated from the OpenAPI contract, and a [typed Markdown reference](docs/reference/README.md)
19
+ - **Automatic retries** on 429, 5xx and network errors — only when repeating the request cannot apply it twice
20
+ - **Idempotency keys** on every POST, reused by that call's retries; `createOnce` for invoices you must never duplicate
21
+ - **Typed errors** — catch `BeeLNotFoundError` instead of checking status codes, read the API's `apiCode`
18
22
  - **Webhook signature verification** with HMAC-SHA256
19
23
  - **Instance-based client** — multiple API keys, no global state
20
24
  - **ESM + CommonJS**
@@ -25,30 +29,204 @@ Official Node.js/TypeScript SDK for the [BeeL API](https://beel.es) — Spanish
25
29
  npm install @beel_es/sdk
26
30
  ```
27
31
 
28
- ## Quick Start
32
+ ## Common tasks
33
+
34
+ Every example is typed: the request bodies are `components['schemas'][...]` of the contract,
35
+ which you can import with `import type { components } from '@beel_es/sdk'`.
36
+
37
+ ### Set up the client
29
38
 
30
39
  ```typescript
31
40
  import { BeeL } from '@beel_es/sdk';
32
41
 
33
- const beel = new BeeL({ apiKey: 'beel_sk_live_...' });
42
+ // beel_sk_test_* → sandbox (VeriFactu test mode, no quota), beel_sk_live_* → production
43
+ const beel = new BeeL({ apiKey: process.env.BEEL_API_KEY! });
44
+ ```
34
45
 
35
- // Address a company (NIF) explicitly — the recommended multi-NIF surface
36
- const company = beel.company('company-uuid');
46
+ ### Find the company id
47
+
48
+ Every invoice belongs to a company (a NIF), addressed by its UUID — not by the NIF itself.
49
+ Your API key tells you its account; the account lists its companies:
50
+
51
+ ```typescript
52
+ const { account_id } = await beel.catalogs.identity();
53
+ const { companies } = await beel.account(account_id).companies.list({ search: 'B12345678' });
54
+
55
+ const company = beel.company(companies[0].id!); // keep this id in your configuration
56
+ ```
37
57
 
38
- // Create a draft invoice
39
- const invoice = await company.invoices.create({
58
+ ### Create and issue a standard invoice
59
+
60
+ A create makes a `DRAFT`; `issue` numbers it and submits it to VeriFactu. Or do both in one
61
+ call with `options.issue_directly`.
62
+
63
+ ```typescript
64
+ import type { components } from '@beel_es/sdk';
65
+
66
+ type CreateInvoiceRequest = components['schemas']['CreateInvoiceRequest'];
67
+
68
+ const body: CreateInvoiceRequest = {
40
69
  type: 'STANDARD',
41
- recipient: { customer_id: 'customer-uuid' },
70
+ external_ref: 'ORD-2026-0042', // your order id: unique per live invoice, searchable
71
+ recipient: { customer_id: 'customer-uuid' }, // or the recipient's data inline
72
+ lines: [
73
+ {
74
+ description: 'Consulting',
75
+ quantity: 10,
76
+ unit_price: 85.5,
77
+ main_tax: { type: 'IVA', percentage: 21, regime_key: '01' }, // required on every line
78
+ },
79
+ ],
80
+ };
81
+
82
+ const draft = await company.invoices.create(body);
83
+ const issued = await company.invoices.issue(draft.id);
84
+ console.log(issued.invoice_number); // e.g. "A-2026/0001"
85
+
86
+ // Or in one call:
87
+ const invoice = await company.invoices.create({ ...body, options: { issue_directly: true } });
88
+ ```
89
+
90
+ ### Create a simplified invoice (ticket)
91
+
92
+ For sales up to the legal limit without identifying the customer. The recipient may be empty
93
+ and must not carry a NIF.
94
+
95
+ ```typescript
96
+ const ticket = await company.invoices.create({
97
+ type: 'SIMPLIFIED',
98
+ external_ref: 'TICKET-2026-0815',
99
+ recipient: {},
42
100
  lines: [
43
- { line_type: 'NORMAL', description: 'Consulting', quantity: 1, unit_price: 100, discount_percentage: 0 },
101
+ { description: 'Menú del día', quantity: 2, unit_price: 14.5, main_tax: { type: 'IVA', percentage: 10, regime_key: '01' } },
44
102
  ],
103
+ options: { issue_directly: true },
45
104
  });
105
+ ```
46
106
 
47
- console.log(invoice.id); // UUID assigned immediately
107
+ If the customer later asks for a full invoice, exchange it:
108
+ `company.invoices.createSimplifiedExchange({ simplified_invoice_ids: [ticket.id], recipient: { customer_id } })`.
48
109
 
49
- // Issue it to assign an invoice number and trigger VeriFactu
50
- const issued = await company.invoices.issue(invoice.id);
51
- console.log(issued.invoice_number); // e.g. "A-2026/0001"
110
+ ### Correct an issued invoice
111
+
112
+ An issued invoice is never edited. If the operation happened but the invoice is wrong, issue a
113
+ corrective; if it was issued by mistake, void it.
114
+
115
+ ```typescript
116
+ // Partial: only the difference (here, a discount granted after the sale)
117
+ const corrective = await company.invoices.createCorrective(issued.id, {
118
+ rectification_type: 'PARTIAL',
119
+ rectification_code: 'R1',
120
+ reason: 'Discount agreed with the customer after the sale',
121
+ lines: [
122
+ { description: 'Discount', quantity: -1, unit_price: 50, main_tax: { type: 'IVA', percentage: 21, regime_key: '01' } },
123
+ ],
124
+ });
125
+
126
+ // Total: cancels everything still invoiced on the original (no lines)
127
+ await company.invoices.createCorrective(issued.id, {
128
+ rectification_type: 'TOTAL',
129
+ rectification_code: 'R1',
130
+ reason: 'Order cancelled by agreement with the customer before delivery',
131
+ });
132
+
133
+ // Issued by mistake (a test, an accidental duplicate):
134
+ await company.invoices.void(issued.id, { reason: 'Duplicate invoice issued by mistake', issued_in_error: true });
135
+ ```
136
+
137
+ A corrective is not covered by the one-invoice-per-`external_ref` rule (it carries the order
138
+ reference of the invoice it corrects), so `createOnce` does not create correctives. To retry one
139
+ safely, list by `rectified_invoice_id` (`company.invoices.list({ rectified_invoice_id })`) before
140
+ sending it again, or pass the same `idempotencyKey` (see below).
141
+
142
+ ### Find invoices by your own reference
143
+
144
+ ```typescript
145
+ const { invoices } = await company.invoices.list({ external_ref: 'ORD-2026-0042' });
146
+ const invoice = invoices.find((i) => i.type === 'STANDARD' || i.type === 'SIMPLIFIED');
147
+ ```
148
+
149
+ At most one live (not deleted) standard or simplified invoice can hold a given `external_ref`
150
+ in each environment: a second create answers `409` with `apiCode`
151
+ `INVOICE_DUPLICATE_EXTERNAL_REFERENCE`.
152
+
153
+ ### Read the VeriFactu status
154
+
155
+ AEAT answers asynchronously: a successful issue means *accepted for submission*.
156
+
157
+ ```typescript
158
+ const current = await company.invoices.get(invoiceId);
159
+ current.verifactu?.submission_status; // 'PENDING' | 'ACCEPTED' | 'REJECTED' | 'VOIDED' | 'NOT_SUBMITTED'
160
+ current.verifactu?.qr_url; // AEAT verification URL, from submission on
161
+ current.verifactu?.error_code; // AEAT's code when it reported a problem
162
+
163
+ // The records themselves (registration, then cancellation if voided)
164
+ const records = await company.invoices.verifactuRecords(invoiceId);
165
+
166
+ // Everything AEAT rejected
167
+ const { invoices: rejected } = await company.invoices.list({ verifactu_status: 'REJECTED' });
168
+ ```
169
+
170
+ To be told instead of polling, subscribe a webhook to `verifactu.status.updated` (see
171
+ [Webhooks](#webhooks)).
172
+
173
+ ### Retries, idempotency and never duplicating an invoice
174
+
175
+ What the client does on its own (defaults: `maxRetries: 3`, `autoIdempotencyKey: true`):
176
+
177
+ | Situation | Automatic retry? |
178
+ |---|---|
179
+ | `429` rate limit | Yes, after `Retry-After`, any method. The API refused the request unprocessed. |
180
+ | `409 IDEMPOTENCY_KEY_PROCESSING` | Yes, with the same key: the first request is still running. |
181
+ | `5xx` or network error on a `GET` | Yes, with exponential backoff. |
182
+ | `5xx` or network error on a `POST` | Yes, **with the same `Idempotency-Key`**: if the first attempt took effect, the API replays its answer instead of running it again. A `5xx` the API replays (`Idempotency-Replay: true`) is final and is not retried. |
183
+ | `5xx` or network error on a `PUT`/`PATCH`/`DELETE`, or a `POST` sent without a key | No. |
184
+ | Any other `4xx` | No. |
185
+
186
+ - **One call, one key.** Every `POST` gets an `Idempotency-Key` (a UUID) once, and every
187
+ automatic retry of that call reuses it. An automatic retry never creates a second invoice.
188
+ - **A new call is a new key.** If `create()` finally throws a `5xx` or a network error, the
189
+ outcome is unknown: the invoice may exist. Calling `create()` again sends a new key and can
190
+ duplicate it. Do not do that.
191
+ - **The API stores the answer of a key for 24 hours** (a `2xx` or a `5xx`; a `4xx` is not
192
+ stored, so the corrected request can reuse the key). Retrying with the same key after a `5xx`
193
+ returns the same `5xx`.
194
+
195
+ The recommended pattern: give the invoice your order id as `external_ref`, and create it with
196
+ `createOnce`. It looks the reference up first, creates only if nothing holds it, and when the
197
+ create fails with an unknown outcome or a `409 INVOICE_DUPLICATE_EXTERNAL_REFERENCE`, it returns
198
+ the invoice that does exist. Calling it again after any error is safe.
199
+
200
+ ```typescript
201
+ const invoice = await company.invoices.createOnce({
202
+ type: 'STANDARD',
203
+ external_ref: order.id,
204
+ recipient: { customer_id: order.customerId },
205
+ lines: [{ description: 'Order', quantity: 1, unit_price: 100, main_tax: { type: 'IVA', percentage: 21, regime_key: '01' } }],
206
+ options: { issue_directly: true },
207
+ });
208
+ ```
209
+
210
+ If you manage keys yourself, persist one per operation and pass it on every attempt:
211
+
212
+ ```typescript
213
+ await company.invoices.create(body, undefined, { idempotencyKey: `order-${order.id}`, timeoutMs: 15_000 });
214
+ ```
215
+
216
+ Errors keep the API's own code in `apiCode` (`code` stays the generic class code for backward
217
+ compatibility):
218
+
219
+ ```typescript
220
+ import { BeeLConflictError, BeeLErrorCodes } from '@beel_es/sdk';
221
+
222
+ try {
223
+ await company.invoices.create(body);
224
+ } catch (error) {
225
+ if (error instanceof BeeLConflictError && error.apiCode === BeeLErrorCodes.INVOICE_DUPLICATE_EXTERNAL_REFERENCE) {
226
+ // someone already created it: look it up by external_ref
227
+ }
228
+ throw error;
229
+ }
52
230
  ```
53
231
 
54
232
  ## Companies (multi-NIF)
@@ -157,6 +335,9 @@ It is the same client the resources use — authentication, retries and `Idempot
157
335
  still apply — and it stays typed: a path, parameter or body the contract does not have
158
336
  will not compile. What you give up is the sugar and the naming stability of the wrappers.
159
337
 
338
+ Error handling is the SDK's too: a non-2xx answer is **thrown** as a typed error (below), so a
339
+ `raw` call never resolves with openapi-fetch's `{ error }` — `data` is always the success body.
340
+
160
341
  ## Accounts (managed accounts, members & webhooks)
161
342
 
162
343
  For integrators managing several accounts (provisioning, gestorías, platforms):
@@ -257,21 +438,27 @@ try {
257
438
 
258
439
  | Error class | Status | When |
259
440
  |---|---|---|
260
- | `BeeLAuthError` | 401, 403 | Invalid or missing API key |
441
+ | `BeeLAuthError` | 401, 403 | Invalid or missing API key, or no access to the resource |
261
442
  | `BeeLNotFoundError` | 404 | Resource doesn't exist |
262
- | `BeeLValidationError` | 422 | Invalid data |
263
- | `BeeLConflictError` | 409 | Duplicate (e.g. same NIF) |
264
- | `BeeLRateLimitError` | 429 | Too many requests (auto-retried) |
265
- | `BeeLApiError` | 5xx | Server error (auto-retried) |
443
+ | `BeeLValidationError` | 422 | Invalid data or a business rule |
444
+ | `BeeLConflictError` | 409 | Duplicate (`external_ref`, NIF…) or an `Idempotency-Key` conflict |
445
+ | `BeeLRateLimitError` | 429 | Too many requests (retried automatically first) |
446
+ | `BeeLApiError` | 400, 5xx | Bad request, or a server error (retried when safe) |
447
+
448
+ Every error carries `statusCode`, `apiCode` (the API's `error.code`, e.g.
449
+ `INVOICE_DUPLICATE_EXTERNAL_REFERENCE`), `code` (a generic code per class), `details` and
450
+ `requestId` — quote the last one when you contact support. See
451
+ [docs/reference/errors.md](docs/reference/errors.md).
266
452
 
267
453
  ## Automatic Retries
268
454
 
269
- 429 and 5xx errors are retried with exponential backoff. Client errors (400, 401, 404, 422) are never retried.
455
+ See [Retries, idempotency and never duplicating an invoice](#retries-idempotency-and-never-duplicating-an-invoice)
456
+ for exactly what is retried. Tune it with:
270
457
 
271
458
  ```typescript
272
459
  const beel = new BeeL({
273
460
  apiKey: 'beel_sk_live_...',
274
- maxRetries: 5, // default: 3
461
+ maxRetries: 5, // default: 3 (0 disables retries)
275
462
  retryDelayMs: 1000, // default: 500
276
463
  maxRetryDelayMs: 60000, // default: 30000
277
464
  });
@@ -334,6 +521,7 @@ const customer = CustomerBuilder.create()
334
521
 
335
522
  const invoice = InvoiceBuilder.create()
336
523
  .forCustomer('customer-uuid')
524
+ .mainTax({ type: 'IVA', percentage: 21, regime_key: '01' }) // required on every line
337
525
  .addLine('Service A', 5, 100.0)
338
526
  .addLine('Service B', 3, 150.0)
339
527
  .metadata({ stripe_id: 'pi_abc123' })
@@ -359,12 +547,15 @@ const beel = new BeeL({
359
547
  maxRetries: 3, // Default
360
548
  retryDelayMs: 500, // Default
361
549
  maxRetryDelayMs: 30000, // Default
550
+ autoIdempotencyKey: true, // Default
362
551
  });
363
552
  ```
364
553
 
365
554
  ## Documentation
366
555
 
367
- Full API docs: [docs.beel.es](https://docs.beel.es)
556
+ - SDK reference: [docs/reference/README.md](docs/reference/README.md) — every resource and
557
+ method, generated from the source and the contract (`npm run docs:reference`).
558
+ - API docs: [docs.beel.es](https://docs.beel.es)
368
559
 
369
560
  ## License
370
561