NewThe global rate limit drops to 1000 requests per minute
BeeL
Get StartedMulti-NIFVeriFactuStripeAPI ReferenceChangelog

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/search is the one trap. Its successor is GET /v1/companies/{company_id}/products, which takes the same query filter but answers a different shape: the old route returned data: [ … ], the successor returns data: { 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} and PUT /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 three x-no-successor, and the API sends them no Link: rel="successor-version", precisely so that nobody migrates them blind. Moving one of them to the company-scoped PATCH changes 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, passing null in every field you want cleared. Their PATCH siblings — 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 only PUT here 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-sent and revert-to-issued all become PUT /v1/companies/{company_id}/invoices/{invoice_id}/status with the target status in the body; pause and resume do 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 into pdf-archive, deliveries, batches and exports, 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/emails and /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, a Sunset date 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 the Link is one of the three replacing PUTs, which you have to move by hand.
  • Check whether you send the BeeL-Active-Company header. 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.

PhaseWhat happens
TodayEvery successor route is live, and every deprecated route keeps answering exactly as before. Nothing 404s; nothing has been withdrawn.
Grace periodThe 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 2026The 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.

GETPOSTWas/v1/invoicesNow/v1/companies/{company_id}/invoices
GETDELETEWas/v1/invoices/{invoice_id}Now/v1/companies/{company_id}/invoices/{invoice_id}
PUTWas/v1/invoices/{invoice_id}NowPATCH/v1/companies/{company_id}/invoices/{invoice_id}Only the verb and the path change. This PUT never replaced the invoice — it applied just the fields present in the body, exactly as the successor PATCH does.
POSTWas/v1/invoices/{invoice_id}/issueNow/v1/companies/{company_id}/invoices/{invoice_id}/issue
POSTWas/v1/invoices/{invoice_id}/voidNow/v1/companies/{company_id}/invoices/{invoice_id}/void
POSTWas/v1/invoices/{invoice_id}/correctiveNow/v1/companies/{company_id}/invoices/{invoice_id}/corrective
POSTWas/v1/invoices/{invoice_id}/sendNow/v1/companies/{company_id}/invoices/{invoice_id}/send
POSTWas/v1/invoices/{invoice_id}/convert-to-invoiceNow/v1/companies/{company_id}/invoices/{invoice_id}/convert-to-invoice
GETWas/v1/invoices/{invoice_id}/pdfNow/v1/companies/{company_id}/invoices/{invoice_id}/pdf
GETWas/v1/invoices/{invoice_id}/pdf/previewNow/v1/companies/{company_id}/invoices/{invoice_id}/pdf/preview
POSTWas/v1/invoices/{invoice_id}/mark-paidNowPUT/v1/companies/{company_id}/invoices/{invoice_id}/statusTarget status goes in the body.
POSTWas/v1/invoices/{invoice_id}/mark-sentNowPUT/v1/companies/{company_id}/invoices/{invoice_id}/statusTarget status goes in the body.
POSTWas/v1/invoices/{invoice_id}/revert-to-issuedNowPUT/v1/companies/{company_id}/invoices/{invoice_id}/statusTarget status goes in the body.
POSTWas/v1/invoices/{invoice_id}/scheduleNowPUT/v1/companies/{company_id}/invoices/{invoice_id}/schedule
PATCHWas/v1/invoices/{invoice_id}/rescheduleNowPUT/v1/companies/{company_id}/invoices/{invoice_id}/scheduleSame route as schedule.
POSTWas/v1/invoices/{invoice_id}/unscheduleNowDELETE/v1/companies/{company_id}/invoices/{invoice_id}/schedule
POSTWas/v1/invoices/{invoice_id}/duplicateNowPOST/v1/companies/{company_id}/invoices/derivations
POSTWas/v1/invoices/{invoice_id}/create-recurringNowPOST/v1/companies/{company_id}/recurring-invoices/derivations
POSTWas/v1/invoices/bulk/pdfNowPOST/v1/companies/{company_id}/invoices/pdf-archive
POSTWas/v1/invoices/bulk/sendNowPOST/v1/companies/{company_id}/invoices/deliveries
POSTWas/v1/invoices/bulk/statusNowPOST/v1/companies/{company_id}/invoices/batches
POSTWas/v1/invoices/export/excelNowPOST/v1/companies/{company_id}/invoices/exports

Configuration

14 operations

GETPOSTWas/v1/configuration/seriesNow/v1/companies/{company_id}/series
PATCHDELETEWas/v1/configuration/series/{series_id}Now/v1/companies/{company_id}/series/{series_id}
POSTWas/v1/configuration/series/{series_id}/defaultNowPUT/v1/companies/{company_id}/series/{series_id}/default
POSTWas/v1/configuration/series/defaultsNowPUT/v1/companies/{company_id}/series/defaults
GETWas/v1/configuration/series/defaults-statusNowGET/v1/companies/{company_id}/series/defaults
GETPUTWas/v1/configuration/verifactuNow/v1/companies/{company_id}/verifactu-configuration
GETPUTWas/v1/configuration/taxesNow/v1/companies/{company_id}/tax-configuration
PUTWas/v1/configuration/languageNowPATCH/v1/meThe preferred language belongs to the person, not to a NIF.
GETWas/v1/configuration/tax-typesNowGET/v1/tax-typesThe catalogue is the same for every NIF, so it left /v1/configuration/.
GETWas/v1/configuration/invoice-customization-optionsNowGET/v1/invoice-customization-options

