Troubleshooting
Use this guide when local validation, framework adapters, Prisma setup, signing, or PDF rendering fails.
Prisma Model Was Not Found
Run the copy command:
npx @akira-io/efatura prisma
Then run your migration:
npx prisma migrate dev
or:
npx prisma db push
If your project uses a single schema.prisma, print only the model blocks and append them manually:
npx @akira-io/efatura prisma --models-only --print
Use --force only when replacing the generated e-Fatura fragment is intentional.
Prisma 7 Driver Adapter Error
Prisma 7 and newer require a driver adapter when constructing PrismaClient. Configure the adapter in your app, regenerate the client, then pass prisma.efaturaSequence to PrismaSequenceStore.
prisma db push Fails On A Known Schema
Check that the e-Fatura model fragment is in the schema folder Prisma reads. In multi-file schemas, the default target is:
prisma/schema/efatura-sequence.prisma
In single-file schemas, append the model to the file referenced by your Prisma config.
xmllint Is Missing
XmllintXsdValidator shells out to xmllint. Install libxml2 tools for your operating system or inject another XsdValidator.
If no validator is configured, XSD validation reports that the official validator is unavailable.
Certificate Or Private Key Fails Validation
Run:
await efatura.validateCertificate({
certificate,
privateKey,
caCertificates,
});
Common causes:
- private key does not match the certificate;
- PEM content was escaped incorrectly in the environment;
- certificate chain is missing;
- OpenSSL is not available in the runtime image.
Signing Fails
signDfeXml() and signEventXml() require certificate and private key material for the default signer.
Check:
- certificate and private key are loaded on the server;
- XML was built before signing;
- the document has the expected
Id; - the signing time is valid when supplied.
Adapter Returns 401
The package adapters call the authorization hook before executing fiscal operations. Check the app session, bearer token, or guard that wraps the adapter route.
Do not remove the authorization hook to fix local tests. Browser clients must not reach fiscal routes without application authorization.
Adapter Returns 404 For /dfa
Confirm that the installed package version includes the DFA route and that the server was restarted after dependency changes.
For Fastify, the route is mounted under the plugin prefix. If the prefix is /efatura, the full route is:
POST /efatura/dfa
If the app is using a local tarball, reinstall it after rebuilding the package.
CORS Fails From A SPA
Register CORS on the application server before mounting the adapter and allow the SPA origin:
await app.register(cors, {
origin: 'http://localhost:5173',
credentials: true,
});
The browser request must also set credentials: 'include' when authorization depends on cookies.
Fiscal Readiness Is Skipped
External PE and DNRE checks require options.accessToken.
await efatura.validateFiscalReadiness(invoice, {
accessToken,
});
Without a token, the result can still pass local validation, but external checks are reported as skipped.
DFA PDF Has Missing Details
Pass the invoice to renderDfa() or POST /dfa:
await efatura.renderDfa({ iud, invoice });
IUD-only rendering can create a PDF, but it cannot include parties, lines, tax totals, or invoice issue details that are not present in the IUD.
BCV Quote Is Unavailable
exchange_rate.provider_unavailable covers network, timeout, and HTTP failures. exchange_rate.response_invalid means the current page no longer contains exactly one identified rate table and anchored spanning publication row, or provider options are invalid. Check network access, the HTTPS source without user information, positive safe-integer timeout and response limit, nonnegative safe-integer publication age, and BCV page format.
The package does not switch to World Bank or another provider. World Bank is an annual reference source, not a fiscal daily fallback. Stop issuance or apply an explicit application policy with accurate provenance.
BCV Date Is Unavailable Or Stale
The current BCV print page is dynamic and is not a documented historical API. Strict matching is the default, so an earlier page publication produces exchange_rate.date_unavailable. Matching uses the requested instant’s fixed UTC-01 Cape Verde calendar date, including month and year rollovers, rather than the UTC date.
For a weekend or public holiday, configure both fields:
new BcvExchangeRateProvider({
allowPreviousPublication: true,
maxPublicationAgeDays: 3,
});
exchange_rate.stale means the earlier publication exceeds the configured maximum age. exchange_rate.date_invalid can mean the request date is invalid or the page returned a future publication. Use a fixed or callback provider for an approved historical quote when the current page cannot satisfy the date.
Exchange-Rate Pair Or Currency Does Not Match
exchange_rate.currency_unsupported means the input is absent from the active embedded e-Fatura XSD canonical currency list or the selected provider lacks a required mapping. The XSD has 179 enumeration entries, but only 178 are canonical uppercase codes. Its mixed-case IdR entry is an active schema typo and limitation: the schema rejects canonical IDR, while the package refuses to emit noncanonical IdR. Host Intl support does not change this contract. Invalid codes fail before the provider is called. exchange_rate.pair_mismatch means the quote pair or rate type differs from the request. Confirm that the source code is supported by the active schema, the target is CVE, BCV uses buy or sell, and World Bank uses reference.
validation.payable_alternative_currency_unsupported identifies an unsupported currencyCode in a caller-supplied low-level payableAlternativeAmounts entry. Replace it with a canonical uppercase code from the active e-Fatura schema before generating XML.
Rates follow amountInTarget = amountInSource * rate. A EUR to CVE rate must express CVE per EUR, not EUR per CVE.
Exchange-Rate Precision Fails
exchange_rate.rate_invalid means the rate is non-finite, non-positive, or rounds to zero at five fractional digits. The active XSD limits ExchangeRate to five fractional digits. Supply a positive source-to-CVE multiplier and let the package normalize it.
exchange_rate.invoice_invalid means totals are absent or independently rounded CVE fields no longer pass fiscal consistency validation. Currency preparation requires a payable amount and never records zero metadata for a document without totals. When totals exist, inspect the validation cause and correct the source values. Do not alter one converted line to force totals to match.
Alternative Amounts Conflict
exchange_rate.alternatives_conflict means the source invoice already contains payableAlternativeAmounts. Automatic preparation cannot verify those values. Remove them before preparation or manage them through the low-level invoice and XML APIs without calling prepareInvoiceToCve().
DFA Conversion Evidence Is Missing
Pass both fields from the stored preparation result:
await efatura.renderDfa({
iud,
invoice: prepared.invoice,
conversion: prepared.conversion,
});
Do not use the deprecated currency field to label an unconverted invoice. It will be removed in v1.0.0. A defined value emits one Node.js DeprecationWarning per process with code EFATURA_RENDER_DFA_CURRENCY_DEPRECATED; a foreign label in IUD-only rendering then throws dfa.currency_invalid. Remove the field. Use prepareInvoiceToCve() for a foreign invoice and pass the stored prepared invoice and conversion metadata to renderDfa(). Reprints must reconstruct stored date strings as Date objects and reuse the original converted invoice and metadata instead of obtaining a new quote.
Direct metadata that lacks an invoice, targets a currency other than CVE, or disagrees with invoice and alternative payable values throws dfa.conversion_invalid. Reuse both members of the same stored PreparedCurrencyInvoice result.
Runtime Uses The Wrong Node Version
The package requires Node 20 or newer. Check:
node --version
If a local package manager or shell integration selects an older runtime, switch to Node 20 or newer and reinstall dependencies.