Automation MCP Server Features Blog Pricing Contact
Integration ZUGFeRD Format

Create and Validate ZUGFeRD and XRechnung Invoices in Ruby: Complete Guide

Both German e-invoice formats from one Ruby integration: ZUGFeRD hybrid PDFs for B2B and XRechnung pure XML for the public sector. Creating from a plain hash, validating against EN 16931 and the KoSIT rules, reading incoming invoices (deterministic extraction plus AI parsing for old-school PDFs), extracting attachments, rendering XRechnung previews, and Rails patterns with Faraday and ActiveJob. Standard library only, no gem to maintain.

German e-invoicing is a two-format problem, and most Ruby teams selling into Germany eventually need both. Since January 2025 every German business must be able to receive structured e-invoices, with mandatory issuance phasing in from 2027. For B2B that means ZUGFeRD, the hybrid PDF with embedded XML. Sell to a federal ministry, a state agency, or a municipality, and the portals demand XRechnung instead: pure XML, no PDF, and a routing field (the Leitweg-ID) with its own rejection rule. Two deliverables, one semantic model underneath.

This guide covers both from a single Ruby integration. That is a deliberate choice rather than convenience: ZUGFeRD and XRechnung share the EN 16931 core, the API accepts the same invoice hash for both, and the only thing that changes between a B2B and a B2G invoice in the code below is the endpoint and two extra fields. If your market is France rather than Germany, the same hybrid format travels under the Factur-X brand, and the companion guide to Factur-X in Ruby walks that side with French requirements and sample data.

A word on tooling expectations: Ruby has no Mustangproject. There is no maintained gem for either format, and in-process validation is blocked by Ruby's XML stack itself, as the gem section explains. Everything here therefore runs over HTTP against the ZUGFeRD API and XRechnung API, using Net::HTTP from the standard library, with a Rails section for teams on Faraday, ActiveJob, and RSpec.

Germany's two e-invoice formats

The B2B mandate runs on ZUGFeRD. Receiving e-invoices has been mandatory for all German businesses since January 2025: a buyer can no longer refuse a compliant e-invoice, and an ordinary PDF no longer counts as one. Issuing becomes mandatory from January 2027 for companies above EUR 800k turnover and from January 2028 for everyone else. ZUGFeRD profiles EN 16931 and above qualify; the MINIMUM and BASIC WL profiles are booking aids and do not. The current version is ZUGFeRD 2.3, identical to Factur-X 1.0.7, and the embedded attachment is named factur-x.xml in both brandings.

The B2G side runs on XRechnung. Invoices to German federal authorities have had to be XRechnung since November 2020, with state and municipal buyers following. XRechnung is Germany's national CIUS of EN 16931, maintained by KoSIT, and version 3.0 has been in force since February 2024. It adds roughly two dozen German business rules (the BR-DE codes), makes several optional EN 16931 fields mandatory, and is submitted through portals such as ZRE and OZG-RE that enforce the KoSIT rule set on acceptance.

GoBD archiving touches both. German bookkeeping rules require invoices to be archived as received, immutable and machine-evaluable, for the statutory retention period. ZUGFeRD makes that convenient (one PDF/A-3 file is both the visual record and the data), while XRechnung archives as the XML itself, which is one reason a rendering step for human review matters on the B2G side.

The engineering under both is the deep part: PDF/A-3 containers with embedded fonts and XMP profile declarations, CII XML satisfying about 200 EN 16931 business rules, German rule overlays on top, and Schematron to prove all of it. The technical overview of ZUGFeRD automation dissects the layers if you want the full picture.


Which format does your invoice need?

A decision rule that covers nearly every case:

Your customerFormatDeliverableEndpoint
German business (B2B)ZUGFeRD EN 16931Hybrid PDF/A-3/v1/create/zugferd
Federal or state authority (B2G)XRechnung 3.0Pure XML + Leitweg-ID/v1/create/xrechnung
French businessFactur-XHybrid PDF/A-3/v1/create/facturx
Peppol network recipientPeppol BIS 3.0UBL XML/v1/create/ubl

Because every row consumes the same invoice hash, "supporting all of them" in Ruby means a case statement over endpoints, not four integrations. This guide works the first two rows; the French and Peppol rows have guides of their own.


