English | Deutsch

Introduction

eauXchange is a dedicated server established for interaction with the GKVKomServer. It supports automatic and scheduled sending and receiving of data, keeps insurance company information up to date and tracks the public keys of the communication partners.

Two procedures are supported, each licensed separately:

EAU (/eau)

electronic certificates of incapacity for work (elektronische Arbeitsunfähigkeitsbescheinigung).

Vorerkrankung / LFZ (/pre)

preexisting condition enquiries for continued payment of wages (Entgeltersatzleistungen).

Every installation serves its own documentation: /help is the interactive API reference of the version you are running, /changelog lists the changes of all versions. Both work without a token.

Licensing

The license file is created by Javiis GmbH upon request. For this, the company number (BNR) (test or production) for which a license is issued is required. All requests are then sent for these company numbers. (For test mode, the test company number is used.)

The license also decides which procedures the installation may use (EAU, Vorerkrankung or both). Calls to a procedure that is not licensed are rejected.

Running the service

eauXchange is delivered as a Docker container and uses a PostgreSQL database to store all relevant information.

Place the following files into the config folder next to your docker-compose.yml:
  • application.yml - the server configuration (see below)

  • the license file provided by Javiis GmbH

  • the certificate and private key of your BNR (<bnr>.p7c / <bnr>.pkey), from which the keystore gkv.p12 is created

services:
  eauxchange:
    container_name: eauxchange_server
    image: www.eauxchange.de/eauxchange:latest
    ports:
      - "8085:8085"
    depends_on:
      - db
    environment:
      - SPRING_CONFIG_LOCATION=/opt/app/config/application.yml
      - DATABASE_CONNECTION=eauxchange_db
      - DATABASE_PORT=5432
    volumes:
      - ./config:/opt/app/config
  db:
    container_name: eauxchange_db
    image: postgres:14.12-bullseye
    restart: unless-stopped
    shm_size: 128mb
    environment:
      POSTGRES_DB: eauxchange
      POSTGRES_PASSWORD: <password>
      POSTGRES_USER: gkv_user
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      - <EXTERNAL_PATH>:/var/lib/postgresql/data

The image is pulled from the registry www.eauxchange.de; the access data is provided together with the license. The database schema is created and migrated automatically on startup.

Server configuration

The configuration lives in application.yml in the config folder. The most important settings:

spring:
  datasource:
    username: gkv_user
    password: <password>
    url: jdbc:postgresql://eauxchange_db:5432/eauxchange

server:
  port: 8085

com:
  javiis:
    gkv:
      bnrTest: <test BNR>
      bnrProductive: <production BNR>
      # true = nothing is transmitted to the insurance companies (test/dry run)
      dryRunning: true
      # false = test certificate, true = production certificate
      use_production_certificate: false
      product:
        manufacturer: <company>
        name: <product name>
        version: <version>
        prodID: <product number>
        modID: <module number>
        # field "Abrechnungsprogramm" from eAU data format 3.0.0: 1 = payroll program,
        # 2 = filling aid, 3 = other system-checked programs (default)
        abrechnungsprogramm: 3
      contact:                       # technical contact, sent with every request
        name: Max Mustermann
        gender: M
        street: Musterstrasse
        streetNr: 74
        # from 1 January 2027 a German postal code with exactly 5 digits, in quotes
        zip: "12345"
        city: Berlin
        tel: 0123456789
        mail: kontakt@example.com
      keystore:
        test:
          path: /opt/app/config/test/gkv.p12
          password: <password>
          keyPassword: <password>
          alias: <BNR>
      receivePath: /opt/app/config/receive
      logSentFiles: true
      xmlLogPath: /opt/app/config/sent

Schedules

The background jobs run on cron expressions. The defaults are usually fine; all of them can be overridden:

Property Default Task

com.javiis.gkv.send.request.cron

0 0 1 * * *

Sends pending requests to the insurance companies

com.javiis.gkv.status.request.cron

0 30 * * * *

Asks the GKV server for new answers and downloads them

com.javiis.gkv.eau.import.cron

0 35 * * * *

Imports downloaded answer files

