Table of Contents

Kapitel 1. In case of technical questions

Please contact

Jochen Stärk

or

Martin Wachtveitl

The status (e.g. upcoming maintenances) of the server is available on it’s status page.

Kapitel 2. Server access

User access to the web interface is possible via https://api.usegroup.de/. Terms of service are listed in the chapter Terms of service.

To register please access the Mustangserver signup page

select a desired username, enter your email and click the "send" button on the bottom left.

You will receive a mail with a activation link you have to access to activate your free 4 week evaluation period. If you do not react on reminder emails your accont will expire after your evaluation.

Afterwards you can login on https://api.usegroup.de/devportal/ .

You will need it your username in order to log in and in case you want to reset your password, both are not possible using your email address. In case you forgot your username feel free to inquire at info@mustangproject.org using the email address you are requesting the username for. This username does not have anything to do with optional username parameters mentioned below.

After being logged in you need to register your interest, i.e. „subscribe“ to the APIs you require.

If you select the desired Mustangserver version and click on the blue "try out" button (not the link in the navigation) on the next page you should be able to click a "get test key" button.

A screenshot

images/Pictures/screenshot_apis.png

e.g. when you open the "ping" operation and click "try it out" and "execute" you should get a "pong" response.

You can always change your password on https://api.usegroup.de/devportal/settings/change-password/

There is a optional „username“ field for all operations: Please ignore it. It serves as a placeholder where the API management transmits your username, if it were set by any application the value would be overwritten anyway.

Kapitel 3. Authentication

3.1. OAuth2

First of all you will have to subscribe to the API. You can manage your subscriptions in the left navigation of the devportal but it’s often easiest to subscribe via overview|try out, which also allows you to try it.

Then you will have to enable client credentials in the applications tab, https://api.usegroup.de:9443/devportal/applications Default Application, Oauth2 tokens. The procedure is described more detailled in the PHP tutorial but is generic to all examples and does not apply for PHP only.

Please note that most clients will use the Mustangserver version which had been selected in the backend when downloading the OpenAPI definition. Feel free to replace /mustang/<version>/mustang by as described in Endpoint for most-recent API versions.

3.2. Api Key

In the applications tab, select Default Application, Oauth2 tokens, Production Keys, API Key. Select according restrictions if required, click Generate Key, select lifetime and click generate. Copy&safely store the generated key.

screenshot_api_key_generate.png API key can be used e.g. within Bruno.

screenshot_bruno_ui_ping.png

Within PHP you also might pass just as additional header attribute making your source code look like

<?php
require_once(__DIR__ . '/vendor/autoload.php');
$apikey="<your key>";
$config = Swagger\Client\Configuration::getDefaultConfiguration();
$gc=new GuzzleHttp\Client(['headers' => ['apikey' => $apikey]]);
$apiInstance = new Swagger\Client\Api\MustangControllerApi($gc,$config);
try {
$result = $apiInstance->ping();
print_r($result);
} catch (Exception $e) {
http_response_code(500);
echo 'Exception when calling ErrorControllerApi->handle: ',
$e->getMessage(), PHP_EOL;
}

Kapitel 4. Endpoint for most-recent API versions

If you leave out the version number in the request to the gateway you will always be using the latest recommended (usually the latest) version. In that case e.g. your „ping“ endpoint changes from https://gw.usegroup.de:8243/mustang/1.0.0/mustang/ping to https://gw.usegroup.de:8243/mustang/mustang/ping .

New versions may be retired as soon as six month after release of the successor. It is possible to always use the latest (more precisely: recommended) version by removing the version from the endpoint. This is an example of the validate endpoint: screenshot_vernum.png

Please note that a API key encodes the subscriptions at the time of the creation of the key, and that a newer version will require a new subscription (to that version), i.e. you should not use the most recent version in conjuntion with API keys because you will require new API keys for new versions.

Kapitel 5. Errors and Exceptions

In case of an exception Mustangserver will return a http status code of 400 and the message of the Exception will be returned in the „message“ field of the according JSON.

{
  "requestUrl": "http://127.0.0.1:8000/mustang/combineXML",
  "httpCode": 400,
  "errorCode": "MSE1000:Unbekannte Fehler während der Request
Ausführung!",
  "message": "File is not a valid PDF/A-1 input file"

}

Kapitel 6. Mediation, Transformation and Orchestration

The http://api.usegroup.de/ uses WSO² as API management which in turn uses Apache Synapse (https://synapse.apache.org/) for mediation/transformation/orchestration. This means that mediation and orchestration can be developed e.g. in WSO²‘s Integration Studio (https://wso2.com/integration/integration-studio/) and uploaded as XML file. Apart from acting as a load balancer and central authentication this allow to

  • override certain states in the process, e.g. implement a timeout after a certain number of seconds

  • invoke a chain of operations in only one virtual endpoint, e.g. conversion from plain PDF, parallely converting invoice data to XML, merging PDF/A and XML and validation thereof and/or

  • map any custom specific input- or output parameter to the values used by Mustangserver internally

Kapitel 7. Document types

7.1. Invoice

The invoice has a „documentCode“ attribute of 380.

7.2. Cancellation

A cancellation has a „documentCode“ attribute of 381.

7.3. Corrected Invoice

A corrected invoice has a „documentCode“ attribute of 384. Please note that amounts are usually negative, so this is a minimal example for a corrected invoice:

{
"documentCode": "384",
"number": "471102",
"currency": "EUR",
"issueDate": "2018-03-04T00:00:00.000+01:00",
"dueDate": "2018-03-04T00:00:00.000+01:00",
"deliveryDate": "2018-03-04T00:00:00.000+01:00",
"sender": {
"name": "Lieferant GmbH",
"zip": "80333",
"street": "Lieferantenstraße 20",
"location": "München",
"country": "DE",
"taxID": "201/113/40209",
"vatID": "DE123456789",
"globalID": "4000001123452",
"globalIDScheme": "0088"
},
"recipient": {
"name": "Kunden AG Mitte",
"zip": "69876",
"street": "Kundenstraße 15",
"location": "Frankfurt",
"country": "DE"
},
"zfitems": [
{
"price": 9.9,
"quantity": -20,
"product": {
"unit": "H87",
"name": "Trennblätter A4",
"description": "",
"vatpercent": 19,
"taxCategoryCode": "S"
}
},
{
"price": 5.5,
"quantity": -50,
"product": {
"unit": "H87",
"name": "Joghurt Banane",
"description": "",
"vatpercent": 7,
"taxCategoryCode": "S"
}
}
]
}

7.4. Credit Note

A credit note has a „documentCode“ attribute of 389.

Kapitel 8. Classes

Mustangserver has two important main classes, invoice and calculatedInvoice. Invoice contains tradeparty classes for recipients and senders and item classes which in turn contain instances of product classes.

The difference between invoice and calculatedInvoice is that the latter contains (redundant, because calculatable) properties like grandTotal. These attributes are provide for courtesy when reading invoices but are not required when wrinting. However, if they are present when writing (e.g. combine or invoice2XML) they will raise an error if the invoice calculation does not match. This can be useful when a PDF file has been generated with another process and the Factur-X-XML is supposed to have the same values, in which case the PDF values can be provided because an error would be more helpful than a silently incorrect Factur-X file because the PDF has been calculated incorrectly, or uses features not yet available in Mustangserver.

8.1. Invoice class

The invoice class is used for all document types.

8.1.1. Invoice

Minimal example
{
"number": "471102",
"currency": "EUR",
"issueDate": "2018-03-04T00:00:00.000+01:00",
"dueDate": "2018-03-04T00:00:00.000+01:00",
"deliveryDate": "2018-03-04T00:00:00.000+01:00",
"sender": {
"name": "Lieferant GmbH",
"zip": "80333",
"street": "Lieferantenstraße 20",
"location": "München",
"country": "DE",
"taxID": "201/113/40209",
"vatID": "DE123456789",
"globalID": "4000001123452",
"globalIDScheme": "0088"
},
"recipient": {
"name": "Kunden AG Mitte",
"zip": "69876",
"street": "Kundenstraße 15",
"location": "Frankfurt",
"country": "DE"
},
"zfitems": [
{
"price": 9.9,
"quantity": 20,
"product": {
"unit": "H87",
"name": "Trennblätter A4",
"description": "",
"vatpercent": 19,
"taxCategoryCode": "S"
}
},
{
"price": 5.5,
"quantity": 50,
"product": {
"unit": "H87",
"name": "Joghurt Banane",
"description": "",
"vatpercent": 7,
"taxCategoryCode": "S"
}
}
]
}
Sample JSON structure to write invoices

screenshot_jsoncrack_min_invoice