Recurring invoices

12 operations

GETPOSTWas/v1/recurring-invoicesNow/v1/companies/{company_id}/recurring-invoices
GETPATCHDELETEWas/v1/recurring-invoices/{recurring_invoice_id}Now/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}The 1:1 move: the successor PATCH merges the fields you send, just as this one does.
PUTWas/v1/recurring-invoices/{recurring_invoice_id}NowPATCH/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}No declared successor — the contract marks this route x-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.
POSTWas/v1/recurring-invoices/{recurring_invoice_id}/pauseNowPUT/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/statusTarget status goes in the body.
POSTWas/v1/recurring-invoices/{recurring_invoice_id}/resumeNowPUT/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/statusTarget status goes in the body.
POSTWas/v1/recurring-invoices/{recurring_invoice_id}/skipNow/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/skip
POSTWas/v1/recurring-invoices/{recurring_invoice_id}/generateNow/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/generate
GETWas/v1/recurring-invoices/{recurring_invoice_id}/historyNow/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/history
GETWas/v1/recurring-invoices/{recurring_invoice_id}/previewNowGET/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/next-occurrence

Customers

11 operations

GETPOSTWas/v1/customersNow/v1/companies/{company_id}/customers
GETDELETEWas/v1/customers/{customer_id}Now/v1/companies/{company_id}/customers/{customer_id}
PUTWas/v1/customers/{customer_id}NowPATCH/v1/companies/{company_id}/customers/{customer_id}No declared successor — the contract marks this route x-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.
PATCHWas/v1/customers/{customer_id}Now/v1/companies/{company_id}/customers/{customer_id}The 1:1 move: this PATCH already existed and its successor behaves identically.
POSTDELETEWas/v1/customers/bulkNow/v1/companies/{company_id}/customers/bulk
POSTWas/v1/customers/import-csv-previewNowPOST/v1/companies/{company_id}/customers/imports/preview
POSTWas/v1/customers/import-holded-contactsNowPOST/v1/companies/{company_id}/customers/imports
POSTWas/v1/customers/templates/csvNowGET/v1/templates/customer-importReading a fixed template is a GET, and it is the same for every NIF.

Products

9 operations

GETPOSTWas/v1/productsNow/v1/companies/{company_id}/products
GETDELETEWas/v1/products/{product_id}Now/v1/companies/{company_id}/products/{product_id}
PUTWas/v1/products/{product_id}NowPATCH/v1/companies/{company_id}/products/{product_id}No declared successor — the contract marks this route x-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.
PATCHWas/v1/products/{product_id}Now/v1/companies/{company_id}/products/{product_id}The 1:1 move: this PATCH already existed and its successor behaves identically.
POSTDELETEWas/v1/products/bulkNow/v1/companies/{company_id}/products/bulk
GETWas/v1/products/searchNowGET/v1/companies/{company_id}/productsDifferent response shape. The old route returned data: [ … ]; the successor returns data: { products, pagination }.

Webhooks

8 operations

Webhook subscriptions belong to the account, so they move under @.

GETPOSTWas/v1/webhooksNow/v1/accounts/{account_id}/webhooks
GETPATCHDELETEWas/v1/webhooks/{webhook_id}Now/v1/accounts/{account_id}/webhooks/{webhook_id}
POSTWas/v1/webhooks/{webhook_id}/secretNow/v1/accounts/{account_id}/webhooks/{webhook_id}/secret
GETWas/v1/webhooks/{webhook_id}/deliveriesNow/v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries
POSTWas/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/retryNow/v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry

Emails

2 operations

GETWas/v1/emailsNow/v1/accounts/{account_id}/emails
GETWas/v1/emails/indicatorsNow/v1/accounts/{account_id}/email-indicatorsA sibling of the collection, not a member of it, so it does not reserve indicators as an id.

Request logs

2 operations

GETWas/v1/developers/request-logsNow/v1/accounts/{account_id}/request-logs
GETWas/v1/developers/request-logs/{request_id}Now/v1/accounts/{account_id}/request-logs/{request_id}

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:upgrade

From 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.

Or paste this prompt
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

It proposes; it does not apply. You get a report and a diff to review, not a rewritten branch. Nothing changes until you say so.
It reads the route table rather than guessing. The old-to-new mapping, the 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.
It tells you what is already fine. Most of the answer is usually the list of calls that need nothing, and that is the part that lets you size the job.

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.
Converged action verbs. 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