Automation MCP Server Blog Pricing
Automation

ZUGFeRD and XRechnung in SAP Business One: E-Invoicing via the Service Layer and One API Call

Many German SMEs run SAP Business One. They must send and receive e-invoices now. This guide shows how to create ZUGFeRD invoices with a small C# service, the Service Layer and one REST API. Nothing is installed in Business One. It also covers email delivery by the API, XRechnung for public buyers, and checking and importing supplier invoices as A/P drafts.

This guide adds ZUGFeRD and XRechnung to SAP Business One. You need a small service next to Business One and one REST API. The service talks to Business One through the Service Layer and to the API with HTTPS. Nothing is installed inside Business One. No add-on, no DI API, no change to the print layouts. The email section shows how the API sends the invoice to the customer. The import section shows how to check supplier invoices and turn them into A/P invoice drafts. The code is C#. The same calls work from Node.js, Python or any language with an HTTP client.


Why Business One needs ZUGFeRD now

German companies on SAP Business One must send and receive structured e-invoices. Since January 2025, every German business must accept e-invoices. From January 2027, businesses above EUR 800,000 turnover must send them. From 2028, all businesses must. The PDF from the Crystal Reports layout does not count. The law wants a hybrid file: a PDF the customer can open, with XML inside that their software can read. This file is ZUGFeRD. The Germany mandate page lists the dates and formats.

Business One partners serve many companies. One service, built once, works for every customer database: the Service Layer is the same on 9.3 and 10.0, on SAP HANA and on Microsoft SQL Server. The seller data comes from configuration, the invoice data from the Service Layer. Roll it out per customer with a login and an API key.

The integration does not change when the formats change. ZUGFeRD, XRechnung and the EN 16931 rule sets get new versions. The API applies them on their effective dates. Your service sends the same JSON before and after. Nothing to redeploy at the customer.


What you need

  • SAP Business One 9.3 or 10.0 with the Service Layer enabled (port 50000 by default). SAP HANA or Microsoft SQL Server, on-premise or cloud.
  • A Service Layer user with rights on A/R invoices, business partners, attachments and drafts.
  • Two user-defined fields: U_EInvoiceDone on marketing documents (the service marks what it has processed) and U_LeitwegID on business partners (public-sector customers only).
  • A place to run the service: a Windows service, a Linux container or a scheduled job. It needs HTTPS to the Service Layer and to api.invoicexml.com.
  • An InvoiceXML account. Create one. You get an API key and 100 free credits. No credit card is needed.

How the integration works

An A/R invoice is added in Business One. The service reads it through the Service Layer, builds a JSON body and posts it to one endpoint. The API calculates the totals and the VAT, creates the XML, embeds it in a PDF/A-3 file, validates it and returns the PDF. The service uploads the PDF to the invoice's attachments, or lets the API email it to the customer. One call, one validated file.

CustomerFormatEndpointResult
German business (B2B)ZUGFeRD EN 16931/v1/create/zugferdHybrid PDF/A-3
German business, delivered by the APIZUGFeRD EN 16931/v1/create/zugferd/emailEmail with the PDF attached
German public buyer (B2G)XRechnung/v1/create/xrechnungXML with Leitweg-ID
French businessFactur-X EN 16931/v1/create/facturxHybrid PDF/A-3

All endpoints take the same JSON body. The service has five classes. They are yours, built from the listings in this guide:

  • ServiceLayerClient: login and the OData calls (GET, POST, PATCH) with the session cookie.
  • InvoiceXmlClient: the API calls (JSON body and file upload).
  • InvoiceMapper: reads the A/R invoice, the business partner and the VAT groups and builds the JSON.
  • EInvoiceService: creates the file, attaches it, sends it by email, polls for new invoices.
  • InboundService: validates incoming invoices, sends the report back, reads the data and creates the A/P draft.

Everything else is standard Business One: the Invoices, BusinessPartners, VatGroups, Attachments2 and Drafts entities of the Service Layer.