Comprehensive example
{
  "documentCode": "380",
  "number": "Test_EeISI_100",
  "referenceNumber": "123",
  "buyerOrderReferencedDocumentID": "abc",
  "currency": "EUR",
  "paymentTermDescription": "total amount",
  "issueDate": "2018-11-11T23:00:00.000+00:00",
  "dueDate": "2018-11-29T23:00:00.000+00:00",
  "deliveryDate": "2018-12-03T23:00:00.000+00:00",
  "sender": {
    "name": "Seller name",
    "zip": "12345",
    "street": "Seller address line 1",
    "location": "Seller city",
    "country": "DE",
    "taxID": "DE49294093",
    "vatID": "DE12345677",
    "description": "Seller additional legal information",
    "additionalAddress": "Seller address line 2",
    "additionalAddressExtension": "Seller address line 3",
    "debitDetails": [
      {
        "mandate": "Mandate reference identifier",
        "paymentMeansCode": "4",
        "paymentMeansInformation": "SEPA",
        "iban": "IT1212341234123412"
      }
    ],
    "contact": {
      "name": "Seller contact point",
      "phone": "+41 345 654455",
      "email": "seller@contact.de"
    },
    "vatid": "DE12345677",
    "legalOrganisation": {
      "tradingBusinessName": "Seller trading name"
    },
    "globalID": "Seller identifier 2",
    "globalIDScheme": "0110",
    "email": "Seller electronic address"
  },
  "recipient": {
    "name": "Buyer name",
    "zip": "34562",
    "street": "Buyer address line 1",
    "location": "Buyer city",
    "country": "IE",
    "vatID": "IE394838894",
    "additionalAddress": "Buyer address line 2",
    "additionalAddressExtension": "Buyer address line 3",
    "bankDetails": [
      {
        "accountName": "Payment account name",
        "paymentMeansCode": "58",
        "paymentMeansInformation": "SEPA credit transfer",
        "iban": "IT1212341234123412"
      }
    ],
    "contact": {
      "name": "Buyer contact point",
      "phone": "+353 2948584",
      "email": "buyer@contact.ie"
    },
    "vatid": "IE394838894",
    "legalOrganisation": {
      "tradingBusinessName": "Buyer trading name"
    },
    "globalID": "Buyer identifier",
    "globalIDScheme": "0190",
    "email": "Buyer electronic address"
  },
  "deliveryAddress": {
    "name": "Deliver to party name",
    "zip": "98765",
    "street": "Deliver to address line 1",
    "location": "Deliver to city",
    "country": "IE",
    "additionalAddress": "Deliver to address line 2",
    "additionalAddressExtension": "Deliver to address line 3",
    "globalID": "deliver location identifier",
    "globalIDScheme": "0045"
  },
  "payee": {
    "name": "Payee name",
    "legalOrganisation": {},
    "globalID": "Payee identifier",
    "globalIDScheme": "0098"
  },
  "sellerOrderReferencedDocumentID": "def",
  "totalPrepaidAmount": 0,
  "invoiceReferencedDocumentID": "abc123",
  "despatchAdviceReferencedDocumentID": "lmn",
  "creditorReferenceID": "Bank assigned creditor identifier",
  "roundingAmount": 0,
  "paymentReference": "Remittance information",
  "lineTotalAmount": 200,
  "duePayable": 205,
  "grandTotal": 205,
  "taxBasis": 200,
  "valid": true,
  "additionalReferencedDocuments": [
    {
      "filename": "filename0",
      "mimetype": "application/pdf",
      "relation": "Data",
      "description": "Additional file attachment",
      "data": "ZGVmYXVsdA=="
    }
  ],
  "ownTaxID": "DE49294093",
  "ownVATID": "DE12345677",
  "notesWithSubjectCode": [
    {
      "content": "invoice note text"
    },
    {
      "content": "invoice note text 2"
    }
  ],
  "detailedDeliveryPeriodFrom": "2018-11-11T23:00:00.000+00:00",
  "detailedDeliveryPeriodTo": "2018-11-29T23:00:00.000+00:00",
  "zfallowances": [
    {
      "totalAmount": 10,
      "basisAmount": 100,
      "taxPercent": 5,
      "reason": "Doc allowance reason text",
      "reasonCode": "95",
      "categoryCode": "S"
    }
  ],
  "zfcharges": [
    {
      "totalAmount": 10,
      "basisAmount": 100,
      "taxPercent": 5,
      "reason": "Doc charge reason text",
      "reasonCode": "AAA",
      "categoryCode": "S"
    }
  ],
  "zfitems": [
    {
      "price": 10,
      "quantity": 10,
      "basisQuantity": 1,
      "detailedDeliveryPeriodFrom": "2018-11-11T23:00:00.000+00:00",
      "detailedDeliveryPeriodTo": "2018-11-29T23:00:00.000+00:00",
      "id": "1a",
      "buyerOrderReferencedDocumentLineID": "12345",
      "product": {
        "unit": "EA",
        "name": "Item name",
        "sellerAssignedID": "Item seller's identifier",
        "buyerAssignedID": "Item buyer's identifier",
        "description": "Item description",
        "taxCategoryCode": "S",
        "attributes": {
          "Size": "L",
          "Color": "Red"
        },
        "classifications": [
          {
            "classCode": {
              "listID": "ZZZ",
              "code": "Item classification identifier0",
              "listVersionID": "version0"
            }
          }
        ],
        "vatpercent": 5,
        "globalID": "Item standar identifier\n        ",
        "globalIDScheme": "0060",
        "intraCommunitySupply": false,
        "reverseCharge": false
      },
      "accountingReference": "6789",
      "itemAllowances": [
        {
          "percent": 1,
          "totalAmount": 1,
          "basisAmount": 100,
          "taxPercent": 0,
          "reason": "Invoice line allowance reason",
          "categoryCode": "S"
        }
      ],
      "itemCharges": [
        {
          "percent": 1,
          "totalAmount": 1,
          "basisAmount": 100,
          "taxPercent": 0,
          "reason": "Invoice line charge reason",
          "categoryCode": "S"
        }
      ],
      "additionalReferences": [
        {
          "issuerAssignedID": "Line object identifier",
          "typeCode": "130"
        }
      ],
      "notesWithSubjectCode": [
        {
          "content": "Invoice line note"
        }
      ],
      "value": 10
    },
    {
      "price": 10,
      "quantity": 10,
      "basisQuantity": 1,
      "id": "1b",
      "product": {
        "unit": "EA",
        "name": "Item name 2",
        "taxCategoryCode": "Z",
        "vatpercent": 0,
        "intraCommunitySupply": false,
        "reverseCharge": false
      },
      "value": 10
    }
  ],
  "tradeSettlement": [
    {
      "mandate": "Mandate reference identifier",
      "paymentMeansCode": "4",
      "paymentMeansInformation": "SEPA",
      "iban": "IT1212341234123412"
    }
  ],
  "ownStreet": "Seller address line 1",
  "ownZIP": "12345",
  "ownLocation": "Seller city",
  "ownCountry": "DE"
}
JSONCrack visualization of the JSON of a comprehensive invoice

JSONCrack visualization of the JSON of a comprehensive invoice

8.1.2. BT-IDs

The following BTs are mapped as follows:

JSONPath BT ID

recipient

recipient.name

BT-44

recipient.id

BT-46

recipient.vatid ( also memtioned as recipient.vatID)

BT-48

recipient.street

BT-50

recipient.additionalAddress

BT-51

recipient.additionalAddressExtension

BT-163

recipient.location

BT-52

recipient.zip

BT-53

recipient.country

BT-55

recipient.globalID

ID BT-46

recipient.globalIDScheme

ID Scheme BT-46

recipient.contact.name

BT-56

recipient.contact.phone

BT-57

recipient.contact.email

BT-58

recipient.legalOrganisation.tradingBusinessName

BT-45

sender

sender.name

BT-27

sender.legalOrganisation.tradingBusinessName

BT-28

sender.globalID

BT-29

sender.globalIDScheme

BT-29-ID

sender.vatid ( also mentioned as sender.vatID)

BT-31

sender.taxID

BT-32

sender.street

BT-35

sender.additionalAddress

BT-36

sender.additionalAddressExtension

BT-162

sender.zip

BT-38

sender.location

BT-37

sender.country

BT-40

sender.contact.name

BT-41

sender.contact.phone

BT-42

sender.contact.email

BT-43

Invoice

number

BT-1

documentCode

BT-3

currency

BT-5

buyerOrderReferencedDocumentID

BT-13

sellerOrderReferencedDocumentID

BT-14

despatchAdviceReferencedDocumentID

BT-16

referenceNumber

BT-10

Payment Details / Invoice Comment

issueDate

BT-9

tradeSettlement.iban

BT-84

Price details – items

zfitems.quantity

BT-129

zfitems.product.unit

BT-130

zfitems.price

BT-146

zfitems.product.taxCategoryCode

BT-151

zfitems.product.vatpercent

BT-152

zfitems.buyerOrderReferencedDocumentLineID

BT-132

zfitems.product.name

BT-153

zfitems.product.sellerAssignedID

BT-155

zfitems.product.globalID

BT-157

zfitems.product.globalIDScheme

BT-157-ID

zfitems.additionalReferences.issuerAssignedID

