Project

ksef-rb

0.0
The project is in a healthy, maintained state
A Ruby client for the Polish National e-Invoicing System (KSeF 2.0). Targets the FA(3) schema, supports token-based authentication, interactive sessions, and inbound invoice retrieval (metadata, XML, and visualisation). Pure Ruby with no Rails dependency.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 0.2
>= 2.0, < 3.0
>= 2.0, < 3.0
>= 1.15
 Project Readme

ksef-rb

Ruby client for the Polish KSeF 2.0 (Krajowy System e-Faktur) National e-Invoicing System.

Targets the FA(3) schema (mandatory since February 2026). Built against the official OpenAPI spec at https://api-test.ksef.mf.gov.pl/docs/v2/openapi.json and the CIRFMF reference clients for C# and Java.

Status

Pre-1.0. The public API is small on purpose and stable across the v0.1 line, but additions are expected as more KSeF features land.

Installation

gem "ksef-rb"

Quick start

require "ksef"

Ksef.configure do |c|
  c.environment = :test          # :test, :demo, or :production
  c.user_agent  = "MyApp / ksef-rb #{Ksef::VERSION}"
end

client = Ksef::Client.new(
  nip:         "1234567890",
  credentials: Ksef::Credentials::Token.new(ENV.fetch("KSEF_TOKEN"))
)

client.sessions.with_interactive do |_session|
  headers = client.invoices.query(
    subject_type: :recipient,
    date_from:    Time.now.utc - (7 * 24 * 3600),
    date_to:      Time.now.utc,
    date_type:    :issue  # :permanent_storage (default), :invoicing, or :issue
  )

  headers.each do |h|
    puts "#{h.ksef_reference_number}  #{h.issuer_nip}  #{h.gross_amount} #{h.currency}"
  end

  xml = client.invoices.fetch_xml(headers.first.ksef_reference_number)
  File.write("invoice.xml", xml)
end

#query returns at most page_size headers per call (default 100, max 250). Paginate by bumping page_offset until a short page comes back:

all = []
offset = 0
loop do
  page = client.invoices.query(
    subject_type: :recipient,
    date_from:    Date.new(2026, 4, 1),
    date_to:      Date.new(2026, 5, 31),
    date_type:    :issue,
    page_size:    250,
    page_offset:  offset
  )
  all.concat(page)
  break if page.size < 250

  offset += 1
end

Ksef::InvoiceHeader exposes (among others): ksef_reference_number, invoice_number, issuer_nip, issuer_name, recipient_nip, recipient_name, issued_on, gross_amount, net_amount, vat_amount, currency, invoicing_mode, invoice_type, form_code, form_schema_version, permanently_stored_at, has_attachment?, self_invoicing?, and the original payload via raw.

Authentication

v0.1 ships with token-based auth using the long-lived integration tokens minted in the KSeF portal after a Profil Zaufany / qualified-seal login.

The full handshake — /auth/challenge, /auth/ksef-token, status polling at /auth/{ref}, and /auth/token/redeem — is performed automatically by Ksef::Sessions#with_interactive. The integration token is encrypted with RSA-OAEP (SHA-256) using the public key returned by /security/public-key-certificates.

with_interactive always tears the session down by calling DELETE /auth/sessions/current, even when the block raises.

Errors

All KSeF-specific errors inherit from Ksef::Error:

Class When
Ksef::AuthError 401, 403, or auth-status failure (code: 450, etc.)
Ksef::NotFoundError 404
Ksef::RateLimitError 429 (exposes #retry_after in seconds when sent)
Ksef::ServerError 5xx
Ksef::ClientError other 4xx
Ksef::ConfigurationError bad config

Every error captures status, body, and the KSeF-supplied code.

What's not in v0.1.0

Feature Status
Token-based auth shipped
Interactive sessions shipped
Inbound invoice metadata query shipped
Inbound invoice XML fetch shipped
Inbound invoice PDF visualisation stubbed — KSeF 2.0 has no server-side PDF endpoint; render client-side from the XML using the official XSLT (wizualizacja-faktury_v3-0.xsl)
Certificate-based auth (qualified seal) stubbed (Ksef::Credentials::Certificate)
Batch sessions not yet
Outbound invoice issuance not yet
UPO download stubbed (Ksef::Invoices#fetch_upo)
Offline / QR-code modes not yet

NotImplementedError is raised from the stubs.

Configuration reference

Ksef.configure do |c|
  c.environment  = :test        # :test, :demo, :production
  c.user_agent   = "..."        # appended to every request
  c.timeout      = 30           # seconds
  c.open_timeout = 10           # seconds
  c.api_version  = "v2"         # path segment; defaults to v2
  c.base_url     = nil          # override entirely (useful for tests)
  c.logger       = Logger.new($stdout)  # wires Faraday's logger middleware
end

Ksef::Client.new(configuration:) accepts a per-client Configuration, which is the duplicated global configuration by default.

Development

bundle install
bundle exec rspec

The suite uses VCR (opt-in, via the :vcr metadata tag) and WebMock. Live re-recording against the sandbox is gated behind KSEF_RECORD=true and a real token in KSEF_TOKEN.

License

MIT — see LICENSE.txt.