DATEV payroll export (LODAS / Lohn und Gehalt)
Produce DATEV LODAS or Lohn und Gehalt movement data (Bewegungsdaten) from approved TimeTracking hours, with an export mapping plus a small post-processing step.
Last updated: · Markdown version
1. Summary
| What syncs | Approved hours per hour type (Lohnart), allowances, approved expenses, approved leave |
| Direction | TimeTracking → DATEV LODAS or DATEV Lohn und Gehalt (usually via the tax adviser / Steuerberater) |
| Mechanism | Export mapping (format: "txt", ;, decimal comma) + post-processing (ANSI encoding, LODAS header block) → ASCII import |
| Trigger | Monthly payroll |
Status: Recipe — works today with a custom mapping and a post-processing script. No native DATEV connector, and TimeTracking cannot yet write the DATEV header block or ANSI encoding by itself.
2. Mechanism
- Use the German rule pack
de-basic(ArbZG breaks, 11 h rest, daily overtime after 8 h →overtime_125, night 23:00–06:00 →night, Sunday →sunday, public holidays →holiday) or your own pack aligned with the Tarifvertrag. - A custom export mapping writes one line per employee per Lohnart per month.
- The committed file is downloaded with
GET /exports/runs/{id}/file, which delivers awindows-1252mapping as Windows-1252 (ANSI). For LODAS, a small post-processor prepends the required header sections. - The tax adviser or payroll office imports the file in LODAS or Lohn und Gehalt (ASCII import of Bewegungsdaten).
Assumption — DATEV LODAS ASCII import files consist of sections such as [Allgemein] (Ziel, Version, Berater-/Mandantennummer, Abrechnungszeitraum), [Satzbeschreibung] (record layout definition) and [Bewegungsdaten] (data lines), in ANSI encoding with ; separators and decimal comma. Lohn und Gehalt uses a similar ASCII import with a different record definition. The exact record numbers and field lists must be agreed with the tax adviser; verify with your DATEV consultant / Steuerberater.
3. Prerequisites
- TimeTracking admin; API key with
writescope. - Org country
DE(public holidays) and rule packde-basicor a custom pack. employee_number= DATEV Personalnummer (numeric, up to 5 digits in many setups — assumption, verify).- DATEV Beraternummer, Mandantennummer and the Lohnart numbers per hour type.
4. Entity mapping
| DATEV field | TimeTracking | Notes / external ID |
|---|---|---|
| Beraternummer / Mandantennummer | Not in TimeTracking | Header block (post-processing). |
| Abrechnungszeitraum | Export range | Header block, e.g. 01/2027. |
| Personalnummer | employee_number → employeeNumber |
Only stable key today. |
| Lohnart | hourType via map |
normal, overtime_125, night, sunday, holiday, leave_*, expense_*. |
| Stunden | minutes → hours_decimal with , |
|
| Betrag | amountCents → cents_decimal with , |
Expenses, allowances. |
| Kostenstelle | placementRef or clientName |
Optional. |
| Tage (days) | Not available | PayLine has no day count. |
External IDs: upsert people, clients and placements idempotently with PUT /{people|clients|placements}/by-external/{source}/{ref} (or POST /…/batch with externalSource + externalRef per row, up to 500 rows). The response includes external_source, external_ref and created: true|false. Use for example source datev with the Personalnummer as ref.
5. Step-by-step setup
export TT=https://app.timetracking.tech/api/v1
export KEY=tt_live_… # write scope
Select rule pack
de-basicin Regels & koppelingen (admin).Create the mapping yourself (admin, dashboard): open the export mappings screen (
/export/mappings, linked from Export), copypreset-genericorpreset-sdworx, edit columns, code tables and validation, and optionally set it as the org default. Saved mappings are validated; max 50 per org. The mapping id is the UUID in its URL (/export/mappings/<uuid>): pass it asmappingIdtoPOST /exportsandtt export --mapping. Example JSON for the editor (assumed layout — verify with your DATEV / Steuerberater consultant):
{
"id": "datev-lodas",
"name": "DATEV LODAS — Bewegungsdaten",
"format": "txt",
"delimiter": ";",
"includeHeader": false,
"encoding": "windows-1252",
"lineEnding": "crlf",
"groupBy": ["employeeNumber", "hourType"],
"columns": [
{ "header": "Personalnummer", "source": "employeeNumber" },
{ "header": "Lohnart", "source": "hourType", "transform": { "kind": "map", "table": "lohnart" } },
{ "header": "Stunden", "source": "minutes", "transform": { "kind": "hours_decimal", "decimals": 2, "decimalSeparator": "," } },
{ "header": "Betrag", "source": "amountCents", "transform": { "kind": "cents_decimal", "decimalSeparator": "," } },
{ "header": "Kostenstelle", "source": "placementRef" }
],
"tables": { "lohnart": { "normal": "100", "overtime_125": "200", "night": "300", "sunday": "310", "holiday": "320",
"leave_vacation": "400", "leave_sick": "410", "expense_km": "500" } },
"validation": [
{ "column": "Personalnummer", "required": true, "pattern": "^\\d{1,5}$" },
{ "column": "Lohnart", "required": true }
]
}
Lohnart numbers are placeholders.
- Preview, then commit the month:
tt export --from 2027-01-01 --to 2027-01-31 --mapping datev-lodas
tt export --from 2027-01-01 --to 2027-01-31 --mapping datev-lodas --commit -o datev-commit.txt # UTF-8 copy
Then download the ANSI file of that run (run id from the commit output, GET /exports/runs or the export.completed webhook):
curl -s "$TT/exports/runs/<runId>/file" -H "Authorization: Bearer $KEY" -o datev-body.txt # charset=windows-1252
- Post-process: prepend the header block (plain ASCII, so the file stays Windows-1252). Example (header content is an assumption — get the real one from your Steuerberater):
{
printf '[Allgemein]\r\nZiel=LODAS\r\nVersion_SST=1.0\r\nBeraterNr=12345\r\nMandantenNr=99999\r\nAbrechnungszeitraum=01/2027\r\n\r\n'
printf '[Satzbeschreibung]\r\n1;u_lod_bwd_buchung_standard;pnr#bwd;la_eigene#bwd;bs_wert_butab#bwd;kostenstelle#bwd;\r\n\r\n'
printf '[Bewegungsdaten]\r\n'
LC_ALL=C sed 's/^/1;/' datev-body.txt
} > datev-lodas-2027-01.txt
If you start from the UTF-8 commit copy instead, pipe the result through iconv -f UTF-8 -t WINDOWS-1252. The sed prefixes each line with the record number defined in [Satzbeschreibung]. Field codes in the example are illustrative.
- Send
datev-lodas-2027-01.txtto the tax adviser or import it yourself. An admin locks January in the dashboard after payroll is final.
6. Sync schedule and triggering events
- Monthly, after all weeks of the month are closed. The dashboard works per week; the API accepts the month range.
export.completed{runId,startsOn,endsOn,rows,mapping}andperiod.locked{startsOn,endsOn,kind:"month"}(admin creates webhooks). A receiver can download the committed body withGET /exports/runs/{runId}/file, run the post-processor and drop the result for the Steuerberater.- Committed files stay retrievable:
GET /exports/runs(paginated{items, next_cursor}),GET /exports/runs/{id}(metadata,mapping_snapshot,pay_lines,warnings) andGET /exports/runs/{id}/file(the exact committed file,text/csv). A read-only key is enough.
7. Corrections, conflicts and locked periods
- Only approved, conflict-free days are exported;
pendingCountshows the rest. - Exported/locked days are immutable (
423). Book corrections in the current month; DATEV receives them in the next file ("Korrektur im Folgemonat"). Retroactive DATEV corrections (Rückrechnung) are done in DATEV.
8. Testing checklist
- File opens in a text editor as ANSI; umlauts (ä, ö, ü, ß) display correctly; no
?where a name or note had a character outside Latin-1. - Header values (Berater/Mandant/period) confirmed by the Steuerberater.
- Test import into a DATEV test client (Testmandant) succeeds.
- Night and Sunday hours appear as separate Lohnarten for a test week.
-
break_too_shortwarnings reviewed (ArbZG §4).
9. Troubleshooting
| HTTP / code | Message | Fix |
|---|---|---|
403 forbidden |
API key is read-only | write key. |
404 not_found |
Export mapping not found | Check mapping id. |
409 conflict |
This period was just exported; refresh before exporting again | Avoid double commit. |
409 conflict |
N day(s) in this period are not exported yet | Lock refused; export remaining days. |
422 invalid |
Export has N validation issue(s) | Personalnummer too long / non-numeric, unmapped Lohnart. |
422 invalid |
Nothing approved to export in this period | Approve first. |
423 locked |
This day is exported or locked; book a correction in an open period | Correct in an open month. |
| DATEV import error on characters | — | You used the UTF-8 commit content or dashboard download: use GET /exports/runs/{id}/file or run iconv. |
10. Limits and known gaps
No header/trailer sections in mappings; the LODAS header block needs post-processing.
No record-number prefix column besides
const:(you can add{ "header": "Satz", "source": "const:1" }as first column instead ofsed).No day-count (Tage) values; only hours and amounts.
groupBykeeps the first line's date (irrelevant for monthly Bewegungsdaten without a date column).Encoding: a mapping with
encoding: "windows-1252"is delivered as Windows-1252 (Latin-1) only byGET /exports/runs/{id}/file(content-type: text/csv; charset=windows-1252, no BOM). Characters outside Latin-1 (€, curly quotes, em dash) become?. The preview/commit JSONcontentand the dashboard download stay UTF-8.
Planned (not in API v1.1) — export-mapping CRUD via the API.
11. Build it with Claude
Help me produce a DATEV LODAS import from TimeTracking.
1. With the TimeTracking MCP tools, run export_preview for 2027-01-01..2027-01-31 (mappingId "preset-generic") and list
distinct hour types and employees whose employee number is not 1–5 digits.
2. Draft a datev-lodas ExportMapping JSON (see docs/developers/data-out/export-mappings.md) with a lohnart table
(TODO numbers) and a shell post-processor that adds the [Allgemein]/[Satzbeschreibung]/[Bewegungsdaten] header and
relies on GET /api/v1/exports/runs/{id}/file for the WINDOWS-1252 bytes. Mark every DATEV field as "verify with Steuerberater".
3. Do not commit or lock anything.