Step 1: Connect to the Service Layer and the API

The Service Layer client logs in once and keeps the session cookie. The API client sends the key as a bearer token. Store the Service Layer password and the API key in your service configuration or a secret store, never in the code.

// ServiceLayerClient.cs: log in once, then call OData entities with the session cookie
using System.Net;
using System.Net.Http.Json;
using System.Text.Json.Nodes;

public sealed class ServiceLayerClient
{
    private readonly HttpClient _http;

    public ServiceLayerClient(string baseUrl)   // https://b1server:50000/b1s/v1/
    {
        var handler = new HttpClientHandler { UseCookies = true, CookieContainer = new CookieContainer() };
        _http = new HttpClient(handler) { BaseAddress = new Uri(baseUrl) };
    }

    public async Task LoginAsync(string companyDb, string userName, string password)
    {
        var response = await _http.PostAsJsonAsync("Login",
            new { CompanyDB = companyDb, UserName = userName, Password = password });
        response.EnsureSuccessStatusCode();   // the B1SESSION cookie is now in the container
    }

    public Task<JsonNode?> GetAsync(string path) => _http.GetFromJsonAsync<JsonNode>(path);

    public async Task<JsonNode?> PostAsync(string path, HttpContent content)
    {
        var response = await _http.PostAsync(path, content);
        if (!response.IsSuccessStatusCode)
            throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
        return await response.Content.ReadFromJsonAsync<JsonNode>();
    }

    public async Task PatchAsync(string path, object body)
    {
        var request = new HttpRequestMessage(HttpMethod.Patch, path) { Content = JsonContent.Create(body) };
        var response = await _http.SendAsync(request);
        if (!response.IsSuccessStatusCode)
            throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
    }
}

// InvoiceXmlClient.cs: one method for JSON bodies, two for file uploads
public sealed class InvoiceXmlClient
{
    private readonly HttpClient _http = new() { BaseAddress = new Uri("https://api.invoicexml.com/") };

    public InvoiceXmlClient(string apiKey)
        => _http.DefaultRequestHeaders.Authorization = new("Bearer", apiKey);

    public async Task<byte[]> PostJsonAsync(string path, object body)
    {
        var response = await _http.PostAsJsonAsync(path, body);
        if (!response.IsSuccessStatusCode)
            // 400 returns JSON with an errors array: one entry per violated rule
            throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
        return await response.Content.ReadAsByteArrayAsync();
    }

    // The upload endpoints take a multipart form with one field named "file"
    public async Task<(bool Valid, byte[] Report)> ValidateReportAsync(string path, byte[] file, string fileName)
    {
        using var form = new MultipartFormDataContent();
        form.Add(new ByteArrayContent(file), "file", fileName);
        form.Add(new StringContent("de"), "validationLanguage");   // language of the report
        var response = await _http.PostAsync(path, form);
        response.EnsureSuccessStatusCode();
        var valid = response.Headers.TryGetValues("X-Invoice-Valid", out var values)
                    && values.First() == "true";
        return (valid, await response.Content.ReadAsByteArrayAsync());
    }

    public async Task<JsonNode> ExtractAsync(byte[] file, string fileName)
    {
        using var form = new MultipartFormDataContent();
        form.Add(new ByteArrayContent(file), "file", fileName);
        var response = await _http.PostAsync("v1/extract/json", form);
        if (!response.IsSuccessStatusCode)
            throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
        return (await response.Content.ReadFromJsonAsync<JsonNode>())!;
    }
}

The validation report endpoints answer with a PDF and put the verdict in the X-Invoice-Valid header, so ValidateReportAsync returns both. The Service Layer session expires after 30 minutes of inactivity by default. Log in again when a call returns 401.


Step 2: Map Business One fields to JSON

