Factuarea APIDevelopers
Added

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.

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}/invitations takes email, role (admin or member), grants and send_email (true by default) in place of company_id. The invitation now carries grants, send_email and external_advisor.
  • New operation. GET /v1/accounts/{account}/invitations/{invitation} reads one invitation.
  • The secret is shown once. With send_email: false the response carries the acceptance secret and accept_url exactly once. When the email delivers the link, both are null, and so they are in an idempotent replay.
  • Resending. POST …/invitations/{invitation}/resend issues a new secret and now answers 200 with the invitation, where it used to answer 204.
  • 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-transfers names the tax ID in company_id and returns the secret exactly once. A transfer lasts 24 hours, or the expires_at you choose, up to 7 days.
  • Accept. The receiving account calls POST …/nif-transfers/accept with the secret.
  • Revoke. POST …/nif-transfers/{nif_transfer}/revoke is new: it cancels a transfer that nobody accepted. GET …/nif-transfers lists the ones you issued.
  • Contract. Issue, accept and revoke are irreversible and require an Idempotency-Key. The scopes are nif_transfers:read and nif_transfers:write, the codes are nif_transfer_invalid and nif_transfer_expired, and the four tools are create_nif_transfer, list_nif_transfers, accept_nif_transfer and revoke_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/stats returns one row per tax ID with invoices_count, total_invoiced (a string with two decimals) and last_invoice, for the period you pass in date_from and date_to, or the current calendar year.
  • The tax ID resource. paid_until is the new name of the paid-coverage field of the tax ID (see Renamed), and suspension_reason and readiness are new. include=readiness adds the readiness to the list, with the same contract as issuing-readiness: can_issue and a list of blockers. verifactu_certificate_missing is one of those blockers, not an error code.
  • Lifecycle. A tax ID is active, inactive or archived. DELETE archives it and never charges. The account must always keep one active tax ID, so deactivating or archiving the last one answers 422 business_rule_violation with the subcode account_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 empresario or enterprise plan, trial included, and in live mode with a paid subscription it is charged at once. The contract cap max_additional_nifs answers 422 business_rule_violation with the subcode additional_nif_limit_reached, and seat-charge-preview shows the charge before you commit.
  • The gate codes. additional_nif_plan_required and additional_nif_charge_failed are 402. additional_nif_not_in_plan and billing_managed_by_provider are 403.
  • Scopes. companies:read, companies:write and companies:delete now work in any plan. The plan is checked inside the operation that adds a tax ID.
  • Billing. GET /v1/companies/{company}/billing keeps the block gestoria_seats under 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}/companies documents 409 resource_already_exists with the subcode managed_company_tax_id_already_exists, and its example now reads Ya 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/accounts provisions, GET /v1/accounts and GET /v1/accounts/{account} read, PATCH …/access-level changes the provider's level, DELETE …/management ends the management, POST …/claim-tokens reissues the voucher, PATCH …/plan and GET …/plan/preview change and preview the plan, and GET …/provisioning-usage reads the usage.
  • Privileged scopes. accounts:read and accounts:write are privileged: the * scope does not cover them, OAuth never grants them, and the account needs the provider privilege that GET /v1/me shows in is_provider and privileged_scopes. Without it the answer is 403 privileged_scope_required.
  • Idempotency and voucher. Provisioning is irreversible and requires an Idempotency-Key. external_ref is your reference: it makes the call idempotent and it is unique across both environments. The claim_token and claim_url come back exactly once, or only by email with send_email: true.
  • Levels. The provider keeps none, view or operate. 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 emprendedor or empresario, monthly, charged to the managed-accounts subscription of the provider. enterprise is by contract (422 managed_account_plan_not_self_serve), a provider in trial can only create test accounts (422 managed_account_provider_in_trial), and max_managed_accounts (25 by default) answers 422 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 none there is nothing left to grant, so the relation ends. No account.* event is published for it. You notice it because GET /v1/accounts/{account} answers 404 and the account appears in client_accounts of GET /v1/me with origin: "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/accounts creates a test account: no charge, no voucher and no claim (managed_account_not_claimable).
  • The client's VeriFactu certificate. A provider with operate uploads it with POST /v1/companies/{company}/verifactu/certificates, and verifactu_certificate_missing shows 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/template downloads the template (accounts:read). POST /v1/accounts/imports/preview checks the files and prices each row without writing anything. POST /v1/accounts/imports applies them. Both POST need accounts:write and take multipart/form-data with accounts_file, an optional customers_file and options.
  • Modes. Every row chooses its mode, with no default: own_nif adds a tax ID to your own account as an additional tax ID, and managed_account provisions a separate account for your client.
  • Limits. 5 MB per file, 100 rows in accounts_file and 1,000 in customers_file: import_file_too_large is 413, and import_row_limit_exceeded and import_file_invalid are 422.
  • 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 by external_ref: it never duplicates an account or charges twice.
  • Test keys. The managed_account rows create test accounts. The own_nif rows come out ERROR with live_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. view reads and operate also writes. A write at view answers 403 company_read_only and 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 answer 404 account_not_found.
  • The account segment. {account} in /v1/accounts/{account}/… is always the account of your credential. Naming any other answers the same 404 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/me no longer needs any scope. It returns account with is_provider and privileged_scopes, and client_accounts with the accounts you reach by relation (origin is granted or provisioned), filled only when the key has the privileged accounts:read scope, plus client_accounts_visible and client_accounts_has_more.
  • Narrowing keys. PATCH /v1/accounts/{account}/api-keys/{api_key}/nif-scope replaces the list of tax IDs of a key with scoped_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}/webhooks creates 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-logs and GET …/request-logs/{request_id} read the requests of every key of the account, in the environment of the key that asks.
  • Equivalence. retry matches replay, secret matches rotate-secret and test matches test-event. There is no account ping. The retry of an account re-arms the same delivery, where the replay of a company creates a new one.
  • Filter and environment. account_relationship is own, managed or all (the default). An endpoint belongs to one environment, live or test, 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_required when the tier allows none and 422 account_webhook_endpoint_limit_reached at the limit.
  • Company endpoints on a related tax ID. The webhooks and the request logs of a company answer 403 forbidden_action when 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.