The Ruby gem situation

Search RubyGems for ZUGFeRD or XRechnung and the honest summary is: nothing you would put in a production Gemfile. The handful of hits date from the ZUGFeRD 1.0 era and have been dormant for years, predating ZUGFeRD 2.x, XRechnung 3.0, and most of the rules that matter today. Building the stack yourself runs into three walls, in ascending order of severity:

XML generation is the tractable one. Nokogiri can build the CII or UBL document, but the EN 16931 model is roughly 160 business terms with around 200 interlocking rules, plus the BR-DE overlay for XRechnung. Well-formed is easy; compliant is the project.

PDF/A-3 is the expensive one. For ZUGFeRD you need archival-grade PDF/A-3 with an embedded attachment and exact XMP metadata. Prawn does not produce PDF/A; HexaPDF can embed files and target PDF/A conformance but is AGPL-licensed for commercial use, and converting an existing invoice PDF into genuinely conformant PDF/A-3 (fonts, transparency, color spaces) is unsolved in Ruby.

Validation is the wall. The official rule sets, EN 16931 Schematron and the KoSIT artifacts for XRechnung, compile to XSLT 2.0. Ruby's XML stack, Nokogiri over libxslt, implements XSLT 1.0 and stops there; there is no Saxon for Ruby. The KoSIT validator itself is a Java application, so Ruby teams determined to validate locally end up managing a JVM subprocess and parsing its XML reports, which is a service dependency with extra steps.

The conclusion this guide draws: keep the compliance machinery server-side behind a REST API, and let your Ruby code do what Ruby is good at, moving data and orchestrating. Every operation below is one HTTP call.


Client setup with Net::HTTP

The standard library covers the whole integration, multipart uploads included. Nothing in this guide requires a Gemfile until the Rails section.

Sign up for a free InvoiceXML account and you will receive 100 free credits, no credit card required. Load the key from the environment or your secrets manager, never from source:

require "net/http"
require "json"
require "uri"

BASE_URL = "https://api.invoicexml.com"
API_KEY  = ENV.fetch("INVOICEXML_API_KEY")

def api_http(uri)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.open_timeout = 10
  http.read_timeout = 120
  http
end

Create ZUGFeRD invoices in Ruby

The payload is a plain hash. Send net prices, quantities, and rates; totals and the per-rate VAT breakdown are computed server-side, and the result is validated against EN 16931 before you ever see it. The sample mixes Germany's 19% standard and 7% reduced rates to show the breakdown doing real work:

invoice = {
  invoice: {
    invoiceNumber: "RE-2026-0347",
    issueDate: "2026-07-28",
    currency: "EUR",
    seller: {
      name: "Rheinwerk Digital GmbH",
      vatIdentifier: "DE298765432",
      postalAddress: {
        line1: "Hansaring 27",
        city: "Köln",
        postCode: "50670",
        country: "DE"
      }
    },
    buyer: {
      name: "Nordlicht Handel GmbH",
      postalAddress: {
        line1: "Alsterufer 3",
        city: "Hamburg",
        postCode: "20354",
        country: "DE"
      }
    },
    paymentDetails: {
      paymentAccountIdentifier: "DE44500105175407324931"
    },
    lines: [
      {
        quantity: 40,
        item: { name: "Managed Hosting Juli 2026" },
        priceDetails: { netPrice: 89.00 },
        vatInformation: { rate: 19 }
      },
      {
        quantity: 25,
        item: { name: "Technisches Handbuch (Druckausgabe)" },
        priceDetails: { netPrice: 24.00 },
        vatInformation: { rate: 7 }
      }
    ]
  }
}

uri = URI("#{BASE_URL}/v1/create/zugferd")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{API_KEY}"
request["Content-Type"]  = "application/json"
request.body = JSON.generate(invoice)

response = api_http(uri).request(request)
raise response.body unless response.is_a?(Net::HTTPSuccess)

File.binwrite("rechnung-zugferd.pdf", response.body)