com.javiis.gkv.sync.request.cron

0 45 * * * *

Assigns answers that arrived without a usable reference to their request

com.javiis.gkv.certificate.expiry.cron

0 0 3 * * *

Checks the remaining validity of the communication certificates

Status requests must not run more often than every 15 minutes (restriction of the GKV communication server).

Notifications

Set com.javiis.gkv.mailSettings (host, port, sender, username, password, defaultRecipient) and com.javiis.gkv.sendEmailOnError: true to be informed about failures of the background jobs and about certificates that are about to expire.

Authentication

All API calls require an API token; only the built-in help (/help, /api-docs) and the release notes (/changelog) are open. A token is a JWT that is valid for the company numbers it was issued for and carries an expiry date.

Creating the first token
  1. On the very first start the server creates the user root and writes its generated password once into the log. Note it down; it is not shown again.

  2. Create an API token with that user (HTTP basic authentication):

    curl -u root:<password from the log> \
         -X POST 'https://<host>/security/createAPIToken?expiryDate=2027-12-31T23:59:59&note=ERP' \
         -H 'Content-Type: application/json' \
         -d '["<BNR>"]'
  3. Use the returned token for every further call:

    curl -X POST 'https://<host>/eau/getOpenEauResponses' \
         -H 'Authorization: Bearer <token>'

Tokens can be listed, disabled, enabled and deleted (/security/getTokens, /security/disableTokens, /security/enableTokens, /security/deleteTokens).

A request whose BNR is not covered by the token is rejected with HTTP 403. When you add a new company number, issue a new token for it - otherwise those requests stay unsent.

How to use the service

For a detailed list of API calls and schemas visit the built-in help of your installation under /help. It documents exactly the version you are running.

Creating an eAU request

In the built-in help: createEauRequests

To schedule an eAU request, an object of EauRequest type is sent. The startDate field refers to the point in time when the absence was taken. If the field contactPerson is empty, the default contact person set at creation time will be used. The response will be an errorCode per request sent (NONE if the request is scheduled successfully).

If a payroll office such as a tax consultant works for the company, enter its company number (Betriebsnummer) in the optional field accountingOfficeIdentifier. From 1 January 2027 it is passed on to the health insurance fund in eAU data format 3.0.0.

Requests are checked against the rules of the eAU error catalogue as soon as they are created, for example for permitted characters in names and in the file reference (Aktenzeichen). A request that violates them is rejected with HTTP 400; the message names the error numbers of the catalogue, for example DXAU230.

HTTP request

POST /eau/createEauRequests HTTP/1.1
Content-Type: application/json
Accept: application/json
Authorization: Bearer <Token>
Content-Length: 1311
Host: localhost:8080

[ {
  "startDate" : "2023-01-01",
  "eauRequestPlanDate" : null,
  "gkvDataID" : null,
  "gkvDataIDBeforeStorno" : null,
  "accountingOfficeIdentifier" : null,
  "gender" : "M",
  "employeeAddress" : {
    "countryCode" : "D",
    "postalCode" : "12345",
    "city" : "Musterstadt",
    "street" : "Musterstrasse",
    "streetNr" : "1",
    "addressExtra" : ""
  },
  "contactPerson" : {
    "nameAnsprechpartner" : "Max Mustermann",
    "telefonAnsprechpartner" : "0123456789",
    "name" : "Firma XYZ Abteilung IT",
    "plz" : "12345",
    "ort" : "Musterstadt",
    "strasse" : null,
    "hausNr" : null,
    "faxansprechpartner" : null,
    "emailAnsprechpartner" : "max.mustermann@example.org",
    "faxAnsprechpartner" : null,
    "eMailAnsprechpartner" : "max.mustermann@example.org"
  },
  "lastname" : "Mustermann",
  "firstname" : "Max",
  "externalID" : "0152",
  "insuranceNumber" : "65170839M006",
  "companyIdentifier" : "99301347",
  "insuranceCompanyIdentifcationNumber" : "99300660",
  "birthday" : "1939-08-17",
  "birthname" : null,
  "birthplace" : null,
  "id" : null,
  "errorCode" : null,
  "errorDetails" : null,
  "eauRequestState" : null,
  "eauResponseState" : null,
  "stornoDate" : null,
  "requestedAt" : null,
  "responseReceivedAt" : null,
  "aktenzeichenVerursacher" : null
} ]