The API takes an EN 16931 invoice as JSON. The invoice data comes from the Invoices entity and the business partner. The seller data (your customer's company) comes from the service configuration. This is the mapping:

Business One fieldJSON pathNote
Invoices.DocNuminvoice.invoiceNumberBT-1. DocEntry is the key, DocNum the printed number
Invoices.DocDate, DocDueDateinvoice.issueDate, dueDateFirst 10 characters of the OData value
Invoices.DocCurrencyinvoice.currencyISO code
Invoices.NumAtCardinvoice.purchaseOrderReferenceThe customer's order number, BT-13
Configurationinvoice.sellerName, VAT id, address, IBAN of the company
BusinessPartners.CardName, FederalTaxID, bill-to address from BPAddressesinvoice.buyerCountry as ISO code (DE, AT, FR)
DocumentLines.ItemDescriptionlines[].item.namePer line
DocumentLines.Quantity, LineTotallines[].quantity, lines[].priceDetails.netPriceLineTotal / Quantity is the net price after the line discount
DocumentLines.MeasureUnitlines[].unitCodeUN/ECE code: H87 for pieces, HUR for hours
DocumentLines.VatGrouplines[].vatInformation.rateRate from VatGroups_Lines
BusinessPartners.U_LeitwegIDinvoice.buyerReferenceXRechnung only

You do not send totals, rounding or VAT amounts. The API calculates them from the lines. If the numbers do not add up, the API returns an error instead of a file. This catches rounding and discount problems before the customer sees them.

// InvoiceMapper.cs: A/R invoice + business partner + VAT groups -> InvoiceXML JSON
public sealed class InvoiceMapper
{
    private readonly ServiceLayerClient _b1;
    private readonly SellerSettings _seller;   // name, VAT id, address, IBAN from your configuration

    public InvoiceMapper(ServiceLayerClient b1, SellerSettings seller) => (_b1, _seller) = (b1, seller);

    // Returns the "invoice" object; the callers wrap it as { invoice } or { invoice, email }
    public async Task<object> BuildInvoiceAsync(int docEntry)
    {
        var inv = (await _b1.GetAsync(
            $"Invoices({docEntry})?$select=DocNum,DocDate,DocDueDate,DocCurrency,CardCode,NumAtCard,DocumentLines"))!;
        var bp = (await _b1.GetAsync(
            $"BusinessPartners('{inv["CardCode"]}')?$select=CardName,FederalTaxID,EmailAddress,BPAddresses,U_LeitwegID"))!;

        // The bill-to address of the business partner
        var billTo = bp["BPAddresses"]!.AsArray()
            .First(a => (string?)a!["AddressType"] == "bo_BillTo");

        var lines = new List<object>();
        foreach (var line in inv["DocumentLines"]!.AsArray())
        {
            var quantity = (decimal)line!["Quantity"]!;
            if (quantity == 0) continue;   // text lines
            lines.Add(new
            {
                quantity,
                unitCode = UnitCode((string?)line["MeasureUnit"]),
                item = new { name = (string?)line["ItemDescription"] },
                // LineTotal is net and after the line discount, so the unit price is exact
                priceDetails = new { netPrice = Math.Round((decimal)line["LineTotal"]! / quantity, 4) },
                vatInformation = new { rate = await VatRateAsync((string)line["VatGroup"]!) },
            });
        }

        return new
        {
            invoiceNumber = inv["DocNum"]!.ToString(),
            issueDate = ((string)inv["DocDate"]!)[..10],     // "2026-11-05T00:00:00Z" -> "2026-11-05"
            dueDate = ((string)inv["DocDueDate"]!)[..10],
            currency = (string?)inv["DocCurrency"],
            purchaseOrderReference = (string?)inv["NumAtCard"],
            buyerReference = (string?)bp["U_LeitwegID"],      // XRechnung only, null otherwise
            seller = new
            {
                name = _seller.Name,
                vatIdentifier = _seller.VatId,
                postalAddress = new { line1 = _seller.Street, city = _seller.City, postCode = _seller.PostCode, country = _seller.Country },
            },
            buyer = new
            {
                name = (string?)bp["CardName"],
                vatIdentifier = (string?)bp["FederalTaxID"],
                postalAddress = new
                {
                    line1 = (string?)billTo["Street"],
                    city = (string?)billTo["City"],
                    postCode = (string?)billTo["ZipCode"],
                    country = (string?)billTo["Country"],
                },
            },
            paymentDetails = new { paymentAccountIdentifier = _seller.Iban },
                lines,
        };
    }

    // Business One VAT groups hold the rate in VatGroups_Lines; cache the result per code
    private readonly Dictionary<string, decimal> _rates = new();
    private async Task<decimal> VatRateAsync(string vatGroup)
    {
        if (!_rates.TryGetValue(vatGroup, out var rate))
        {
            var group = (await _b1.GetAsync($"VatGroups('{vatGroup}')?$select=VatGroups_Lines"))!;
            rate = (decimal)group["VatGroups_Lines"]!.AsArray().Last()!["Rate"]!;
            _rates[vatGroup] = rate;
        }
        return rate;
    }

    // UN/ECE Recommendation 20 codes for the units your items use
    private static string UnitCode(string? measureUnit) => measureUnit switch
    {
        "Stk" or "Stück" or "pcs" => "H87",
        "Std" or "h" => "HUR",
        "kg" => "KGM",
        "m" => "MTR",
        _ => "C62",
    };
}

Anonymous objects serialize to JSON with the property names as written, so the C# names are the JSON field names. A property with a null value is left out when you set DefaultIgnoreCondition to WhenWritingNull in the serializer options. That keeps buyerReference out of B2B invoices.


Step 3: Create the ZUGFeRD invoice

One method builds the body and posts it. The result is the PDF as bytes. The caller can attach it, send it or save it.

// EInvoiceService.cs: create, attach, email
public sealed class EInvoiceService
{
    private readonly ServiceLayerClient _b1;
    private readonly InvoiceXmlClient _api;
    private readonly InvoiceMapper _mapper;

    public EInvoiceService(ServiceLayerClient b1, InvoiceXmlClient api, InvoiceMapper mapper)
        => (_b1, _api, _mapper) = (b1, api, mapper);

    public async Task<byte[]> CreateHybridAsync(int docEntry)
    {
        var invoice = await _mapper.BuildInvoiceAsync(docEntry);
        return await _api.PostJsonAsync("v1/create/zugferd", new { invoice });
    }

For French customers, post the same body to v1/create/facturx. ZUGFeRD and Factur-X are the same standard under two names. Pick the endpoint by the business partner's country. The Factur-X API toolkit covers the French side.


Step 4: Attach the PDF to the A/R invoice

Store the PDF on the A/R invoice as an attachment. It then shows on the Attachments tab of the invoice, and your users can open and send it from there. The Service Layer takes the file as a multipart upload to Attachments2 and stores it in the attachment folder set in General Settings. Then one PATCH links it to the invoice and sets the user-defined field.

    public async Task AttachAsync(int docEntry, string docNum, byte[] pdf)
    {
        // Attachments2 takes the file as a multipart upload and stores it in the attachment folder
        using var form = new MultipartFormDataContent();
        form.Add(new ByteArrayContent(pdf), "files", $"{docNum}-zugferd.pdf");
        var attachment = (await _b1.PostAsync("Attachments2", form))!;
        var absoluteEntry = (int)attachment["AbsoluteEntry"]!;

        // Link the attachment to the A/R invoice and mark it done
        await _b1.PatchAsync($"Invoices({docEntry})",
            new { AttachmentEntry = absoluteEntry, U_EInvoiceDone = "Y" });
    }

    // Poll for posted invoices that have no e-invoice yet
    public async Task ProcessNewInvoicesAsync()
    {
        var page = (await _b1.GetAsync(
            "Invoices?$select=DocEntry,DocNum&$filter=U_EInvoiceDone eq null and Cancelled eq 'tNO'&$orderby=DocEntry"))!;
        foreach (var inv in page["value"]!.AsArray())
        {
            var docEntry = (int)inv!["DocEntry"]!;
            var pdf = await CreateHybridAsync(docEntry);
            await AttachAsync(docEntry, inv["DocNum"]!.ToString(), pdf);
        }
    }

When to run it. ProcessNewInvoicesAsync runs every minute as a scheduled job. It picks up every added A/R invoice that has no e-invoice yet. If you use the Integration Framework, trigger the same two calls from the A/R invoice add event instead. For a manual button, add a menu item with the UI API or a Crystal Reports button that calls your service.

Sending from Business One. If your users send invoices from Business One, change the email template to attach the hybrid PDF from the Attachments tab instead of the Crystal Reports PDF. The hybrid PDF already contains the readable invoice. The customer gets one PDF that opens normally and that their software can also read.


Send the invoice by email from the API

You can skip the sending in Business One. The API creates the invoice, validates it and emails it to the customer in one call. Your service never handles the file. This is enough for Germany: there is no central platform for B2B e-invoices, and an email with a ZUGFeRD PDF is a complete delivery.

Set up your sending domain once in the dashboard: add the domain and publish the three CNAME records it shows. From then on, every address on that domain can send. The customer sees your address, and replies reach you. The request is the same JSON body plus an email object. The recipient comes from the business partner's email field:

    public async Task SendByEmailAsync(int docEntry, string docNum)
    {
        var inv = (await _b1.GetAsync($"Invoices({docEntry})?$select=CardCode"))!;
        var bp = (await _b1.GetAsync($"BusinessPartners('{inv["CardCode"]}')?$select=EmailAddress"))!;
        var invoice = await _mapper.BuildInvoiceAsync(docEntry);

        await _api.PostJsonAsync("v1/create/zugferd/email", new
        {
            invoice,
            email = new
            {
                from = "[email protected]",            // an address on your verified sending domain
                to = (string)bp["EmailAddress"]!,
                subject = $"Rechnung {docNum}",
                message = $"Guten Tag,\n\nanbei erhalten Sie unsere Rechnung {docNum} als ZUGFeRD-PDF.\n\nMit freundlichen Grüßen",
            },
        });
        // 200 means: created, validated and handed to delivery
        await _b1.PatchAsync($"Invoices({docEntry})", new { U_EInvoiceDone = "Y" });
    }
}

Use this route when the API should send. Use the plain endpoint from step 3 when the file should sit on the invoice in Business One and your users send it themselves. The email endpoint also exists for XRechnung (/v1/create/xrechnung/email) and Factur-X.


Import incoming supplier invoices

Since January 2025, German companies must accept e-invoices. More and more supplier invoices arrive as ZUGFeRD PDFs and XRechnung XML files. The pipeline has three steps: validate, send the report back if the invoice is invalid, extract the data if it is valid. A mailbox watcher or a folder watcher hands the files to the service.

  1. Validate. Send the file to POST /v1/validate/zugferd/report or /v1/validate/xrechnung/report. The API checks the file against EN 16931 and the German rules. The response is a PDF validation report, and the header X-Invoice-Valid says if the invoice passed. The validationLanguage field sets the language of the report, here German. If you only want the findings as JSON, use /v1/validate/zugferd without /report.
  2. Reply with the report. If the invoice is invalid, email the PDF report to the sender and stop. The report lists every violated rule with the field and the message. The supplier can fix the invoice without a call to your accounting team.
  3. Extract. If the invoice is valid, send the file to POST /v1/extract/json. The API reads the embedded XML, or the XRechnung XML, and returns it as JSON in the same shape as the create request.
// InboundService.cs: validate, send the report back, extract, create a draft
public sealed class InboundService
{
    private readonly ServiceLayerClient _b1;
    private readonly InvoiceXmlClient _api;
    private readonly IMailer _mailer;   // your SMTP wrapper

    public InboundService(ServiceLayerClient b1, InvoiceXmlClient api, IMailer mailer)
        => (_b1, _api, _mailer) = (b1, api, mailer);

    public async Task ProcessAsync(byte[] file, string fileName, string senderEmail)
    {
        // 1. Validate. PDF files are ZUGFeRD, XML files are XRechnung.
        var path = fileName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)
            ? "v1/validate/xrechnung/report"
            : "v1/validate/zugferd/report";
        var (valid, report) = await _api.ValidateReportAsync(path, file, fileName);

        // 2. Invalid: send the PDF report to the sender and stop
        if (!valid)
        {
            await _mailer.SendAsync(senderEmail,
                subject: $"Ihre Rechnung {fileName} konnte nicht verarbeitet werden",
                text: "Die angehängte Prüfung zeigt die Fehler. Bitte senden Sie eine korrigierte Rechnung.",
                attachment: ($"Pruefbericht-{fileName}.pdf", report));
            return;
        }

        // 3. Valid: read the data and create the A/P invoice draft
        var extracted = await _api.ExtractAsync(file, fileName);
        await CreateDraftAsync(extracted["invoice"]!);
    }

Plain PDFs: some suppliers still send PDFs without XML. Send those to POST /v1/parse/json. The API reads typed, scanned and photographed invoices with AI and returns the same JSON plus a confidence object with scores between 0.0 and 1.0. Your service can create drafts for high scores and park the rest for review. Files that are not invoices are rejected with error code 4008.


Create the A/P invoice draft from the JSON

The JSON from both endpoints has the same shape, so one method creates the Business One document. It finds the vendor by the VAT id from the invoice and creates an A/P invoice draft through the Drafts entity. A draft is the right target: accounts payable sees it in the draft list, checks the account and the cost center, and adds it. Nothing is posted without a look.

    private async Task CreateDraftAsync(JsonNode invoice)
    {
        // The vendor comes from the VAT id the API read from the invoice
        var vatId = (string)invoice["seller"]!["vatIdentifier"]!;
        var vendors = (await _b1.GetAsync(
            $"BusinessPartners?$select=CardCode&$filter=CardType eq 'cSupplier' and FederalTaxID eq '{vatId}'"))!;
        var cardCode = (string?)vendors["value"]!.AsArray().FirstOrDefault()?["CardCode"]
            ?? throw new InvalidOperationException($"No vendor with VAT id {vatId}");

        var lines = invoice["lines"]!.AsArray().Select(line => new
        {
            ItemDescription = (string?)line!["item"]!["name"],
            Quantity = (decimal)line["quantity"]!,
            UnitPrice = (decimal)line["priceDetails"]!["netPrice"]!,
            VatGroup = VatGroupFor((decimal)line["vatInformation"]!["rate"]!),   // 19 -> A1, 7 -> A2, 0 -> A0
            AccountCode = "4400",   // expense account for service documents
        });

        // A draft, not a posted document: accounts payable checks it and adds it
        await _b1.PostAsync("Drafts", JsonContent.Create(new
        {
            DocObjectCode = "oPurchaseInvoices",
            DocType = "dDocument_Service",
            CardCode = cardCode,
            NumAtCard = (string?)invoice["invoiceNumber"],
            DocDate = (string?)invoice["issueDate"],
            DocDueDate = (string?)invoice["dueDate"],
            DocCurrency = (string?)invoice["currency"],
            Comments = $"Imported from e-invoice, total {invoice["totals"]!["grandTotalAmount"]}",
            DocumentLines = lines,
        }));
    }
}