The response is a complete ZUGFeRD 2.3 document: PDF/A-3 container, embedded factur-x.xml, XMP metadata declaring the EN 16931 profile, checked against the official Schematron. If the data cannot produce a compliant invoice, the API answers HTTP 400 with the violated rules as structured findings, so an invalid document never leaves your system.


Validate ZUGFeRD invoices in Ruby

Validate before invoices go out, and validate what suppliers send in. The endpoint extracts the embedded XML, detects the declared profile from BT-24, and runs the matching XSD plus Schematron rules. Where the Java and C# guides hand-roll a multipart body builder, Ruby's Net::HTTP has multipart built in via set_form:

def validate_zugferd(path)
  uri = URI("#{BASE_URL}/v1/validate/zugferd")
  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{API_KEY}"

  File.open(path, "rb") do |file|
    request.set_form([["file", file]], "multipart/form-data")
    JSON.parse(api_http(uri).request(request).body)
  end
end

report = validate_zugferd("rechnung-zugferd.pdf")

if report["valid"]
  puts "Valid ZUGFeRD, profile #{report.dig("data", "profile")}"
else
  report["errors"].each do |finding|
    puts "[#{finding["rule"]}] #{finding["message"]}"
    puts "  fields: #{Array(finding["fields"]).join(", ")}"
  end
end

Both valid and invalid invoices answer HTTP 200; the verdict lives in valid, and non-2xx statuses mean transport problems (bad key, unreadable upload). Findings carry the rule id, a plain-language message, business term codes, and field paths, ready to surface in a UI or forward to a supplier as-is.


Create XRechnung invoices in Ruby

Same hash shape, different endpoint, two additions that XRechnung enforces and base EN 16931 does not:

buyerReference carries the Leitweg-ID. This maps to BT-10 and is checked by rule BR-DE-15. The public sector buyer assigns it during onboarding; store it per buyer and inject it into every request. It is routing infrastructure, not invoice data, so no source document will ever contain it.

Seller contact and electronic addresses are mandatory. XRechnung requires the full seller.contact group (name, phone, email) and electronic addresses for both parties.

xrechnung = {
  invoice: {
    invoiceNumber: "RE-2026-0348",
    issueDate: "2026-07-28",
    dueDate: "2026-08-27",
    currency: "EUR",
    buyerReference: "05315-00023-11",
    seller: {
      name: "Rheinwerk Digital GmbH",
      vatIdentifier: "DE298765432",
      postalAddress: {
        line1: "Hansaring 27",
        city: "Köln",
        postCode: "50670",
        country: "DE"
      },
      contact: {
        name: "Julia Brandt",
        phone: "+49 221 9876543",
        email: "[email protected]"
      },
      electronicAddress: { identifier: "DE298765432", schemeId: "0204" }
    },
    buyer: {
      name: "Stadt Köln, Amt für Gebäudewirtschaft",
      postalAddress: {
        line1: "Willy-Brandt-Platz 2",
        city: "Köln",
        postCode: "50679",
        country: "DE"
      },
      electronicAddress: { identifier: "05315-00023-11", schemeId: "0204" }
    },
    paymentDetails: {
      paymentMeansCode: 58,
      paymentAccountIdentifier: "DE44500105175407324931"
    },
    lines: [
      {
        quantity: 120,
        unitCode: "HUR",
        item: { name: "Wartung Gebäudeleittechnik" },
        priceDetails: { netPrice: 95.00 },
        vatInformation: { rate: 19, categoryCode: "S" }
      }
    ]
  }
}

uri = URI("#{BASE_URL}/v1/create/xrechnung")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{API_KEY}"
request["Content-Type"]  = "application/json"
request.body = JSON.generate(xrechnung)

response = api_http(uri).request(request)
raise response.body unless response.is_a?(Net::HTTPSuccess)

File.binwrite("rechnung-xrechnung.xml", response.body)

The response is XRechnung 3.0 in CII syntax, already checked against the EN 16931 Schematron and the KoSIT rules. XRechnung also permits a UBL binding, which matters if your stack serves the Peppol network as well; create it via POST /v1/create/ubl with options: { profile: "xrechnung" } and the same invoice hash. Both outputs are legally equivalent.


Validate XRechnung invoices in Ruby

