Anleitungen
Vermietungsquote aller Garagenparks täglich abrufen
Token mit Scope occupancy:read anlegen, dann täglich GET /v1/occupancy abrufen (alle Seiten).
curl -s "https://api.manage2hero.com/v1/occupancy?limit=200" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/occupancy?limit=200" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/occupancy?limit=200', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/occupancy?limit=200",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Vermietungsquote in Excel (Power Query) — ohne Programmieren
Excel → Daten → Daten abrufen → Aus anderen Quellen → Leere Abfrage → Erweiterter Editor, folgenden Text einfügen und DEIN_TOKEN ersetzen. Danach „Schließen & laden“; mit „Alle aktualisieren“ holen Sie jederzeit den neuesten Stand.
let
Token = "DEIN_TOKEN",
Antwort = Json.Document(Web.Contents("https://api.manage2hero.com/v1/occupancy",
[Query = [limit = "200"], Headers = [Authorization = "Bearer " & Token]])),
Liste = Antwort[data][items],
Tabelle = Table.FromRecords(Liste),
Typen = Table.TransformColumnTypes(Tabelle, {
{"units_total", Int64.Type}, {"units_rented", Int64.Type},
{"occupancy_rate", Percentage.Type}})
in
Typenoccupancy:read. Excel fragt beim ersten Abruf nach dem Datenschutz der Quelle — „Organisation“ wählen.Verlauf der Vermietungsquote (Monate oder Tage)
Mit demselben Scope occupancy:read liefert GET /v1/occupancy/history die Entwicklung über die Zeit — ohne location_id für die ganze Organisation, mit location_id für einen Garagenpark. Gemessen wird stündlich; der Verlauf beginnt mit der Einführung des Endpunkts (history_starts_on).
curl -s "https://api.manage2hero.com/v1/occupancy/history?from=2026-01-01&to=2026-12-31&interval=month" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/occupancy/history?from=2026-01-01&to=2026-12-31&interval=month" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/occupancy/history?from=2026-01-01&to=2026-12-31&interval=month', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/occupancy/history?from=2026-01-01&to=2026-12-31&interval=month",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Aufteilung nach Garagentyp für heute: GET /v1/occupancy?group_by=storage_type.
Umsatz je Monat in Excel oder Power BI
Token mit Scope bookings:read. GET /v1/revenue liefert dieselbe Zahl wie das Dashboard der Verwaltung; group_by=month ergibt eine Zeile je Monat (auch Monate ohne Umsatz).
curl -s "https://api.manage2hero.com/v1/revenue?from=2026-01-01&to=2026-12-31&group_by=month" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/revenue?from=2026-01-01&to=2026-12-31&group_by=month" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/revenue?from=2026-01-01&to=2026-12-31&group_by=month', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/revenue?from=2026-01-01&to=2026-12-31&group_by=month",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])let
Token = "DEIN_TOKEN",
Antwort = Json.Document(Web.Contents("https://api.manage2hero.com/v1/revenue",
[Query = [from = "2026-01-01", to = "2026-12-31", group_by = "month"],
Headers = [Authorization = "Bearer " & Token]])),
Tabelle = Table.FromRecords(Antwort[data][groups]),
Typen = Table.TransformColumnTypes(Tabelle, {{"revenue_net", Currency.Type}})
in
TypenGET /v1/bookings verwenden.Zahlungseingänge mit der Finanzbuchhaltung abgleichen
Token mit payments:read (und bookings:read für die Gegenbuchungen im Journal). GET /v1/payments nennt je Eingang, welchen Buchungen und Verträgen er zugeordnet ist, und was noch offen ist (unallocated_amount). Braucht Ihr System Name, IBAN und Verwendungszweck der Zahlenden, vergeben Sie zusätzlich payments:read_counterparty — nur dann enthält die Antwort diese Felder.
curl -s "https://api.manage2hero.com/v1/payments?from=2026-09-01&to=2026-09-30&match_status=unmatched" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/payments?from=2026-09-01&to=2026-09-30&match_status=unmatched" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/payments?from=2026-09-01&to=2026-09-30&match_status=unmatched', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/payments?from=2026-09-01&to=2026-09-30&match_status=unmatched",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Offene Posten und Mahnstand, z. B. für ein Inkassobüro
Token nur mit open_items:read und dunning:read anlegen (für Namen und Kontaktdaten zusätzlich tenants:read). GET /v1/dunning/leases?min_level=3 liefert alle Verträge, die mindestens die dritte Mahnstufe erreicht haben — mit offener Summe, Gebühren und den einzelnen Forderungen.
curl -s "https://api.manage2hero.com/v1/dunning/leases?min_level=3&limit=100" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/dunning/leases?min_level=3&limit=100" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/dunning/leases?min_level=3&limit=100', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/dunning/leases?min_level=3&limit=100",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Summen und Altersstruktur für die Liquiditätsplanung: GET /v1/open-items/summary?group_by=location. Die Einzelposten liefert GET /v1/open-items.
Aktuelle Mieterliste eines Garagenparks exportieren
Token mit tenants:read und locations:read. Erst die Id des Garagenparks über GET /v1/locations ermitteln, dann:
curl -s "https://api.manage2hero.com/v1/locations/6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b/tenants?limit=200" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/locations/6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b/tenants?limit=200" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/locations/6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b/tenants?limit=200', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/locations/6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b/tenants?limit=200",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])import csv, os, requests
TOKEN = os.environ["M2H_API_TOKEN"]
STANDORT = "6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b"
zeilen, cursor = [], None
while True:
params = {"limit": 200, **({"cursor": cursor} if cursor else {})}
body = requests.get(f"https://api.manage2hero.com/v1/locations/{STANDORT}/tenants",
headers={"Authorization": f"Bearer {TOKEN}"}, params=params, timeout=30).json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
zeilen += body["data"]["items"]
cursor = body["data"]["next_cursor"]
if not cursor:
break
with open("mieter.csv", "w", newline="", encoding="utf-8") as f:
w = csv.writer(f, delimiter=";")
w.writerow(["Einheit", "Vertrag", "Status", "Kundennummer", "Vorname", "Nachname", "Firma", "E-Mail", "Telefon"])
for z in zeilen:
t = z["tenant"]
w.writerow([z["unit"]["number"], z["lease"]["contract_number"], z["lease"]["status"],
t["customer_number"], t["first_name"], t["last_name"], t["company"], t["email"], t["phone"]])Bestandsmieter importieren
Der Import über die API ist derselbe Import wie unter Administration → Datenimport— nur mit JSON statt Datei. Ablauf:
1. Token mit tenants:import (und locations:read, um die Id des Garagenparks zu finden).
2. POST /v1/tenant-imports mit genau einem Garagenpark und 1–500 Datensätzen. Jeder Datensatz wird geprüft wie eine Zeile der Import-Datei; die Antwort nennt je Datensatz Fehler und Hinweise (z. B. „E-Mail gibt es schon bei einem Mieter“).
3. In der Verwaltung erscheint der Import im Import-Assistenten. Dort lassen sich fehlerhafte Datensätze korrigieren. Übernehmen können Sie dort oder per API (POST /v1/tenant-imports/{id}/commit, siehe unten).
4. Mit GET /v1/tenant-imports/{id} sehen Sie den Stand; nach der Übernahme stehen dort tenant_id und lease_id.
Felder eines Datensatzes
Dieselben Feldnamen wie in der Muster-Datei des Datei-Imports. Text als String, Beträge und Zahlen als JSON-Zahl (89.5), Ja/Nein als true/false, Datum als JJJJ-MM-TT. Pflicht sind Anrede, Vorname, Nachname, Geburtsdatum, E-Mail, Straße, Hausnummer, PLZ und Ort (bei Anrede „Firma“ zusätzlich der Firmenname). Die Vertragsnummer hat die Form Kürzel Einheit-lfd. Nr., z. B. HH01 42-1; über sie ordnet der Import-Assistent die Garage automatisch zu.
| Feld | Typ | Pflicht |
|---|---|---|
salutation | Text: Herr, Frau, Firma oder Divers | ja |
first_name | Text | ja |
last_name | Text | ja |
company_name | Text | |
date_of_birth | Datum JJJJ-MM-TT | ja |
email | Text (E-Mail) | ja |
street | Text | ja |
house_number | Text | ja |
postal_code | Text | ja |
city | Text | ja |
country | Text | |
phone | Text | |
mobile | Text | |
contract_number | Text (Kürzel Einheit-Nr.) | |
lease_start | Datum JJJJ-MM-TT | |
rent_net | Zahl | |
deposit | Zahl | |
deposit_paid | true/false | |
move_in_date | Datum JJJJ-MM-TT | |
has_electricity_230v | true/false | |
has_electricity_400v | true/false | |
electricity_meter_fee | Zahl | |
electricity_advance_payment | Zahl | |
electricity_price_kwh_net | Zahl | |
notice_period_months | ganze Zahl | |
minimum_lease_months | ganze Zahl | |
meter_reading | Zahl | |
meter_reading_date | Datum JJJJ-MM-TT |
iban, bic, account_holder) können über die API nicht übergeben werden — eine Anfrage damit wird abgelehnt (field_not_allowed). Bankverbindungen tragen Sie in der Verwaltung ein.Wiederholen ohne doppelten Import
Jede Anfrage braucht den Kopf Idempotency-Key — einen eigenen Schlüssel je Vorgang, z. B. eine UUID. Schicken Sie nach einem Abbruch (Timeout, Netzwerkfehler) dieselbe Anfrage mit demselben Schlüssel erneut, bekommen Sie denselben Import zurück (Status 200 statt 201) — es entsteht kein zweiter. Derselbe Schlüssel mit anderem Inhalt wird abgelehnt (idempotency_conflict).
curl -s -X POST "https://api.manage2hero.com/v1/tenant-imports" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data @import.json$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = Get-Content -Raw -Encoding UTF8 import.json
Invoke-RestMethod -Method Post -Uri "https://api.manage2hero.com/v1/tenant-imports" -Headers $kopf -ContentType "application/json; charset=utf-8" -Body $bodyimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-post-v1-tenant-imports-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/tenant-imports', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
'Content-Type': 'application/json',
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
"location_id": "6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b",
"name": "Übernahme aus Altsystem",
"records": [
{
"salutation": "Herr",
"first_name": "Max",
"last_name": "Mustermann",
"date_of_birth": "1980-05-17",
"email": "max.mustermann@example.org",
"street": "Musterstraße",
"house_number": "1",
"postal_code": "22297",
"city": "Hamburg",
"phone": "+49 40 1234567",
"contract_number": "HH01 42-1",
"lease_start": "2023-04-01",
"rent_net": 89.5,
"deposit": 150,
"has_electricity_230v": true,
"electricity_advance_payment": 10,
"electricity_meter_fee": 2.5
}
]
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const { id, records } = body.data; // Import-Id und Ergebnis je Datensatzimport hashlib, json, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-post-v1-tenant-imports-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.post(
"https://api.manage2hero.com/v1/tenant-imports",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
json=json.load(open("import.json", encoding="utf-8")),
timeout=60,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"]["id"], body["data"]["records"])Stand abfragen
curl -s "https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Import abbrechen
Einen Import, den Sie nicht mehr brauchen (falsche Zuordnung, Testlauf), geben Sie mit DELETE /v1/tenant-imports/{id} frei. Das löscht den Import samt Datensätzen und gibt seinen Platz in der Grenze der offenen Importe sofort zurück — sonst bliebe er bis zur Aufbewahrungsfrist (30 bis 90 Tage) belegt. Es genügt der Scope tenants:import; der Ersteller des Tokens braucht in der Verwaltung zusätzlich Datenimport mit Vollzugriff — wie beim Löschen eines Imports dort (sonst 403 insufficient_permissions). Die Antwort lautet { "session_id": "…", "deleted": true }.
Abbrechen lässt sich nur ein Import, der über die API angelegt wurde. Unbekannte, fremde oder in der Verwaltung hochgeladene Importe und ein zweiter Aufruf liefern 404 not_found. Ist mindestens ein Datensatz schon übernommen, wird nichts gelöscht (409 session_has_transferred_records): Mieter und Verträge verweisen auf den Import. Wer den Import gerade im Import-Assistenten bearbeitet, verliert ihn — stimmen Sie sich bei gemeinsam genutzten Importen ab. Das Abbrechen eines Imports, der jünger als eine Stunde ist, zählt gegen die Stundengrenze neuer Importe je Token (429 import_rate_limited). Insgesamt sind je Token und Stunde also höchstens doppelt so viele neue Importe möglich wie die Grenze: einmal die Grenze beim Anlegen, einmal beim Abbrechen und erneuten Anlegen.
curl -s -X DELETE "https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Method Delete -Uri "https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.delete(
"https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Dokumente abrufen und herunterladen
Mit documents:read listen Sie die Mieter- und Standortdokumente Ihrer Organisation: GET /v1/documents (Filter tenant_id, lease_id, location_id, category und der Zeitfilter nach Erstellungsdatum). Die Antwort nennt nur Angaben zur Datei — Name, Größe, Art, Datum und die Zuordnung — nie den Speicherort. Ausgeschlossen sind vertrauliche Arten wie Ausweise und Bonitätsunterlagen sowie archivierte Dokumente. Der Abgleich nach Änderungen (/v1/documents/changes) meldet auch, wenn eine Schlussabrechnung storniert wird (settlement_cancelled).
Die Datei selbst holen Sie mit POST /v1/documents/{id}/download-link (zusätzlich documents:download). Die Antwort enthält einen Link, der 5 Minuten gültig ist — rufen Sie die Datei sofort ab und speichern Sie den Link nicht. Jede Erzeugung zählt als schreibender Aufruf (20 statt 120 je Minute) und steht im Zugriffsprotokoll. Gibt es zum Eintrag keine Datei mehr, antwortet die API mit 404 file_not_available.
E-Mails an Mieter abrufen
GET /v1/emails (Scope emails:read) zeigt die System-E-Mails, die Ihre Organisation an Mieter versendet hat — Verträge, Abrechnungen, Rechnungen, Mahnungen, Kündigungen, Nachträge, Termine, Protokolle, Entscheidungen und Schadensmeldungen (38 freigegebene Vorlagen, dieselben wie im E-Mail-Verlauf des Mieterportals). Filter: tenant_id, lease_id, location_id (alle Mieter des Standorts), template_key, status (sent, delivered, opened) und der Zeitfilter nach Versandzeitpunkt. Eine Mail hat Betreff, einen Auszug von höchstens 200 Zeichen, Status und Zeitpunkt. Den Volltext als reinen Text (body_text) liefert die API nur mit dem Zusatz-Scope emails:read_body; behandeln Sie ihn beim Anzeigen als Text, nicht als HTML.
Nie enthalten: die Empfängeradresse, Kennungen des Versanddienstes, Fehlertexte, Mails mit Zugangsdaten oder Einmal-Links, Nachrichten an Interessenten und die E-Mails zum API-Zugang selbst (die der Plattform-Betreiber verwaltet). Der Mieterbezug entsteht beim Versand; ältere Mails ohne Bezug erscheinen nicht.
Zugriffsprotokoll lesen
GET /v1/usage (Scope usage:read) zeigt die Abrufe aller Tokens Ihrer Organisation, neueste zuerst: Token, Methode, Pfad (ohne Query-String), Status, Zeitpunkt und request_id. Filter: token_id, status (ein Code wie 404 oder eine Klasse wie 4xx), path_prefix und der Zeitfilter. IP-Adressen, User-Agent und Inhalte der Anfragen erscheinen nie. Abrufe werden 90 Tage aufbewahrt. Bei abgelehnten Anmeldungen bestimmt der Aufrufer den Pfad selbst — zeigen Sie ihn deshalb nie ungeprüft als HTML an.
Übernehmen per API
Mit einem Token mit tenants:import_commit übernehmen Sie einen API-Import ohne den Import-Assistenten: je Datensatz entstehen Mieter, laufender Vertrag mit Kostensatz, Anfangs-Zählerstand und die monatliche Dauerbuchung ab dem nächsten Monatsersten — rückwirkend wird nichts gebucht. Der Ersteller des Tokens braucht in der Verwaltung Datenimport (Bearbeiten) und Vermietungsprozess (Bearbeiten); er steht als Bearbeiter an den angelegten Daten.
Datensätze ohne Garage werden wie im Assistenten über die Vertragsnummer (HH01 42-1 → Einheit 42) zugeordnet. Mit rows übernehmen Sie nur bestimmte Zeilen, ohne rows alle. on_duplicate regelt Datensätze, deren E-Mail schon einem Mieter gehört: reject (Vorgabe) übernimmt sie nicht, reuse hängt den Vertrag an genau diesen Mieter, create_new legt einen neuen Mieter an.
curl -s -X POST "https://api.manage2hero.com/v1/tenant-imports/01923f4e-7a2b-7c3d-8e4f-5a6b7c8d9e0f/commit" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"on_duplicate": "reject"}'Die Antwort nennt je Zeile created (mit tenant_id, lease_id), skipped, failed (mit Code, z. B. not_assigned, unit_occupied) oder already_transferred. Ein wiederholter Aufruf — etwa nach einem Timeout — ist unschädlich: bereits übernommene Zeilen werden nicht erneut angelegt und melden already_transferred mit tenant_id, lease_id und contract_number. Meldet eine Zeile record_changed, wurde sie währenddessen in der Verwaltung geändert; rufen Sie die Übernahme einfach erneut auf. Eine Übernahme ist endgültig. Der Body ist höchstens 64 KB groß (413 payload_too_large).
on_duplicate: reject meldet die Antwort skipped mit duplicate_tenant — im Import (GET /v1/tenant-imports/{id}) bleibt die Zeile aber mit Status pending und error_code: tenant_decision_required stehen. Sie wird nicht als übernommen geführt; entscheiden können Sie sie im Import-Assistenten oder mit einem erneuten Aufruf mit reuse bzw. create_new.Verträge lesen
GET /v1/contracts und GET /v1/contracts/{id} (Scope contracts:read) liefern Verträge mit Status, Laufzeit, Kaution, Unterschriftsstand, Kündigungen und Nachträgen (Strom, Wasser). Der Ersteller des Tokens braucht Vermietungsprozess und Mieter, jeweils mindestens Lesen. Filter: tenant_id, location_id (Verträge an Garagen des Standorts) und status. Mit ?format=csv oder xlsx bekommen Sie die Liste als Datei.
Der Zeitfilter bezieht sich auf die letzte Änderung (updated_at); ohne Angabe gelten die letzten 30 Tage. Für alle Verträge verwenden Sie period=all, für einen Abgleich seit einem Zeitpunkt from=<Zeitpunkt>. updated_at ist das Neueste aus dem Vertrag, seinen Kündigungen, Nachträgen und Unterschriften.
curl -s "https://api.manage2hero.com/v1/contracts?period=last_7_days&status=termination_received" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/contracts?period=last_7_days&status=termination_received" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/contracts?period=last_7_days&status=termination_received', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/contracts?period=last_7_days&status=termination_received",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])GET /v1/contracts/changes/cursor (Einstieg, vor dem Vollabruf), GET /v1/contracts/changes (geänderte Kennungen seit dem Cursor) und GET /v1/contracts/by-ids (aktueller Stand, höchstens 100 Kennungen). Er meldet auch, was ein Zeitfilter nie zeigt: das Zurücksetzen einer Gegenzeichnung, das Löschen einer Kündigung oder eines Nachtrags, gelöschte und zum Löschen vorgemerkte Verträge (die Kennung fehlt dann in by-ids). Ablauf und Fehler (410 cursor_expired nach 90 Tagen) wie bei den übrigen Ressourcen, siehe „Änderungsabgleich“. Das unterschriebene PDF ist über die API nicht abrufbar; Vertragstext, Hashes, Bankdaten und Freitexte der Kündigungen erscheinen nie. Zum Löschen vorgemerkte Verträge fehlen in der Liste.Einheiten und Garagen lesen
GET /v1/units und GET /v1/units/{id} (Scope units:read; der Ersteller braucht Garagen/Einheiten und Standorte, jeweils Lesen) zeigen die einzelnen Stellplätze mit Status, Maßen, Ausstattung, aktuellem Vertrag, Mieter und Grundmiete. Filter: location_id, storage_type_id und status (available, reserved, rented, blocked).
curl -s "https://api.manage2hero.com/v1/units?status=available&limit=200" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/units?status=available&limit=200" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/units?status=available&limit=200', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/units?status=available&limit=200",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Abweichung bei der Liste: Ohne Zeitfilter ist die Liste vollständig (Stammdaten); ein Zeitfilter bezieht sich auf die letzte Änderung der Einheit selbst. Eine abgelaufene, noch nicht freigegebene Reservierung erscheint bereits als available. Der Sperrgrund wird nie ausgegeben.
Der aktuelle Vertrag ist der Vertrag, der heute läuft (Europe/Berlin): Status von „eingezogen“ bis „Abrechnung“, Mietbeginn erreicht. Künftige, beendete und zum Löschen markierte Verträge erscheinen nie. current_rent ist die Grundmiete ohne Nebenkosten, Strom, Wasser und Kaution (net je Monat, vat_rate als Bruch, gross auf Cent gerundet). Es ist null, wenn kein Vertrag läuft oder der Vertrag heute keinen gültigen Kostensatz hat. Vom Mieter sehen Sie nur Kennung und Anzeigename; Details gibt es über GET /v1/tenants/{id}.
updated_at der Einheit.Nebenkosten-Abrechnungen und Zähler
Lesen mit utility:read: GET /v1/utility/statements (Strom- und Wasserabrechnungen je Mieter und Vertrag), GET /v1/meters (Zähler) und GET /v1/meters/{id}/readings (Zählerstände). Der Ersteller braucht Zählerstände, Strom und Wasser, jeweils mindestens Lesen. Alle drei Listen gibt es auch als CSV oder Excel.
Abrechnungen
Filter: year (Jahr, in dem der Abrechnungszeitraum endet), status (finalized, sent, cancelled, je Position), tenant_id, lease_id, location_id und updated_since. Der Zeitfilter bezieht sich auf den Erstellungszeitpunkt; mit year, lease_id oder updated_since ohne Zeitangabe gilt period=all, sonst die letzten 30 Tage. Beträge sind die gespeicherten Werte der Verwaltung; die API rechnet nichts neu. Entwürfe erscheinen nie.
curl -s "https://api.manage2hero.com/v1/utility/statements?year=2025" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/utility/statements?year=2025" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/utility/statements?year=2025', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/utility/statements?year=2025",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])updated_at, dann id) aufsteigend; der Cursor trägt genau dieses Paar. Eine Zeile, die sich beim Blättern ändert, rückt ans Ende und wird noch geliefert. Die Zählerstände bleiben „neueste Ablesung zuerst“. Zähler haben keinen Zeitfilter (Stammdaten).Zählerstand eintragen
POST /v1/meters/{id}/readings (Scope utility:write zusätzlich zu utility:read; der Ersteller braucht Bearbeiten) trägt eine Zwischenablesung ein. Pflicht sind der Kopf Idempotency-Key sowie reading_date (Tag, höchstens ein Tag in der Zukunft) und meter_value (Zahl ab 0). Am Hauptstromzähler ist außerdem obis_code Pflicht (1.8.0, 1.8.1, 1.8.2 Netzbezug; 2.8.0, 2.8.1, 2.8.2 Einspeisung), an allen anderen Zählern ist er nicht erlaubt. Den Vertrag bestimmt die API wie die Verwaltung aus dem Ablesedatum, Ableser ist der Ersteller des Tokens. Ein- und Auszugsprotokolle, Korrekturen und Fotos gehen nicht über die API; deaktivierte Zähler nehmen keine neuen Stände an.
curl -s -X POST "https://api.manage2hero.com/v1/meters/5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a/readings" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data '{"reading_date":"2026-10-01","meter_value":1873.2}'$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = @'
{
"reading_date": "2026-10-01",
"meter_value": 1873.2
}
'@
Invoke-RestMethod -Method Post -Uri "https://api.manage2hero.com/v1/meters/5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a/readings" -Headers $kopf -ContentType "application/json; charset=utf-8" -Body $bodyimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-post-v1-meters-5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a-readings-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/meters/5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a/readings', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
'Content-Type': 'application/json',
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
"reading_date": "2026-10-01",
"meter_value": 1873.2
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport hashlib, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-post-v1-meters-5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a-readings-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.post(
"https://api.manage2hero.com/v1/meters/5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a/readings",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
json={
"reading_date": "2026-10-01",
"meter_value": 1873.2
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Antwort 201 mit der angelegten Ablesung (source: "api"). Eine Wiederholung mit gleichem Schlüssel und gleichem Inhalt liefert 200 mit derselben Ablesung (nichts wird doppelt), anderer Inhalt 409 idempotency_conflict. Der Schlüssel gilt je Token.
Plausibilitätsprüfung wie in der Verwaltung: Ein Stand kleiner als der Vorwert oder größer als ein späterer Stand (chronological_order) und ein Datum in einem bereits abgerechneten Zeitraum (in_billed_period) ergeben 400 validation_error; die Codes stehen in error.details.plausibility. Eine Warnung (unrealistic_consumption) ergibt ebenfalls 400, mit error.details.plausibility_warnings; bestätigen Sie sie mit force_warnings: true (mit demselben oder einem neuen Schlüssel). Wird die Ablesung später in der Verwaltung gelöscht, legt ein Retry mit demselben Schlüssel sie neu an. Ändert sich der Zähler gleichzeitig, prüft die API selbst bis zu dreimal erneut; danach kommt 503 service_unavailable und Sie senden mit demselben Schlüssel erneut.
DATEV-Export anfordern und herunterladen
Mit datev:export stoßen Sie den Buchungsstapel eines Standorts und Monats an. Für den Scope selbst braucht der Ersteller Finanzbuchhaltung (Lesen); der DATEV-Export in der Verwaltung prüft dasselbe Finanzrecht. Das Token anlegen darf nur, wer in der Verwaltung API-Zugang mit Vollzugriff hat (gilt für jedes Token). Es gibt keinen getrennten Zusatz-Scope für den Download; weil die Datei Buchungsdaten enthält, gehört das Token in sichere Hände. Der Export schreibt nichts in die Buchhaltung; festgehalten wird nur der Verlauf.
POST /v1/datev/exportsmitIdempotency-Keyundlocation_id,year(2020–2099),month(1–12), optionalformat(datevals Standard in Windows-1252 odercsvin UTF-8 mit BOM). Der Export läuft sofort; die Antwort 201 zeigtstatus = ready,row_countundfile_available.- Bei
status = failednenntfailure_codeden Grund (z. B. fehlende Berater- oder Mandantennummer am Standort, Buchung ohne sicheren Steuerschlüssel, leerer Stapel). Fordern Sie nach der Korrektur mit einem neuen Schlüssel erneut an. POST /v1/datev/exports/{id}/download-linkliefert einen Link, der 5 Minuten gültig ist (nie länger, als die Datei noch aufbewahrt wird);expires_in_secondsnennt die tatsächliche Dauer. Laden Sie die Datei sofort und beachten Sie den Zeichensatz inencoding.
curl -s -X POST "https://api.manage2hero.com/v1/datev/exports" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data '{"location_id":"6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b","year":2026,"month":9,"format":"datev"}'$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = @'
{
"location_id": "6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b",
"year": 2026,
"month": 9,
"format": "datev"
}
'@
Invoke-RestMethod -Method Post -Uri "https://api.manage2hero.com/v1/datev/exports" -Headers $kopf -ContentType "application/json; charset=utf-8" -Body $bodyimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-post-v1-datev-exports-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/datev/exports', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
'Content-Type': 'application/json',
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
"location_id": "6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b",
"year": 2026,
"month": 9,
"format": "datev"
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport hashlib, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-post-v1-datev-exports-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.post(
"https://api.manage2hero.com/v1/datev/exports",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
json={
"location_id": "6a1f0c2e-4d7b-4b8e-9f10-2c3d4e5f6a7b",
"year": 2026,
"month": 9,
"format": "datev"
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])curl -s -X POST "https://api.manage2hero.com/v1/datev/exports/2f6c1d3e-8a4b-4c5d-9e7f-0a1b2c3d4e5f/download-link" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
Invoke-RestMethod -Method Post -Uri "https://api.manage2hero.com/v1/datev/exports/2f6c1d3e-8a4b-4c5d-9e7f-0a1b2c3d4e5f/download-link" -Headers $kopfimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-post-v1-datev-exports-2f6c1d3e-8a4b-4c5d-9e7f-0a1b2c3d4e5f-download-link-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/datev/exports/2f6c1d3e-8a4b-4c5d-9e7f-0a1b2c3d4e5f/download-link', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport hashlib, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-post-v1-datev-exports-2f6c1d3e-8a4b-4c5d-9e7f-0a1b2c3d4e5f-download-link-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.post(
"https://api.manage2hero.com/v1/datev/exports/2f6c1d3e-8a4b-4c5d-9e7f-0a1b2c3d4e5f/download-link",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Den Stand lesen Sie mit GET /v1/datev/exports/{id}; GET /v1/datev/exports zeigt den Verlauf der per API angeforderten Exporte (Zeitfilter nach Anlage, status, location_id). Exporte aus der Verwaltung erscheinen dort nicht.
file_expires_at), danach ist file_available falsch und der Link ergibt 404 file_not_available; der Verlaufseintrag bleibt. Ist der Export noch nicht fertig, antwortet der Link mit 409 export_not_ready. Jeder Link-Aufruf erzeugt einen neuen Link, zählt als schreibender Aufruf (20 statt 120 je Minute) und sollte nie gespeichert oder weitergegeben werden. Stundenlimit: höchstens 30 neue Exporte je Organisation und Stunde (429 rate_limited, Retry-After: 3600); Wiederholungen mit demselben Schlüssel zählen nicht. Ein Auftrag, der länger als 15 Minuten offen bleibt, gilt als failed mit timeout. Der Export zeigt den aktuellen Stand des Stapels; ein Storno nach dem Export erzeugt keine Korrekturbuchung im bereits exportierten Stapel.Handwerker und Aufträge lesen
Mit contractors:read lesen Sie GET /v1/contractors (Handwerksbetriebe) und GET /v1/contractor-orders (Reparatur-Aufträge je Schadensposition). Der Ersteller braucht Artikel und Handwerker, Garagen/Einheiten und Vermietungsprozess, jeweils mindestens Lesen; fehlt eines, lehnt schon die Token-Erstellung ab. Beide Listen gibt es auch als CSV oder Excel.
curl -s "https://api.manage2hero.com/v1/contractor-orders?updated_since=2026-10-01T00:00:00Z&status=sent" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/contractor-orders?updated_since=2026-10-01T00:00:00Z&status=sent" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/contractor-orders?updated_since=2026-10-01T00:00:00Z&status=sent', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/contractor-orders?updated_since=2026-10-01T00:00:00Z&status=sent",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Die Betriebe haben keinen Zeitfilter (kleine Stammdatenliste, vollständig abrufen), nur active. categories sind die Kategorien der Artikel, die der Betrieb ausführt. Bei den Aufträgen gilt der Zeitfilter für den Anlagezeitpunkt (ohne Angabe 30 Tage); Filter: contractor_id, status (not_sent, sent, failed), location_id, unit_id. Für einen täglichen Abgleich verwenden Sie updated_since allein: Dann gilt kein Anlage-Zeitraum, und ein vor Monaten angelegter, heute versendeter Auftrag erscheint.
updated_since meldet keine gelöschten Aufträge. Rufen Sie deshalb zusätzlich regelmäßig, etwa wöchentlich, mit period=all vollständig ab und entfernen Sie Kennungen, die nicht mehr erscheinen. Nie enthalten sind Adressen, Telefon, E-Mail, Steuer- und Bankdaten der Betriebe sowie Empfängeradresse, Fehlertexte und Anhänge der Aufträge. amount_net ist leer (null) bei Abrechnung nach Aufwand oder ohne Preisangabe; sonst ist es Menge mal Einzelpreis, auf 2 Stellen gerundet, bei einem Einzelpreis von 0 also 0.Schäden lesen, anlegen und den Status setzen
GET /v1/damages und GET /v1/damages/{id} (Scope damages:read) zeigen die Schadensakten:kind unit (an einer Einheit) oder location (am Gelände). Meldungen von Mietern und Hausmeistern erscheinen erst nach der Annahme durch die Verwaltung. Der Zeitfilter gilt für den Meldezeitpunkt (ohne Angabe 30 Tage). Filter: location_id, unit_id, lease_id, tenant_id, status (WEITERE_KLAERUNG, PLANEN_BEAUFTRAGEN, BEAUFTRAGT, ABGESCHLOSSEN, KEINE_REPARATUR), kind, reporter_type und updated_since (mit Zeitzone). Eine unbekannte Kennung als Filter ergibt eine leere Liste.
curl -s "https://api.manage2hero.com/v1/damages?status=WEITERE_KLAERUNG" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/damages?status=WEITERE_KLAERUNG" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/damages?status=WEITERE_KLAERUNG', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/damages?status=WEITERE_KLAERUNG",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])Änderungsabgleich: GET /v1/damages/changes/cursor (Einstieg, vor dem Vollabruf), GET /v1/damages/changes (geänderte Kennungen seit dem Cursor) und GET /v1/damages/by-ids (aktueller Stand, höchstens 100 Kennungen, Listenform ohne documents) melden neue und geänderte Akten, geänderte oder gelöschte Dokumente und Meldungen sowie gelöschte Akten (die Kennung fehlt dann in by-ids). Die Kennungen der Dokumente einer Akte liefert GET /v1/damages/{id}. Für den Vollabruf period=all verwenden.
Mit damages:write (zusätzlich damages:read; der Ersteller braucht Garagen/Einheiten und Vermietungsprozess mit Bearbeiten) legen Sie Schäden an und setzen den Status. Beides verlangt den Kopf Idempotency-Key (je Token, 90 Tage gültig; Body höchstens 64 KB).
curl -s -X POST "https://api.manage2hero.com/v1/damages" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data '{"unit_id":"5b1e7a30-9c42-4d6f-a8e1-0c3f2d4b6a79","damage_type":"dent","damage_date":"2026-10-02","description":"Delle im Rolltor, links unten.","priority":"normal"}'$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = @'
{
"unit_id": "5b1e7a30-9c42-4d6f-a8e1-0c3f2d4b6a79",
"damage_type": "dent",
"damage_date": "2026-10-02",
"description": "Delle im Rolltor, links unten.",
"priority": "normal"
}
'@
Invoke-RestMethod -Method Post -Uri "https://api.manage2hero.com/v1/damages" -Headers $kopf -ContentType "application/json; charset=utf-8" -Body $bodyimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-post-v1-damages-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/damages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
'Content-Type': 'application/json',
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
"unit_id": "5b1e7a30-9c42-4d6f-a8e1-0c3f2d4b6a79",
"damage_type": "dent",
"damage_date": "2026-10-02",
"description": "Delle im Rolltor, links unten.",
"priority": "normal"
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport hashlib, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-post-v1-damages-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.post(
"https://api.manage2hero.com/v1/damages",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
json={
"unit_id": "5b1e7a30-9c42-4d6f-a8e1-0c3f2d4b6a79",
"damage_type": "dent",
"damage_date": "2026-10-02",
"description": "Delle im Rolltor, links unten.",
"priority": "normal"
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])POST /v1/damages legt einen Schaden an einer Einheit an: unit_id, damage_type (paint, dent, oil_stain, hole, scratch, dirt, other), damage_date (nicht vor 2000-01-01, höchstens bis morgen), description (1–5000 Zeichen) und optional priority. Es entsteht dieselbe Akte wie in der Verwaltung, im Status WEITERE_KLAERUNG; die Antwort ist 201, eine Wiederholung 200. Das Ereignis damage.created ist abonnierbar (siehe Webhooks).
curl -s -X PATCH "https://api.manage2hero.com/v1/damages/2f6c1b84-3a9d-4e57-8c10-b7d5e0a9c312/status" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data '{"status":"PLANEN_BEAUFTRAGEN"}'$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = @'
{
"status": "PLANEN_BEAUFTRAGEN"
}
'@
Invoke-RestMethod -Method Patch -Uri "https://api.manage2hero.com/v1/damages/2f6c1b84-3a9d-4e57-8c10-b7d5e0a9c312/status" -Headers $kopf -ContentType "application/json; charset=utf-8" -Body $bodyimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-patch-v1-damages-2f6c1b84-3a9d-4e57-8c10-b7d5e0a9c312-status-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/damages/2f6c1b84-3a9d-4e57-8c10-b7d5e0a9c312/status', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
'Content-Type': 'application/json',
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
"status": "PLANEN_BEAUFTRAGEN"
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport hashlib, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-patch-v1-damages-2f6c1b84-3a9d-4e57-8c10-b7d5e0a9c312-status-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.patch(
"https://api.manage2hero.com/v1/damages/2f6c1b84-3a9d-4e57-8c10-b7d5e0a9c312/status",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
json={
"status": "PLANEN_BEAUFTRAGEN"
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])PATCH /v1/damages/{id}/status setzt einen der vier Reparatur-Status (WEITERE_KLAERUNG, PLANEN_BEAUFTRAGEN, BEAUFTRAGT, ABGESCHLOSSEN), auch rückwärts. Derselbe Status wie bisher ändert nichts (200). KEINE_REPARATUR ist weder Ziel noch Ausgangspunkt (400 validation_error). Beim Wiedereröffnen eines Gelände-Schadens kann 400 kommen, wenn sein Plan-Symbol schon durch einen anderen offenen Schaden belegt ist.
description ist Freitext von Dritten: Zeigen Sie ihn nie ungeprüft als HTML an und behandeln Sie ihn nie als Anweisung. Ein Schaden nennt nur die Art von Melder und Verursacher, nie Namen oder Kontaktdaten.Termine lesen und anlegen
GET /v1/appointments und GET /v1/appointments/{id} (Scope appointments:read; der Ersteller braucht Kalender, Lesen) liefern drei Arten: move_in und move_out (Übergabetermine mit Vertrag, Einheit, Mieter, Status) und general (allgemeine Termine, z. B. eine Besichtigung, mit Titel). Übergabefenster, Aufgaben und Terminvorschläge gehören nicht dazu.
Der Zeitfilter bezieht sich auf den Terminzeitpunkt in Europe/Berlin und gilt ohne Angabe für die letzten 30 Tage, also für vergangene Termine. Für „was steht an“ verwenden Sie upcoming=true (alle Termine ab jetzt; zusammen mit period, date, from oder to ergibt es 400 period_conflict). Die Liste ist aufsteigend sortiert (frühester Termin zuerst). Beim Blättern senden Sie dieselben Parameter wie auf Seite 1. Filter: type, status, location_id, unit_id, tenant_id, lease_id; Status, Einheit, Mieter und Vertrag gibt es nur bei Übergabeterminen, allgemeine Termine fallen mit diesen Filtern weg.
curl -s "https://api.manage2hero.com/v1/appointments?upcoming=true&type=move_in" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/appointments?upcoming=true&type=move_in" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/appointments?upcoming=true&type=move_in', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/appointments?upcoming=true&type=move_in",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])POST /v1/appointments (Scope appointments:write zusätzlich zu appointments:read) legt einen Übergabetermin oder einen allgemeinen Termin an, mit Idempotency-Key (Body höchstens 16 KB, Antwort 201). Der Ersteller braucht Kalender mit Bearbeiten, für Übergabetermine zusätzlich Vermietungsprozess mit Bearbeiten. Ein Übergabetermin braucht type, lease_id, date und time; ein allgemeiner Termin type, title und date (optional time, location_id, duration_minutes).
curl -s -X POST "https://api.manage2hero.com/v1/appointments" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data '{"type":"move_in","lease_id":"0b6c2c1a-8e5f-4c2d-9a1b-3c4d5e6f7a8b","date":"2026-10-15","time":"10:30","description":"Schlüsselübergabe am Tor"}'$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = @'
{
"type": "move_in",
"lease_id": "0b6c2c1a-8e5f-4c2d-9a1b-3c4d5e6f7a8b",
"date": "2026-10-15",
"time": "10:30",
"description": "Schlüsselübergabe am Tor"
}
'@
Invoke-RestMethod -Method Post -Uri "https://api.manage2hero.com/v1/appointments" -Headers $kopf -ContentType "application/json; charset=utf-8" -Body $bodyimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-post-v1-appointments-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/appointments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
'Content-Type': 'application/json',
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
"type": "move_in",
"lease_id": "0b6c2c1a-8e5f-4c2d-9a1b-3c4d5e6f7a8b",
"date": "2026-10-15",
"time": "10:30",
"description": "Schlüsselübergabe am Tor"
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport hashlib, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-post-v1-appointments-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.post(
"https://api.manage2hero.com/v1/appointments",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
json={
"type": "move_in",
"lease_id": "0b6c2c1a-8e5f-4c2d-9a1b-3c4d5e6f7a8b",
"date": "2026-10-15",
"time": "10:30",
"description": "Schlüsselübergabe am Tor"
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])- Uhrzeit ist Ortszeit Europe/Berlin. Eine Uhrzeit, die es bei der Zeitumstellung nicht gibt (die Lücke im Frühjahr), wird abgelehnt (400
validation_error,time: invalid_value). - Datum: nicht in der Vergangenheit. Für heute zählt nur eine Uhrzeit, die noch nicht vorbei ist; ein allgemeiner Termin heute ohne Uhrzeit zählt ab Tagesbeginn und wird abgelehnt. Höchstens 3 Jahre voraus. Verstoß: 400 mit
date: invalid_value. - Kein Umplanen: Gibt es für Vertrag und Art schon einen aktiven Übergabetermin, ergibt das 409
appointment_conflict, auch bei zwei gleichzeitigen Anfragen. Die API legt nur an, sie plant nicht um, sagt nicht ab und löscht nicht. - Hausmeister: Der Standard-Hausmeister des Standorts wird eingetragen. Ist keiner hinterlegt, kommt 400 (
lease_id: invalid_value). Der Hausmeister erscheint nie in der Antwort. - Wie in der Verwaltung rückt der Vertrag bei Einzug auf „Einzugsprotokoll ausstehend“ und bei Auszug auf „Auszugsprotokoll ausstehend“.
- Keine E-Mails und keine Benachrichtigungen: Anders als die Verwaltung benachrichtigt die API weder Hausmeister noch Mieter. Der Termin erscheint im Kalender der Verwaltung und des Hausmeisters.
Interessenten und Warteliste
Mit prospects:read (Vermietungsprozess, Lesen) lesen Sie GET /v1/prospects, GET /v1/prospects/{id} und GET /v1/waitlist. Die Antworten enthalten keine Kontaktdaten: nur Kennung, Status, Herkunft, Zeitpunkte, Name oder Firma, Nutzungsart und die Wünsche (Standort, Garagentyp, Mietbeginn, Strom, Wasser); nie E-Mail, Telefon, Anschrift, Geburtsdatum, Bank- oder Ausweisdaten. Die Warteliste nennt nur Kennungen. Einen Zusatz-Scope für Kontaktdaten gibt es bewusst nicht.
Anfragen: Der Zeitfilter gilt für den Anfragezeitpunkt (ohne Angabe 30 Tage); Filter location_id, storage_type_id, status, source (public_form, tenant_portal, api) und updated_since. Einen Filter unit_id gibt es nicht, denn Anfragen nennen Garagentypen. Die Warteliste zeigt je Eintrag den position (Platz am Standort, 1 = vorn, nur bei waiting und notified); auch hier gilt ohne Angabe der Zeitfilter der letzten 30 Tage, ältere offene Einträge erscheinen nur mit period=all.
curl -s "https://api.manage2hero.com/v1/waitlist?period=all&status=waiting" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/waitlist?period=all&status=waiting" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/waitlist?period=all&status=waiting', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/waitlist?period=all&status=waiting",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])POST /v1/prospects (Scope prospects:write zusätzlich zu prospects:read; der Ersteller braucht Vermietungsprozess mit Bearbeiten) liefert eine Mietanfrage ein, etwa von Ihrer eigenen Webseite. Pflicht sind der Kopf Idempotency-Key und privacy_consent: true; die Pflichtfelder und Prüfungen sind dieselben wie im öffentlichen Formular (Anrede, Name, Geburtsdatum, Anschrift, Telefon oder Mobil, E-Mail, Nutzung, Herkunft, Mietbeginn und 1–5 Standorte mit Wünschen; Strom- und Wasseroption sind optional).
curl -s -X POST "https://api.manage2hero.com/v1/prospects" \
-H "Authorization: Bearer $M2H_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data '{"salutation":"Herr","first_name":"Max","last_name":"Muster","date_of_birth":"1985-06-15","street":"Musterstraße","house_number":"12","postal_code":"20095","city":"Hamburg","country":"Deutschland","mobile":"+49 170 1234567","email":"max.muster@example.com","usage_type":"private","usage_description":"Oldtimer-Einstellplatz","referral_source":"internet","lease_start_date":"2026-11-01","locations":[{"location_id":"5f1e9b0c-3a7d-4c82-b6e4-9d2a8c7f1e05","priority":1,"unit_types":[{"storage_type_id":"a3c8d4e2-6b19-4f7a-8e05-1c9b2d3f4a60","priority":1,"power_option":"normalstrom"}]}],"privacy_consent":true}'$kopf = @{
Authorization = "Bearer $env:M2H_API_TOKEN"
"Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = @'
{
"salutation": "Herr",
"first_name": "Max",
"last_name": "Muster",
"date_of_birth": "1985-06-15",
"street": "Musterstraße",
"house_number": "12",
"postal_code": "20095",
"city": "Hamburg",
"country": "Deutschland",
"mobile": "+49 170 1234567",
"email": "max.muster@example.com",
"usage_type": "private",
"usage_description": "Oldtimer-Einstellplatz",
"referral_source": "internet",
"lease_start_date": "2026-11-01",
"locations": [
{
"location_id": "5f1e9b0c-3a7d-4c82-b6e4-9d2a8c7f1e05",
"priority": 1,
"unit_types": [
{
"storage_type_id": "a3c8d4e2-6b19-4f7a-8e05-1c9b2d3f4a60",
"priority": 1,
"power_option": "normalstrom"
}
]
}
],
"privacy_consent": true
}
'@
Invoke-RestMethod -Method Post -Uri "https://api.manage2hero.com/v1/prospects" -Headers $kopf -ContentType "application/json; charset=utf-8" -Body $bodyimport { createHash, randomUUID } from 'node:crypto';
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
// Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
// Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
const VORGANG = 'auftrag-2026/1001'; // EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
const VORGANG_ID = createHash('sha256').update(VORGANG).digest('hex').slice(0, 16); // als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
const KEY_FILE = `idempotency-key-post-v1-prospects-${VORGANG_ID}.txt`;
const neu = randomUUID();
const tmp = `${KEY_FILE}.${neu}`; // eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
writeFileSync(tmp, neu, { flag: 'wx' }); // erst vollständig in eine temporäre Datei schreiben
try {
linkSync(tmp, KEY_FILE); // veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
} catch (e) {
if (e.code !== 'EEXIST') throw e; // vorhandener Schlüssel bleibt unverändert
} finally {
unlinkSync(tmp);
}
const idempotencyKey = readFileSync(KEY_FILE, 'utf8').trim();
const res = await fetch('https://api.manage2hero.com/v1/prospects', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.M2H_API_TOKEN}`,
'Content-Type': 'application/json',
// Bei jeder Wiederholung (z. B. nach einem Timeout) diesen gespeicherten Schlüssel erneut senden.
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
"salutation": "Herr",
"first_name": "Max",
"last_name": "Muster",
"date_of_birth": "1985-06-15",
"street": "Musterstraße",
"house_number": "12",
"postal_code": "20095",
"city": "Hamburg",
"country": "Deutschland",
"mobile": "+49 170 1234567",
"email": "max.muster@example.com",
"usage_type": "private",
"usage_description": "Oldtimer-Einstellplatz",
"referral_source": "internet",
"lease_start_date": "2026-11-01",
"locations": [
{
"location_id": "5f1e9b0c-3a7d-4c82-b6e4-9d2a8c7f1e05",
"priority": 1,
"unit_types": [
{
"storage_type_id": "a3c8d4e2-6b19-4f7a-8e05-1c9b2d3f4a60",
"priority": 1,
"power_option": "normalstrom"
}
]
}
],
"privacy_consent": true
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport hashlib, os, uuid, requests
from pathlib import Path
# Schlüssel EINMAL erzeugen und VOR dem Senden dauerhaft speichern (hier: Datei, eine je Vorgang). Nach einem
# Neustart wird derselbe Schlüssel gelesen. Für einen neuen, anderen Vorgang einen neuen Dateinamen wählen. Datei erst nach der endgültigen Antwort löschen.
VORGANG = "auftrag-2026/1001" # EIGENE Kennung je Vorgang setzen (z. B. Auftrags-Id); nie für zwei Vorgänge wiederverwenden
VORGANG_ID = hashlib.sha256(VORGANG.encode()).hexdigest()[:16] # als Dateiname gehasht: beliebige Zeichen (/, \ …) in der Kennung sind unkritisch
KEY_FILE = Path(f"idempotency-key-post-v1-prospects-{VORGANG_ID}.txt")
neu = str(uuid.uuid4())
tmp = Path(f"{KEY_FILE}.{neu}") # eindeutiger Name je Versuch, nie eine vorhandene Datei überschreiben
with tmp.open("x") as f:
f.write(neu) # erst vollständig in eine temporäre Datei schreiben
try:
os.link(tmp, KEY_FILE) # veröffentlicht sie atomar, ohne eine vorhandene zu überschreiben
except FileExistsError:
pass # vorhandener Schlüssel bleibt unverändert
finally:
tmp.unlink()
idempotency_key = KEY_FILE.read_text().strip()
res = requests.post(
"https://api.manage2hero.com/v1/prospects",
headers={
"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}",
"Idempotency-Key": idempotency_key,
},
json={
"salutation": "Herr",
"first_name": "Max",
"last_name": "Muster",
"date_of_birth": "1985-06-15",
"street": "Musterstraße",
"house_number": "12",
"postal_code": "20095",
"city": "Hamburg",
"country": "Deutschland",
"mobile": "+49 170 1234567",
"email": "max.muster@example.com",
"usage_type": "private",
"usage_description": "Oldtimer-Einstellplatz",
"referral_source": "internet",
"lease_start_date": "2026-11-01",
"locations": [
{
"location_id": "5f1e9b0c-3a7d-4c82-b6e4-9d2a8c7f1e05",
"priority": 1,
"unit_types": [
{
"storage_type_id": "a3c8d4e2-6b19-4f7a-8e05-1c9b2d3f4a60",
"priority": 1,
"power_option": "normalstrom"
}
]
}
],
"privacy_consent": True
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])- Limits: höchstens 30 neue Anfragen je Token und Stunde und 5 je Token und E-Mail-Adresse in 24 Stunden; sonst 429
rate_limitedmitRetry-After(3600 bzw. 86400). Gezählt werden nur die über dieses Token eingelieferten Anfragen. Dazu gilt das schreibende Kontingent (20 je Minute). Body höchstens 50 KB. - Keine Bestätigungs-Mail: Die API versendet keine E-Mail an die anfragende Person. Die Adresse gilt als nicht bestätigt (
email_verified: false),sourceistapi. Bestätigung und Einwilligungshinweis sind Ihre Aufgabe. - Nicht angenommen:
iban,bic,account_holder,credit_check_consent(400field_not_allowed) sowie Status, Herkunft und Bestätigung. - Gehört die E-Mail-Adresse schon einer anderen Person, wird die Anfrage trotzdem angelegt und in der Verwaltung intern markiert; die Antwort verrät davon nichts. Eine Wiederholung mit demselben Schlüssel liefert 200 mit derselben Anfrage.
Zeitleiste eines Mieters
GET /v1/tenants/{id}/timeline (Scope timeline:read) zeigt die Ereignisse eines Mieters aus E-Mail-Verlauf, Mahnungen, Zahlungen, Dokumenten und Vertrag in einer Liste, neueste zuerst. Der Ersteller des Tokens braucht für den Scope das Recht Mieter (Lesen).
emails:read (E-Mails), dunning:read (Mahnungen), payments:read (Zahlungen), documents:read (Dokumente), contracts:read (Vertrag). Der Ersteller braucht dafür auch die Rechte des jeweiligen Scopes. timeline:read allein liefert 200 mit leerer Liste und sources: [], keinen Fehler. Das Feld sources nennt die Quellen, die für Ihr Token enthalten sind. Die Zusatz-Scopes emails:read_body, payments:read_counterparty und documents:download gelten hier nicht: Die Zeitleiste enthält nur Kurzangaben (Zeitpunkt, Typ, Objekt-Kennung, Bezeichnung, Status), keine Volltexte, Beträge, Gegenparteien, Dateipfade oder Download-Links. Details holen Sie über object_id aus dem Einzel-Endpunkt; ein Ereignis erscheint nur, wenn der Einzel-Endpunkt es für dasselbe Token ebenfalls zeigt.curl -s "https://api.manage2hero.com/v1/tenants/9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/timeline?period=last_3_months" \
-H "Authorization: Bearer $M2H_API_TOKEN"$kopf = @{ Authorization = "Bearer $env:M2H_API_TOKEN" }
Invoke-RestMethod -Uri "https://api.manage2hero.com/v1/tenants/9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/timeline?period=last_3_months" -Headers $kopfconst res = await fetch('https://api.manage2hero.com/v1/tenants/9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/timeline?period=last_3_months', {
headers: { Authorization: `Bearer ${process.env.M2H_API_TOKEN}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const daten = body.data; // hier weiterverarbeitenimport os, requests
res = requests.get(
"https://api.manage2hero.com/v1/tenants/9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/timeline?period=last_3_months",
headers={"Authorization": f"Bearer {os.environ['M2H_API_TOKEN']}"},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"])- Ereignisse und Zeitpunkt (
occurred_at):email_sent(Versand),dunning_notice_issued(Ausstellungsdatum, Beginn des Tages in Europe/Berlin),payment_received(Buchungsdatum, Beginn des Tages in Europe/Berlin),document_added(Eingang in die Mieterakte),contract_created,contract_signed(jüngste Unterschrift des Mieters),contract_countersigned(jüngste Gegenzeichnung, erst wenn alle unterschriebenen Dokumente gegengezeichnet sind),termination_filedundsupplementary_agreement_filed(Eingang einer Kündigung bzw. eines Nachtrags; je Vertrag die jüngsten 50 Kündigungen und 100 Nachträge, wie in/v1/contracts). Mietbeginn und Mietende sind keine Ereignisse. - Zahlungen erscheinen nur, wenn sie dem Mieter über eine gespeicherte Zuordnung zu seinem Vertrag zugeordnet sind, nicht über Name oder IBAN. Eine auf mehrere Mieter verteilte Zahlung steht in jeder beteiligten Zeitleiste.
- Felder:
id(<Typ>:<Zeilen-Id>, eindeutig, zum Entdoppeln),source,type,object_id(bei Kündigung und Nachtrag der Vertrag),title(E-Mail-Betreff, Dateiname, Vertragsnummer; sonstnull) undstatus(Mailstatus, Mahnstufelevel_<n>, Zuordnungsstatus der Zahlung, Dokumentkategorie, Kündigungs- oder Nachtragsstatus; sonstnull). - Zeitfilter auf
occurred_atwie bei den übrigen Listen; ohne Angabe gelten die letzten 30 Tage, für die gesamte Geschichteperiod=all. Weitere Parameter gibt es nicht, auch keine CSV-/Excel-Ausgabe. - Blättern: Die Folgeseiten sind kein Schnappschuss. Ändert sich zwischen zwei Abrufen ein berechneter Zeitpunkt (etwa durch eine neue Unterschrift), kann ein Ereignis fehlen oder doppelt erscheinen; entdoppeln Sie über
idund rufen Sie bei Bedarf neu ab. - Unbekannter oder fremder Mieter: 404
not_found.