HTTP response

HTTP/1.1 200 OK
Content-Type: application/json
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 42

[ "c90ef8fc-06d5-4ba1-94f9-12e68f0b9d17" ]

Retrieving an eAU response

In the built-in help: getOpenEauResponses

You can retrieve open eAU responses with the method getOpenEauResponses. All eAU responses that are not marked as checked are retrieved.

HTTP request

POST /eau/getOpenEauResponses HTTP/1.1
Content-Type: application/json
Accept: application/json
Authorization: Bearer <Token>
Host: localhost:8080

HTTP response

HTTP/1.1 200 OK
Content-Type: application/json
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 3

[ ]

Marking an eAU response as finished

In the built-in help: markEauResponsesAsChecked

In order to avoid data loss, all eAU responses must be marked as completed once they are retrieved from the server. This status update is achieved by invoking the markEauResponsesAsChecked method.

HTTP request

POST /eau/markEauResponsesAsChecked HTTP/1.1
Content-Type: application/json
Accept: application/json
Authorization: Bearer <Token>
Content-Length: 40
Host: localhost:8080

["f2415f26-fdff-49e9-a67e-387fd64fc931"]

HTTP response

HTTP/1.1 200 OK
Content-Type: application/json
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 4

true

Schedule eAU cancellation

In the built-in help: stornoEauRequest

You can abort an eAU request by invoking the stornoEauRequest method. eAU requests that have not yet been transmitted will be obliterated instantly, while requests that have already been dispatched will be queued for cancellation during the next synchronization process.

HTTP request

POST /eau/stornoEauRequest HTTP/1.1
Content-Type: application/json
Accept: application/json
Authorization: Bearer <Token>
Content-Length: 42
Host: localhost:8080

[ "f44aad32-f95e-4fce-804d-c9bf5ac9b3f7" ]

HTTP response

HTTP/1.1 200 OK
Content-Type: application/json
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 4

true

Resending requests

scheduleEauRequestsForResend queues requests again, for example after an insurance company asked for a repetition. scheduleEauRequestsForResendWithContactPerson does the same but replaces the technical contact, which is the right call after the contact person has changed.

Searching requests and responses

getOpenEauRequests returns everything that is still waiting for an answer. findEauRequestByCriteria and findEauResponseByCriteria search by freely combinable criteria (name, insurance number, period, state). getEauRequest and getEauResponseFromRequestID fetch a single record.

If a request stays open although the answer has already arrived, syncResponsesWithRequests assigns pending answers to their requests. The same runs automatically (see the schedules above).

Execute sending/retrieving job manually

In the built-in help: manualexecuteRequestSending

You have the option to initiate a manual send or retrieve task by invoking the methods manualExecuteRequestSending and manualExecuteStatusRequest in /common. Typically, these methods are used for testing or when an answer is needed before the next scheduled run.

HTTP request

POST /common/manualExecuteStatusRequest HTTP/1.1
Content-Type: application/json
Accept: application/json
Authorization: Bearer <Token>
Host: localhost:8080

Preexisting condition enquiries (Vorerkrankung / LFZ)

Requires the Vorerkrankung license. The flow mirrors the eAU procedure and uses the /pre endpoints.

  1. createPreexistingConditionInfoRequests schedules the enquiries. Duplicates - same person, same period - are rejected right away with an explaining error instead of being sent twice.

  2. getOpenPreexistingConditionInfoRequests lists everything that is still waiting for an answer.

  3. getOpenPreexistingConditionInfoResponses returns the answers that have not been marked as checked. The optional parameter limit restricts the number of records returned.

  4. markResponseAsChecked confirms the processing, exactly like in the eAU procedure.

getPreexistingConditionInfoRequest, getPreexistingConditionInfoResponse and findPreexistingConditionInfoRequestsByAbAGDate fetch single records or search by the date of the employer enquiry.