POST /v1/validate/xrechnung auto-detects CII or UBL syntax and runs three layers: the XSD, the EN 16931 core Schematron, and the German BR-DE rules from the KoSIT Schematron, the same rule set ZRE and OZG-RE enforce on submission. It accepts XML from any source, so it doubles as an independent check on hand-built or third-party output:

def validate_xrechnung(path)
  uri = URI("#{BASE_URL}/v1/validate/xrechnung")
  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{API_KEY}"

  File.open(path, "rb") do |file|
    request.set_form([["file", file]], "multipart/form-data")
    JSON.parse(api_http(uri).request(request).body)
  end
end

report = validate_xrechnung("rechnung-xrechnung.xml")

unless report["valid"]
  report["errors"].each do |finding|
    layer = finding["layer"] # "xsd", "en16931", or "cius"
    puts "[#{finding["rule"]}] (#{layer}) #{finding["message"]}"
  end
end

The layer tag on each finding is worth surfacing to users: a cius failure means the document is a perfectly fine EN 16931 invoice that specifically misses a German requirement, and the classic example is BR-DE-15 firing on a missing Leitweg-ID. Note this endpoint accepts XML only; a ZUGFeRD hybrid PDF declaring the XRechnung reference profile goes to /v1/validate/zugferd instead, and the embedded XML is routed to the same KoSIT rules automatically.


Extract invoice data: AI parsing, XML extraction, attachments

Receiving is the half of the mandate that arrived first: since January 2025 you cannot refuse a compliant e-invoice, so your intake pipeline has to handle whatever suppliers send. In practice that means three kinds of documents, each with its own endpoint: structured e-invoices (ZUGFeRD PDFs or XRechnung XML), old-school PDFs with no structured data, and invoices carrying embedded supporting documents.

Deterministic JSON from ZUGFeRD and XRechnung

For documents that already carry structured data, POST /v1/extract/json is pure XML parsing, no AI involved: upload a ZUGFeRD or Factur-X hybrid PDF (the embedded XML is pulled out automatically) or a standalone XRechnung, CII, or UBL XML file, and get back a normalized InvoiceDocument JSON whose field names mirror the /v1/create request bodies:

def extract_invoice(path)
  uri = URI("#{BASE_URL}/v1/extract/json")
  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{API_KEY}"

  File.open(path, "rb") do |file|
    request.set_form([["file", file]], "multipart/form-data")
    JSON.parse(api_http(uri).request(request).body)
  end
end

data = extract_invoice("lieferant-zugferd.pdf")
puts "Rechnung #{data["invoiceNumber"]} von #{data.dig("seller", "name")}"

Because the parsing is deterministic, the output is an exact mapping of the source XML: what the supplier declared is what your ERP imports. For the raw embedded XML instead, /v1/extract/xml streams it out of the PDF container untouched, ready for Nokogiri.

AI parsing for old-school invoice PDFs

Not every supplier sends e-invoices yet. For typed, scanned, or photographed PDFs with no embedded XML, POST /v1/parse/json reads the document with AI and returns the same InvoiceDocument shape plus a confidence object that makes the non-determinism explicit: an overall score and four area scores (seller identification, buyer identification, tax calculation, line items), each from 0.0 to 1.0. Automate the confident reads, route the shaky ones to a human:

def parse_invoice_pdf(path)
  uri = URI("#{BASE_URL}/v1/parse/json")
  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{API_KEY}"

  File.open(path, "rb") do |file|
    request.set_form([["file", file]], "multipart/form-data")
    JSON.parse(api_http(uri).request(request).body)
  end
end

result     = parse_invoice_pdf("lieferant-scan.pdf")
invoice    = result["invoice"]
confidence = result.dig("confidence", "overall")

if confidence < 0.7
  queue_for_human_review(invoice, result["confidence"])
else
  import_into_erp(invoice)
end

The two endpoints compose into one intake method: try the deterministic route first, and fall back to AI only when the API reports error code 4006 (no embedded XML):

