Account model
Accounts, tax IDs, members, granted access, managed accounts, bulk import, account webhooks and the operations and fields they add or rename.
30 September 2026 — in Factuarea the account is now the customer. It holds the plan, the credentials, the team and the tax IDs (NIF) that invoice. It can give another account access to some of those tax IDs, and a platform can provision accounts for its own clients over the API. This page lists what that model adds and what it renames, topic by topic, with the guide that explains each one. The operations it adds or changes are tabulated at the end.
Read the window first. Every change in this model that can break an existing integration is announced once, in the one breaking window that ships in the same release. This page does not repeat it: use it to see what is new, and use the window to plan the migration.
Members and grants
The team now belongs to the account, not to a tax ID. A member carries an account role (owner, admin or member), an external_advisor mark, a list of grants and a permissions block that says what the reader can do on that member. A grant is the access of a member to one tax ID, with the level view (read only) or operate. An owner or an admin reaches every tax ID without grants.
- New operations.
GET /v1/accounts/{account}/members/{member}and the four operations over one grant, under/v1/accounts/{account}/members/{member}/grants: list, show, set withPUTand revoke. - Changed operations. Creating a member takes
grantsinstead ofcompany_id.PATCH /v1/accounts/{account}/members/{member}changes therole, theexternal_advisormark or both, and moving a member toadminrevokes its grants. - New codes.
grants_only_for_member,grant_company_not_in_accountandexternal_advisor_already_assigned, all422. - Scopes and plan.
members:readandmembers:writeneed theuser_managementmodule, andmembers:*andinvitations:*are never granted through OAuth. Afact_test_key reads the account like a live key, but the writes that change the account answer403 live_credential_required.
The external advisor is the one outside person an account can give access to, in any plan: always a member, one per account, and it takes no seat. External advisor explains how to mark one, and Accounts and tax IDs covers the whole team.
Account invitations
An invitation adds a person to the account with a role and the grants they start with, instead of naming one tax ID.
- Body and resource.
POST /v1/accounts/{account}/invitationstakesemail,role(adminormember),grantsandsend_email(trueby default) in place ofcompany_id. The invitation now carriesgrants,send_emailandexternal_advisor. - New operation.
GET /v1/accounts/{account}/invitations/{invitation}reads one invitation. - The secret is shown once. With
send_email: falsethe response carries the acceptancesecretandaccept_urlexactly once. When the email delivers the link, both arenull, and so they are in an idempotent replay. - Resending.
POST …/invitations/{invitation}/resendissues a new secret and now answers200with the invitation, where it used to answer204. - The advisor's invitation is sent only from the web app. Through the API you mark the member afterwards.
Invitations has the statuses and the errors of each write.
Ownership
The account has exactly one owner. Ownership moves only with PATCH /v1/accounts/{account}/owner: it needs the account:owner scope, the person behind the credential must be the current owner, and it is irreversible, so it requires an Idempotency-Key. The previous owner becomes admin. Both keep every tax ID, and no tax ID, API key or plan changes.
The role of the owner cannot be changed with the member operation, and the owner cannot be removed from the account (member_is_last_owner): transfer the ownership first. The event account.owner_transferred announces the change to your account webhooks. Account ownership has the details.
Tax ID transfers
A tax ID changes account with a tax ID transfer, which replaces the claim token of the previous contract. The giving account always starts it.
- Issue.
POST /v1/accounts/{account}/nif-transfersnames the tax ID incompany_idand returns thesecretexactly once. A transfer lasts 24 hours, or theexpires_atyou choose, up to 7 days. - Accept. The receiving account calls
POST …/nif-transfers/acceptwith thesecret. - Revoke.
POST …/nif-transfers/{nif_transfer}/revokeis new: it cancels a transfer that nobody accepted.GET …/nif-transferslists the ones you issued. - Contract. Issue, accept and revoke are irreversible and require an
Idempotency-Key. The scopes arenif_transfers:readandnif_transfers:write, the codes arenif_transfer_invalidandnif_transfer_expired, and the four tools arecreate_nif_transfer,list_nif_transfers,accept_nif_transferandrevoke_nif_transfer. - The price of receiving. An account that already has a titular NIF receives the tax ID as an additional tax ID. The gate runs before anything moves.
The word claim now belongs only to managed accounts. Tax ID transfers walks through the flow.
Tax ID portfolio, lifecycle and the additional tax ID
The tax IDs of an account are its portfolio. Each one beyond the titular NIF is an additional tax ID.
- Statistics.
GET /v1/accounts/{account}/companies/statsreturns one row per tax ID withinvoices_count,total_invoiced(a string with two decimals) andlast_invoice, for the period you pass indate_fromanddate_to, or the current calendar year. - The tax ID resource.
paid_untilis the new name of the paid-coverage field of the tax ID (see Renamed), andsuspension_reasonandreadinessare new.include=readinessadds the readiness to the list, with the same contract asissuing-readiness:can_issueand a list ofblockers.verifactu_certificate_missingis one of those blockers, not an error code. - Lifecycle. A tax ID is
active,inactiveorarchived.DELETEarchives it and never charges. The account must always keep one active tax ID, so deactivating or archiving the last one answers422 business_rule_violationwith the subcodeaccount_must_keep_one_nif. - The additional tax ID. Every tax ID besides the titular one is an additional tax ID that the account pays for. It needs the
empresarioorenterpriseplan, trial included, and in live mode with a paid subscription it is charged at once. The contract capmax_additional_nifsanswers422 business_rule_violationwith the subcodeadditional_nif_limit_reached, andseat-charge-previewshows the charge before you commit. - The gate codes.
additional_nif_plan_requiredandadditional_nif_charge_failedare402.additional_nif_not_in_planandbilling_managed_by_providerare403. - Scopes.
companies:read,companies:writeandcompanies:deletenow work in any plan. The plan is checked inside the operation that adds a tax ID. - Billing.
GET /v1/companies/{company}/billingkeeps the blockgestoria_seatsunder its name, and it now describes the additional tax ID subscription. Its amounts are euros as strings with two decimals: the previous description said integer cents. - The 409 of the registration.
POST /v1/accounts/{account}/companiesdocuments409 resource_already_existswith the subcodemanaged_company_tax_id_already_exists, and its example now readsYa existe una empresa con ese NIF/CIF/NIE en la cuenta.
Tax ID portfolio and Additional tax IDs cover registering, charging and reactivating, and the state of a tax ID covers the lifecycle.
Managed accounts and provisioning
A provider can now create accounts for its own clients. A managed account is a separate, normal account with its own titular NIF and plan. It goes from provisioned to claimed and then active, and the client becomes its owner by claiming it with a voucher. The provider keeps the level it had.
- Operations.
POST /v1/accountsprovisions,GET /v1/accountsandGET /v1/accounts/{account}read,PATCH …/access-levelchanges the provider's level,DELETE …/managementends the management,POST …/claim-tokensreissues the voucher,PATCH …/planandGET …/plan/previewchange and preview the plan, andGET …/provisioning-usagereads the usage. - Privileged scopes.
accounts:readandaccounts:writeare privileged: the*scope does not cover them, OAuth never grants them, and the account needs the provider privilege thatGET /v1/meshows inis_providerandprivileged_scopes. Without it the answer is403 privileged_scope_required. - Idempotency and voucher. Provisioning is irreversible and requires an
Idempotency-Key.external_refis your reference: it makes the call idempotent and it is unique across both environments. Theclaim_tokenandclaim_urlcome back exactly once, or only by email withsend_email: true. - Levels. The provider keeps
none,vieworoperate. It can raise the level only before the client claims the account (422 access_level_raise_forbidden), and it can lower it at any time. - Pricing. The plan is
emprendedororempresario, monthly, charged to themanaged-accountssubscription of the provider.enterpriseis by contract (422 managed_account_plan_not_self_serve), a provider in trial can only create test accounts (422 managed_account_provider_in_trial), andmax_managed_accounts(25 by default) answers422 managed_accounts_limit_reached. - While you pay. The holder of a managed account that you pay cannot buy a plan, employee seats or additional tax IDs, and gets
403 billing_managed_by_provider. - Taking over the payment. When the client assumes the payment, the provider keeps its level as a granted access and the client keeps the same plan and price. With the level
nonethere is nothing left to grant, so the relation ends. Noaccount.*event is published for it. You notice it becauseGET /v1/accounts/{account}answers404and the account appears inclient_accountsofGET /v1/mewithorigin: "granted". - Ending the management does not give the provider a granted access. The relation ends and the provider loses all access to the account from the next request.
- Test accounts. With a
fact_test_key,POST /v1/accountscreates a test account: no charge, no voucher and no claim (managed_account_not_claimable). - The client's VeriFactu certificate. A provider with
operateuploads it withPOST /v1/companies/{company}/verifactu/certificates, andverifactu_certificate_missingshows up as a blocker in the readiness of the tax ID. The client's certificate explains the steps. - MCP. Nine tools mirror these operations, and the bulk import has none. They appear only for a key that lists the privileged scope, and the tool count of the MCP catalog does not include them.
The new codes are managed_account_external_ref_in_other_environment, provisioning_account_claimed and enterprise_contract_transition_required, besides the ones above. Start with Managed accounts, then pricing and payer change and testing with fact_test_. The tools are in the MCP catalog.
Bulk import
A provider can load its clients from a CSV in one go. The three operations are API only, with no MCP tool by design.
- Operations.
GET /v1/accounts/imports/templatedownloads the template (accounts:read).POST /v1/accounts/imports/previewchecks the files and prices each row without writing anything.POST /v1/accounts/importsapplies them. BothPOSTneedaccounts:writeand takemultipart/form-datawithaccounts_file, an optionalcustomers_fileandoptions. - Modes. Every row chooses its
mode, with no default:own_nifadds a tax ID to your own account as an additional tax ID, andmanaged_accountprovisions a separate account for your client. - Limits. 5 MB per file, 100 rows in
accounts_fileand 1,000 incustomers_file:import_file_too_largeis413, andimport_row_limit_exceededandimport_file_invalidare422. - Applying. It is irreversible and requires an
Idempotency-Key. Imports of the same provider run one at a time (409 import_in_progress), and importing the same file again reconciles byexternal_ref: it never duplicates an account or charges twice. - Test keys. The
managed_accountrows create test accounts. Theown_nifrows come outERRORwithlive_credential_required.
The event account.import_completed tells your account webhooks that it finished. Bulk import describes the columns, the row statuses and what the import does not do.
Reach by relation on {company}
An account can let another account work with some or all of its tax IDs. The receiving account reaches them with its own credential through /v1/companies/{company}/…, like one of its own.
- Levels.
viewreads andoperatealso writes. A write atviewanswers403 company_read_onlyand creates or changes nothing. - What stays with the granting account. A tax ID reached by relation never opens the capabilities of the account that owns it. On the company axis, the operations that belong to the owning account (billing, company webhooks and request logs, automation usage and Stripe credentials) answer
403 forbidden_action, and the account-level ones answer404 account_not_found. - The account segment.
{account}in/v1/accounts/{account}/…is always the account of your credential. Naming any other answers the same404 account_not_found, so you reach the tax IDs of a granting account through{company}, never through{account}. - Keys. The level of a tax ID in a key is fixed when the key is issued and can only go down. When the access narrows or ends, the keys of the receiving account are recalculated on the next request.
- Where it is managed. Granting, changing and revoking an access is done only in the web app. There is no route and no MCP tool for it, on purpose. It needs no plan or module and takes no user seat.
- Seeing it.
GET /v1/meno longer needs any scope. It returnsaccountwithis_providerandprivileged_scopes, andclient_accountswith the accounts you reach by relation (originisgrantedorprovisioned), filled only when the key has the privilegedaccounts:readscope, plusclient_accounts_visibleandclient_accounts_has_more. - Narrowing keys.
PATCH /v1/accounts/{account}/api-keys/{api_key}/nif-scopereplaces the list of tax IDs of a key withscoped_company_ids, and the new scope can never exceed yours.
Granted access has the flow, and reach a tax ID and scoped keys cover the credential side.
Account webhooks and request logs
An account has its own webhook endpoints and request logs, under /v1/accounts/{account}/…. Ten operations manage the endpoints and two read the logs, with the same scopes as their company twins (webhooks:read, webhooks:write, webhooks:delete and developers:read). There is one MCP tool per operation, and none of them is available through OAuth.
- Operations.
POST /v1/accounts/{account}/webhookscreates an endpoint. The others list, show, update and delete it, list and show its deliveries, retry one, rotate the secret and send a test.GET …/request-logsandGET …/request-logs/{request_id}read the requests of every key of the account, in the environment of the key that asks. - Equivalence.
retrymatchesreplay,secretmatchesrotate-secretandtestmatchestest-event. There is no accountping. The retry of an account re-arms the same delivery, where thereplayof a company creates a new one. - Filter and environment.
account_relationshipisown,managedorall(the default). An endpoint belongs to one environment,liveortest, and never changes it. - Who can. Only your own account, with an issuer who is an owner or admin and a key that reaches every tax ID of the account. Creating, editing, rotating, testing, retrying and deleting need a live key.
- Limits. The number of endpoints depends on the tier of the payer:
402 addon_requiredwhen the tier allows none and422 account_webhook_endpoint_limit_reachedat the limit. - Company endpoints on a related tax ID. The webhooks and the request logs of a company answer
403 forbidden_actionwhen the tax ID is reached by relation. They keep their contract on your own tax IDs.
Account webhooks has the table of equivalences and the envelope, and Deduplicate webhook deliveries explains why the same event keeps the same id at both kinds of endpoint.
account.* events
Fifteen events tell you what happens to the account itself. Only account endpoints can subscribe to them.
| Family | Events |
|---|---|
| Members and ownership | account.member_added, account.member_removed, account.member_role_changed, account.owner_transferred, account.member_grant_changed |
| Access between accounts | account.access_granted, account.access_revoked, account.access_level_changed |
| Managed accounts | account.managed_account_provisioned, account.managed_account_claimed, account.management_ended |
| Tax IDs | account.nif_added, account.nif_transferred, account.nif_archived |
| Bulk import | account.import_completed |
They are not in GET /v1/event-catalog and they never trigger an automation. The envelope of an account delivery is the company envelope plus account, account_relationship and company_id, and the headers add Factuarea-Account-Relationship. The event of a tax ID keeps the same id it has at a company endpoint, while an account.* event has no company twin. The account.* catalog lists each name with its payload, and Events says what they are for.
Corrections to the published contract
Five points of the published contract change without any operation, status or code that the API returns changing.
- Scopes of the OAuth2 scheme. The
OAuth2scheme of the OpenAPI document now lists the 66 scopes the consent screen offers, in the dotted vocabulary that the authorization server accepts (invoices.read,factuarea.full). It used to list the fine-grained names that API keys use, such asinvoices:read, and a request that names one of those is rejected withinvalid_scope. Scopes and permissions lists the catalog. - OAuth URLs. The authorization and token URLs of that scheme are now the production ones:
https://mcp.factuarea.com/api/oauth/authorizeandhttps://mcp.factuarea.com/api/oauth/token. - Secret rotation too soon.
webhook_secret_recently_rotatedis now a422in the error catalog, no longer a429. The API already answered422, so a client built on the catalog would have retried it as a rate limit.rotate_secretof a company documents it now. seat-charge-preview. The description ofseat-charge-previewsays thatamount,tax_amountandtotalare euros as strings with two decimals, and that onlyrecurring_base_centsandrecurring_total_centsare in cents. It used to say minor units. The values do not change.- Messages of the 409 and the 404. The 409 of the registration now reads
Ya existe una empresa con ese NIF/CIF/NIE en la cuenta.and the 404 of a tax ID readsNIF no encontrado en la cuenta.They used to say «empresa gestionada», which names a managed account and not a tax ID of your own account.portfolio_max_api_keys_exceeded,portfolio_rate_limit_exceededandportfolio_monthly_quota_exceededalso speak of the portfolio of your account and no longer of a gestoría. Thecode, the subcode and theparamdo not change.
Renamed
These names change in the contract. Each row shows the name the previous contract used and the one that replaces it.
| Kind | Previous name | New name |
|---|---|---|
| Route | GET /v1/accounts/{account}/claim-tokens | GET /v1/accounts/{account}/nif-transfers |
| Route | POST /v1/accounts/{account}/claim-tokens | POST /v1/accounts/{account}/nif-transfers |
| Route | POST /v1/accounts/{account}/claim-tokens/accept | POST /v1/accounts/{account}/nif-transfers/accept |
| Scope | claim_tokens:read | nif_transfers:read |
| Scope | claim_tokens:write | nif_transfers:write |
| Error code | claim_token_expired | nif_transfer_expired |
| Error code | claim_token_invalid | nif_transfer_invalid |
| Error code | gestoria_plan_required | additional_nif_plan_required |
| Error code | gestoria_module_required | additional_nif_not_in_plan |
| Error code | seat_charge_failed | additional_nif_charge_failed |
| MCP tool | list_account_claim_tokens | list_nif_transfers |
| MCP tool | create_account_claim_token | create_nif_transfer |
| MCP tool | accept_account_claim_token | accept_nif_transfer |
| Field | seat_paid_until in the tax ID | paid_until |
| Field | platform_role and roles[].membership_role in the member | role and grants[].level |
| Field | company_id in the body of a member or an invitation | grants[] when you add a member or send an invitation |
Two rows need a note. A member's role is now the account role, and the access to each tax ID lives in its grants, so the member fields are replaced rather than renamed. And POST /v1/accounts/{account}/claim-tokens now reissues the claim voucher of a managed account, which is a different operation from the transfer it used to name. The billing field gestoria_seats keeps its name.
Operations
Every operation the account model adds or changes, with its reference page: