Scope and limitations
What the Factuarea API deliberately does not do, what it does not do yet, and the equivalent way to get each job done — plus six capabilities you may assume are missing and are not.
Every platform has boundaries. A boundary you can read before you integrate is a design decision; one you discover in production is a defect. This page is the single canonical list — no other guide keeps its own.
Each row states the scenario, its status, and the workaround: the alternative available today, or an explicit statement that there is none. There are exactly two statuses, because a third fuzzy category is what turns pages like this into wallpaper:
- By design — we will not build it. The alternative is here.
- On the roadmap — deferred, not rejected.
Verified on 3 October 2026 against v1 of the API. A row whose scenario becomes supported is removed in the same change that implements it, rather than left standing as an obsolete limitation.
Limitations verified against the code
| Scenario | Status | Workaround |
|---|---|---|
| Self-billing — the recipient issues the invoice in the supplier's name | By design | Not modelled. The supplier issues their own invoice. If you operate both parties, issue it from the supplier's account. |
| Invoice issued by a third party | By design | The AEAT third-party-issuer field is not emitted. An agency that reaches a client's tax ID, through a granted access or as the provider of a managed account, issues from that company by naming it in the company segment of the path; the invoice is declared as issued by the company itself. |
| Multi-currency | By design | The v1 contract exposes euro, fixed: currency is always EUR and there is no currency column. Filtering a listing by any other currency returns an empty page, not an error. Write public documents in EUR; ecommerce connectors can convert incoming orders to EUR. |
| TicketBAI / Batuz (Basque Country) | By design | No alternative within Factuarea. The Basque provincial systems use different schemas, certificates and endpoints, and require their own certified software. Companies with a Basque tax domicile are warned during onboarding. |
| Reverse charge, and any header operation regime, declared over the API | By design | The header operation_regime is read-only in v1 — neither create nor update accepts it — so every invoice created through the API is born under the general regime and qualifies S1. Exemption and non-subjection are declared per line with lines[].exemption_reason, but reverse charge is qualification S2 and has no line-level equivalent: issue those invoices from the dashboard. See International customers. |
| Disbursements outside the issued invoice — quotes, pro-formas, delivery notes, expenses, recurring templates | By design | Only the issued invoice models disbursements. Include the amount as an ordinary line in the preceding document, and set line_type on the resulting invoice while it is still a draft — the update operation accepts it. |
| Disbursements in the Facturae and UBL XML — the amount to pay in the XML is the fiscal total, not the amount due | On the roadmap | Taxable base and tax amounts are correct — the disbursement is properly excluded — but the payable amount falls short by it, and no element of the XML carries the difference. Do not route an invoice carrying disbursement lines through FACe until the native Facturae 3.2.2 block is mapped: invoice the disbursement outside that channel. |
Disbursements in the aggregate portfolio figures — the pending_amount of GET /v1/companies/{company}/invoices/stats, ageing and top-debtor reports | By design | Those aggregates measure invoiced volume, the same magnitude the annual third-party return declares, and have never subtracted partial payments either. For the amount actually owed, read pending_amount on each invoice, which does measure against the payable amount. |
Deliberate differences from other platforms
These are conscious product decisions, not gaps. Each one exists because the alternative we chose is better for integrators than the pattern being asked for.
| Scenario | Status | Workaround |
|---|---|---|
| Offset pagination with a page count and jump-to-page-N | By design | Stripe-style cursor pagination: limit with validated bounds, plus starting_after or ending_before (mutually exclusive). Responses carry has_more and next_cursor, and next_cursor is null when has_more is false. See Pagination. |
| Permanently dual error envelope — our envelope and RFC 9457 in the same body, always | By design | Content negotiation. Accept: application/problem+json returns pure RFC 9457; anything else — application/json, */*, no Accept header — returns our envelope. See Errors. |
| Total atomicity in bulk create — one bad row rejects the batch | By design | Partial success. The response carries {dry_run, total, successful, failed, results, failures}, where each failure identifies its row by zero-based index with its own error code. Import 480 of 500 and fix the 20. See Bulk operations. |
Mandatory line totals in the request (line_total, taxable_base) | By design | line_total is an optional verified checksum: compared against the computed total with a one-cent tolerance, then discarded — never persisted, never returned. You do not have to replicate our calculation engine. See Disbursements. |
| A request header that switches the active company — one credential plus a per-request profile header | By design | The company travels in the path. The effective company is the {company} segment of /v1/companies/{company}/…, and what a credential reaches is the set of tax IDs inside its scope — its own, the ones granted to its account and the ones of the accounts it manages — never a profile it activates: see which tax IDs a credential reaches. The X-Active-Profile header is retired: a request that sends it is rejected with 400 parameter_unknown, subcode retired_active_profile_header, whatever its value. See Reach a tax ID through the company segment. |
| Substituting simplified invoices in two steps — a corrective plus a new complete invoice | By design | One native step: POST /v1/companies/{company}/invoices/substitute-simplified issues the substitute invoice aggregating several simplified ones. See Simplified or full invoices. |
| Python SDK | On the roadmap | Generate a client from the published OpenAPI document, or use the TypeScript or PHP SDKs, the CLI or the MCP server. |
Which tax IDs a credential reaches
A credential operates on a tax ID by naming it in the {company} segment of the path, and it reaches the tax IDs inside its tax ID scope. Those come from three places:
- Its own account. The tax IDs of the account the credential belongs to.
- A granted access. The tax IDs that another account granted to its account, all of them or a list, at the level
vieworoperate. - A managed account. The tax IDs of the managed accounts that its account provisioned, while the management is in force and the level is not
none.
A key never reaches more than its scope names, and its scope never exceeds what its account reaches. A tax ID that the credential does not reach, or reaches at the level none, answers 404 profile_not_found. On a tax ID reached through a relation between accounts, two limits apply, and neither changes any data:
- The capabilities of the owning account stay with it. The operations that belong to the account that owns the tax ID — the billing, the company webhook endpoints, the company request logs and events, the automation usage and the writes to its payment credentials — answer
403 forbidden_actionbefore anything is read or changed, at either level. The team, the invitations, the ownership, the API keys, the tax ID portfolio and the transfers of that account live under/v1/accounts/{account}/…, where{account}must be your own account: another one answers404 account_not_found. viewreads. With the levelview, every write on the tax ID answers403 company_read_only. Withoperateyou can write, apart from what the first limit keeps with the owner.
What stays with the granting account lists the operations that stay with the owner, and Which {account} routes accept a managed account the routes of {account} that do.
What the account model leaves out, on purpose
The account model puts some decisions in the hands of a person and some surfaces out of the API. Each row below is deliberate, and its workaround is the way to get the job done.
| Scenario | Status | Workaround |
|---|---|---|
| Managing a granted access over the API or the MCP server — granting, accepting, declining, changing, resending, revoking or renouncing an access between accounts | By design | The web app. A person of each account decides and confirms it there, so a leaked key cannot open the tax IDs of its account to another account. The granting account uses Settings → Access to my account and the receiving one Settings → Received access. What the API does give is the reach of a granted access: see which tax IDs a credential reaches and Grant, change and revoke in the web app. |
| The client's side of a managed account over the API — claiming the account, raising or lowering the provider's level, ending the management and taking over the payment | By design | The client does it in the web app. A credential of the provider cannot decide for the holder. Your side, the provider's, is complete over the API and the MCP server: change your level with PATCH /v1/accounts/{account}/access-level, end the management with DELETE /v1/accounts/{account}/management and reissue the voucher. See The client's side is in the app. |
| Bulk import through an MCP tool | By design | The REST API or the import of the web app. The three operations are privileged and the import reads two uploaded files, which the MCP server does not carry. From an agent, provision the accounts one at a time with the tool provision_managed_account, which mirrors POST /v1/accounts. See Two things the import does not do. |
| Account webhooks and request logs beyond your own account, or through OAuth — administering those of a managed or a granting account, or using their MCP tools from an OAuth connection | By design | Your own account, with an API key. {account} is always the account of the credential. A provider follows the events of the accounts it manages at its own endpoint with the managed filter. The twelve MCP tools of account webhooks and request logs work only with an API key: an OAuth connection neither lists nor calls them. See Four differences that are not a name. |
| A company webhook endpoint on a tax ID reached through a relation | By design | An account endpoint. The eleven operations of …/webhook-endpoints and the two of …/request-logs on a tax ID of another account, granted or managed, answer 403 forbidden_action because they belong to the account that owns it. To follow the tax IDs of the accounts you manage, create an account endpoint with the managed or all filter. A granted access is not followed by webhooks on the receiving side: the granting account receives the events of its own tax IDs. See Company endpoints and logs on a tax ID reached through a relation. |
The history of emails of a whole account — GET /v1/accounts/{account}/emails, its detail and its indicators | By design | Read it tax ID by tax ID. The history lives on the company axis, with the emails:read scope and inside the scope of the credential: GET /v1/companies/{company}/emails, its detail and its indicators. The three account-level routes do not exist. |
| Document series in the bulk import — a column or an option to create or choose the numbering of each imported tax ID | By design | Create the series with the company axis. The import registers accounts and tax IDs, not their numbering: its template has a closed set of columns and none is a series. Afterwards, POST /v1/companies/{company}/series/bootstrap creates the missing default series in one call with series:write, and issuing-readiness reports no_default_series while one is missing. See Two things the import does not do. |
| The external advisor over the API or the MCP server, on a plan without user management | By design | The web app, in any plan. Managing the team over the API and the MCP server belongs to the user_management module, which empresario and enterprise include and emprendedor does not: without it the members:* and invitations:* scopes cannot be assigned. The web app invites the advisor, edits their grants, resends or cancels the invitation, removes the advisor and removes the mark in every plan. With the module, an existing member is marked or unmarked with external_advisor in PATCH /v1/accounts/{account}/members/{member}. See Plans without user management. |
Capabilities you may assume are missing
Eight things Factuarea does that integrators arriving from other platforms routinely expect not to find:
| Capability | Where |
|---|---|
| Substitute invoice for simplified ones, in a single call — aggregate several tickets into one complete invoice without a corrective first | Simplified or full invoices · POST /v1/companies/{company}/invoices/substitute-simplified |
| Subsanación of accepted or rejected VeriFactu records, exposed in the public API — correct a record with a new one, without annulling the invoice and without editing the original | VeriFactu record subsanación · POST /v1/companies/{company}/verifactu/records/{record}/subsanar |
| Remission by a third party — Factuarea remits the records with its own certificate under a power of attorney (or the signed annex I of the social collaboration), so the company does not hand over its certificate | VeriFactu auto-submission · POST /v1/companies/{company}/verifactu/representation |
Unattended checkout in one idempotent call — a paid simplified invoice with its VERI*FACTU QR, safe to resend days later by external_id | Unattended checkout invoicing · POST /v1/companies/{company}/invoices |
| Corrective by differences with a negative taxable base — the fiscally correct way to express a refund | Corrective invoices |
| AEAT fiscal catalogue queryable over the API — indirect tax regimes, operation regimes, exemption causes with their LIVA article, withholding rates and the legal VAT-to-surcharge pairs, in three languages | Regime keys · GET /v1/tax-catalog |
| Measured product catalog — UNECE base units, fixed/variable presentations, variants with optional own stock and supplier offers | Product catalog |
| Customer and document price lists — four-decimal prices, stable variant/presentation precedence and explicit draft repricing | Price lists · POST /v1/companies/{company}/price-lists/resolve |
None of these is announced or in development: all eight are live operations today.
Where the fiscal reasoning lives
This page lists boundaries. The guides that explain the rules behind them:
VeriFactu submission states
The record lifecycle, retry and subsanación.
Corrective invoices
R1–R5, substitution versus differences.
Regime keys
Header qualification and the per-line regime catalogue.
Territorial taxes
VAT, IGIC and IPSI.
Disbursements
Amounts paid on the customer's behalf.
International customers
Alternative identification and the scenario map.
Traceability
This page documents the absence of behaviour, which no business rule can assert. Its rows are therefore anchored differently from the other fiscal guides: to a verified point in the code, to the decision recorded for the platform, or — where a business rule does exist — to that rule.
Limitations verified against the code:
| Row | Anchor |
|---|---|
| Self-billing | No such capability in the domain. The only occurrences of the concept are the invoice Factuarea issues to its own subscribers and the system-company check — neither is an API capability. |
| Invoice issued by a third party | The AEAT third-party-issuer field is never emitted; no occurrence in the application code. |
| Multi-currency | InvoiceV1Resource returns the literal 'EUR', and the v1 read repository documents that any other currency yields an empty page. |
| TicketBAI / Batuz | BR-VFC-019 — deliberately out of scope for the VeriFactu context. |
| Reverse charge over the API | No v1 request accepts operation_regime; the invoice resource returns it read-only. BR-VFC-029 derives the qualification from that header regime, and BR-INV-032 limits the line catalogue to exemption and non-subjection causes, with no S codes. |
| Disbursements outside the issued invoice | BR-INV-037 and the line-type value object, which declares that only the issued invoice models disbursements; BR-INV-040 for the simplified-invoice restriction. |
| Disbursements in the Facturae and UBL XML | The warning edge case of BR-INV-042, which records that the payable amount of both documents is the fiscal total and that the native Facturae 3.2.2 block is not mapped yet. |
| Disbursements in the aggregate portfolio figures | The edge case of BR-INV-045, which records that the aggregates measure invoiced volume and are deliberately left measuring it. |
Deliberate differences: anchored to the shared HTTP components that implement
the alternative — cursor pagination, the error content negotiator, the bulk
partial-success resource — to BR-INV-044 for the optional line checksum, and
to BR-INV-015 and BR-INV-016 for the single-step substitution. The Python SDK row reflects a recorded decision to plan it
separately once the specification stabilises: it is deferred, not rejected, which
is why its status is On the roadmap and not By design. The retired profile
header is anchored to the v1 middleware that rejects it by presence, before
any handler runs, and to its catalogued rejection 400 · parameter_unknown ·
retired_active_profile_header.
Reach of a credential: BR-ACC-181 for the company segment as the only mechanism of effective company, which also reaches the tax IDs of relations in force inside the scope, BR-ACC-180 for the scope that never exceeds what the account reaches, BR-ACC-191 for the closed inventory of company-axis operations that answer 403 forbidden_action, BR-ACC-153 for what operate and view mean on a tax ID reached through a relation, and BR-ACC-043 for the 403 company_read_only of view.
Account model exclusions:
| Row | Anchor |
|---|---|
| Managing a granted access over the API or the MCP server | BR-ACC-199: a closed list of twelve route names, with their reason as key, and a gate that checks that each exists and that no v1 route or MCP tool mirrors it. BR-ACC-155 for what each side does in the web app. |
| The client's side of a managed account over the API | BR-ACC-199: seven route names for the holder's side, in the same gate. BR-ACC-164 and BR-ACC-165 for the level and the end of the management, and BR-ACC-194 for the provider's side, which does have its routes and tools. |
| Bulk import through an MCP tool | BR-ACC-200, which records that the import has no tool, and the declared exclusion of the three managed_accounts.imports.* operations in the v1 ↔ MCP parity test. |
| Account webhooks and request logs beyond your own account, or through OAuth | BR-ACC-230 for the capability of the account checked on every request, and BR-ACC-233 for the twelve tools that reject the OAuth channel one by one. |
| A company webhook endpoint on a tax ID reached through a relation | BR-ACC-234, inside the closed inventory of BR-ACC-191. |
| The history of emails of a whole account | BR-ACC-235: the three routes are declared as not exposed in the axis parity gate, and a test fails if one appears in the router. |
| Document series in the bulk import | The closed set of columns of the import template, none of which is a series, and the import request, which has no series option. |
| The external advisor over the API or the MCP server, on a plan without user management | BR-ACC-107: the web app manages the advisor in any plan, while the team scopes of the API follow the user_management module. BR-ACC-199 lists the ten routes that exist only in the web app. |
Capabilities: each is anchored to the live route that materialises it —
public-api.v1.invoices.substitute_simplified,
public-api.v1.verifactu.records.subsanar, public-api.v1.verifactu.representation.register,
public-api.v1.invoices.create and public-api.v1.tax-catalog.show —
plus BR-VFC-042 for remission by a third party, BR-VFC-046 for subsanación as a
new alta, BR-VFC-033 for the negative taxable base and BR-TAX-028 for the fiscal
catalogue, BR-PRD-026…BR-PRD-030 for the measured catalog and
BR-PRI-001…BR-PRI-004 for price lists.
Orders in other currencies
Public document amounts remain in EUR. Ecommerce connectors can ingest a foreign-currency order and convert it to EUR; this does not add arbitrary-currency document writes to v1. Buyer identification and fiscal eligibility still apply, and missing exchange rates or unsupported buyer data produce an observable integration result. See stores.