Resources under the NIF
An account can now hold several NIFs, each named in the path. The old flat routes are deprecated with a Sunset date and a successor — here's what moved where, when the old routes retire, and how to tell whether it affects you.
What's new: an account can now hold several NIFs. A fiscal identity is no longer inferred from your credential — you name it in the path, so one account can invoice under many NIFs. That's the model documented in Multi-NIF and Companies; this note is the API side of it.
Each resource now lives under the axis it belongs to: business resources under
the NIF (/v1/companies/{company_id}), account-wide ones under the account
(/v1/accounts/{account_id}), the shared catalogues at the top level, and your
preferred language on /v1/me. The full move is in the
route table below.
Nothing breaks today. The old flat routes are deprecated, not removed: each
keeps answering and now carries Deprecation: true, a Sunset date and, where
one is declared, a Link to its successor. Three routes deliberately have none
— the PUTs that replace a customer, a product or a recurring invoice — and
the contract says so instead of pointing you at something that behaves
differently. They all retire on 10 September 2026 — migrate on your own
schedule until then.
Want the exact list for your code? If you use Claude Code,
the beel-api plugin has a skill (/beel-api:upgrade) that reads this note and
your repository and comes back with the calls you need to change — jump to
Hand it to Claude Code.
What breaks
Five changes need more than a new path — a mechanical find-and-replace gets each of them wrong:
GET /v1/products/searchis the one trap. Its successor isGET /v1/companies/{company_id}/products, which takes the same query filter but answers a different shape: the old route returneddata: [ … ], the successor returnsdata: { products, pagination }. Swapping the path without reading the schema leaves you mapping over an object.- The
PUTs that really replace have no successor, on purpose.PUT /v1/customers/{customer_id},PUT /v1/products/{product_id}andPUT /v1/recurring-invoices/{recurring_invoice_id}replace the whole resource: a field you leave out is cleared. The canonical form has a single update verb,PATCH, and it merges — so it is not a drop-in destination. The contract marks these threex-no-successor, and the API sends them noLink: rel="successor-version", precisely so that nobody migrates them blind. Moving one of them to the company-scopedPATCHchanges what your call does, from replacing to merging: go through those call sites by hand, and where you relied on replacement send the resource complete, passingnullin every field you want cleared. TheirPATCHsiblings — which already existed on the flat routes — are the ones with a clean 1:1 successor. PUT /v1/invoices/{invoice_id}is the exception, and it costs you nothing. It is the onlyPUThere with a declared successor,PATCH /v1/companies/{company_id}/invoices/{invoice_id}, and it earns it: this route never replaced the invoice. It applied only the fields present in the body and left the rest as they were — exactly what the successor does. The verb was the lie, not the behaviour. Change route and verb, keep the body, and the result is the same as before.- Action verbs converged onto
status.mark-paid,mark-sentandrevert-to-issuedall becomePUT /v1/companies/{company_id}/invoices/{invoice_id}/statuswith the target status in the body;pauseandresumedo the same for recurring invoices. The old path is gone from the successor — the state you want is now data, not a URL. - The four
/v1/invoices/bulk/*routes split by intent intopdf-archive,deliveries,batchesandexports, each with its own request and response. There is no single successor to find-and-replace them onto.
Does this affect you?
- Grep your integration for
/v1/invoices,/v1/customers,/v1/products,/v1/recurring-invoices,/v1/configuration/,/v1/webhooks,/v1/emailsand/v1/developers/request-logs. Every hit is a deprecated route, and the table below gives its successor — or says it has none. - Watch your responses: a deprecated route answers with
Deprecation: true, aSunsetdate and, when a successor is declared,Link: <successor>; rel="successor-version". Log those headers for a week and you have the exact list of calls left to migrate, with no guessing — and a deprecated route arriving without theLinkis one of the three replacingPUTs, which you have to move by hand. - Check whether you send the
BeeL-Active-Companyheader. Only the flat routes read it; once the NIF is in the path, the header plays no part.
The retirement calendar
The deprecated routes retire on 10 September 2026 — about a month from now. One speed, with a grace period on every route until then.
| Phase | What happens |
|---|---|
| Today | Every successor route is live, and every deprecated route keeps answering exactly as before. Nothing 404s; nothing has been withdrawn. |
| Grace period | The 82 deprecated operations keep working, adding Deprecation: true, the Sunset date and — for all but the three replacing PUTs — a Link to their successor. You migrate on your own schedule while this lasts. |
| 10 September 2026 | The deprecated routes stop answering and return 410 Gone. Every response until then carried the same Sunset date in its headers, so you can read it straight from your own traffic rather than trusting a page. |
A deprecated route keeps working until its Sunset date and then answers
410 Gone, so an integration left untouched keeps issuing invoices right up to
a date it was told about in every response it ever received. Nothing degrades
silently in between: the headers are there from day one.
Find your route
Every route this release touched, grouped by resource. All 82 keep answering and
carry Deprecation and Sunset, and all but the three replacing PUTs also
carry Link: <successor>; rel="successor-version" — migrate before the date
Sunset announces. Both columns show the route in full. Where the Now cell
leads with a method, the method changed too, and a path-only find-and-replace
leaves you calling a verb the successor does not serve. Read the note on those
rows before you move them: PUT becoming PATCH is harmless on invoices and a
change of behaviour on customers, products and recurring invoices.
Invoices
24 operations
The bulk/* routes split by intent; each successor has its own response.
| Method | Was | Now |
|---|---|---|
| GETPOST | Was | Now |
| GETDELETE | Was | Now |
| PUT | Was | NowPATCHPUT never replaced the invoice — it applied just the fields present in the body, exactly as the successor PATCH does. |
| POST | Was | Now |
| POST | Was | Now |
| POST | Was | Now |
| POST | Was | Now |
| POST | Was | Now |
| GET | Was | Now |
| GET | Was | Now |
| POST | Was | NowPUT |
| POST | Was | NowPUT |
| POST | Was | NowPUT |
| POST | Was | NowPUT |
| PATCH | Was | NowPUTschedule. |
| POST | Was | NowDELETE |
| POST | Was | NowPOST |
| POST | Was | NowPOST |
| POST | Was | NowPOST |
| POST | Was | NowPOST |
| POST | Was | NowPOST |
| POST | Was | NowPOST |
Configuration
14 operations
| Method | Was | Now |
|---|---|---|
| GETPOST | Was | Now |
| PATCHDELETE | Was | Now |
| POST | Was | NowPUT |
| POST | Was | NowPUT |
| GET | Was | NowGET |
| GETPUT | Was | Now |
| GETPUT | Was | Now |
| PUT | Was | NowPATCH |
| GET | Was | NowGET/v1/configuration/. |
| GET | Was | NowGET |
Recurring invoices
12 operations
| Method | Was | Now |
|---|---|---|
| GETPOST | Was | Now |
| GETPATCHDELETE | Was | NowPATCH merges the fields you send, just as this one does. |
| PUT | Was | NowPATCHx-no-successor and the API sends it no Link. It replaces the schedule, lines and recipient, clearing every field you omit; the PATCH shown merges instead, so moving there changes what your call does. Send the template complete, with null in the fields you want cleared. |
| POST | Was | NowPUT |
| POST | Was | NowPUT |
| POST | Was | Now |
| POST | Was | Now |
| GET | Was | Now |
| GET | Was | NowGET |
Customers
11 operations
| Method | Was | Now |
|---|---|---|
| GETPOST | Was | Now |
| GETDELETE | Was | Now |
| PUT | Was | NowPATCHx-no-successor and the API sends it no Link. It replaces the customer, clearing every field you omit; the PATCH shown merges instead, so moving there changes what your call does. Send the customer complete, with null in the fields you want cleared. |
| PATCH | Was | NowPATCH already existed and its successor behaves identically. |
| POSTDELETE | Was | Now |
| POST | Was | NowPOST |
| POST | Was | NowPOST |
| POST | Was | NowGETGET, and it is the same for every NIF. |
Products
9 operations
| Method | Was | Now |
|---|---|---|
| GETPOST | Was | Now |
| GETDELETE | Was | Now |
| PUT | Was | NowPATCHx-no-successor and the API sends it no Link. It replaces the product, resetting every field you omit to its creation default; the PATCH shown merges instead, so moving there changes what your call does. Send the product complete, with null in the fields you want cleared. |
| PATCH | Was | NowPATCH already existed and its successor behaves identically. |
| POSTDELETE | Was | Now |
| GET | Was | NowGETdata: [ … ]; the successor returns data: { products, pagination }. |
Webhooks
8 operations
Webhook subscriptions belong to the account, so they move under @.
| Method | Was | Now |
|---|---|---|
| GETPOST | Was | Now |
| GETPATCHDELETE | Was | Now |
| POST | Was | Now |
| GET | Was | Now |
| POST | Was | Now |
Emails
2 operations
| Method | Was | Now |
|---|---|---|
| GET | Was | Now |
| GET | Was | Nowindicators as an id. |
Request logs
2 operations
| Method | Was | Now |
|---|---|---|
| GET | Was | Now |
| GET | Was | Now |
Hand it to Claude Code
This note can tell you what moved. Only something looking at your code can tell you where. If you use Claude Code, the beel-api plugin has a skill that reads this note and your repository and comes back with the list.
/beel-api:upgradeFrom the beel-api plugin. It reads /api/changelog, matches the route table against your own calls, and writes up what it finds. Run it with no arguments for everything, or name an area (/beel-api:upgrade invoices) to narrow it.
Migrate my BeeL. API integration to the per-NIF routes.
First, read https://docs.beel.es/api/changelog and find the entry whose slug is
"resources-under-the-nif". Treat its `routeMigration` groups as the authoritative
old-to-new mapping, and its `breakingChanges` as the list of changes that a
find-and-replace will get wrong.
Then find every BeeL. API call in this codebase and report, before editing anything:
1. Each call to a DEPRECATED route — give me the successor and the Sunset date
read from the response headers.
2. Calls that need more than a new path: a different HTTP verb (PUT becoming
PATCH), a different request body, or a different response shape.
Propose a diff for (1). For (2), show me each call site and stop for my review —
do not rewrite them automatically.
Finally, tell me which of my calls need no change at all, so I know the scope.What to expect
Sunset date and the traps all come from this note, served as JSON at /api/changelog — so it migrates to the route we actually published.Review these by hand
GET /v1/products/search. Its successor GET /v1/companies/{company_id}/products takes the same filter but returns data: { products, pagination } where the old route returned data: [ … ]. The path swap compiles and the mapping breaks at runtime.PUT becoming PATCH — only one of these is safe. PUT /v1/invoices/{invoice_id} has a declared successor and always applied just the fields you sent, so the move to PATCH /v1/companies/{company_id}/invoices/{invoice_id} changes the verb and the path and nothing else. The PUTs on customers, products and recurring invoices are the opposite: they replace the resource, they are marked x-no-successor in the contract, and no Link header points anywhere. Migrating them to the company-scoped PATCH turns a replace into a merge — review each call site and resend the resource complete, with null in the fields you want cleared.mark-paid, mark-sent, revert-to-issued, pause and resume collapse onto a single status route with the target state in the body — a change of request shape, not just of URL.See also
- Multi-NIF — the model behind the axis: one account, many fiscal identities.
- Companies — creating and addressing each NIF.
- Payment connections — Stripe per NIF.
- Managed accounts — issuing on behalf of another account.
- VeriFactu auto-submit — what changes for fiscal submission per NIF.
- Node/TypeScript SDK — the official typed client, generated from this contract, so it targets the per-NIF routes.
`external_reference` is now `external_ref`
The invoice field first announced as external_reference is now called external_ref — the old name still works when you write, but responses no longer return it.
The global rate limit drops to 1000 requests per minute
The ceiling that applies to every request, whatever the endpoint, goes from 2000 to 1000 requests per 60 seconds. The per-endpoint tiers are unchanged. An integration that bursts above the new figure will start seeing `429` where it did not before.