NewProvince is only required for addresses in Spain
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

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) and accounts:read (list, get, usage) when you create the key. They are never part of the default key; an OWNER or ADMIN can add them. See Scopes.
  • Capability. Your account also needs the managed-accounts capability, which BeeL. enables on request. Until then these endpoints answer 403 FEATURE_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_levelWhat the holder can do
NONECover their subscription. No access to their data. This is the default.
VIEWRead their invoices, customers, products, series and fiscal data.
OPERATEEverything 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.

  • OPERATE is 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.
  • VIEW is 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 answers 403 — see When it does not add up.
  • NONE is 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, not NONE.

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_name is required.
  • access_level picks 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 get NONE. See Access levels.
  • external_ref is your own id for the account, and its idempotency key: re-sending the same one returns 201 with the same account (not 409) — even if the email differs, in which case the new email is ignored. Pick something stable in your system — a customer id, not a timestamp.
  • tax_profile takes the same fields as creating a NIF, with the same rules: the NIF must be registered in the AEAT census, in the sandbox too, and default_irpf_rate is a declaration you only send when the holder made it (why). It is required when access_level is OPERATE — operating someone's invoicing needs a NIF to issue under, so omitting it is 422 VALIDATION_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_token is shown once, so capture it here. claim_url is the same token wrapped in the link for the current environment. See Claim tokens.
  • An absent claim_token is not an error — read status. With CLAIMED or ACTIVE the holder already took ownership, so what they need is a password reset, not another claim link. With claim_link_already_issued: true you resent the external_ref of 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_id is in the response. Without a tax_profile that company has no NIF yet: its holder registers it on claim, and until then its issuing readiness reports COMPANY_HAS_NO_NIF. company_id is null only when you resend an external_ref whose 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 youErrorStatus
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_CLAIMED409
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_REGISTERED409

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 OPERATE account with its NIF switched on in Test and in Live. Its claim_token and claim_url come back in this response and nowhere else. A row that already exists keeps a claim link that is still valid: it comes back with claim_token null and claim_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_ref returns 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 409 PROVISIONING_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  →  ACTIVE
  • PROVISIONED — created, not claimed yet.
  • CLAIMED — the holder took ownership, but the account has no NIF yet. It appears for an account provisioned without a tax_profile, until its holder registers a NIF: the company it was born with does not count while it has no NIF. The list filter status=CLAIMED returns these accounts.
  • ACTIVE — claimed, with a NIF. An account provisioned with a tax_profile already has its NIF, so claiming takes it straight from PROVISIONED to ACTIVE.

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_level survives untouched.
  • You can no longer raise your own access. Before the claim you set it freely; after it, only the holder can raise it (403 MANAGED_SCOPE_ELEVATION_REQUIRES_OWNER). You may still keep it or lower it. If you will ever need OPERATE, 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 an external_ref you 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_cursor until it comes back null. 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.