Understanding an answer

For every enquiry the data delivered by the insurance company is available in raw and in parsed form: getRawRequest, getRawResponse, getRawResponseByRequest, getRawErrorResponse and the corresponding getParsed…​ methods. This is the fastest way to clarify a rejection without a support request - the rejection reason of the insurance company is part of the answer.

Validation service

The server provides methods for validating insurance company numbers and employee insurance numbers according to the standard validation rules (/validation/companyBnr, /validation/insuranceNr). More information: In the built-in help: validateInsuranceNr

Insurance companies

The insurance company data is ordinarily synchronized automatically, however, you also have the opportunity to manually trigger this synchronization by calling the triggerInsuranceCompanyRefresh method in /insuranceCompany. This action updates all insurance companies and their public keys, in addition to their associations with their respective DAVs. findInsuranceCompanyByBnr, findInsuranceCompanyByCriteria and getAllValidInsuranceCompanies read the data.

Switching to eAU data format 3.0.0

From 1 January 2027 the eAU uses data format 3.0.0. eauXchange switches automatically: until 31 December 2026 requests are sent in the previous format, from 1 January 2027 in the new one. Responses are read in both formats.

From 1 January 2027 the health insurance funds answer in format 3.0.0 only – including requests that were still sent in the old format. Install a version supporting the new format – the version of this guide or later – before the turn of the year.

Please check before 1 January 2027:

Postal code of the company

contact.zip must be a German postal code with exactly 5 digits, quoted in application.yml (zip: "01234") so that a leading zero is kept. If the configuration does not fit the new format, eauXchange reports it in the log at startup ("configuration does not fit the eAU data format 3.0.0"); from 1 January 2027 nothing would be sent.

Abrechnungsprogramm

The new mandatory field is filled from product.abrechnungsprogramm, default "3" (other system-checked programs).

Payroll office

If a payroll office works for the company, pass its company number (Betriebsnummer) in the request field accountingOfficeIdentifier.

File reference

The file reference of a request (externalID) may only contain letters without umlauts, digits, spaces, dots, hyphens and slashes.

Checking requests

Requests are checked against the rules of the eAU error catalogue as soon as they are created and rejected with HTTP 400 and the error numbers of the catalogue (e.g. DXAU230). In the new format every request is checked again on its own before sending; a faulty request gets the status ERROR with the error number, the others are sent.

Response reason 6

From 2027 the health insurance funds call it "special case" (day-care hospital stay, full-day outpatient, mobile or combined rehabilitation) and send no periods for it. It is still reported as PARTIAL_HOSPITAL.

Falling back to the old format

For emergencies only: eauXmlVersion: V2_0_0 keeps sending in the old format until 28 February 2027. After that the data acceptance offices no longer accept it and the setting is ignored.

Operating notes

Which version am I running?

/changelog shows the version of the installation and the changes of all versions; /changelog.json delivers the same information in machine-readable form.

An answer file could not be imported

Files that could not be processed are kept. reimportFailedResponses processes them again after the cause has been fixed; manualTriggerResponseImports triggers the import of everything that has been downloaded.

Nothing is transmitted

Check dryRunning (must be false in production), the certificate selection (use_production_certificate) and whether the API token covers the company number of the requests.

Certificates

The remaining validity is checked daily. With the mail settings configured, a warning is sent early enough to exchange the certificate before the transmission stops.

Looking at the file sequence number

Use POST /dav/getCurrentDAVFileCounter - it only reads. The returned value is the number of the file that was sent last. Every DAV keeps one counter per procedure: the optional parameter procedure selects it, EAA (default) for eAU and EEL for preexisting condition enquiries. The procedure’s module has to be licensed - EAA requires EAU, EEL requires VORERKRANKUNG - otherwise the call is answered with 403.

POST /dav/drawNextDAVFileCounter draws the next number and consumes it. The number cannot be given back, and none of your files will carry it. Use it only in recovery situations, never to look at a value. The older path getCurrentFileCounterForBnr does the same thing and still works, but is deprecated - its name reads like a query although the call changes data.