- Business terms
- BT-1 Invoice number BT-2 Invoice issue date BT-3 Invoice type code BT-10 Buyer reference BT-72 Actual delivery date BT-106 Sum of Invoice line net amount BT-109 Invoice total amount without VAT BT-112 Invoice total amount with VAT
- Code samples
- Command line, Delphi
- Published
In short: Your application – whether Delphi, VB6, Access, COBOL or any other language – writes the invoice data to four CSV files and calls the command-line tool zugcmd. zugcmd reads the files, creates the e-invoice XML according to EN 16931 (XRechnung 3.0, CII syntax) and embeds it into your existing PDF invoice. The result is a hybrid ZUGFeRD invoice (PDF/A-3) that you send by e-mail as usual. No programming against the SDK is needed.
The process
- Your application creates the PDF invoice as before.
- It writes the invoice data to the CSV files (see below).
- It starts zugcmd and evaluates the exit code (0 = success).
- zugcmd writes the XML and the ZUGFeRD PDF with the embedded XML.
- Your application sends the ZUGFeRD PDF by e-mail. zugcmd does not send it.
Without an existing PDF, zugcmd can also create the PDF from a Word template (DOCX); this is described on the zugcmd page.
Which CSV files does zugcmd need?
| File | Content | Parameter | Required |
|---|---|---|---|
seller.csv | Seller: name, address, contact, VAT ID, bank details | -s | yes – hardly changes, can stay in place as a master file |
buyer.csv | Buyer / invoice recipient | -b | yes |
items.csv | Invoice lines, one row per line | -p | yes |
totals.csv | Invoice header, totals and VAT breakdown | -t | yes |
allowances.csv | Charges and allowances at document level | -z | only if needed |
deliverto.csv / deliverfrom.csv | different delivery or ship-from address | -h / -g | only if needed |
Structure of the CSV files
- First row = column names, the data below.
items.csvhas one row per line; the other files have exactly one data row. - Mapping by column name: upper/lower case and the order of the columns do not matter. Simply leave out columns you do not need.
- Amounts with a dot as the decimal separator (
59.50), dates asYYYYMMDD. - Separator: specify it with
-y(e.g.,or;). Put values that contain the separator in quotes. - Character set: specify it with
-c: the default isutf8; programs that write Windows ANSI (e.g. Delphi 5 to 2007, VB6) use-c ansi. - Several VAT rates:
totals.csvholds up to 11 VAT rates – the first in the columnsTAX…, further ones inTAX…1toTAX…10(e.g.TAXBasisAmount1).
Tested minimal example
An invoice for 10 × EUR 5.00 net at 19 %. With these four files the SDK creates an XML that the KoSIT validator (XRechnung 3.0.2) accepts without errors and without warnings (checked on 16 October 2026 with the function ImportCSV, which zugcmd also uses). The sample values are German, as in a real German invoice.
seller.csv
Name,personname,Street1,city,phone,email,vat,country,postcode,iban,bic,paymentmeans,paymentdescription,accountname
Muster GmbH,Erika Muster,Hauptstr. 1,Hamburg,+49 40 123456,rechnung@muster.example,DE123456789,DE,20095,DE02120300000000202051,BYLADEM1001,58,SEPA-Überweisung,Muster GmbH
buyer.csv
id,Name,Street1,city,email,country,postcode
K-1001,Kunde AG,Marktplatz 5,München,einkauf@kunde.example,DE,80331
items.csv
AssignedID,productname,description,quantity,unit,netprice,netbasisquantity,netbasisunit,taxpercent,taxtypecode,taxcategorycode,totalamount
ART-100,Papier A4,500 Blatt,10,C62,5.00,1,C62,19,VAT,S,50.00
totals.csv
type,currency,invoiceid,invoicedate,DeliveryDate,leitwegid,LineTotalAmount,ChargeTotalAmount,AllowanceTotalAmount,TaxBasisTotalAmount,TaxTotalAmount,GrandTotalAmount,TotalPrepaidAmount,DuePayableAmount,PaymentTerms,duedate,TAXCalculatedAmount,TAXTypeCode,TAXBasisAmount,TAXCategoryCode,TAXRateApplicablePercent
380,EUR,RE-2027-0001,20270104,20270104,K-1001,50.00,0.00,0.00,50.00,9.50,59.50,0.00,59.50,Zahlbar innerhalb 30 Tagen netto,20270203,9.50,VAT,50.00,S,19
The most important columns and their business terms
| File | Column | Meaning |
|---|---|---|
| totals.csv | type | Invoice type, BT-3 (380 invoice, 381 credit note, 875 construction partial invoice) |
| totals.csv | invoiceid / invoicedate | Invoice number BT-1 / invoice date BT-2 |
| totals.csv | currency | Currency, BT-5 |
| totals.csv | DeliveryDate | Delivery date, BT-72 |
| totals.csv | leitwegid | Buyer reference or Leitweg-ID, BT-10 |
| totals.csv | PaymentTerms / duedate | Payment terms BT-20 / due date BT-9 |
| totals.csv | LineTotalAmount | Sum of the lines, BT-106 |
| totals.csv | AllowanceTotalAmount / ChargeTotalAmount | Sum of allowances BT-107 / charges BT-108 |
| totals.csv | TaxBasisTotalAmount | Net amount, BT-109 |
| totals.csv | TaxTotalAmount | Total VAT, BT-110 |
| totals.csv | GrandTotalAmount / DuePayableAmount | Gross amount BT-112 / amount due BT-115 |
| totals.csv | TAXBasisAmount / TAXCalculatedAmount | Tax basis BT-116 / tax amount BT-117 per VAT rate |
| totals.csv | TAXCategoryCode / TAXRateApplicablePercent | VAT category BT-118 (S, Z, E, AE, K …) / VAT rate BT-119 |
| items.csv | quantity / unit | Quantity BT-129 / unit of measure BT-130 (e.g. C62 piece, H87 piece, KGM kilogram) |
| items.csv | netprice / netbasisquantity / netbasisunit | Net price BT-146 per base quantity BT-149 in unit BT-150 |
| items.csv | totalamount | Net amount of the line, BT-131 |
All further columns (order number, project, contract, allowances per line, global article number …) are described on the pages of the individual files, linked in the table above (German).
For a valid XRechnung you also need the buyer reference or Leitweg-ID (leitwegid, BT-10), a seller contact with name, phone and e-mail (personname, phone, email) and an e-mail address for both parties.
Your application calculates the totals
zugcmd takes the totals exactly as they are in totals.csv. The most common errors:
- Tax basis with allowances:
TAXBasisAmountis the net amount after the allowances and charges of this VAT rate, not the sum of the lines – see BR-S-08, BR-S-09 and BR-CO-17. - Rounded lines:
LineTotalAmountis the sum of the rounded line amounts (BR-CO-10). - Rounding only on the amount due: see BR-CO-15 and BR-CO-16.
Starting zugcmd from your own application
You do not have to start zugcmd by hand on the command line; your application calls it itself. Besides zugcmd.exe, the package contains zugcmd_wnd.exe, which runs without a console window – handy for calls from programs with a user interface. A call for one invoice (on one line):
zugcmd_wnd.exe -s "C:\ERechnung\stamm\seller.csv" -b "C:\ERechnung\RE-2027-0001\buyer.csv" -p "C:\ERechnung\RE-2027-0001\items.csv" -t "C:\ERechnung\RE-2027-0001\totals.csv" -i "C:\ERechnung\RE-2027-0001\RE-2027-0001.pdf" -x "C:\ERechnung\RE-2027-0001\RE-2027-0001.xml" -o "C:\ERechnung\RE-2027-0001\RE-2027-0001_zugferd.pdf" -y "," -c ansi -l YOUR-LICENCE-KEY
Always specify full paths (with drive and folder), or set a base directory with -d against which bare file names are resolved. A bare file name without -d is not found.
You recognise success by the exit code: 0 means success; examples of errors are 57 "input PDF not found" or 59 "XML could not be created". The complete list is on the zugcmd page. Functions such as ShellExecute (Delphi) or Shell (VB6) do not return the exit code; use CreateProcess with GetExitCodeProcess. Example for Delphi (version 5 and later):
uses Windows, SysUtils;
function StarteZugcmd(const Parameter: string): DWORD;
var
SI: TStartupInfo;
PI: TProcessInformation;
Cmd: string;
begin
Result := DWORD(-1);
Cmd := '"C:\zugcmd\zugcmd_wnd.exe" ' + Parameter;
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
if CreateProcess(nil, PChar(Cmd), nil, nil, False, 0, nil, nil, SI, PI) then
begin
WaitForSingleObject(PI.hProcess, INFINITE); // wait until zugcmd has finished
GetExitCodeProcess(PI.hProcess, Result); // 0 = success
CloseHandle(PI.hThread);
CloseHandle(PI.hProcess);
end;
end;
File names and volumes
- File names are set by your application with
-x(XML) and-o(PDF), ideally using the invoice number. Without them, the files are calledOutputXML.xmlandOutputPDF.pdf; there is no automatic numbering. - Many invoices (e.g. a few hundred per day) are no problem: one call per invoice. Put the CSV files of each invoice in their own folder or give them unique names so that parallel calls do not overwrite each other.
- Existing files are overwritten by zugcmd by default; with
-f falsethey are kept.
Tip: look at the mapping on a real invoice
zugcmd also works in reverse: -r invoice.xml (or a ZUGFeRD PDF) splits an existing e-invoice into exactly these CSV files. That way you can see on a real example which field belongs in which column. The download package zugcmd.zip (downloads) also contains sample CSV files with the BT numbers written directly into the values, two complete sample sets (credit transfer, direct debit) and the readme.txt with all parameters.
If you prefer to program directly, use the SDK via COM: with Delphi or with VB6, VB5 and VBA.
Frequently asked questions
Do I need SDK programming skills to use zugcmd?
No. Your application writes CSV files and starts zugcmd with parameters. The only programming needed is writing the CSV files and the call.
Is there a description of the CSV columns?
Yes. Each file has its own page with all columns and business terms: seller.csv, buyer.csv, items.csv, totals.csv and allowances.csv. The columns are mapped by their names; the order is arbitrary.
Does zugcmd have to be started on the command line?
No. Any application can start zugcmd as a process. zugcmd_wnd.exe runs without a console window; exit code 0 means success.
How are the generated files named?
As your application specifies them with -x (XML) and -o (PDF), for example after the invoice number. Without them, they are called OutputXML.xml and OutputPDF.pdf.
Can zugcmd handle 200 invoices a day?
Yes. One call per invoice is enough. It is only important that each invoice has its own CSV and output files.
Does zugcmd also send the invoice by e-mail?
No. zugcmd creates the XML and the ZUGFeRD PDF; sending is done by your application or your e-mail program.
Business terms in this article: BT-1 BT-2 BT-3 BT-10 BT-72 BT-106 BT-109 BT-112 BT-115 BT-116 BT-117 BT-5 BT-9 BT-20 BT-107 BT-108 BT-110 BT-118 BT-119 BT-129 BT-130 BT-131 BT-146