nfse-js
v0.3.0
Published
Spec-first TypeScript toolkit for Brazil's National NFS-e standard.
Maintainers
Readme
nfse-js
Spec-first TypeScript tools for Brazil's National NFS-e standard.
nfse-js builds and validates National DPS XML and processes National
NFS-e documents without coupling your application to a CLI, YAML format,
framework, storage layer, certificate provider, or municipal legacy layout.
The library produces the DPS submitted by a contributor. SEFIN validates that DPS and, when it accepts the submission, produces the NFS-e.
Capability status
| Capability | Status | Meaning |
| --- | --- | --- |
| Typed DPS model | Complete for reachable v1.01 TCInfDPS types | Every reachable complex type has an explicit TypeScript representation |
| DPS serialization | Complete for the modeled v1.01 structure | Named coverage fixtures serialize deterministically and validate against the bundled XSD |
| XSD structural validation | Available for DPS, NFS-e, and events | Proves conformance to the bundled schema structure, not acceptance by SEFIN |
| Local semantic validation | Partial deterministic coverage | Implements documented facets and locally decidable National rules tracked in the coverage manifest |
| Authoritative code-table validation | Available with supplied provider | Checks generally applicable ISO, IBGE/National location, BACEN, service, NBS, and IBS/CBS code membership without fetching or bundling tables |
| Signing and transport | Implemented with synthetic tests | XMLDSig defaults, active wrappers, and response shapes still require live confirmation |
| Restricted-production evidence | Not yet captured | No sanitized accepted and rejected issuance evidence is retained |
An XSD-valid DPS can still be rejected because of National business rules, authoritative reference data, municipal parameters, taxpayer state, signature requirements, or the active SEFIN API contract.
For the detailed implementation state, known limitations, architectural
decisions, and the completion roadmap, see
PROJECT_STATUS.md.
Machine-readable DPS coverage claims are tracked in
schemas/1.01/dps-coverage.json.
Task guides and the generated public export index are available in
docs/. Runtime and release guarantees are defined in
SUPPORT.md, COMPATIBILITY.md, and
SECURITY.md.
Design
- National NFS-e is the only wire standard.
- Domain input is plain typed data, not files or configuration formats.
- Decimal values stay strings and are serialized exactly.
- Core XML generation is deterministic and synchronous.
- XML parsing rejects DTD/entity declarations and applies byte/depth limits.
- The root entry point preserves the core, error, and XSD compatibility surface.
- Parsing, events, parameters, signing, and transport use explicit subpath imports.
- XML signing is optional and isolated behind a subpath import.
- SEFIN transport is optional and isolated behind a subpath import.
- Official schemas are committed unchanged with recorded source URLs and hashes.
- Every
TCInfDPSdescendant has an explicit type and serializer. - XSD choices are represented by TypeScript unions.
Install
npm install nfse-jsNode.js 20 or newer is required.
Build an unsigned DPS
import { createDps, decimal, serializeDps } from "nfse-js/core";
const dps = createDps({
infDPS: {
tpAmb: "2",
dhEmi: "2026-06-11T10:30:00+01:00",
verAplic: "my-application",
serie: "1",
nDPS: "1",
dCompet: "2026-06-11",
tpEmit: "1",
cLocEmi: "3550308",
prest: {
CNPJ: "12345678000195",
regTrib: { opSimpNac: "1", regEspTrib: "0" },
},
toma: {
CPF: "12345678909",
xNome: "Example Customer",
},
serv: {
locPrest: { cLocPrestacao: "3550308" },
cServ: {
cTribNac: "010101",
xDescServ: "Software consulting",
},
},
valores: {
vServPrest: { vServ: decimal("100.00") },
trib: {
tribMun: { tribISSQN: "1", tpRetISSQN: "1" },
totTrib: { indTotTrib: "0" },
},
},
},
});
const xml = serializeDps(dps, { pretty: true });createDps derives the official 45-character Id from the municipality,
selected issuer CNPJ/CPF, series, and DPS number. You can supply infDPS.Id
when importing an existing document.
Fiscal decimal fields have XSD-specific constructors: decimal15v2,
decimal3v2, decimal2v2, and decimal1v2. decimal() is the
decimal15v2 monetary default.
Validate local rules
import {
validateDps,
validateDpsWithReferenceData,
validateDpsWithMunicipalParameters,
} from "nfse-js/core";
const result = validateDps(dps);
const referenceResult = validateDpsWithReferenceData(dps, referenceData);
const municipalResult = validateDpsWithMunicipalParameters(dps, {
municipality: "3550308",
serviceCode: "010101",
providerMunicipalRegistrationRequired: false,
allowedDeductionModes: ["percentage", "value"],
allowedWithholding: ["1", "2"],
});Local validation covers centralized XSD facets, CPF/CNPJ check digits, real
dates, DPS identifier consistency, and deterministic cross-field rules from
the official v1.01 Annex I. Issues include a stable category, official
rejection code where documented, and source metadata. Use
validateDps(dps, { mode: "fail-fast" }) to stop at the first issue.
Reference-data validation accepts caller-supplied authoritative datasets and performs no network calls or bundled-snapshot fallback. It distinguishes a confirmed unknown code from an unavailable dataset and reports source metadata for audit. See Reference data validation for the provider contract and covered fields.
Municipal validation accepts already-resolved parameters and also performs no network calls. Rules that require SEFIN, CNC, municipal parameter, taxpayer state, or IBS/CBS calculator state are intentionally not guessed by the pure validator.
Validate against the XSD
import { validateDpsXml } from "nfse-js/validation";
await validateDpsXml(xml);Validation uses libxml2 compiled to WebAssembly. Importing nfse-js/core
does not load it.
To collect errors rather than throw:
const result = await validateDpsXml(xml, { throwOnInvalid: false });Other validators are available for generated NFS-e documents and events:
validateNfseXml, validateEventRequestXml, validateEventXml, and the
generic validateXml.
Parse received documents
import {
parseDpsXml,
parseEventRequestXml,
parseNfseXml,
parseRegisteredEventXml,
parseSefinDocumentResponse,
} from "nfse-js/parsing";
const parsedDps = parseDpsXml(xml);
const parsedNfse = parseNfseXml(nfseXml);
const parsedRequest = parseEventRequestXml(eventRequestXml);
const parsedEvent = parseRegisteredEventXml(eventXml);
const response = parseSefinDocumentResponse(responseBody, {
status: 200,
contentType: "application/json",
});Parsers preserve the exact original XML, parsed signature material, and raw XML trees alongside typed document fields. SEFIN JSON envelopes are parsed without relying on undocumented property names: plain XML and gzip/base64 XML documents are discovered recursively, while rejection payloads remain available as structured JSON.
Sign and verify XML
import {
createPkcs12Signer,
signDpsXml,
verifyNationalXmlSignature,
} from "nfse-js/signing";
const signer = createPkcs12Signer(pfxBytes, { password: process.env.PFX_PASSWORD });
const signedXml = await signDpsXml(xml, signer);
const verification = verifyNationalXmlSignature(signedXml, {
trustedCertificates: [trustedCertificatePem],
requireTrustedCertificate: true,
});PEM private keys and certificate chains are supported through
createPemSigner. Applications using an HSM, cloud KMS, or remote signing
service can implement the asynchronous XmlSigner interface without exporting
private key material.
Signing targets the document information element by Id, inserts the
enveloped signature in schema order, and supports DPS, generated NFS-e, event
requests, and registered events. Verification checks the digest, signature,
reference target, algorithm profile, certificate dates, and an optional
caller-provided trust chain. Only cryptographically authenticated referenced
XML is returned.
The accessible National NFS-e documentation requires W3C XML Digital Signature but does not publish the canonicalization, digest, and signature algorithm URIs. The default profile therefore uses inclusive C14N 1.0 and RSA-SHA256, and is explicit and configurable. Confirm this profile against the restricted SEFIN environment before production use.
Call SEFIN and ADN APIs
import {
createNodeHttpTransport,
createSefinClient,
} from "nfse-js/transport";
const transport = createNodeHttpTransport({
tls: {
pfx: connectionCertificateBytes,
passphrase: process.env.PFX_PASSWORD,
},
});
const sefin = createSefinClient({
environment: "restricted-production",
transport,
});
const result = await sefin.submitDps(signedXml, {
signal: abortController.signal,
timeoutMs: 30_000,
});The client models the official restricted-production and production hosts and the documented NFS-e, DPS, event, and contributor ADN routes. It supports DPS submission, NFS-e and DPS reconciliation queries, event registration and queries, and contributor ADN document/event queries. NSU document lookups support the optional CNPJ selector for another establishment with the same CNPJ root as the connection certificate.
Connection certificates are configured on the HTTP transport and remain independent from XML-signing credentials. GET and HEAD requests retry transient network failures and selected HTTP statuses; POST operations are never retried automatically because the official material does not document a safe idempotency mechanism. Logs contain operation metadata only, not URLs, headers, taxpayer identifiers, XML, certificates, or response bodies.
The public manuals describe DPS submission as XML and event communication as
JSON, but the accessible documentation does not publish every current Swagger
wrapper property. Raw XML and JSON payloads are supported directly. When the
active environment requires gzip/base64 JSON, use
gzipBase64XmlJsonPayload(xml, propertyName) with the property name from that
environment's Swagger instead of relying on a guessed field.
Build event requests
import { createEventRequest, serializeEventRequest } from "nfse-js/events";
import { signEventRequestXml } from "nfse-js/signing";
const request = createEventRequest({
infPedReg: {
tpAmb: "2",
verAplic: "my-application",
dhEvento: "2026-06-12T10:00:00-03:00",
autor: { CNPJAutor: "12345678000195" },
chNFSe: accessKey,
evento: {
code: "e101101",
cMotivo: "1",
xMotivo: "Documento emitido incorretamente",
},
},
});
const eventXml = serializeEventRequest(request);
const signedEventXml = await signEventRequestXml(eventXml, signer);All 16 request variants in the v1.01 event schema are discriminated TypeScript
unions. The library generates the official fixed descriptions and PRE
identifier, validates local facets and references, serializes in XSD order,
and reuses the signing and transport modules for registration.
Resolve municipal parameters
import { createMunicipalParameterResolver } from "nfse-js/parameters";
const parameters = createMunicipalParameterResolver({
client: sefin,
ttlMs: 5 * 60_000,
map(snapshot) {
return mapCurrentSwaggerResponses(snapshot);
},
});
const resolved = await parameters.resolve({
municipality: "3550308",
serviceCode: "010101",
contributorTaxId: "12345678000195",
});The resolver fetches convention, service, and optional contributor parameters,
deduplicates concurrent requests, applies bounded TTL caching, and returns the
same ResolvedMunicipalParameters contract consumed by
validateDpsWithMunicipalParameters. The current public manuals do not define
stable response fields, so raw responses remain lossless and applications
provide an explicit mapper for the active Swagger version.
Entry points
| Import | Purpose |
| --- | --- |
| nfse-js | Full public API |
| nfse-js/core | Types, DPS IDs, semantic validation, XML generation |
| nfse-js/events | Event IDs, typed request construction, validation, XML |
| nfse-js/parameters | Municipal parameter resolution and bounded caching |
| nfse-js/parsing | Secure DPS, NFS-e, event, and SEFIN response parsing |
| nfse-js/signing | XML signing, credentials, and signature verification |
| nfse-js/transport | SEFIN/ADN clients, retries, timeouts, and mutual TLS |
| nfse-js/validation | XSD validation |
| nfse-js/schemas | Access to bundled National NFS-e schemas |
Scope
This library does not implement ABRASF or municipality-specific legacy formats. Municipal configuration is still relevant to National NFS-e, but it is data obtained from National APIs rather than a separate DPS layout.
Remaining work includes completing locally decidable semantic validation, capturing restricted-production conformance evidence, independently verifying XMLDSig interoperability, and exercising releases in real consumer applications.
Production readiness
The library has broad implementation coverage, automated tests, cross-platform CI, package-consumer checks, security controls, and release automation. It should still be treated as an implementation candidate until the following evidence exists:
- successful and rejected DPS submissions have been exercised in the official restricted-production environment and retained as sanitized fixtures;
- the default XMLDSig canonicalization, digest, and signature algorithms have been confirmed against SEFIN and independently verified outside this package;
- event registration and municipal-parameter responses have been exercised against the active restricted-production Swagger contracts;
- remaining locally decidable National business rules and optional DPS schema branches have exhaustive positive and negative coverage;
- certificate revocation and ICP-Brasil taxpayer identity requirements have an agreed production policy;
- official schema updates have a publisher-authenticated provenance or digest channel, rather than relying only on an operator-supplied hash;
- release candidates have been exercised by multiple real applications with different document profiles, certificate providers, and deployment environments.
Until those conditions are met, validate generated XML against the bundled XSD, keep POST retry decisions application-controlled, verify certificates against application-provided trust anchors, and test the exact taxpayer, municipality, service, and event flows in restricted production before relying on the library operationally.
The detailed evidence requirements and current limitations are tracked in
PROJECT_STATUS.md.
Help wanted
External usage and conformance evidence are now more valuable than adding another abstraction. Contributions are especially useful in these areas:
- Real consumers: integrate a release candidate into an application and report the Node.js version, runtime, package entry points, document features, certificate provider, and any compatibility problems encountered.
- Restricted-production testing: help exercise accepted and rejected DPS issuance, reconciliation queries, events, and municipal parameters. Sanitized request/response fixtures and current Swagger observations are particularly valuable.
- Independent signature interoperability: verify an
nfse-jssignature with another XMLDSig implementation, or provide a compatible fixture produced by Java, .NET, OpenSSL-based tooling, an HSM, or a cloud signing service. - National rule review: map remaining locally decidable rules from official manuals and technical notes to source references, rejection codes, and positive/negative tests.
- Schema provenance: identify an official authenticated channel for schema archive digests or publisher signatures.
- Deployment diversity: test ESM and CommonJS consumers on supported Node versions, Windows/macOS/Linux, serverless platforms, containers, and bundlers.
Please open an issue or pull request with a reproducible description and the
smallest safe fixture possible. Never publish private keys, certificate
passwords, taxpayer secrets, or unsanitized fiscal documents. Security
vulnerabilities should follow SECURITY.md; general contribution
guidance is in CONTRIBUTING.md.
Schema sources and hashes
The v1.01 schemas were published by the Brazilian National NFS-e project on
February 9, 2026. See schemas/manifest.json for the
official source URL, retrieval date, hashes, and a documented libxml2
compatibility adjustment applied only to the generated runtime copy.
Applications can pin a supported standard version explicitly:
import {
getNationalNfseSchemaSet,
SUPPORTED_NATIONAL_NFSE_VERSIONS,
} from "nfse-js/schemas";
const schemas = getNationalNfseSchemaSet("1.01");SUPPORTED_NATIONAL_NFSE_VERSIONS is the authoritative list. Supporting a new
version requires adding a separate immutable schema directory and public type
support; an existing version is never replaced in place.
Maintainers can stage an official directory or ZIP bundle for review:
npm run schema:stage -- \
--source ./reference-docs/nfse/anexos \
--version 1.01The command writes schemas and a hash-based diff report under the ignored
.schema-staging/ directory. It never edits the supported bundle. ZIP inputs
require the unzip command. Remote downloads are restricted to official
gov.br HTTPS URLs and require an independently verified archive digest:
npm run schema:stage -- \
--source https://www.gov.br/path/to/official-schemas.zip \
--sha256 <verified-archive-sha256> \
--version 1.01Every redirect is revalidated and the verified final URL and archive digest
are recorded in the report. Technical-note review state is tracked separately
in schemas/technical-notes.json.
Development checks
npm run verify
npm run test:coverage
npm run benchmarknpm run docs:api regenerates the committed API export reference. The normal
verification command fails when that reference is stale.
License
MIT
