Managed accounts
Build an accounting-firm or platform integration — provision accounts for your clients, hand them over with claim tokens, operate their invoicing and receive their events.
A managed account is a separate account that a provisioner — an accounting firm (gestoría), an agency or a fleet operator — creates and pays for on behalf of its holder. You keep one access level over each account and can operate its invoicing without the holder ever touching the API. How managed accounts fit next to members and grants is in the Overview.
Before you start.
- Scopes. Tick
accounts:write(provision, claim tokens, imports, access level, end management) andaccounts:read(list, get, usage) when you create the key. They are never part of the default key; anOWNERorADMINcan add them. See Scopes. - Capability. Your account also needs the managed-accounts capability, which BeeL.
enables on request. Until then these endpoints answer
403FEATURE_NOT_AVAILABLE.
Every result is scoped to you: you only ever see accounts you provisioned.
Build an accounting-firm integration
The whole flow, in order. Each step links to the section that explains it; rehearse steps
1–7 with a beel_sk_test_ key, then repeat the Live ones with your beel_sk_live_ key.
Get two keys
A test key and a live key, both with accounts:read and accounts:write, plus what you
need to operate for your clients: companies:read, companies:write, customers:write,
invoices:write, webhooks:write. Some writes only work with the live one — the list is
in Test and live keys.
Provision each client
One POST /v1/accounts with a tax_profile and access_level: "OPERATE" creates the
account and its NIF, switched on in Test with its default series, and returns the
company_id you invoice against. → Provision an account. Many
clients at once: → Onboard many accounts from a file.
Hand the account over (optional)
Deliver the claim_url so the holder takes ownership of their account; you keep your
access. → Claim tokens
Add NIFs and switch them on in Live
A second NIF for the same client is POST /v1/accounts/{account_id}/companies
(Create a company). Going live is
POST /v1/companies/{company_id}/activations with "environment": "PROD"
(Switching a NIF on). Every NIF
switched on in Live is billed to you.
Get the representation signed
Generate the VeriFactu representation, have the holder sign it, and upload the signed PDF with the live key. → Signing the VeriFactu representation
Confirm it can issue
GET /v1/companies/{company_id}/issuing-readiness with the live key: ready says
whether it can issue in Live, and verifactu.ready whether AEAT registration will go
through. → Is a NIF ready to invoice?
Issue its invoices
Create the invoice under their company_id and issue it. →
Issue invoices from a managed account
Receive their events
One subscription on your account, with account_relationship: "managed". →
Receive their webhooks
The lifecycle
sequenceDiagram
autonumber
participant You as Provisioner
participant API as BeeL. API
participant Holder as Account holder
You->>API: POST /v1/accounts (with tax_profile)
API-->>You: 201 account_id, company_id, claim_token — NIF on in Test
opt a second NIF
You->>API: POST /v1/accounts/{account_id}/companies
end
You->>API: POST /v1/companies/{company_id}/activations (PROD)
You->>API: POST /v1/companies/{company_id}/representation
Holder->>Holder: signs the representation PDF
You->>API: POST …/representation/submit (live key)
You->>API: GET /v1/companies/{company_id}/issuing-readiness
You->>API: POST /v1/companies/{company_id}/invoices, then …/issue
Holder->>API: claims the account (claim_url)
You->>API: DELETE /v1/accounts/{account_id}/management (live key)
Access levels
Your access over a managed account is one of three access_level values:
| access_level | What the holder can do |
|---|---|
NONE | Cover their subscription. No access to their data. This is the default. |
VIEW | Read their invoices, customers, products, series and fiscal data. |
OPERATE | Everything in VIEW, plus creating and editing them. Issuing on their behalf additionally requires a signed fiscal representation from the account holder. |
Which one do you actually need?
The level is cheap to lower and, after the claim, impossible to raise — so the choice is really "what is the most I will ever need from this account?", asked once, up front.
OPERATEis for platforms that operate the holder's invoicing: fleets that run their riders' invoicing, accounting firms that manage it for clients who never log in. It is the only level that lets you create invoices, customers and payment connections under their NIF. Take it whenever issuing is part of your product, even if you do not issue on day one.VIEWis for platforms that report on an account they set up: dashboards, reconciliation, "your invoices" screens. You see the data and the readiness state but cannot change anything, so an integration bug can never emit a fiscal document under someone else's NIF. A write answers403— see When it does not add up.NONEis for a relationship that is over in practice but not on paper — you provisioned the account, you still pay for it, and you no longer want access to its data. If you also want to stop paying, what you want is ending the management, notNONE.
Access level never affects billing. Whoever provisioned an account pays for its
subscription regardless of the level they keep over it — including NONE — and
regardless of whether the holder has claimed it.
Marketplaces: one managed account per partner
A marketplace whose partners — professionals, sellers — each have their own NIF fits this model: one managed account per partner, provisioned and paid for by you, with OPERATE access.
- Each partner is the issuer. Its invoices carry its NIF and fiscal data, come from its own series with its own numbering, and go to AEAT under its NIF, with its own chain of records. Nothing is shared between partners.
- You provision the account and pay for it. The partner never needs to log in. Hand it over later with a claim token if it ever wants its own access.
- One step needs the partner: signing its VeriFactu representation, once, before its invoices reach the real AEAT.
- You issue from your backend with Create an invoice on the partner's
company_id, typically when your own payment flow confirms the charge — see Issue invoices from a managed account.
One limit to know before you design around it:
- Stripe Connect Express is not connected partner by partner. BeeL.'s Stripe integration connects a Stripe account that authorises BeeL. itself, one account per NIF; it does not read the connected accounts of your own Connect platform. With Express, issue through the API as above — see Stripe Connect Express and Standard.
Pricing is per NIF: see Pricing.
Provision an account
POST /v1/accounts — accounts:write, either key. Include a tax_profile and the
account comes back ready to invoice: its NIF, its default invoice series (ordinary F,
simplified S, corrective R) and its VeriFactu configuration are set up in the same
request, switched on in Test, and the response carries the company_id you put in the path
of every call that invoices for them.
curl -X POST https://app.beel.es/api/v1/accounts \
-H "Authorization: Bearer beel_sk_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f8c1e10-3b2a-4c9d-8e7f-1a2b3c4d5e6f" \
-d '{
"email": "cliente@example.com",
"display_name": "Asesoría Cliente SL",
"external_ref": "your-ref-0042",
"language": "es",
"access_level": "OPERATE",
"tax_profile": {
"nif": "B12345674",
"legal_name": "Asesoría Cliente SL",
"entity_type": "LEGAL_ENTITY",
"address": {
"street": "Calle Mayor",
"number": "10",
"postal_code": "28001",
"city": "Madrid",
"province": "Madrid"
}
}
}'The full field list, with types and defaults, is in Provision an account. The fields that decide the shape of the whole relationship:
display_nameis required.access_levelpicks how much you can do afterwards, and it is the one choice you cannot fully undo: once the holder claims the account, only they can raise it. Omit it and you getNONE. See Access levels.external_refis your own id for the account, and its idempotency key: re-sending the same one returns201with the same account (not409) — even if theemaildiffers, in which case the new email is ignored. Pick something stable in your system — a customer id, not a timestamp.tax_profiletakes the same fields as creating a NIF, with the same rules: the NIF must be registered in the AEAT census, in the sandbox too, anddefault_irpf_rateis a declaration you only send when the holder made it (why). It is required whenaccess_levelisOPERATE— operating someone's invoicing needs a NIF to issue under, so omitting it is422VALIDATION_ERROR.
For exact response idempotency on network retries — the same body, the same claim token, no
second email — also send an Idempotency-Key header: a repeat with the same key within
24 hours returns the cached response with Idempotency-Replay: true.
Response
{
"success": true,
"data": {
"person_id": "9f8c1e10-3b2a-4c9d-8e7f-1a2b3c4d5e6f",
"account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "PROVISIONED",
"claim_token": "claim_one_time_secret",
"claim_url": "https://app.beel.es/reclamar?token=claim_one_time_secret",
"company_id": "550e8400-e29b-41d4-a716-446655440000"
}
}What the shape does not tell you:
claim_tokenis shown once, so capture it here.claim_urlis the same token wrapped in the link for the current environment. See Claim tokens.- An absent
claim_tokenis not an error — readstatus. WithCLAIMEDorACTIVEthe holder already took ownership, so what they need is a password reset, not another claim link. Withclaim_link_already_issued: trueyou resent theexternal_refof an unclaimed account whose earlier link is still valid: that link keeps working, and no new one or email is issued. - Every account is born with one company, and its
company_idis in the response. Without atax_profilethat company has no NIF yet: its holder registers it on claim, and until then its issuing readiness reportsCOMPANY_HAS_NO_NIF.company_idisnullonly when you resend anexternal_refwhose account now holds two or more companies — find them with List its NIFs.
A 409 means the email already belongs to a BeeL. account that this request can neither
reuse (same external_ref) nor reactivate:
| What it means for you | Error | Status |
|---|---|---|
| You did manage this account and ended it, but its holder has already claimed it. It is theirs now — ask them to grant you access; provisioning cannot take it back. | PROVISIONING_ACCOUNT_CLAIMED | 409 |
| The email belongs to an account you never managed, one someone else manages, or one whose management someone else ended. Use a different email or contact support. | PROVISIONING_EMAIL_ALREADY_REGISTERED | 409 |
With a tax_profile, the NIF is validated against the AEAT census before anything is
created. If the census cannot be reached, the call answers
502 EXTERNAL_SERVICE_ERROR and nothing is stored: repeat the same request
later, with the same Idempotency-Key. The account keeps the country of the tax_profile
address.
Onboard many accounts from a file
A portfolio arrives as a spreadsheet, so there is a file import. Download the template with
GET /v1/templates/account-import: ;-separated,
UTF-8 with a byte order mark, one account per row. A file holds up to
100 rows and 5 MB;
a larger portfolio goes in several passes.
Preview first. It writes nothing and bills nothing, and tells you per row whether the
NIF is in the census, whether its external_ref already exists and whether it would be
switched on in Live:
curl -X POST https://app.beel.es/api/v1/accounts/imports/preview \
-H "Authorization: Bearer beel_sk_live_xxx" \
-F "accounts_file=@accounts.csv"Read statistics.live_activations_pending before importing: every NIF switched on in Live
is billed to you, and this is the only place to see the total beforehand.
Then import the same file. It requires an Idempotency-Key
(400 IDEMPOTENCY_KEY_REQUIRED without one):
curl -X POST https://app.beel.es/api/v1/accounts/imports \
-H "Authorization: Bearer beel_sk_live_xxx" \
-H "Idempotency-Key: 0b6c3f4e-8f1a-4d7e-9a2b-5c6d7e8f9a0b" \
-F "accounts_file=@accounts.csv"- Each new row becomes an
OPERATEaccount with its NIF switched on in Test and in Live. Itsclaim_tokenandclaim_urlcome back in this response and nowhere else. A row that already exists keeps a claim link that is still valid: it comes back withclaim_tokennullandclaim_link_already_issued: true. - It is not atomic: a bad row is reported and the good ones stay.
- It is safe to re-run with the same file: rows are matched by
external_ref, so fixing a bad row and uploading again creates only what is missing. - An optional
customers_file(the customer import template) is applied to every account of the import.
Row statuses, options and every field: Preview an import of managed accounts and Import managed accounts from a file.
Hand the account over: claim tokens
The claim_url you got at provisioning is how the holder takes ownership of their account.
Claiming is done by the holder on BeeL., not by you: there is no public claim
endpoint. They open the link, set a password if they are new, and accept BeeL.'s terms,
privacy policy and a billing mandate that names your organisation as the payer. You learn
it from the account.claimed event, or by polling Get one.
- A claim token lasts 30 days, and only the last one issued is live: issuing a new one kills the previous link.
- To resend the claim, issue a fresh token. Re-provisioning with the same
external_refreturns one only when the account is unclaimed and has no valid link left (expired, or never issued); a link that is still valid is kept, never revoked. - Once the holder has claimed, there is nothing left to claim: the call answers
409PROVISIONING_ACCOUNT_CLAIMED.
curl -X POST https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/claim-tokens \
-H "Authorization: Bearer beel_sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{}'The 201 carries claim_token, claim_url and expires_at. See
Issue a claim token.
Account status
status on the account tells you where it stands:
PROVISIONED → CLAIMED → ACTIVEPROVISIONED— created, not claimed yet.CLAIMED— the holder took ownership, but the account has no NIF yet. It appears for an account provisioned without atax_profile, until its holder registers a NIF: the company it was born with does not count while it has no NIF. The list filterstatus=CLAIMEDreturns these accounts.ACTIVE— claimed, with a NIF. An account provisioned with atax_profilealready has its NIF, so claiming takes it straight fromPROVISIONEDtoACTIVE.
What claiming changes for you
- You keep your access and the bill. Claiming makes the holder the owner of their
account; it does not evict you, and your
access_levelsurvives untouched. - You can no longer raise your own access. Before the claim you set it freely; after
it, only the holder can raise it
(
403MANAGED_SCOPE_ELEVATION_REQUIRES_OWNER). You may still keep it or lower it. If you will ever needOPERATE, take it before you hand the link over. - Reactivation stops being possible. See End your management.
Track the accounts you provisioned
List
GET /v1/accounts — accounts:read. Newest first, cursor-paginated, only the accounts
you provisioned. Filter by status, or look one up by your own external_ref.
curl "https://app.beel.es/api/v1/accounts?status=ACTIVE&limit=50" \
-H "Authorization: Bearer beel_sk_live_xxx"- Look accounts up by your own
external_ref, not by email. It returns 0..1 accounts, and anexternal_refyou never used returns an empty list rather than an error — so the same call answers "do I already have this client?" safely. - Follow
data.next_cursoruntil it comes backnull. A portfolio that grows between two calls will not skip or repeat rows the way an offset would.
Filters and fields: List provisioned accounts.
Get one
GET /v1/accounts/{account_id} — accounts:read. The authoritative status,
access_level and claim state of one account. An account you did not provision, whose
management you ended, or that does not exist answers
403 PROVISIONING_ACCOUNT_NOT_ACCESSIBLE.
List its NIFs
GET /v1/accounts/{account_id}/companies — companies:list, and an access_level
other than NONE. POST /v1/accounts returns company_id once; this is how you get it
back, and the only way to reach a NIF the holder registered later.
Issue invoices from a managed account
With OPERATE access, put the managed NIF's company_id in the path. In Test you can
issue right away; in Live, real AEAT submission under the holder's NIF requires their signed
VeriFactu representation first.
curl -X POST https://app.beel.es/api/v1/companies/550e8400-e29b-41d4-a716-446655440000/invoices \
-H "Authorization: Bearer beel_sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"recipient": {
"legal_name": "Cliente Final SL",
"nif": "B87654323",
"address": {
"street": "Calle Mayor", "number": "10",
"postal_code": "28001", "city": "Madrid", "province": "Madrid"
}
},
"lines": [ { "description": "Monthly service fee", "quantity": 1, "unit_price": 100,
"main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" } } ]
}'This creates a draft. Issue it — which numbers it and, where VeriFactu applies, submits
it under their NIF — with the id from the response:
curl -X POST https://app.beel.es/api/v1/companies/550e8400-e29b-41d4-a716-446655440000/invoices/7c9e6679-7425-40de-944b-e07fc1f90ae7/issue \
-H "Authorization: Bearer beel_sk_test_xxx"Or do both in one call with "options": { "issue_directly": true } in the body. Customers
are created the same way, under the same NIF: POST /v1/companies/{company_id}/customers.
See Create an invoice and Issue an invoice.
Receive their webhooks
Events of the accounts you manage reach you through a subscription on your own account
— POST /v1/accounts/{your_account_id}/webhooks — with "account_relationship": "managed"
(only theirs) or "all" (yours and theirs). It defaults to own. Three events exist only for
provisioners: account.claimed, company.created and representation.signed (see
Available events). Only accounts whose data
you can see send events: one you hold at NONE sends none. Each delivered event carries
account_id, account_external_ref and account_relationship, so you can route it to the
right client.
See Webhooks and Events.
You cannot manage members, invitations or webhooks inside a managed account
(MEMBER_MANAGEMENT_FORBIDDEN / ACCOUNT_MANAGEMENT_FORBIDDEN):
those belong to its holder.
Connect a payment provider (white-label)
With OPERATE access you also connect Stripe to each managed NIF white-label — the
holder authorizes from your own portal and charges are auto-invoiced under the correct NIF.
See Connect Stripe for the flow, and Stripe
for how connected charges become invoices.
Change your access level
PATCH /v1/accounts/{account_id}/access-level — accounts:write, live key
(403 LIVE_CREDENTIAL_REQUIRED with a test key: it acts on an account
that may already be invoicing). Set access_level to NONE, VIEW or OPERATE. Returns
204.
curl -X PATCH https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/access-level \
-H "Authorization: Bearer beel_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "access_level": "VIEW" }'An account you do not manage, or that does not exist, answers
403 ACCOUNT_NOT_ACCESSIBLE. Raising the level after the holder has
claimed the account answers 403 MANAGED_SCOPE_ELEVATION_REQUIRES_OWNER.
End your management of an account
DELETE /v1/accounts/{account_id}/management — accounts:write, live key
(403 LIVE_CREDENTIAL_REQUIRED with a test key). Returns 204. You
lose access to the account at once and its NIFs stop counting towards your billable usage.
Nothing is deleted or anonymised: the holder keeps the account, its NIFs and its invoices,
and becomes responsible for their own subscription.
curl -X DELETE https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/management \
-H "Authorization: Bearer beel_sk_live_xxx"Reversible only while the account stays unclaimed. Provisioning the same email again
brings the same account back — same holder, NIFs and invoices — under the external_ref
and access_level of the new request. Only the manager who ended the relationship can do
that. Once the holder has claimed it, provisioning answers
409 PROVISIONING_ACCOUNT_CLAIMED: getting the relationship back then
needs their consent, not just their email address.
Provisioning usage
GET /v1/accounts/{account_id}/usage —
accounts:read, with your own account_id (from GET /v1/me/identity). Usage
belongs to the provisioner, so any other id answers
404 PROVISIONING_ACCOUNT_NOT_ACCESSIBLE.
curl https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/usage \
-H "Authorization: Bearer beel_sk_live_xxx"Read nifs as "billable units", not as "real NIFs". The unit that is billed is the
provisioned account: every account you provision counts as one, including the empty and
unclaimed ones, so nifs normally equals provisioned_accounts and only diverges when an
account holds more than one NIF. An account you provisioned and forgot about is still on the
bill until you end your management of it.