VatGroupFor maps the VAT rate from the invoice to your input VAT group (19 to A1, 7 to A2, 0 to A0 in the German chart of accounts). For invoices that match a purchase order, read the open PurchaseOrders for the vendor and copy the lines with BaseType, BaseEntry and BaseLine instead of service lines. The totals from the invoice go into the comment, so the clerk can compare them with the draft.


XRechnung for German public buyers

Invoices to German federal, state and municipal buyers must be XRechnung, not ZUGFeRD. XRechnung is pure XML with no PDF. Portals such as ZRE and OZG-RE validate it against the KoSIT rules. The same service creates it. You need a third endpoint and a few more fields:

// In BuildInvoiceAsync, for public-sector customers (bp["U_LeitwegID"] is set):
seller = new
{
    name = _seller.Name,
    vatIdentifier = _seller.VatId,
    postalAddress = new { /* as above */ },
    contact = new { name = "Buchhaltung", phone = "+49 30 1234567", email = "[email protected]" },
    electronicAddress = new { identifier = _seller.VatId, schemeId = "9930" },   // 9930 = German VAT id
},
buyer = new
{
    /* as above */
    electronicAddress = new { identifier = (string?)bp["FederalTaxID"], schemeId = "9930" },
},

// Then post to v1/create/xrechnung instead of v1/create/zugferd.
// The response is XRechnung XML, not a PDF. Attach it the same way, as {docNum}-xrechnung.xml.