FamilyEvents
Members and ownershipaccount.member_added, account.member_removed, account.member_role_changed, account.owner_transferred, account.member_grant_changed
Access between accountsaccount.access_granted, account.access_revoked, account.access_level_changed
Managed accountsaccount.managed_account_provisioned, account.managed_account_claimed, account.management_ended
Tax IDsaccount.nif_added, account.nif_transferred, account.nif_archived
Bulk importaccount.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 OAuth2 scheme 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 as invoices:read, and a request that names one of those is rejected with invalid_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/authorize and https://mcp.factuarea.com/api/oauth/token.
  • Secret rotation too soon. webhook_secret_recently_rotated is now a 422 in the error catalog, no longer a 429. The API already answered 422, so a client built on the catalog would have retried it as a rate limit. rotate_secret of a company documents it now.
  • seat-charge-preview. The description of seat-charge-preview says that amount, tax_amount and total are euros as strings with two decimals, and that only recurring_base_cents and recurring_total_cents are 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 reads NIF 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_exceeded and portfolio_monthly_quota_exceeded also speak of the portfolio of your account and no longer of a gestoría. The code, the subcode and the param do not change.

Renamed

These names change in the contract. Each row shows the name the previous contract used and the one that replaces it.

KindPrevious nameNew name
RouteGET /v1/accounts/{account}/claim-tokensGET /v1/accounts/{account}/nif-transfers
RoutePOST /v1/accounts/{account}/claim-tokensPOST /v1/accounts/{account}/nif-transfers
RoutePOST /v1/accounts/{account}/claim-tokens/acceptPOST /v1/accounts/{account}/nif-transfers/accept
Scopeclaim_tokens:readnif_transfers:read
Scopeclaim_tokens:writenif_transfers:write
Error codeclaim_token_expirednif_transfer_expired
Error codeclaim_token_invalidnif_transfer_invalid
Error codegestoria_plan_requiredadditional_nif_plan_required
Error codegestoria_module_requiredadditional_nif_not_in_plan
Error codeseat_charge_failedadditional_nif_charge_failed
MCP toollist_account_claim_tokenslist_nif_transfers
MCP toolcreate_account_claim_tokencreate_nif_transfer
MCP toolaccept_account_claim_tokenaccept_nif_transfer
Fieldseat_paid_until in the tax IDpaid_until
Fieldplatform_role and roles[].membership_role in the memberrole and grants[].level
Fieldcompany_id in the body of a member or an invitationgrants[] 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:

New endpoints33