def read_incoming_invoice(path)
  uri = URI("#{BASE_URL}/v1/extract/json")
  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{API_KEY}"

  response = File.open(path, "rb") do |file|
    request.set_form([["file", file]], "multipart/form-data")
    api_http(uri).request(request)
  end

  if response.is_a?(Net::HTTPSuccess)
    return { "source" => "embedded-xml", "invoice" => JSON.parse(response.body) }
  end

  error = JSON.parse(response.body)
  raise response.body unless error["errorCode"] == 4006

  # No embedded XML: an old-school PDF, hand it to the AI parser
  { "source" => "ai" }.merge(parse_invoice_pdf(path))
end

Extract embedded attachments

E-invoices can carry their supporting documents inside the invoice itself: delivery notes, timesheets, or the original order, embedded base64 in the EN 16931 attachment group (BG-24). XRechnung makes this routine, because German B2G portals transmit supporting documents inside the XML rather than as separate files. POST /v1/extract/attachments collects every embedded payload and returns the original files as one ZIP:

def extract_attachments(path, zip_path)
  uri = URI("#{BASE_URL}/v1/extract/attachments")
  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{API_KEY}"

  File.open(path, "rb") do |file|
    request.set_form([["file", file]], "multipart/form-data")
    response = api_http(uri).request(request)
    return nil if response.is_a?(Net::HTTPNotFound) # no embedded attachments
    raise response.body unless response.is_a?(Net::HTTPSuccess)
    File.binwrite(zip_path, response.body)
    zip_path
  end
end

extract_attachments("rechnung-xrechnung.xml", "belege.zip")

It accepts a standalone XML e-invoice (XRechnung in either syntax, CII, or UBL) or a ZUGFeRD / Factur-X hybrid PDF, and the attachment bytes are decoded into the ZIP verbatim, never parsed or rendered server-side. An invoice without embedded attachments answers 404 rather than an empty archive, which is why the snippet treats that status as a normal outcome.


Render XRechnung previews

XRechnung has no visual layer, so before submission (or when an incoming one needs human review) render it as a readable PDF:

def render_xrechnung(xml_path, pdf_path)
  uri = URI("#{BASE_URL}/v1/render/xrechnung/to/pdf")
  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{API_KEY}"

  File.open(xml_path, "rb") do |file|
    request.set_form([["file", file]], "multipart/form-data")
    File.binwrite(pdf_path, api_http(uri).request(request).body)
  end
end

render_xrechnung("rechnung-xrechnung.xml", "rechnung-vorschau.pdf")

The preview is for human eyes only; the XML remains the legal document, and the rendered PDF should never be submitted to a portal.


Rails integration

In Rails, wrap the two formats in one service object; Faraday with faraday-multipart is the idiomatic client, and Rails credentials hold the key. The format decision from earlier collapses into a single public method:

# Gemfile
gem "faraday"
gem "faraday-multipart"

# app/services/german_invoice_service.rb
class GermanInvoiceService
  BASE_URL = "https://api.invoicexml.com"

  # Returns [bytes, extension] so the caller can attach and name the file.
  def create(invoice_payload, public_sector:)
    endpoint = public_sector ? "/v1/create/xrechnung" : "/v1/create/zugferd"
    response = connection.post(endpoint) do |req|
      req.headers["Content-Type"] = "application/json"
      req.body = JSON.generate(invoice: invoice_payload)
    end
    raise InvoiceComplianceError, response.body unless response.success?

    [response.body, public_sector ? "xml" : "pdf"]
  end

  def validate(bytes, public_sector:)
    endpoint = public_sector ? "/v1/validate/xrechnung" : "/v1/validate/zugferd"
    filename = public_sector ? "invoice.xml" : "invoice.pdf"
    part = Faraday::Multipart::FilePart.new(
      StringIO.new(bytes), "application/octet-stream", filename
    )
    JSON.parse(connection.post(endpoint, { file: part }).body)
  end

  private

  def connection
    @connection ||= Faraday.new(url: BASE_URL) do |f|
      f.request :multipart
      f.headers["Authorization"] =
        "Bearer #{Rails.application.credentials.dig(:invoicexml, :api_key)}"
    end
  end
end

class InvoiceComplianceError < StandardError; end

Run generation in a background job (ActiveJob over Sidekiq or GoodJob), attach the result with ActiveStorage, and retry only what deserves retrying: transport failures and 5xx, never HTTP 400, which carries findings about your data that will not change on resend. For public sector buyers, store the Leitweg-ID on the buyer record and treat a missing one as a data error before the job even enqueues.