BT-128

8.1.3. Included Notes

Remarks on document level are passed in the Array notesWithSubjectCode, subjectCode is optional.

"notesWithSubjectCode": [

 [{
"content": "MUSTER-Autovermietung GMBH\nMusterstr. 99\n99199
MUSTERHAUSEN\nGeschäftsführung:\nMaxima Musterfrau\nUSt-IdNr:
DE136695976\nTelefon: +49 711-50885524\nwww.musterlieferant.de\nHRB Nr.
372876\nAmtsgericht Musterstadt\nGLN 4304171000002",
"subjectCode": "REG"
},

{
"content": "Bei Rückfragen:\nTelefon: +49 711-50885524\nE-Mail :
info@muster-autovermietung.de"
}]

The meaning of the Subject codes is as follows

AAI

General information

SUR

Seller notes

REG

Regulatory information

ABL

Legal information

TXD

Tax information

CUS

Customs information

ACY

Introduction

AAK

Discount and bonus agreements

ABZ

Vehicle license number

8.1.4. File attachments

File attachments are Base64-encoded in the attribute additionalReferencedDocuments. Example:

{
"additionalReferencedDocuments": [
{
"data": "b25ldHdvdGhyZWU=",
"description": "Additional file attachment",
"filename": "text.txt",
"mimetype": "text/plain",
"relation": "Data"
}
],
"number": "471102",
"currency": "EUR",
"issueDate": "2018-03-04T00:00:00.000+01:00",
"dueDate": "2018-03-04T00:00:00.000+01:00",
"deliveryDate": "2018-03-04T00:00:00.000+01:00",
"sender": {
"name": "Lieferant GmbH",
"zip": "80333",
"street": "Lieferantenstraße 20",
"location": "München",
"country": "DE",
"taxID": "201/113/40209",
"vatID": "DE123456789",
"globalID": "4000001123452",
"globalIDScheme": "0088"
},
"recipient": {
"name": "Kunden AG Mitte",
"zip": "69876",
"street": "Kundenstraße 15",
"location": "Frankfurt",
"country": "DE"
},
"zfitems": [
{
"price": 9.9,
"quantity": 20,
"product": {
"unit": "H87",
"name": "Trennblätter A4",
"description": "",
"vatpercent": 19,
"taxCategoryCode": "S"
}
},
{
"price": 5.5,
"quantity": 50,
"product": {
"unit": "H87",
"name": "Joghurt Banane",
"description": "",
"vatpercent": 7,
"taxCategoryCode": "S"
}
}
]
}

8.1.5. Cancellations, Corrected Invoices, Credit Memos

For cancellations use documentCode 381, for credit memos use 389. Corrected invoices have documentCode 384 and the quantity of the items to be corrected, which should no longer be billed to the customer, should be negative, resulting in a negative grand total.

8.1.6. Small businesses

Example for small business

{
"number": "471102",
"currency": "EUR",
"issueDate": "2018-03-04T00:00:00.000+01:00",
"dueDate": "2018-03-04T00:00:00.000+01:00",
"deliveryDate": "2018-03-04T00:00:00.000+01:00",
"sender": {
"name": "Lieferant GmbH",
"zip": "80333",
"street": "Lieferantenstraße 20",
"description": "Kleinunternehmer nach §119 UStG",
"location": "München",
"country": "DE",
"taxID": "201/113/40209",
"vatID": "DE123456789",
"globalID": "4000001123452",
"globalIDScheme": "0088"
},
"recipient": {
"name": "Kunden AG Mitte",
"zip": "69876",
"street": "Kundenstraße 15",
"location": "Frankfurt",
"country": "DE"
},
"zfitems": [
{
"price": 9.9,
"quantity": 20,
"product": {
"unit": "H87",
"name": "Trennblätter A4",
"description": "",
"vatpercent": 0,
"taxExemptionReason": "Small business, §119 UStG",
"taxCategoryCode": "E"
}
}
]
}

8.1.7. Example for intra community supply

{
"number": "471102",
"currency": "EUR",
"issueDate": "2018-03-04T00:00:00.000+01:00",
"dueDate": "2018-03-04T00:00:00.000+01:00",
"deliveryDate": "2018-03-04T00:00:00.000+01:00",
"sender": {
"name": "Lieferant GmbH",
"zip": "80333",
"street": "Lieferantenstraße 20",
"location": "München",
"country": "DE",
"taxID": "201/113/40209",
"vatID": "DE123456789",
"globalID": "4000001123452",
"globalIDScheme": "0088"
},
"recipient": {
"name": "Kunden AG Mitte",
"zip": "69876",
"street": "Kundenstraße 15",
"location": "Frankfurt",
"country": "DE"
},
"zfitems": [
{
"price": 9.9,
"quantity": 20,
"product": {
"unit": "H87",
"name": "Trennblätter A4",
"description": "",
"vatpercent": 0,
"taxExemptionReason": "intra-community supply",
"taxCategoryCode": "K"
}
}
]
}

8.1.8. Cash discounts

For cash discounts add an array (because there can be multiple rates, at multiple intervals) with days and percent to the invoice:

"cashDiscounts": [
{
"days": 14,
"percent": 2
}
]

8.1.9. Leitweg-ID

The german B2G ID can be set in the invoice root level as

"referenceNumber": "04011000-12345-34"

8.1.10. Item allowances/charges

In the item in zfitems there may be arrays itemAllowances or itemCharges. E.g. 10 cent allowance would be

{
"basisQuantity": 1,
"itemAllowances": [
{
"categoryCode": "S",
"totalAmount": 0.1
}
],
"price": 3.0,
...
},

and 50% charges are

{
"basisQuantity": 1,
"itemCharges": [
{
"categoryCode": "S",
"percent": 50,
"taxPercent": 0
}
]

Please note that this will only be interpreted when writing since XML parsing already adjusts the net price to already contain all allowances/charges.

8.2. CalculatedInvoice

{ "documentName": null, "documentCode": "380", "number":
"RE-20201121/508", "ownOrganisationFullPlaintextInfo": null,
"referenceNumber": "AB321", "shipToOrganisationID": null,
"shipToOrganisationName": null, "shipToStreet": null, "shipToZIP": null,
"shipToLocation": null, "shipToCountry": null,
"buyerOrderReferencedDocumentID": null, "invoiceReferencedDocumentID":
null, "buyerOrderReferencedDocumentIssueDateTime": null,
"ownForeignOrganisationID": null, "ownOrganisationName": "Bei Spiel
GmbH", "currency": "EUR", "paymentTermDescription": null, "issueDate":
"2020-11-20T23:00:00.000+00:00", "dueDate":
"2020-12-11T23:00:00.000+00:00", "deliveryDate":
"2020-11-09T23:00:00.000+00:00", "sender": { "name": "Bei Spiel GmbH",
"zip": "12345", "street": "Ecke 12", "location": "Stadthausen",
"country": "DE", "taxID": null, "vatID": "DE136695976",
"additionalAddress": null, "additionalAddressExtension": null,
"bankDetails": [ { "accountName": null, "bic": null, "iban":
"DE88200800000970375700" } ], "contact": null, "legalOrganisation":
null, "uriUniversalCommunicationID": null,
"uriUniversalCommunicationIDScheme": null, "globalID": null,
"globalIDScheme": null, "vatid": "DE136695976", "id": null, "email":
null, "asTradeSettlement": [ { "accountName": null, "bic": null,
"iban": "DE88200800000970375700" } ] }, "recipient": { "name": "Theodor
Est", "zip": "88802", "street": "Bahnstr. 42", "location": "Spielkreis",
"country": "DE", "taxID": null, "vatID": null, "additionalAddress":
null, "additionalAddressExtension": null, "bankDetails": [], "contact":
null, "legalOrganisation": null, "uriUniversalCommunicationID": null,
"uriUniversalCommunicationIDScheme": null, "globalID": null,
"globalIDScheme": null, "vatid": null, "id": "2", "email": null,
"asTradeSettlement": null }, "deliveryAddress": null, "cashDiscounts":
[], "notes": null, "sellerOrderReferencedDocumentID": null,
"contractReferencedDocument": null, "totalPrepaidAmount": null,
"paymentTerms": null, "invoiceReferencedIssueDate": null,
"specifiedProcuringProjectID": null, "specifiedProcuringProjectName":
null, "despatchAdviceReferencedDocumentID": null, "creditorReferenceID":
null, "grandTotal": 571.04, "valid": false, "ownStreet": "Ecke 12",
"ownZIP": "12345", "zfallowances": null, "zfitems": [ { "price": 160,
"quantity": 1, "tax": null, "grossPrice": null, "lineTotalAmount": null,
"basisQuantity": 1, "detailedDeliveryPeriodFrom": null,
"detailedDeliveryPeriodTo": null, "id": null, "product": { "unit":
"HUR", "name": "Design (hours)", "sellerAssignedID": null,
"buyerAssignedID": null, "description": "", "countryOfOrigin": null,
"attributes": null, "intraCommunitySupply": false, "vatpercent": 7,
"globalID": null, "globalIDScheme": null, "reverseCharge": false,
"taxCategoryCode": "S", "taxExemptionReason": null }, "notes": null,
"referencedDocuments": null, "additionalReferences": null,
"buyerOrderReferencedDocumentLineID": null, "itemAllowances": null,
"itemCharges": null, "itemTotalAllowances": null,
"additionalReferencedDocumentID": null, "value": 160 }, { "price":
0.79, "quantity": 400, "tax": null, "grossPrice": null,
"lineTotalAmount": null, "basisQuantity": 1,
"detailedDeliveryPeriodFrom": null, "detailedDeliveryPeriodTo": null,
"id": null, "product": { "unit": "H87", "name": "Ballons",
"sellerAssignedID": null, "buyerAssignedID": null, "description": "",
"countryOfOrigin": null, "attributes": null, "intraCommunitySupply":
false, "vatpercent": 19, "globalID": null, "globalIDScheme": null,
"reverseCharge": false, "taxCategoryCode": "S", "taxExemptionReason":
null }, "notes": null, "referencedDocuments": null,
"additionalReferences": null, "buyerOrderReferencedDocumentLineID":
null, "itemAllowances": null, "itemCharges": null,
"itemTotalAllowances": null, "additionalReferencedDocumentID": null,
"value": 0.79 }, { "price": 0.025, "quantity": 800, "tax": null,
"grossPrice": null, "lineTotalAmount": null, "basisQuantity": 1,
"detailedDeliveryPeriodFrom": null, "detailedDeliveryPeriodTo": null,
"id": null, "product": { "unit": "LTR", "name": "Hot air „heiße Luft“
(litres)", "sellerAssignedID": null, "buyerAssignedID": null,
"description": "", "countryOfOrigin": null, "attributes": null,
"intraCommunitySupply": false, "vatpercent": 19, "globalID": null,
"globalIDScheme": null, "reverseCharge": false, "taxCategoryCode": "S",
"taxExemptionReason": null }, "notes": null, "referencedDocuments":
null, "additionalReferences": null,
"buyerOrderReferencedDocumentLineID": null, "itemAllowances": null,
"itemCharges": null, "itemTotalAllowances": null,
"additionalReferencedDocumentID": null, "value": 0.025 } ], "ownTaxID":
null, "tradeSettlement": [ { "accountName": null, "bic": null, "iban":
"DE88200800000970375700" } ], "ownVATID": "DE136695976", "ownLocation":
"Stadthausen", "ownCountry": "DE", "zfcharges": null,
"detailedDeliveryPeriodTo": null, "notesWithSubjectCode": null,
"detailedDeliveryPeriodFrom": null, "vatdueDateTypeCode": null,
"zflogisticsServiceCharges": null, "additionalReferencedDocuments":
null, "subjectNote": null, "tradeSettlementPayment": null }

screenshot_jsoncrack_invoice

Kapitel 9. Operations/Endpoints

9.1. /mustang

Mustangserver’s available operations are

9.1.1. Ping

Just a test, always just returns „pong“. The only operation acessible via HTTP GET, the rest is POST.

9.1.2. Validate

Validate a Factur-X/ZUGFeRD, XRechnung CII or UBL or Order-X/-CIO File using Mustang’s validator. Requires a file and returns a Mustang XML report. The format to be validated against will be read from it’s guideline ID.

(optional parameter: ignoreNotices, boolean, default false)

Warnings and errors will always be contained in the XML result, if ignoreNotices is set to true no notices will be present. Currently notices mention additional validation results, i.e. if you pass a EN16931 Factur-X file there may be notices which additional elements may be required to also get a valid XRechnung.

9.1.3. Phive

Validate a international and/or Peppol e-invoice file using Phive. Requires a file and returns a Phive JSON report. Specification of a format/version combination (VES ID) to validate against is optional but the list of the 310 available format/standard/version-combinations are listed online.

9.1.4. parse

 Read a Factur-X/ZUGFeRD/XRechnung and create a JSON representation.
Requires a Factur-X , Order-X or Xrechnung-file.

Will return a calculatedInvoice, i.e. a grandTotal will be available. If it is set when writing (invoice2XML and combineInvoice), it will be compared vis á vis the calculated items and an exception will be thrown if the values do not match. In particular when writing a Factur-X PDF this should be used to ensure the machine readable XML-values match the human readable PDF values.

The object will look as described for CalculatedInvoice example.

parse is the reverse operation to „invoice2XML“, i.e. if you have access to a XML sample you want to reproduce, running it through parse will give you JSON which should create a very similar XML if passed through invoice2XML.

9.1.5. Invoice2xml

Converts a Factur-X/ZUGFeRD/XRechnung JSON representation to XML. Requires a input JSON string, a format (ZUGFeRD = zf, XRechnung = xr, Factur-X = fx or Order-X=ox), a version (usually 2 for ZUGFeRD and 1 for Factur-X) and a profile ("MINIMUM","BASICWL","BASIC","EN16931","EXTENDED" or "XRECHNUNG"
for Factur-X, for ZUGFeRD 1 "BASIC","COMFORT" or "EXTENDED"). For XRechnung only "XRECHNUNG".

Please refer to the documentation of the Invoice class.

invoice2XML is the reverse operation to „parse“, i.e. if you have access to a XML sample you want to reproduce, running it through parse will give you JSON which should create

a very similar XML if passed through invoice2XML.

9.1.6. Extract

Extracts just the XML (not as JSON like parse) from a Factur-X/ZUGFeRD/Order-X file.

9.1.7. detach

Parameter: file

Extracts all file attachments from the PDF (including e.g. a factur-x.xml) and the XML, if the invoice has attachments, and returns a JSON structure with base64-encoded contents like this:

{
"pdf": [],
"xml":
[{"aFileA.png":"iVBORw0KGgoAAAANSUhEUgAAAAgAAAC1CAQAAADIUnarAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfkAQIXGQ0qsHJfAAAAo0lEQVRIx+3MoQrCUBjF8f+9dzBtgha72eAYLJgMQ4tvMREfQuw+wGA2mxarxTeYgi9gEwyDNaOfYTrM5u+kw4/DcQAwi4PO+Q5gK2AsUVW+UEdBQUFBQUFBQUFB4W9IQhMRL3oABpKppLaLmEIm2cXMR6897c++cEM3WBPUBw2eVrzfR/Gtd6Cs4SFHl1/DFn18oDTLbOcgPwWFacpNVpstvAG3bSYVfhBdGAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wMS0wMlQyMzoyNToxMyswMDowMEN9AywAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDEtMDJUMjM6MjU6MTMrMDA6MDAyILuQAAAAAElFTkSuQmCC"},\{"sameFileB.png":"iVBORw0KGgoAAAANSUhEUgAAAAgAAAC1CAQAAADIUnarAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfkAQIXGQ0qsHJfAAAAo0lEQVRIx+3MoQrCUBjF8f+9dzBtgha72eAYLJgMQ4tvMREfQuw+wGA2mxarxTeYgi9gEwyDNaOfYTrM5u+kw4/DcQAwi4PO+Q5gK2AsUVW+UEdBQUFBQUFBQUFB4W9IQhMRL3oABpKppLaLmEIm2cXMR6897c++cEM3WBPUBw2eVrzfR/Gtd6Cs4SFHl1/DFn18oDTLbOcgPwWFacpNVpstvAG3bSYVfhBdGAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wMS0wMlQyMzoyNToxMyswMDowMEN9AywAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDEtMDJUMjM6MjU6MTMrMDA6MDAyILuQAAAAAElFTkSuQmCC"}]
}

The input file can be a PDF/A-3, or a XML. Please note a PDF/A-3 may have both pdf and xml attachments at the same time (embedded within the xml which is embedded in the PDF).

9.1.8. combine

Combines a JSON encoded invoice object (as described for „parse“) and a PDF/A document to a Factur-X/ZUGFeRD PDF/A-3 document. Requires a input PDF/A-1 or A-3 file, a format (ZUGFeRD = zf, Factur-X = fx or Order-X = ox), a version (usually 2 for ZUGFeRD and 1 for Factur-X) and a profile ("MINIMUM","BASICWL","BASIC","EN16931","EXTENDED" or "XRECHNUNG" for Factur-X, for ZUGFeRD 1 "BASIC","COMFORT" or "EXTENDED").

If returnJSON (optional) is true (default false) the return value will be a JSON whose key „pdf“ is base64 encoded. If ignorePDFAErrors (optional) is true (default false) the PDFbox pre-validation will raise no exceptions if the input PDF/A file is invalid.

The attribute grandTotal will be calculated by multiplying item quantities with their prices, adding the lines, adding charges and removing allowances, and adding the calculated VAT amounts.

Sample for writing, e.g. Invoice2XML: Please refer to the documentation of the Invoice class.

9.1.9. combineXML

Combines CII XML and a PDF/A document to a Factur-X/ZUGFeRD PDF/A-3 document. Requires a input PDF/A-1 or A-3 file, a format (ZUGFeRD = zf, Factur-X = fx or Order-X = ox), a version (usually 2 for ZUGFeRD and 1 for Factur-X) and a profile ("MINIMUM","BASICWL","BASIC","EN16931","EXTENDED" or "XRECHNUNG" for Factur-X, for ZUGFeRD 1 "BASIC","COMFORT" or "EXTENDED").

If returnJSON (optional) is true (default false) the return value will be a JSON whose key „pdf“ is base64 encoded. If ignorePDFAErrors (optional) is true (default false) the PDFbox pre-validation will raise no exceptions if the input PDF/A file is invalid.

9.1.10. styledinvoicetofx

Converts a calculated invoice object and a HTML template to Factur-X. StyledInvoice is a JSON structure around a invoice rather than a class with login in itself.

Templates

The mustache (i.e. double-curly-bracket-embraced) placeholders ({{number}}, {{currency}}) in the HTML follow the Mustang JSON structure, ZFItems is an array and within a {{#ZFItems}}…​{{/ZFItems}} block you have access to item elements like this

{{#ZFItems}}
  <tr>
    <td>{{quantity}} </td>
    <td>{{#UNIT}}{{product.unit}}{{/UNIT}}</td>
    <td>{{product.name}} {{product.description}}</td>
    <td>{{product.VATPercent}}%</td>
    <td>{{price}} €</td>
    <td>{{calculation.itemTotalNetAmount}} €</td>
  </tr>
{{/ZFItems}}

Not all HTML and CSS features may be supported, SVG is. There is a sandbox where HTML can be made working and a guide on some peculiarities.

Minimal example
{
"mustache": "<html><body>Hello world</body></html>"
}

Will just create a PDF/A file without Factur-X, for which you would have to provide an invoice key. Useful only if you e.g. want to reuse your letterhead.

{
"mustache": "<html><body>Hello customer {{recipient.name}}</body></html>",
"invoice": {"number": "471102", "currency": "EUR", "issueDate": "2018-03-04T00:00:00.000+01:00",
           "dueDate": "2018-03-04T00:00:00.000+01:00",   "deliveryDate": "2018-03-04T00:00:00.000+01:00",
           "sender": {"name": "Lieferant GmbH", "zip": "80333", "street": "Lieferantenstraße 20",
           "location": "München", "country": "DE", "taxID": "201/113/40209",  "vatID": "DE123456789",
           "globalID": "4000001123452", "globalIDScheme": "0088"},
           "recipient": {"name": "Kunden AG Mitte",   "zip": "69876",  "street": "Kundenstraße 15",
           "location": "Frankfurt", "country": "DE"},
           "zfitems": [  {"price": 9.9, "quantity": 20, "product": {"unit": "H87",
           "name": "Trennblätter A4", "description": "", "vatpercent": 19, "taxCategoryCode": "S"} } ]
     }
}

Will work but of course a productive invoice should show all XML data also in the PDF. The Mustache template names reflect the JSON structure.

Default is Factur-X in EN16931 profile, this may become customizable in the future.

Resources

In the template you may use http, https and data for URLs, additionally you can use the s3 or base64-"protocol". The base64-"protocol" allows you to specify name+base 64 encoded value pairs. This is similar to data-urls but it allows to reuse your "file" multiple times.

To encode a binary file you can use online tools like https://www.base64encode.org/ or offline tools like https://www.gunamoi.com.au/soft/base64convert/index.html

this has to be in one line and e.g. no tabs are allowed test in https://jsonformatter.curiousconcept.com/# jsonlint or jsoncrack

Note
the HTML/Mustache template has to be passed as a JSON string, i.e. is quoted, single line, without tabs (\n or \t may be used) while the invoice object is another JSON structure and may therefore be spread over multiple lines.

Similarly, to access s3-objects, you have to specify the referred name vis á vis the object name.

S3-objects can be stored on your own, or rented, S3 space, or temporarily uploaded to your quota.

Note
Instead of Mustache, you could also use only HTML. But even if you don’t use Mustache placeholders please still make sure your invoice object is corrrect and complete as it will be embedded as XML into your PDF.
Resource example
{
"mustache": "<html><head>    <style>        body {        font-family:\"Sans\";        margin: 0 auto;        font-size:12px;        padding:1rem;        }    </style></head><body>Hello <img src='base64:excl.svg'/> image</body></html>",
"invoice": {
"number": "471102",
"currency": "EUR",
"issueDate": "2018-03-04T00:00:00.000+01:00",
"dueDate": "2018-03-04T00:00:00.000+01:00",
"deliveryDate": "2018-03-04T00:00:00.000+01:00",
"sender": {
"name": "Lieferant GmbH",
"zip": "80333",
"street": "Lieferantenstraße 20",
"location": "München",
"country": "DE",
"taxID": "201/113/40209",
"vatID": "DE123456789",
"globalID": "4000001123452",
"globalIDScheme": "0088"
},
"recipient": {
"name": "Kunden AG Mitte",
"zip": "69876",
"street": "Kundenstraße 15",
"location": "Frankfurt",
"country": "DE"
},
"zfitems": [
{
"price": 9.9,
"quantity": 20,
"product": {
"unit": "H87",
"name": "Trennblätter A4",
"description": "",
"vatpercent": 19,
"taxCategoryCode": "S"
}
},
{
"price": 5.5,
"quantity": 50,
"product": {
"unit": "H87",
"name": "Joghurt Banane",
"description": "",
"vatpercent": 7,
"taxCategoryCode": "S"
}
}
]
},
"base64":{"excl.svg":"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNjQwIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIHY3LjEuMCBieSBAZm9udGF3ZXNvbWUgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbSBMaWNlbnNlIC0gaHR0cHM6Ly9mb250YXdlc29tZS5jb20vbGljZW5zZS9mcmVlIENvcHlyaWdodCAyMDI1IEZvbnRpY29ucywgSW5jLi0tPjxwYXRoIGQ9Ik0zMjAgNjRDMzM0LjcgNjQgMzQ4LjIgNzIuMSAzNTUuMiA4NUw1NzEuMiA0ODVDNTc3LjkgNDk3LjQgNTc3LjYgNTEyLjQgNTcwLjQgNTI0LjVDNTYzLjIgNTM2LjYgNTUwLjEgNTQ0IDUzNiA1NDRMMTA0IDU0NEM4OS45IDU0NCA3Ni44IDUzNi42IDY5LjYgNTI0LjVDNjIuNCA1MTIuNCA2Mi4xIDQ5Ny40IDY4LjggNDg1TDI4NC44IDg1QzI5MS44IDcyLjEgMzA1LjMgNjQgMzIwIDY0ek0zMjAgNDE2QzMwMi4zIDQxNiAyODggNDMwLjMgMjg4IDQ0OEMyODggNDY1LjcgMzAyLjMgNDgwIDMyMCA0ODBDMzM3LjcgNDgwIDM1MiA0NjUuNyAzNTIgNDQ4QzM1MiA0MzAuMyAzMzcuNyA0MTYgMzIwIDQxNnpNMzIwIDIyNEMzMDEuOCAyMjQgMjg3LjMgMjM5LjUgMjg4LjYgMjU3LjdMMjk2IDM2MS43QzI5Ni45IDM3NC4yIDMwNy40IDM4NCAzMTkuOSAzODRDMzMyLjUgMzg0IDM0Mi45IDM3NC4zIDM0My44IDM2MS43TDM1MS4yIDI1Ny43QzM1Mi41IDIzOS41IDMzOC4xIDIyNCAzMTkuOCAyMjR6Ii8+PC9zdmc+"}
}
Note
You will not have access to system default fonts (except "Serif" and "Sans"), fonts like "Arial", "Helvetica", "Verdana" or "Times New Roman" will not work unless a TTF file is specified. Incorrect fonts will not render the according paragraph. In your CSS, you may have to specify (TTF) fonts to be used via https, http, s3 or base64.

There is information on how fonts can be embedded and free and open source fonts available.

Functions/"Lambdas"

The following functions ("Lambdas") can be used. A {{#<NAME>}} indicates their beginning and a {{/<NAME>}} their end, like this:

  • CURRENCYFORMAT: e.g.{{#CURRENCYFORMAT}}21.9000231{{/CURRENCYFORMAT}} or {{#CURRENCYFORMAT}}{{calculated}}{{/CURRENCYFORMAT}} rounds to two digits (21.90)

  • DATEFORMAT local-specific date, e.g. 21.12.2025 and

  • UNIT returns a human readable version of unit codes for most commonly used codes, e.g. H87=piece

Note
The specified language effects e.g. unit codes.

Additional to the structure of calculatedinvoice, a currency field VATtotal is available.

Sophisticated example

This is a rather sophisticated example using CSS including VATtotal, page count and lambdas for date, currency and unit format:

<html>
 <head>
     <style>

         body {
         font-family:"Sans";
         margin: 0 auto;
         font-size:12px;
         padding:1rem;
         }
         .seller-subscript, .grey{
             color: grey;
             font-size:8px;
             margin-left: 2rem;
         }
         .grey{
             color:grey;
             font-size:x-small;
         }
         .invoice-date, .page-no{
             color: grey;
         }

         .img-logo{
            float:right;
             width:3cm;
         }
         .buyer-details{
             height:20rem;
             line-height:2.5;
             margin-top: 5rem;
         }

          .seller-details{
             color: #a5a5a5;
             font-size: x-small;
             margin:0;
             line-height: 1.5;
             float: right;
          }
           .footer{
                 float:right;
                 color: #a5a5a5;
                 font-size: x-small;
                 bottom:0;
                 margin:4rem 0;
                 width:12rem;
           }
         .center_div {
             border: 1px solid gray;
             margin-left: auto;
             margin-right: auto;
             width: 90%;
             text-align: left;
             padding: 8px;
         }
         table, th, td {
           border: 1px solid black;
           border-collapse: collapse;
           vertical-align:top;
         }
         .number{
         margin:0;
         }
         .power{
         font-size: small;
         }



     @page {
               @bottom-center {
                 font-family:"Sans";
                 color: #a5a5a5;
                 font-size: x-small;
                 content:'Seite' counter(page) '/' counter(pages);
               }

             }


     </style>
 </head>
 <body>
 <small class = "invoice-date"> {{#DATEFORMAT}}{{issueDate}}{{/DATEFORMAT}} </small>
     <div class = "inner-div">



               <p class="seller-details">
                <img class= "img-logo" src="base64:logo_final_RGB.svg" alt="Logo"/><br>
                      <b>usegroup</b><br>
                         <b>Inh. Jochen Stärk</b><br>
                         Königsteiner 27<br>
                         60481 Frankfurt<br><br>
                         fon(069) 59 66 40-18<br>
                         <br>
                         info@usegroup.de<br>
                         www.usegroup.de<br>
                </p>

               <p class = "buyer-details">
               <small class = "seller-subscript">Jochen Stärk   ●   Königsteiner 27   ●   60481 Frankfurt</small><br>
                  {{recipient.name}}</br>
                  {{recipient.street}}</br>
                  {{recipient.zip}} {{recipient.location}}</br>
                  </p>




         <p class ="number"> Ihre Nummer: {{number}}
            <br><br>
         Rechnung # {{number}} vom {{#DATEFORMAT}}{{issueDate}}{{/DATEFORMAT}}</p>

         <table style="width:100%;height:10%;">
         <tr>
                  <td>Anz. </td>
                  <td>Einh. </td>
                  <td style="width:50%;">Produkt</td>
                  <td>USt</td>
                  <td>Einzelpreis</td>
                  <td>Summe</td>
                  </tr>
         {{#ZFItems}}


         <tr>
         <td>{{quantity}} </td>
         <td>{{#UNIT}}{{product.unit}}{{/UNIT}}</td>
         <td>{{product.name}} {{product.description}}</td>
         <td>{{product.VATPercent}}%</td>
         <td>{{#CURRENCYFORMAT}}{{price}}{{/CURRENCYFORMAT}} €</td>
         <td>{{calculation.itemTotalNetAmount}} €</td>

         {{/ZFItems}}

         </tr>

         </table>

         Summe netto: {{calculation.value}} €<br>
         Zuzügl. USt:<br>
         <table style="width:50%;height:5%;">
            <tr class="grey">
              <td >USt-Satz.</td>
              <td>Nettobetrag</td>
              <td>USt-Betrag</td>
            </tr>

            {{#calculation.taxDetails}}


                <tr class="grey">
                <td>{{applicablePercent}}%</td>
                <td>{{basis}} € </td><td> {{#CURRENCYFORMAT}}{{calculated}}{{/CURRENCYFORMAT}} €</td>
                </tr>
                <tr>
                {{/calculation.taxDetails}}
                <tr>
                 <td colspan="2">Gesamt USt: </td>
                <td>{{VATtotal}} €</td>
                </tr>
                </table>
                <br>
               <b> Bitte überweisen Sie bis zum {{#DATEFORMAT}}{{dueDate}}{{/DATEFORMAT}}: </b>
               <br>
               Summe inkl. USt: {{calculation.duePayable}} €

               <p class = "power"> <b> Powered by MustangAccounting</b></p>

 <p class = "footer">
 USt-ID: DE199922957<br>
 IBAN DE54 5004 0000 0702 7119 00 <br>
 BIC COBADEFFXXX <br>
 Commerzbank Frankfurt<br>
 Kontoinhaber: Jochen Stärk
 </p>
     </div>
 </body>
 </html>

Once you remove the linebreaks and tabs put an according logo_final_RGB.svg in the base64 block you will receive the following result Screenshot of a PDF reader with a Factur-X file generated from a styled invoice

Visual designers

If you want your users to be able to design, there are a couple of embeddable open source WYSIWYG HTML editors which should work fine with Mustache templates, i.e.

S3 return

If the (optional) header parameter writeS3 is set to true the result will not be an octet stream but a JSON structure like

{
  "outputKey": "a9d38052-a2ee-4aab-8e78-3d364c5f6876"
}

which indicates the filename (key) of the resulting factur-x file which has been written to S3.

9.1.11. cii2ubl

transforms XML from the UN/CEFACT Cross Industry Invoice (CII) XML format, the basis of factur-x/ZUGFeRD and the CII version of the XRechnung, to the Universal Business Language format, UBL. Requires a CII string.

9.1.12. xmltohtml

converts a UBL or CII XML file (parameter file) into a human readable HTML in the language specfied in language, which can be EN, DE or FR. The resulting file will require the additional files in the same directory:

9.1.13. xmltopdf

Converts a UBL or CII XML file (parameter file) into a human readable PDF/A-3 in german language. The PDF will not have file attachments, i.e. you still have to combineXML if you want to get a Factur-X/ZUGFeRD file from a XML.

9.2. S3 file management (/s3)

S3 is incrementally introduced for file management. They can e.g. used in StyledInvoiceToFX. Very similar functions are available in Mustang-Docs, in which they are already required.

This means typically you would initiate a REST request, then periodically REST poll for it to complete, then REST the result.

REST file management services and status inquiries are not accounted for in your monthly operational volume.

Your result will be stored at least 24h and each customer has at least 1GB of storage for uploads and results. You can, however, procure or provide your own S3 storage space, in which case no such limits will apply.

9.2.1. upload

By uploading a Multipart File to upload, it will be placed on the bucket.

Parameters
  • file : a multipart file

Return

JSON-Structure with the key of the uploaded file, and the key of the downloaded file to be generated, e.g.

{"outputKey":"27a72345-d3dd-47c5-917e-55ec852a6000"}

9.2.2. download/{key}

After a status is done the output key can be downloaded using /mustang/download/{key} Please use libraries, Bruno or similar if the download exceeds 10MB as some browser may cancel their connection if more data is returned.

9.2.3. delete/{key}

After download, feel free to /mustang/delete/{key} the file. Unless you use a customized S3 bucket all files will be cleaned up, i.e. deleted, after 24h anyway and total space quota per customer is 1GB.

9.2.4. list

using /mustang/list you can receive a list of keys (i.e. files) for your tenant in your bucket, e.g.

{
  "result": [
    "27a72345-d3dd-47c5-917e-55ec852a6000",
    "3ddf0de1-e7db-4909-acbc-1180ac7e6850"
  ]
}

This list does not distinguish between source or result files.

9.2.5. ping

Just a health check, will return the string pong

9.2.6. General information on S3 connectivity

Configure

You can provide your own bucket by logging in on https://mustangserver.com/settings/ with your API credentials.

Naming conventions

Mustangserver uses <Tenantname>/<guid> as keys for the objects. Tenantname is usually your Mustangserver username.

In case you use a standalone version the Tenantname will match the username header for your requests. In the public offering this username header is overwritten by the API management with the username of the API key or oauth token.

You can use graphical UIs like the open source software Cyberdock to manage the files in your S3 installation.

Procure

You can book S3 space at any provider (for as little as 60ct/GB/10yr). If the provider is not AWS, you wull just have to put the endpoint in the URL field.

Provide

You can also arrange access to your own (onsite) S3 server, e.g. using the open source MinIO server. Open firewall ports 9000 and 9001. In case of virtualization (e.g. Proxmox) make sure the neccessary CPU features are available, e.g. by using cpu type "host".

sudo apt install podman
podman run -e MINIO_ROOT_PASSWORD='<root pwd>' -e MINIO_ROOT_USER='admin'  -p 9000:9000 -p 9001:9001   quay.io/minio/minio server /data --console-address ":9001" -d

enter web ui and create bucket, e.g. abucket

   curl https://dl.min.io/client/mc/release/linux-amd64/mc   --create-dirs   -o $HOME/minio-binaries/mc
chmod +x $HOME/minio-binaries/mc
export PATH=$PATH:$HOME/minio-binaries/

You can then add a user

./minio-binaries/mc alias set local http://<servername>:9000 admin '<password>'
./minio-binaries/mc admin user add local <clientid> <secret>

and grant this user read/write privileges on the bucket

./minio-binaries/mc admin policy attach local readwrite --user <clientid>
./minio-binaries/mc admin user info local <clientid>

Afterwards the configuration via mustangserver.com/settings/ is straightforward.

Please only note that despite a "region" field is not needed by MinIO, it is required by the library used so feel free to put an arbitrary value.

bucket: abucket
region: arbitrary (it may just not be blank)
url http://<servername>:9000/
client id:
client secret:

9.3. EEisi (/eeisi), free tier

9.3.1. eigor

Converts a UBL to a CII, FatturaPA, or vice versa

Parameters:

  • sourceFormat: „ubl“, „cii“ or „fatturapa“

  • destFormat: „ubl“, „cii“ or „fatturapa“

  • xml: the XML in the source format to be converted

9.4. PDF to PDF/A (/mustang-docs)

Mustang-Docs is used to convert ordinary PDF documents to PDF/A, which may be required for electronic invoicing, e.g. as a basis for Factur-X/ZUGFeRD.

Since Version 2, Mustang-Docs is a pipelined process.

If you want to access our AMPQ-pipeline directly, please send us a mail. Otherwise, AMQP processes are initiated by a HTTP REST request, their status can be inquired likewise, and accompanying files can be managed with HTTP REST endpoints to upload, list, delete and download files, or, when using customized S3 connectivity, by managing the files on their network share directly.

This means typically you would initiate a REST request, then periodically REST poll for it to complete, then REST the result.

REST file management services and status inquiries are not accounted for in your monthly operational volume.

Your result will be stored at least 24h and each customer has at least 1GB of storage for uploads and results. You can, however, procure or provide your own S3 storage space, in which case no such limits will apply.

Screenshot of the documents endpoints

Screenshot of the documents endpoints

9.4.1. submitPDF

By uploading a Multipart File to submitPDF, specifying the desired PDF/A target version, you can initiate a conversion from PDF to PDF/A.

Parameters
  • file : a multipart file

  • version

    • 1: simplest form

    • 2: supports among others better image compression

    • 3: supports file attachments, vital for Factur-X

    • 4: not yet available

Return

JSON-Structure with the key of the uploaded file, and the key of the downloaded file to be generated, e.g.

{"inputKey":"3ddf0de1-e7db-4909-acbc-1180ac7e6850" ,"outputKey":"27a72345-d3dd-47c5-917e-55ec852a6000"}

9.4.2. status

The output key from submitPDF can be used as key parameter in /mustang/status/{key}, the result will be an array of output keys and their status. A status is available for 24h and can be RECEIVED, i.e. waiting, IN_PROGRESS, DONE or FAILED.

Example return

{"27a72345-d3dd-47c5-917e-55ec852a6000":{"status":"DONE"}}

9.4.3. download/{key}

After a status is done the output key can be downloaded using /mustang/download/{key} Please use libraries, Bruno or similar if the download exceeds 10MB as some browser may cancel their connection if more data is returned.

9.4.4. delete/{key}

After download, feel free to /mustang/delete/{key} the file. Unless you use a customized S3 bucket all files will be cleaned up, i.e. deleted, after 24h anyway and total space quota per customer is 1GB.

9.4.5. list

using /mustang/list you can receive a list of keys (i.e. files) for your tenant in your bucket, e.g.

{
  "result": [
    "27a72345-d3dd-47c5-917e-55ec852a6000",
    "3ddf0de1-e7db-4909-acbc-1180ac7e6850"
  ]
}

This list does not distinguish between source or result files.

9.4.6. ping

Just a health check, will return the string pong

9.4.7. General information on S3 connectivity

Configure

You can provide your own bucket by logging in on https://mustangserver.com/settings/ with your API credentials.

Naming conventions

Mustangserver uses <Tenantname>/<guid> as keys for the objects. Tenantname is usually your Mustangserver username.

In case you use a standalone version the Tenantname will match the username header for your requests. In the public offering this username header is overwritten by the API management with the username of the API key or oauth token.

You can use graphical UIs like the open source software Cyberdock to manage the files in your S3 installation.

Procure

You can book S3 space at any provider (for as little as 60ct/GB/10yr). If the provider is not AWS, you wull just have to put the endpoint in the URL field.

Provide

You can also arrange access to your own (onsite) S3 server, e.g. using the open source MinIO server. Open firewall ports 9000 and 9001. In case of virtualization (e.g. Proxmox) make sure the neccessary CPU features are available, e.g. by using cpu type "host".

sudo apt install podman
podman run -e MINIO_ROOT_PASSWORD='<root pwd>' -e MINIO_ROOT_USER='admin'  -p 9000:9000 -p 9001:9001   quay.io/minio/minio server /data --console-address ":9001" -d

enter web ui and create bucket, e.g. abucket

   curl https://dl.min.io/client/mc/release/linux-amd64/mc   --create-dirs   -o $HOME/minio-binaries/mc
chmod +x $HOME/minio-binaries/mc
export PATH=$PATH:$HOME/minio-binaries/

You can then add a user

./minio-binaries/mc alias set local http://<servername>:9000 admin '<password>'
./minio-binaries/mc admin user add local <clientid> <secret>

and grant this user read/write privileges on the bucket

./minio-binaries/mc admin policy attach local readwrite --user <clientid>
./minio-binaries/mc admin user info local <clientid>

Afterwards the configuration via mustangserver.com/settings/ is straightforward.

Please only note that despite a "region" field is not needed by MinIO, it is required by the library used so feel free to put an arbitrary value.

bucket: abucket
region: arbitrary (it may just not be blank)
url http://<servername>:9000/
client id:
client secret:

9.5. Valitool (/valitool), extra subscription

/valitool has a ping enpoint

9.5.1. hybriddoc

Requires a XML or Factur-X file (parameter name „file“) and returns a XML valitool validation report

Kapitel 10. Clients

Apart from the OpenAPI Generator (which is e.g. used for our xref: C#/.net tutorial), Swagger editor used to offer the generation of clients, but this functionality has now apparently been moved to it’s docker image.

Screenshot of the docker version of editor.swagger.io

Screenshot of the docker version of editor.swagger.io

All generated clients require testing and customization, e.g. as regards oAuth authentication, e.g. see our PHP tutorial. We don’t guarantee support for all of them, some of them are likely to not work at all, but these are the most important swagger-editor-docker generated Mustangserver clients:

Kapitel 11. Tutorials

On our website we elaborated how precisely to e.g. create a

Kapitel 12. Interactive testing using Postman/Bruno

Bruno is an open source alternative to Postman, graphical user interfaces to create/execute requests on REST APIs.

This example is based on Postman, you will need enabled client credentials as described in the PHP tutorial.

Use Import|File|Upload Files to upload you Openapi.yaml file into a Mustangserver collection.

Add a request to https://gw.usegroup.de:9443/oauth2/token?grant_type=client_credentials and call it Token Request. Postman will auto-detect the Parameter in the URL. Change the type to POST.

In Headers, add a new field Content-Type with application/json as its value.

The tab Authorization should be Basic auth with your client id and secret as username and password. Once you click Send, an according access token should be submitted:

screenshot_bruno_token.png

var jsonData = JSON.parse(responseBody);

pm.collectionVariables.set("token", jsonData.access_token);

console.log(jsonData.access_token);

In the „Tests“ tab and View|Show Postman Console (Alt+CTRL+C) . Once you click Send again you should be able to see the access token also in the Console:

screenshot_bruno_tests.png

screenshot_bruno_baseurl_variables.png In the collection, click on the variables tab and add „token“ as a collection variable.

screenshot_bruno_token_placeholder.png

You can now add the variable {{token}} as authorization to any request. The variable will be available and the requests work after you click on „Send“ of the Token Request for the first time.

screenshot_bruno_ui_ping.png

Please note that ping was answered by pong.

Where appropriate, e.g. in the validation endpoint, Postman will allow you to select files, this being a valid factur-x:

screenshot_bruno_validate.png

Kapitel 13. Performance Tests with Jmeter

Jmeter is a generic load testing tool and load generator.

If you want to perform load tests, in order not to affect our production servers, we will happily grant you access to our mirror infrastructure, i.e. we will guarantee that the hardware, software and settings are identical.

https://openapi-generator.tech/ supports a Jmeter export but that does not handle authentication so here we describe how to set up some Jmeter performance test manually.

For this example, you will need enabled client credentials as described in the PHP tutorial.

Right click your Test plan, add a Thread Group with a Once Only controller. Below that, add a HTTP request sampler, we’ll call it Token Request. This is how it is defined: Change protocol to https, method to POST, add server name and port number, and add the path:

screenshot_jmeter_token

Base64encode your <consumer key>:<consumer secret> as described on the applications page of the API management: screenshot_jmeter_token

In Jmeter, below the Token request, add a HeaderManager with Basic Authorization as described:

screenshot_jmeter_header_manager

And from the response, also below Token Request, extract the JSON value access token into a Jmeter Variable access token using a JSON Extractor:

screenshot_jmeter_json

Add a sampler View Result Tree to confirm the results and a debug sampler if you like (in the results tree you will then be able to e.g. see the current variables when you click on the results of the debug sampler).

Run|Start should give you green entries in the results tree.

Now we will set the ordinary authentication as header: add another Header Manager outside of the Token request and add the variable as token, i.e. Authorization being Bearer ${access_token}

screenshot_jmeter_bearer

You can then add a simple ping request in the thread group

and e.g. a Response time graph.You can then set the Thread group loop count to infinite, start the sampling and check the results tree.

screenshot_jmeter_ping

After a while the response time graph will look like this, indicating the initial login took ~440ms and the usual response time to our „ping“ is ~20ms.

screenshot_jmeter_response_time_graph

Kapitel 14. Terms of service

14.1. Evaluation terms

To test and evaluate the service a valid email address has to be provided. Unless otherwise agreed (info@usegroup.de) test access is restricted to one account per legal entity, i.e. usually company. This email address will also be used to send availability, information about the roadmap, development and status with an expected maximum volume of one per week. You can terminate your test phase by unsubscribing from the announcements newsletter list. After the signup, access can then happen free of charge, with a limit of 1,000 operations/month, unless access is revoked by usegroup. You are not allowed to share personal data (e.g. real invoice recipient’s names, addresses, email addresses, bank credentials or real invoice contents). Access may be revoked because the general test phase has ended, the test phase is over for a certain customer, or due to other terms which do not need to be disclosed. Under this test terms we also do not guarantee the availability nor the correctness of the service.

14.2. Production terms

To access Mustangserver productively including a data processing agreement a Mustang Pro license is required. Further info can be obtained at https://www.mustangproject.org/pro/

Kapitel 15. Troubleshooting

  • The ping endpoint is intentionally simple and can be used to check basic functionality

  • A HTTP reponse code 400 Post method not allowed on methods which do allow post may (temporarily) indicate a throttled user or subscription

Kapitel 16. Inhouse variant

16.1. (optional) Set up Sentry

Sentry is an optional centralized Exception Tracking System Mustangserver can send exceptions to. It requires 40gb HDD, 16gb RAM and at least 4 CPUs

16.1.1. Install docker

sudo apt install curl git

sudo su
curl -sSL https://get.docker.com/ | CHANNEL=stable sh
# After the installation process is finished, you may need to enable the service and make sure it is started (e.g. CentOS 7)
systemctl enable docker.service
systemctl start docker.service

curl -L https://github.com/docker/compose/releases/download/$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

16.1.2. Config

VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/getsentry/self-hosted/releases/latest)
VERSION=${VERSION##*/}
git clone https://github.com/getsentry/self-hosted.git
cd self-hosted
git checkout ${VERSION}
./install.sh

Adjust sentry/config.yml

system.url-prefix: 'https://sentry.usegroup.de'

edit sentry/sentry.conf.py, remove comments in front of ssl lines 255-258.

adjust settings for Mail Server ~l 90

mail.host: 'mail.usegroup.de'
mail.port: 587
mail.username: 'info@usegroup.de'
mail.from: 'info@usegroup.de'
mail.password: '<password/>'
mail.use-ssl: false

16.2. Run from Jar/War

java -jar mustangserver.war --spring.profiles.active=local
-Dspring.profiles.active=local

Sentry will be used if at least an environment variable SENTRY_AUTH_TOKEN= is set and the default Sentry DSN can be overwritten if additionally a environment variable SENTRY_DSN is set.

16.3. Run from Docker

If you bought the inhouse version: the container registry is dev.usegroup.de:5050 and the default port the server starts on is 8000

docker login dev.usegroup.de:5050 -u <username> -p <token>

docker run --name Mustangserver -e MUSTANG_SERVER_VERSION=1.7.2 -dp 8000:8000 dev.usegroup.de:5050/internal/mustangserver:1.7.2

Afterwards you should be able to access

and e.g. perform a ping like described above. You can leave username empty. The correct response to ping is „pong“.

screenshot_ping

By specifying additional -e key=value pairs (e.g. docker run --name Mustangserver -e MUSTANG_SERVER_VERSION=1.7.2 -e mustang.allowedIPs=123.45.67.89 -p 8888:8000 dev.usegroup.de:5050/internal/mustangserver:1.7.2) you can set config variables.

Available variables are

Config Default Description

server.port

8000

The port the OpenAPI HTML Client and backend operate on

mustang.allowedIPs

If specified, a comma-separated list of IPs who will be allowed to connect

mustang.oAuth

false

Off by default

mustang.additionalLog

Additional entries for the log line of the request, can e.g. be set to the instance’s ID to allow a consolidated. Logs are stored in /opt/mustangserver/log and in a future version there may be a filebeat to be configured

keycloak.enabled

false

Almost never to be set to true, even if you connect to a keycloak server, if true it will start an keycloak server on its own

openapi.server-url

localhost

Endpoint URL published via swagger file

spring.security.oauth2.client.registration.keycloak.client-id

login-app

Oauth2 specific setting (if enabled)

spring.security.oauth2.client.registration.keycloak.authorization-grant-type

authorization_code

Oauth2 specific setting (if enabled)

spring.security.oauth2.client.registration.keycloak.scope

openid

Oauth2 specific setting (if enabled)

spring.security.oauth2.client.provider.keycloak.issuer-uri

http://localhost:8080/realms/SpringBootKeycloak

Oauth2 specific setting (if enabled)

spring.security.oauth2.client.provider.keycloak.user-name-attribute

preferred_username

Oauth2 specific setting (if enabled)

spring.security.oauth2.resourceserver.jwt.issuer-uri

http://localhost:8080/realms/SpringBootKeycloak

Oauth2 specific setting (if enabled)

message-from-application-properties

Die Anwendung wird in der Default Environment gestartet!

Will just be shown on the console

server.servlet.context-path

Usually not set at all but could be set to e.g. /api/v1

springdoc.api-docs.path

/api-docs

Where the openapi spec file will be found

springdoc.swagger-ui.path

/swagger-ui

Path of the HTML GUI

logging.config

classpath:logback-spring.xml

Kapitel 17. Sentry

You can setup to use a inhouse (or central) „Sentry“ Exception Tracking Server ,

Kapitel 18. Version history

Of this document:

0.7.0 on 2023-01-19 by Jochen.

0.8.0 on 2023-02-25 by Jochen: Added Mustangserver 0.8.0 (=Order-X)

0.8.1 on 2023-02-26 by Jochen: added C++-Client, PDF/A-param to PDF endpoint

1.0.0 on 2023-09-26 by Jochen: most recent endpoint, neccessity to subscribe, split between mustangserver and mustangserver-docs, updated list of Ves-IDs, added change password url

1.1.0 added username field, exception handling, invoice2XML parameter standard was renamed to format. Better Exception handling. CombineXML now also allows PDF/A-3 input.

1.2.0 on 2024-01-31 /combineXML /parse and /invoice2XML now support XRechnung 3.0.1

1.3.0 on 2024-02-29 Endpoint /combine is now available again (combine JSON and PDF to fx), /phive has been updated, now auto-detects Ves-IDs if none specified and now also supports e.g. XR 3.0.1 (from 135 to 143 VesIDs). /xmltohtml and API keys documented. Mentioned Bruno.

1.3.1 on 2024-07-29 JSON structure documented, added Troubleshooting

1.4.0 added description of detach and xmltopdf endpoint and of C# sample client. Added section classes.

1.4.1 added chapter on Docker config for in-house server. Corrected timezone in example. Added BT Mapping and invoice class description.

1.4.2 Added paragraph on most recent versions and API keys, added examples for §19 UStG small businesses and intra-community supply. Added documentation for corrected invoices, cancellations and credit notes.

1.5.0 New in the server (2024-11-20) were improved UBL import, improved JSON return after parse (without empty elements), docker without SecurityAutoConfiguration and the valitool endpoint (not yet documented)

1.5.1 (2024-11-30) BankDetails and IncludedNotes work in the server, possibility to specify server address to be included in OpenAPI

1.6.0 (2024-12-30) File attachments work in the server, documented valitool and PDF, added and documented ignoreNotices parameter to validation. New UBL2CII/CII2UBL endpoints. Javascript example client documented. Added description of eeisi/eigor, file attachments and includednotes.

1.7.0 (2025-08-20) Outsourced the list of PHIVE VES-Ids and the tutorials, added Sentry config, migrated to Asciidoc, added the comprehensive example and swagger editor generated clients.

1.7.1 (2025-10-08) updated BT mapping table, added Mustang-Ghostscript

Index

Glossar

ZUGFeRD

Zentrale User Guides Forum elektronische Rechnung Deutschland