EndpointDescription
PATCH/v1/accounts/{account}/api-keys/{api_key}/nif-scopeChange the tax IDs an API key reaches
GET/v1/accounts/{account}/invitations/{invitation}Retrieve an account invitation
DEL/v1/accounts/{account}/members/{member}/grants/{company}Revoke an account member grant
GET/v1/accounts/{account}/members/{member}/grantsList the grants of an account member
GET/v1/accounts/{account}/members/{member}/grants/{company}Retrieve an account member grant
PUT/v1/accounts/{account}/members/{member}/grants/{company}Set an account member grant
GET/v1/accounts/{account}/members/{member}Retrieve an account member
POST/v1/accounts/{account}/nif-transfers/{nif_transfer}/revokeRevoke a tax ID transfer
GET/v1/accounts/{account}/provisioning-usageRetrieve provisioning usage
GET/v1/accounts/{account}/request-logsList your account API request logs
GET/v1/accounts/{account}/request-logs/{request_id}Retrieve an account API request log
POST/v1/accounts/{account}/webhooksCreate an account webhook endpoint
DEL/v1/accounts/{account}/webhooks/{webhook}Delete an account webhook endpoint
GET/v1/accounts/{account}/webhooks/{webhook}/deliveriesList account webhook deliveries
POST/v1/accounts/{account}/webhooks/{webhook}/deliveries/{delivery}/retryRetry an account webhook delivery
GET/v1/accounts/{account}/webhooks/{webhook}/deliveries/{delivery}Retrieve an account webhook delivery
GET/v1/accounts/{account}/webhooksList account webhook endpoints
POST/v1/accounts/{account}/webhooks/{webhook}/secretRotate an account webhook secret
GET/v1/accounts/{account}/webhooks/{webhook}Retrieve an account webhook endpoint
POST/v1/accounts/{account}/webhooks/{webhook}/testSend a test event to an account webhook endpoint
PATCH/v1/accounts/{account}/webhooks/{webhook}Update an account webhook endpoint
GET/v1/accounts/{account}/companies/statsRetrieve invoicing statistics per company
PATCH/v1/accounts/{account}/access-levelChange the access level on a managed account
POST/v1/accounts/{account}/claim-tokensReissue the claim voucher of a managed account
POST/v1/accountsProvision a managed account
POST/v1/accounts/importsApply a bulk import of managed accounts
POST/v1/accounts/imports/previewPreview a bulk import of managed accounts
GET/v1/accounts/imports/templateDownload the bulk import template
GET/v1/accountsList managed accounts
DEL/v1/accounts/{account}/managementEnd the management of an account
GET/v1/accounts/{account}/plan/previewPreview a managed account plan change
PATCH/v1/accounts/{account}/planChange the plan of a managed account
GET/v1/accounts/{account}Retrieve a managed account

Updated endpoints39

EndpointDescription
POST/v1/accounts/{account}/api-keysCreate an API key
GET/v1/accounts/{account}/api-keysList your API keys
POST/v1/accounts/{account}/api-keys/{api_key}/revokeRevoke an API key
POST/v1/accounts/{account}/api-keys/{api_key}/rotate-secretRotate an API key secret
GET/v1/accounts/{account}/api-keys/{api_key}Retrieve an API key
GET/v1/companies/{company}/billingRetrieve account billing details
DEL/v1/accounts/{account}/invitations/{invitation}Revoke an account invitation
POST/v1/accounts/{account}/invitationsSend an account invitation
GET/v1/accounts/{account}/invitationsList account invitations
POST/v1/accounts/{account}/invitations/{invitation}/resendResend an account invitation
POST/v1/accounts/{account}/membersAdd a member to the account
DEL/v1/accounts/{account}/members/{member}Remove a member from the account
GET/v1/accounts/{account}/membersList account members
GET/v1/accounts/{account}/members/{member}/module-accessRetrieve a member module access level
PATCH/v1/accounts/{account}/members/{member}/module-accessSet a member module access level
PATCH/v1/accounts/{account}/members/{member}Change an account member role
POST/v1/accounts/{account}/nif-transfers/acceptAccept a tax ID transfer
POST/v1/accounts/{account}/nif-transfersIssue a tax ID transfer
GET/v1/accounts/{account}/nif-transfersList issued tax ID transfers
PATCH/v1/accounts/{account}/ownerTransfer account ownership
GET/v1/meRetrieve the calling credential
POST/v1/accounts/{account}/companies/{company}/activateActivate a tax ID of the account
POST/v1/accounts/{account}/companies/activateActivate several tax IDs of the account
POST/v1/accounts/{account}/companiesRegister a tax ID in the account
GET/v1/accounts/{account}/companies/{company}/creation-statusRetrieve the creation status of a tax ID
POST/v1/accounts/{account}/companies/{company}/deactivateDeactivate a tax ID of the account
DEL/v1/accounts/{account}/companies/{company}Archive a tax ID of the account
GET/v1/accounts/{account}/companies/{company}/issuing-readinessCheck whether a portfolio NIF can issue
GET/v1/accounts/{account}/companiesList the tax IDs of your account
GET/v1/accounts/{account}/companies/seat-charge-previewPreview the seat charge of adding a company
GET/v1/accounts/{account}/companies/{company}Retrieve a tax ID of the account
PATCH/v1/accounts/{account}/companies/{company}Update a tax ID of the account
POST/v1/accounts/{account}/companies/{company}/verify-creationVerify the creation of a tax ID
POST/v1/accounts/{account}/employee-seats/cancelCancel the employee seat add-on
POST/v1/accounts/{account}/employee-seats/change-quantitySync the employee seat quantity
GET/v1/accounts/{account}/employee-seats/previewPreview the employee seat charge
GET/v1/accounts/{account}/employee-seatsRetrieve employee seat billing status
POST/v1/accounts/{account}/employee-seats/subscribeSubscribe to the employee seat add-on
GET/v1/accounts/{account}/gestoria/workforce-summaryRetrieve the consolidated workforce compliance overview

On this page

Need a hand?Contact support