The Leitweg-ID is the routing id the public buyer gives you when you register as a supplier. Rule BR-DE-15 rejects invoices without it. Store it in the U_LeitwegID field on the business partner and map it into buyerReference. The seller contact (name, phone, email) and the electronic addresses are required by the German rules. Scheme 9930 means a German VAT id. For Peppol delivery, use the Peppol participant id and its scheme instead.

The API returns the XML, validated against the KoSIT rules. Deliver it the way the buyer requires: upload to ZRE or OZG-RE, Peppol, or email where the buyer accepts it. /v1/render/xrechnung/to/pdf creates a readable PDF preview for your own records. XRechnung is also valid for B2B invoices, so the /email route works for it too. The XRechnung API toolkit covers the format in detail.


DI API and Integration Framework

Integration Framework (B1if). The integration is two HTTP calls: read the invoice, post the JSON. In B1if, build a scenario with the A/R invoice add event as the inbound step, an XSLT that produces the JSON body from the document, and an HTTP outbound call to api.invoicexml.com with the bearer header. The returned PDF goes to a file outbound step or to Attachments2 through the Service Layer.

DI API. If your add-on already uses the DI API, read the Documents object (oInvoices) and the BusinessPartners object in C# or VB.NET, build the same JSON and post it with HttpClient. Attach the PDF with the Attachments2 object. The mapping table in step 2 applies one to one.

