FA(3) XML Structure: The KSeF Guide for Developers

Learn how to map invoice data into FA(3), validate KSeF XML locally, avoid identity-field traps, and promote safely from TEST to production.

Ernest Bursa

Ernest Bursa

Founder · · 12 min read
Four developers mapping an XML invoice schema on a glass board

The FA(3) XML structure is the schema KSeF uses for structured invoices issued from February 1, 2026. Build it as three separate contracts: the XSD controls XML shape, Polish VAT rules control which invoice facts are required, and the transaction controls which conditional branches apply. Validate all three before submission, then treat KSeF’s final status—not the upload response—as acceptance.

This is a technical implementation guide, not tax advice. Confirm the VAT treatment and legally required contents of each invoice type with a qualified adviser.

What is the FA(3) XML structure?

FA(3) is the Ministry of Finance’s logical structure for a Polish structured invoice. It is not a PDF layout or a visual template. It is an XML contract that says which elements may appear, where they appear, how often they may repeat, and which data types and formats they accept.

The Ministry’s KSeF 2.0 handbook draws an important boundary: the structure governs what the XML should or may contain, but article 106e of the VAT Act still governs which information a particular invoice must contain. FA(3) also offers optional fields, such as contact details, that tax law does not generally require.

That creates three validation layers:

Contract What it answers Typical failure
FA(3) XSD Is this element allowed here, in this order, with this type and cardinality? A malformed date, wrong element order, or missing schema branch
VAT and business rules Does this invoice contain the facts required for its type and transaction? Missing exemption or reverse-charge information even though the XML validates
KSeF processing Can the service accept this exact invoice from this seller and session? A duplicate number, future issue date, bad production NIP checksum, or missing permission

Do not collapse these layers into a single “valid XML” Boolean. minOccurs="0" means only that the XSD permits omission. It does not mean the field can be omitted from every legally valid invoice. The reverse is also true: a populated field in a Ministry sample is not mandatory just because the example contains it.

Which FA(3) namespace and header should you use?

Use the target namespace from the current official FA(3) XSD:

http://crd.gov.pl/wzor/2025/06/25/13775/

The trailing slash matters. The schema declares qualified elements, so putting <Faktura> in no namespace and merely attaching a schema location later does not create the same document.

The official Java client sample shows the corresponding header identity:

<Faktura xmlns="http://crd.gov.pl/wzor/2025/06/25/13775/">
  <Naglowek>
    <KodFormularza kodSystemowy="FA (3)" wersjaSchemy="1-0E">FA</KodFormularza>
    <WariantFormularza>3</WariantFormularza>
    <DataWytworzeniaFa>2026-08-28T09:30:00Z</DataWytworzeniaFa>
    <SystemInfo>YourApp 4.2</SystemInfo>
  </Naglowek>
  <!-- Remaining sections in XSD order -->
</Faktura>

Keep the namespace, kodSystemowy, wersjaSchemy, and variant in one versioned serializer module. Do not scatter these strings across view templates. When the Ministry publishes a successor schema, you want an explicit serializer selection and fixture suite, not a search-and-replace migration.

Generate XML 1.0 in UTF-8 without a byte-order mark. The invoice verification rules allow the XML declaration to be omitted, but if it exists it must not declare another encoding. KSeF also rejects processing instructions and specified discouraged Unicode ranges. Normalize and validate text before it reaches the serializer.

What are the eight top-level FA(3) sections?

The Ministry handbook groups FA(3) into eight main elements. Some are always central to an ordinary invoice; others exist only for a particular role or feature.

Section Developer mental model
Naglowek Technical envelope: form identity, generation time, and generator name
Podmiot1 Seller identity, address, and optional contact data
Podmiot2 Buyer identity, address, and optional contact data
Podmiot3 Repeatable third parties such as a factor, payer, recipient, or additional buyer
PodmiotUpowazniony Authorized subject, for example a court bailiff issuing in a defined role
Fa Invoice facts: currency, dates, number, totals, annotations, kind, lines, and payment data
Stopka Optional footer and registry details such as KRS
Zalacznik Optional structured attachment, available only after the required e-US registration

Order is part of the contract. XML element names may look like independent fields, but the XSD commonly wraps them in sequence and choice. A generic object-to-XML mapper can produce every correct value in the wrong order and still fail validation. Define serialization order directly and cover it with a schema test.

Podmiot3, PodmiotUpowazniony, Stopka, and Zalacznik are not boilerplate. Add them only when your domain model says that role or feature exists. Structured attachments also follow a gated workflow and different size/submission constraints; they should be a separate implementation path, not an optional blob bolted onto the ordinary invoice serializer.

What does an FA(3) invoice skeleton look like?

The following skeleton shows the relationship between the main ordinary-invoice elements. It is intentionally incomplete and is not a copy-paste-ready, schema-minimal, or legally sufficient invoice.

<Faktura xmlns="http://crd.gov.pl/wzor/2025/06/25/13775/">
  <Naglowek>
    <KodFormularza kodSystemowy="FA (3)" wersjaSchemy="1-0E">FA</KodFormularza>
    <WariantFormularza>3</WariantFormularza>
    <DataWytworzeniaFa>2026-08-28T09:30:00Z</DataWytworzeniaFa>
    <SystemInfo>YourApp 4.2</SystemInfo>
  </Naglowek>

  <Podmiot1>
    <DaneIdentyfikacyjne>
      <NIP>1234567890</NIP>
      <Nazwa>Example Seller sp. z o.o.</Nazwa>
    </DaneIdentyfikacyjne>
    <!-- Seller address as required for this invoice -->
  </Podmiot1>

  <Podmiot2>
    <DaneIdentyfikacyjne>
      <NIP>9876543210</NIP>
      <Nazwa>Example Buyer sp. z o.o.</Nazwa>
    </DaneIdentyfikacyjne>
    <!-- Buyer address as required for this invoice -->
  </Podmiot2>

  <Fa>
    <KodWaluty>PLN</KodWaluty>
    <P_1>2026-08-28</P_1>
    <P_2>FV/2026/08/1042</P_2>
    <!-- Totals in the exact schema order -->
    <P_15>123.00</P_15>
    <Adnotacje><!-- Applicable markers and branches --></Adnotacje>
    <RodzajFaktury>VAT</RodzajFaktury>
    <FaWiersz>
      <NrWierszaFa>1</NrWierszaFa>
      <P_7>Software subscription</P_7>
      <P_8A>szt.</P_8A>
      <P_8B>1</P_8B>
      <P_9A>100.00</P_9A>
      <P_11>100.00</P_11>
      <P_12>23</P_12>
    </FaWiersz>
    <!-- Payment data, when applicable -->
  </Fa>
</Faktura>

Why not publish a “minimal valid invoice”? Because the minimum changes with invoice kind, seller and buyer identity, VAT treatment, payment state, corrections, advances, and other facts. A document engineered only to satisfy XSD cardinality can omit information required by law. Start from a typed invoice model and explicit scenario fixtures instead.

How should seller and buyer identifiers map into FA(3)?

Identity mapping is load-bearing because it affects both validation and delivery inside KSeF. Keep tax identifiers as structured values, not a single display string.

Party case FA(3) mapping Common mistake
Polish seller Podmiot1/DaneIdentyfikacyjne/NIP Including spaces, hyphens, or PL in the NIP
Seller requiring the Polish VAT prefix Podmiot1/PrefiksPodatnika = PL, NIP remains digits only Concatenating PL and NIP
Polish buyer Podmiot2/DaneIdentyfikacyjne/NIP Putting a Polish NIP in NrID
EU VAT buyer KodUE plus NrVatUE Putting both values into KodKraju and NrID
Third-country buyer with an identifier KodKraju plus NrID Concatenating country and identifier into NrID
Consumer or buyer without an identifier on the invoice Applicable BrakID = 1 branch Inventing a placeholder tax number

The Polish buyer rule has a consequence beyond neat XML. The handbook says KSeF uses Podmiot2/DaneIdentyfikacyjne/NIP to make the invoice available to that buyer. A Polish NIP hidden in NrID can therefore pass through your internal mapping while breaking the expected KSeF delivery behavior.

Store tax_identifier_kind, country_code, identifier, and has_no_identifier separately. Validate mutually exclusive branches before serialization. Strip presentation separators from NIP, but do not silently “repair” an ambiguous identifier. Production KSeF also checks NIP checksums in subject sections, while TEST does not enforce that check in the same way. Run checksum validation yourself so promotion does not reveal a data-quality problem.

How should you model Fa, totals, and line items?

Fa is the transaction body. For an ordinary VAT invoice it usually includes currency (KodWaluty), issue date (P_1), the seller-assigned invoice number (P_2), applicable summary amounts, statutory annotation branches, invoice kind (RodzajFaktury), repeated FaWiersz elements, and payment information where applicable.

Do not use XML field names as your primary business model. Model money, tax categories, quantities, dates, parties, and invoice kind with domain types, then map those types into FA(3). This keeps tax calculations and rounding testable without parsing your own output.

For each supported scenario, assert at least:

  • line net values reconcile with the relevant summary buckets;
  • VAT amounts follow your documented rounding policy;
  • P_15 reconciles to the amount due under the scenario;
  • currency and numeric serialization use dots and no locale thousands separators;
  • P_1 is not later than KSeF acceptance time;
  • P_2 is stable and unique within the service’s duplicate key.

KSeF’s duplicate check combines the seller NIP, RodzajFaktury, and P_2. A retry must therefore resend and reconcile the same logical invoice rather than issue a fresh seller number blindly. Persist an idempotency record around your invoice and KSeF reference, even if your transport client also retries.

Annotations deserve scenario tests, not default guesses. The official sample includes explicit negative or not-applicable markers for several branches. Copying them mechanically can make a document structurally tidy but factually false. Build the annotation branch from invoice facts and test exempt, reverse-charge, split-payment, margin, and other supported cases separately.

How do you validate FA(3) locally before sending it?

Run validation before encryption and upload, against the exact bytes you intend to submit.

  1. Build a typed invoice snapshot. Freeze seller, buyer, lines, tax treatment, totals, dates, and invoice kind for this issuance attempt.
  2. Apply business-context rules. Reject missing legal facts and impossible branch combinations with field-level errors that your operators understand.
  3. Serialize deterministically. Emit XML 1.0, UTF-8 without BOM, the exact FA(3) namespace, schema order, and invariant numeric/date formats.
  4. Validate with the pinned XSD. Cache the official schema and its imported Ministry definitions in a controlled build dependency; record their checksum and source URL.
  5. Freeze the bytes. Compute size and hash from the same byte sequence you encrypt and upload. Re-rendering afterward can change whitespace, encoding, hash, or size.
  6. Persist diagnostics. Store the serializer version, domain snapshot reference, XML hash, byte size, and validation result. Protect invoice XML as sensitive financial data.

The official verification rules cap an invoice without an attachment at 1 MB and one with an attachment at 3 MB. Check the current contract rather than assuming a successful local XSD validation covers service limits.

Your fixture set should be scenario-based: ordinary domestic VAT, EU buyer, third-country buyer, consumer, correction, advance/settlement, exemption, and every special scheme your product actually supports. A single giant “all fields” fixture is useful for coverage but poor at proving that conditional branches are correct.

How should you test FA(3) in TEST, DEMO, and production?

Move the same serializer through three environments, but keep credentials, endpoints, permissions, and data isolated.

The official environment matrix describes TEST as integration-oriented, DEMO as production-like, and PRD as the environment where invoices have legal effect. TEST and DEMO must not contain real production data. TEST accepts self-signed certificates and its data should not be treated as confidential, so use synthetic names, addresses, invoice numbers, and randomly generated test NIPs.

A safe promotion path is:

  1. Unit-test mapping, totals, branch selection, and deterministic serialization.
  2. Validate all scenario fixtures locally against the pinned XSD.
  3. Submit synthetic fixtures to TEST and exercise rejected as well as accepted paths.
  4. Verify credentials, permissions, status polling, and UPO retrieval in DEMO using non-production data.
  5. Promote the same serializer build to PRD behind monitoring and a controlled rollout.

Do not use TEST acceptance as proof that production identifiers are sound. Prevalidate NIP checksums and other production-only semantics. Also version environment capabilities independently: official API releases can reach TEST before DEMO and PRD.

When is a submitted invoice actually accepted?

An online-session submission is asynchronous. The API can accept your request for processing and return an invoice reference before it has accepted the invoice itself. Persist that reference immediately and move the invoice into a visible processing state.

The official status and UPO guide documents session status, invoice lists, individual invoice status, failed invoices, and UPO retrieval. Poll with backoff, retain KSeF’s structured status details, and make terminal failures actionable without overwriting the original evidence.

Your durable state machine should distinguish at least:

  • generated and locally validated;
  • submitted with a KSeF invoice reference;
  • processing;
  • rejected with structured details;
  • accepted with a KSeF number;
  • UPO retrieved and stored.

Only the accepted path should expose the KSeF number as final evidence. Store the UPO with tamper-evident metadata and reconcile long-running sessions. An HTTP 202 response is a queueing event, not a successful invoice outcome.

Build FA(3) yourself or use KSeF Kit?

Build directly when FA(3) mapping is a core product capability, you support invoice sources beyond Stripe, or you need specialized tax scenarios and control over the complete submission lifecycle. Budget for schema tracking, scenario fixtures, credential handling, environment promotion, status monitoring, and operational support—not just XML generation.

If your source of truth is Stripe, KSeF Kit takes the narrower route: it converts finalized Stripe invoices into FA(3), submits them to KSeF, and stores the KSeF number and UPO. Its setup documentation covers connecting Stripe and KSeF, while the product supports test and production workflows.

That does not remove the need to configure correct tax and customer data, nor is it a blanket tax-compliance guarantee. It does remove a substantial integration surface: maintaining the serializer, encryption/upload flow, asynchronous state machine, and evidence handoff for standard Stripe invoicing.

Whichever path you choose, keep the same acceptance standard: the correct domain facts, valid FA(3) XML, successful KSeF processing, and retained evidence. Browse more implementation-minded guidance in the compliance engineering archive.

Related articles

Ready to hire smarter?

Start free for 30 days. Cancel before it ends and you pay nothing. Set up your first hiring pipeline in minutes.

Start hiring free