The highest-leverage Rails habit is a compliance spec in CI. One test pins the entire mapping from your models to both formats:

RSpec.describe GermanInvoiceService do
  subject(:service) { described_class.new }

  it "produces valid ZUGFeRD for B2B customers" do
    bytes, _ext = service.create(build(:invoice).to_api_payload, public_sector: false)
    report = service.validate(bytes, public_sector: false)
    expect(report["valid"]).to be(true), report["errors"].inspect
  end

  it "produces valid XRechnung for public sector buyers" do
    payload = build(:invoice, :with_leitweg_id).to_api_payload
    bytes, _ext = service.create(payload, public_sector: true)
    report = service.validate(bytes, public_sector: true)
    expect(report["valid"]).to be(true), report["errors"].inspect
  end
end

Build vs REST API

In Ruby the local route is not a library choice but a construction project: Nokogiri XML assembly against 200+ rules, an AGPL-licensed PDF/A dependency or a from-scratch container, no in-process path to Schematron at all, and a KoSIT release calendar (a new XRechnung version each year, patches in between) that becomes your operations problem. Teams that go this way usually end up operating a Java validator as a sidecar anyway.

The API side of the ledger: official EN 16931 and KoSIT rule sets applied server-side and updated before their effective dates, PDF/A-3 output that survives portal and recipient checks, deterministic extraction for structured incoming invoices plus AI parsing with confidence scores for old-school PDFs, structured findings with field paths, and both German formats plus Factur-X and Peppol UBL behind one integration. Your Gemfile gains nothing, or just Faraday, and when KoSIT or FeRD ship a new version there is nothing to monitor, no gem to wait for, and no redeploy: the current rules are simply live. That is the difference between a toolkit and a complete compliance service, with deep e-invoicing expertise and professional support behind it and missing features integrated on request.

Data handling is stateless by architecture: documents are processed in memory and purged when the response ships. Nothing is stored, nothing is logged, no invoice data trains any model. VAT numbers, IBANs, and Leitweg-IDs exist server-side only for the duration of a single HTTP exchange.


Endpoint reference

OperationEndpointInputOutput
Create ZUGFeRDPOST /v1/create/zugferdJSONPDF/A-3 binary
Create XRechnung (CII)POST /v1/create/xrechnungJSONXRechnung 3.0 XML
Create XRechnung (UBL)POST /v1/create/ubl + options.profile: xrechnungJSONXRechnung UBL XML
Validate ZUGFeRDPOST /v1/validate/zugferdZUGFeRD PDFValidation JSON
Validate XRechnungPOST /v1/validate/xrechnungXRechnung XML (CII or UBL)Validation JSON
Render XRechnung as PDFPOST /v1/render/xrechnung/to/pdfXRechnung XMLPDF binary
Extract as JSONPOST /v1/extract/jsonZUGFeRD PDF or XRechnung XMLStructured JSON
Parse PDF with AIPOST /v1/parse/jsonAny invoice PDF (typed, scanned, photo)Structured JSON + confidence
Extract attachmentsPOST /v1/extract/attachmentsZUGFeRD PDF or XRechnung XMLZIP archive
Extract CII XMLPOST /v1/extract/xmlZUGFeRD PDFCII XML

Full OpenAPI 3.1 schema: api.invoicexml.com/v1/openapi  |  Interactive API explorer: api.invoicexml.com/v1/scalar


Get started

Every snippet in this guide is complete and self-contained: paste it into a file, set INVOICEXML_API_KEY, and run it with a stock Ruby 3.x, no Gemfile required. Runnable versions of these snippets, alongside Python, PHP, Java, C#, and Node.js examples, live in the Ruby folder of the examples repository.

Create a free InvoiceXML account → get 100 credits for free, no credit card required.

Related resources:


InvoiceXML is a REST API for European e-invoice compliance covering ZUGFeRD, XRechnung, Factur-X, Peppol UBL, and CII. Stateless processing, GDPR compliant by architecture, and callable from any Ruby application: Rails, Sinatra, Hanami, or a plain script.

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded