<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rostislav Havryliuk</title>
    <description>The latest articles on DEV Community by Rostislav Havryliuk (@rostislav_havryliuk).</description>
    <link>https://dev.to/rostislav_havryliuk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2765453%2Fc02d5cc0-094d-4b7a-86b3-ab452d22edec.png</url>
      <title>DEV Community: Rostislav Havryliuk</title>
      <link>https://dev.to/rostislav_havryliuk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rostislav_havryliuk"/>
    <language>en</language>
    <item>
      <title>KSeF: Poland's e-invoicing API will break your store. Here is where</title>
      <dc:creator>Rostislav Havryliuk</dc:creator>
      <pubDate>Wed, 23 Sep 2026 11:22:59 +0000</pubDate>
      <link>https://dev.to/rostislav_havryliuk/polands-e-invoicing-api-will-break-your-store-here-is-where-4kfo</link>
      <guid>https://dev.to/rostislav_havryliuk/polands-e-invoicing-api-will-break-your-store-here-is-where-4kfo</guid>
      <description>&lt;p&gt;Poland made B2B e-invoicing mandatory. Biggest companies from 1 February 2026, everyone else from 1 April 2026. Invoices to companies now go through KSeF, a government system. Not a PDF you email. An XML the state accepts or rejects.&lt;/p&gt;

&lt;p&gt;Two dates that get lost in summaries. &lt;strong&gt;Receiving&lt;/strong&gt; invoices through KSeF is mandatory for everyone from 1 February 2026, the April deferral covers issuing only. So your purchase invoices have to be pulled from the API earlier than you have to push your own. And until the end of 2026 the smallest sellers can stay outside: invoiced sales of 10,000 zł gross or less &lt;strong&gt;per month&lt;/strong&gt;, lost from the invoice that crosses it. B2C sales and receipts do not count toward that threshold. (&lt;a href="https://ksef.podatki.gov.pl/informacje-ogolne-ksef-20/podstawy-prawne-oraz-kluczowe-terminy/" rel="noopener noreferrer"&gt;dates and legal basis, Ministry of Finance&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I integrate stores and SaaS with this. Here is what actually breaks. Facts below are current as of September 2026, KSeF 2.0 API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most of your invoices do not go there
&lt;/h2&gt;

&lt;p&gt;B2C invoices are voluntary. The Ministry says so in its own FAQ. Mandatory means invoices to companies. The trigger is one field: the buyer's tax ID, the NIP. (&lt;a href="https://ksef.podatki.gov.pl/pytania-i-odpowiedzi-ksef-20/" rel="noopener noreferrer"&gt;Ministry FAQ&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Neither Shopify nor Stripe collects it &lt;strong&gt;by default&lt;/strong&gt;. Stripe Checkout has tax ID collection and supports Polish VAT numbers, it is just off unless you turn it on. Shopify exposes tax IDs in B2B and through checkout customisation. Either way somebody has to enable it, map it, and decide what happens when it is missing.&lt;/p&gt;

&lt;p&gt;So the first question is not "which API". It is "does our checkout ask for a NIP, where does it land, and what do we do when a customer asks for a company invoice after the order". Answer that first.&lt;/p&gt;

&lt;p&gt;Out of scope too: OSS and IOSS, pro formas, internal notes, fiscal receipts with a NIP up to 450 zł until the end of 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not call the API per order
&lt;/h2&gt;

&lt;p&gt;The rate limit docs describe an online store as a separate case. The recommendation: collect invoices and send them in batches, for example every 5 minutes. (&lt;a href="https://github.com/CIRFMF/ksef-api/blob/main/limity/limity-api.md" rel="noopener noreferrer"&gt;API limits, CIRFMF/ksef-api&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That answers most limit questions. Numbers as published in September 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 MB per invoice&lt;/li&gt;
&lt;li&gt;10,000 invoices per session&lt;/li&gt;
&lt;li&gt;50 ZIP files per batch, 100 MB per package before encryption&lt;/li&gt;
&lt;li&gt;interactive sending: 10/s, 30/min, 180/h&lt;/li&gt;
&lt;li&gt;over the limit: 429 plus &lt;code&gt;Retry-After&lt;/code&gt;, and the block grows if you keep pushing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Limits count per (context, IP). Test runs roughly 10x higher than production, which is why this surfaces after launch. Check the current numbers in that repo before you size anything, they move between API versions.&lt;/p&gt;

&lt;p&gt;Also: the retrieval API is for syncing to your database. Polling faster than every 15 minutes gets you blocked. So does fetching invoices one by one instead of using export.&lt;/p&gt;

&lt;h2&gt;
  
  
  Midnight will flip your invoices into offline mode
&lt;/h2&gt;

&lt;p&gt;If the issue date &lt;code&gt;P_1&lt;/code&gt; is earlier than the day KSeF accepts the file, the invoice becomes "offline24" automatically. You did not set a flag. It just happens. Now you owe a send deadline. (&lt;a href="https://github.com/CIRFMF/ksef-api/blob/main/offline/automatyczne-okreslanie-trybu-offline.md" rel="noopener noreferrer"&gt;automatic offline mode&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Worse: batch sessions count the moment the session opens, interactive sessions count the moment each invoice is sent. A batch opened at 23:59 saves the day. An interactive one does not.&lt;/p&gt;

&lt;p&gt;Run your nightly job at 2am, not at midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries create second invoices
&lt;/h2&gt;

&lt;p&gt;KSeF blocks duplicates on three fields: seller NIP, document type, invoice number. Duplicate comes back as 440. Uniqueness holds for ten years. (&lt;a href="https://github.com/CIRFMF/ksef-api/blob/main/faktury/weryfikacja-faktury.md" rel="noopener noreferrer"&gt;invoice verification&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This saves you on retries. On one condition: the invoice number must be stable.&lt;/p&gt;

&lt;p&gt;Generate the number at send time and a retry makes a new one. KSeF sees two different invoices. Two KSeF numbers. Two live invoices for one order. The only fix is a credit note to zero.&lt;/p&gt;

&lt;p&gt;There is no idempotency key in the API. Build it yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;persist the invoice number with the order before the first send&lt;/li&gt;
&lt;li&gt;store the SHA-256 of the XML, computed before encryption&lt;/li&gt;
&lt;li&gt;read the session status before any retry, including the list of rejected invoices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out-of-order numbering is fine, by the way. The Ministry says so directly. A rejected invoice sent later does not need a correction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your queue is not FIFO
&lt;/h2&gt;

&lt;p&gt;A credit note must carry the KSeF number of the original. So a credit note for an offline invoice waits until the original gets its number.&lt;/p&gt;

&lt;p&gt;Same-day refunds are normal in e-commerce. Your queue needs that dependency, or it will fail in week one.&lt;/p&gt;

&lt;p&gt;Two more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;correction notes are gone since February 2026, everything goes through the seller&lt;/li&gt;
&lt;li&gt;there is no cancel; wrong buyer NIP means credit note to zero and a new invoice&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rejected means it never existed
&lt;/h2&gt;

&lt;p&gt;No invoice, no number, nothing in the records. You cannot correct it. You send a fixed file.&lt;/p&gt;

&lt;p&gt;In your system the order looks invoiced. The customer waits for a document that does not exist. If rejections only go to a log, nobody notices for a week. Send them to a human.&lt;/p&gt;

&lt;p&gt;KSeF checks the schema, UTF-8 without BOM, the issue date not being in the future, the NIP checksum (production only, so this one survives your tests), sizes and hashes.&lt;/p&gt;

&lt;p&gt;It does not check your math. Wrong totals get accepted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline modes: one flag, four deadlines, and two QR codes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Triggered by&lt;/th&gt;
&lt;th&gt;Send by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;offline24&lt;/td&gt;
&lt;td&gt;you, no reason needed&lt;/td&gt;
&lt;td&gt;next business day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unavailability&lt;/td&gt;
&lt;td&gt;KSeF&lt;/td&gt;
&lt;td&gt;next business day after it ends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;failure&lt;/td&gt;
&lt;td&gt;KSeF&lt;/td&gt;
&lt;td&gt;7 business days after it ends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total failure&lt;/td&gt;
&lt;td&gt;minister&lt;/td&gt;
&lt;td&gt;no obligation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mode itself is one boolean in the request. The deadline math is yours.&lt;/p&gt;

&lt;p&gt;The part that is not one boolean is the visualisation. Any invoice used outside KSeF needs a QR code, and an offline invoice needs &lt;strong&gt;two&lt;/strong&gt;: KOD I, which encodes the verification URL, issue date, seller NIP and a hash of the file, and KOD II, which proves you issued it, and which has to be signed with a KSeF certificate. Once the invoice reaches KSeF, you are back to one code with the KSeF number under it. (&lt;a href="https://github.com/CIRFMF/ksef-api/blob/main/kody-qr.md" rel="noopener noreferrer"&gt;QR codes&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;So offline support means: requesting and storing a certificate, signing locally, and rendering one or two codes depending on state. Budget for it separately.&lt;/p&gt;

&lt;p&gt;In 2026 the Ministry did not declare a single KSeF failure. Go-live pain was in the national login services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small stuff that eats a day
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;buyer NIP in the wrong field means the buyer never sees the invoice, even though it was accepted&lt;/li&gt;
&lt;li&gt;the KSeF number is 35 chars with a checksum, validate it locally&lt;/li&gt;
&lt;li&gt;the XML is the invoice, the PDF is a picture of it&lt;/li&gt;
&lt;li&gt;access tokens live minutes, the auth challenge expires in 10&lt;/li&gt;
&lt;li&gt;product names go into XML as they are, so your HTML entities will break the file&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The currency trap
&lt;/h2&gt;

&lt;p&gt;There was supposed to be a KSeF-specific exchange rate rule. It was repealed before it came into force. The general rule still applies.&lt;/p&gt;

&lt;p&gt;But the consolidated text of the VAT act was published before the repeal, so the dead rule is still printed there as future law. Plenty of articles repeat it.&lt;/p&gt;

&lt;p&gt;If someone shows you a "new KSeF rate rule", check their date. And confirm your case with a tax advisor, not with a blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Penalties
&lt;/h2&gt;

&lt;p&gt;Statutory start is January 2027. In September 2026 the Ministry announced a deferral to the end of 2027, and that announcement says a law still has to be written. (&lt;a href="https://www.gov.pl/web/finanse/przedluzenie-odroczenia-kar-za-bledy-w-stosowaniu-ksef-do-konca-2027-r" rel="noopener noreferrer"&gt;Ministry announcement, 16 September 2026&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;So: not a fact yet. The obligation to use KSeF was never deferred.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are an agency outside Poland
&lt;/h2&gt;

&lt;p&gt;A Polish VAT number alone does not put your client in scope. What matters is a seat or a fixed establishment in Poland taking part in the transaction. The Ministry published guidance on that in January 2026 and still asks for case-by-case assessment.&lt;/p&gt;

&lt;p&gt;Budget it as real work: collect a NIP, keep numbers stable, own a queue with retries and dependencies, surface rejections, handle offline QR, write the KSeF number back to the order.&lt;/p&gt;

&lt;p&gt;If you are wiring a Shopify, WooCommerce or Stripe setup to KSeF and you hit one of these, write to me and I will tell you what it takes: &lt;a href="https://polaczone.pl" rel="noopener noreferrer"&gt;polaczone.pl&lt;/a&gt;. The Polish version of this guide, with the same sources, is &lt;a href="https://polaczone.pl/poradniki/ksef-2026-sklep-internetowy/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>poland</category>
      <category>api</category>
      <category>ecommerce</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