Older versions without the Service Layer. Read the invoice from the OINV and INV1 tables and the partner from OCRD and CRD1 with a database connection. The field names match the Service Layer names. Post the JSON the same way.


Endpoint reference

PurposeEndpointInputOutput
Create ZUGFeRDPOST /v1/create/zugferdInvoice JSONHybrid PDF/A-3
Create and email ZUGFeRDPOST /v1/create/zugferd/emailInvoice JSON + email objectEmail sent from your domain
Create XRechnungPOST /v1/create/xrechnungInvoice JSON with Leitweg-IDXRechnung XML
Create and email XRechnungPOST /v1/create/xrechnung/emailInvoice JSON + email objectEmail sent from your domain
Create Factur-XPOST /v1/create/facturxInvoice JSONHybrid PDF/A-3
Validate with PDF reportPOST /v1/validate/zugferd/report, /xrechnung/reportPDF or XML filePDF report, X-Invoice-Valid header
Validate as JSONPOST /v1/validate/zugferd, /xrechnungPDF or XML fileFindings per rule
Extract structured dataPOST /v1/extract/jsonHybrid PDF or XML fileInvoice JSON
Parse plain PDFPOST /v1/parse/jsonAny PDF or imageInvoice JSON + confidence
Extract attachmentsPOST /v1/extract/attachmentsHybrid PDF or XML fileZIP of embedded files
Render XRechnungPOST /v1/render/xrechnung/to/pdfXRechnung XMLReadable PDF

Get started

The whole integration is the five classes in this guide: connect, map, create and attach or email, check and import what comes in. Nothing installed in Business One. No format logic in your service. Nothing to redeploy when the formats change. This guide is part of the integrations hub, which also covers SAP ECC and S/4HANA, SAP BTP, Business Central, Dynamics NAV and other platforms.

Related guides:

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