Was kann die OnOffice-API?
OnOffice ist ein All-in-One-CRM mit eigener UI, Document-Builder, E-Mail-Modul, Kalender und Aufgaben-Verwaltung. Die REST-API deckt diese Module ab und stellt sie unter https://api.onoffice.de/api/latest/api.php bereit — ein einziger Endpoint, an den du JSON-POST-Requests mit unterschiedlichen actionid-Werten schickst (lesen, schreiben, löschen, etc.).
Die wichtigsten Module für Integrationen:
estate— Immobilien-Objekte mit allen Pflicht- und Marketing-Feldern.address— Personen-Datensätze (Eigentümer, Interessenten, Mieter) inkl. DSGVO-Markierungen.file— Anhänge (Bilder, Grundrisse, Exposés) als Base64-Payload.relation— Verknüpfungen zwischen Estates und Adressen (z. B. „diese Person ist Eigentümer dieses Objekts").task,calendar,agentslog— Workflow-Module für Makler-Tagesgeschäft.fields— Schema-Discovery: welche Felder das CRM für den Account aktiviert hat.
Anders als bei Propstack gibt es keine echten Server-zu-Server-Webhooks — Änderungen erkennst du über das Action-Log oder durch regelmäßiges Diffing. Mehr dazu im Event-Abschnitt.
Zugang: Was du brauchst, bevor du startest
Du brauchst drei Werte, die OnOffice nach Antrag aushändigt:
- API-Token — pro Kunde einmalig, identifiziert deinen Zugang.
- API-Secret — geheim, wird für die HMAC-Signatur gebraucht.
- API-User — der technische Benutzer, dessen Rechte du im OnOffice-Backend rollenbasiert konfigurierst.
Plane bei der Konfiguration im OnOffice-Backend genug Zeit ein: Welche Felder bekommt der API-User zu sehen, welche darf er schreiben, welche Gruppen gehören dazu? Falsch gesetzte Rechte führen zu Status 9-Fehlern, die einem die ersten Stunden Debugging kosten.
HMAC-Signatur in Node.js
OnOffice nutzt eine selbstgebaute Auth-Methode mit folgendem Schema pro Request: Du baust einen sortierten Parameter-Block, hashst zusätzlich den Payload (resourcetype+Parameter), zusammensetzt einen Auth-String mit Timestamp + Token + Resource + Resource-Hash und HMAC-SHA256-signierst das Ganze mit dem Secret. Das Ergebnis (Base64) wandert in den hmac-Parameter, plus eine eindeutige actionid und der frische timestamp.
// onoffice-client.js
import crypto from 'node:crypto';
const TOKEN = process.env.ONOFFICE_TOKEN;
const SECRET = process.env.ONOFFICE_SECRET;
const ENDPOINT = 'https://api.onoffice.de/api/latest/api.php';
function buildHmac(timestamp, resourceType, parameters) {
// 1. Parameter sortiert serialisieren
const sortedParams = Object.keys(parameters).sort()
.map(k => `${k}=${JSON.stringify(parameters[k])}`)
.join('&');
const resourceHash = crypto.createHash('sha256')
.update(`${resourceType}${sortedParams}`)
.digest('hex');
const message = [timestamp, TOKEN, resourceType, resourceHash].join('');
return crypto.createHmac('sha256', SECRET).update(message).digest('base64');
}
export async function onofficeAction({ actionId, resourceType, parameters = {}, identifier = '' }) {
const timestamp = Math.floor(Date.now() / 1000);
const hmac = buildHmac(timestamp, resourceType, parameters);
const body = {
token: TOKEN,
request: {
actions: [{
actionid: actionId,
resourceid: '',
identifier,
resourcetype: resourceType,
timestamp,
hmac,
hmac_version: '2',
parameters,
}],
},
};
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await res.json();
const action = data.response.results[0];
if (action.status.errorcode !== 0) {
throw new Error(`OnOffice ${action.status.errorcode}: ${action.status.message}`);
}
return action.data.records;
}
Falle #1: Die HMAC-Version muss explizit als hmac_version: '2' gesetzt sein. Ohne das Flag rechnet OnOffice mit dem alten v1-Schema, das andere Hash-Reihenfolge nutzt — Folge: Status -10001 ohne hilfreiche Fehlermeldung.
Estates lesen, anlegen, aktualisieren
Estate-Records liest du mit der Action https://api.onoffice.de/api/stable/api.php/read (in unserer Helper-Funktion: actionId: 'urn:onoffice-de-ns:smart:2.5:smartml:action:read'). Du gibst die gewünschten Felder explizit an, um Antwort-Größe zu reduzieren:
// list-estates.js
import { onofficeAction } from './onoffice-client.js';
const estates = await onofficeAction({
actionId: 'urn:onoffice-de-ns:smart:2.5:smartml:action:read',
resourceType: 'estate',
parameters: {
data: ['Id', 'kaufpreis', 'plz', 'ort', 'objekttitel', 'objektnr_extern', 'vermarktungsart', 'objektart'],
filter: {
status: [{ op: '=', val: 1 }], // Status 1 = aktiv
vermarktungsart: [{ op: '=', val: 'kauf' }],
},
listlimit: 200,
listoffset: 0,
sortby: { erstellt_am: 'DESC' },
},
});
console.log(`Gefunden: ${estates.length} aktive Verkaufs-Objekte`);
Schreiben mit create: Du übergibst die Felder direkt im parameters.data-Block. Eine eindeutige externe Referenz (objektnr_extern) als Idempotenz-Anker einbauen, damit du Doppelimporte vermeidest.
// create-estate.js
const created = await onofficeAction({
actionId: 'urn:onoffice-de-ns:smart:2.5:smartml:action:create',
resourceType: 'estate',
parameters: {
data: {
objekttitel: 'Sanierte 3-Zi-Whg Eppendorf',
objektnr_extern: 'EXT-2026-1042',
vermarktungsart: 'kauf',
objektart: 'wohnung',
kaufpreis: 645000,
anzahl_zimmer: 3,
wohnflaeche: 78.5,
plz: '20249',
ort: 'Hamburg',
objektzustand: 'gepflegt',
energieausweistyp: 'verbrauch',
energieverbrauchskennwert: 89,
energieklasse: 'C',
// ... weitere Pflichtfelder je nach Vermarktungsart
},
},
});
console.log('Created Estate ID:', created[0].id);
Adressen-Modul und Verknüpfungen
Eigentümer, Interessenten und Mieter liegen im separaten address-Modul. Eine Adresse mit einer Immobilie verknüpfst du über relation:
// link-owner.js
const owner = await onofficeAction({
actionId: 'urn:onoffice-de-ns:smart:2.5:smartml:action:create',
resourceType: 'address',
parameters: {
data: {
Vorname: 'Anna',
Name: 'Schmidt',
Email: 'a.schmidt@example.com',
Telefon1: '+49 40 123456',
adresstyp: 1, // 1 = Privatperson
artData: 'eigentuemer',
dsgvostatus: 'erklaerung_unterschrieben',
},
},
});
await onofficeAction({
actionId: 'urn:onoffice-de-ns:smart:2.5:smartml:action:create',
resourceType: 'relation',
parameters: {
relationtype: 'urn:onoffice-de-ns:smart:2.5:relationtypes:estate:address:owner',
parentid: estateId,
childid: owner[0].id,
},
});
Achtung beim Feld dsgvostatus: Wenn du Adressen ohne explizite Einwilligung einspielst, riskiert dein Kunde eine Abmahnung. Wir setzen den Status nur, wenn wir ein verifiziertes Opt-In aus dem Quellsystem haben — sonst bleibt das Feld leer und der Makler muss manuell freigeben.
Datei-Upload (Exposé, Fotos, Grundrisse)
Datei-Uploads gehen als Base64-kodierter Inhalt im data.Content-Feld der file-Action. Das macht große Bilder sehr unhandlich — eine Datei von 5 MB wird zu einer ~7-MB-JSON-Payload. Plane Timeouts großzügig (60 s+) und parallelisiere maximal 2-3 Uploads.
// upload-file.js
import fs from 'node:fs/promises';
const buffer = await fs.readFile('./grundriss.pdf');
await onofficeAction({
actionId: 'urn:onoffice-de-ns:smart:2.5:smartml:action:do',
resourceType: 'uploadfile',
parameters: {
data: buffer.toString('base64'),
module: 'estate',
relatedRecordId: estateId,
title: 'Grundriss EG',
Art: 'Grundriss',
file: 'grundriss.pdf',
},
});
Reihenfolge der Bilder steuerst du mit dem Feld oposition in einem nachgelagerten Update — OnOffice nimmt den ersten Datei-Datensatz als Titelbild, wenn nichts anderes konfiguriert ist.
Events und Webhooks
OnOffice hat keine echten Push-Webhooks. Statt das anzuwarten, nutzen wir zwei Tricks für „Near-Realtime"-Sync:
- Action-Log-Polling: Das Modul
agentslogprotokolliert Änderungen pro User. Wir pollen es alle 30–60 Sekunden mit einemletzte_aenderung-Filter — das deckt 90 % der Use-Cases. - Field-Diff: Bei strukturkritischen Migrationen halten wir einen schlanken Snapshot in einer eigenen Datenbank und vergleichen pro Polling-Tick. Aufwendiger, aber zuverlässiger für Pricing- und Adress-Änderungen, die teilweise nicht im Action-Log landen.
Wer eine Bridge nach extern (z. B. zu eigenen Portalen) bauen will, kann das Polling und die Adapter-Logik in einer separaten Worker-Architektur kapseln — den Aufbau haben wir im Detail in Propstack-Webhooks zu Portalen pushen beschrieben.
Stolpersteine in der Praxis
1. Felder, die im UI sichtbar sind, aber per API nicht
OnOffice-Lizenzen unterscheiden zwischen UI-aktivierten Feldern und API-freigeschalteten Feldern. Wenn ein Makler ein Custom-Feld eingerichtet hat, ist es nicht automatisch per API beschreibbar — das muss separat aktiviert werden. fields-Discovery vor dem ersten Import laufen lassen.
2. Multi-Lang-Felder
Bei mehrsprachigen Inseraten erwartet OnOffice die Sprachvariante als Suffix (z. B. objekttitel für DE, objekttitel_engl für EN). Felder ohne Suffix gehen ins Standard-Sprachfeld; falsches Suffix → silent ignore.
3. Status-Codes statt HTTP-Codes
Die API antwortet fast immer mit HTTP 200. Erfolg oder Fehler steht in response.results[].status.errorcode. Wer auf HTTP-Status prüft, sieht keine Fehler. Immer auf die Errorcodes prüfen.
4. Rate-Limit pro Kunde
OnOffice limitiert auf Kunden-Account-Ebene. Ein Token bekommt etwa 20 Requests pro Sekunde — bei größeren Beständen lohnt eine Job-Queue mit Throttle. Bei parallelen Sync-Jobs für mehrere Maklerbüros braucht jeder Mandant ein eigenes Token-Bucket.
5. Bulk-Read braucht Pagination
listlimit max. 500 pro Request. Bei großen Datenbeständen über listoffset paginieren — aber Vorsicht: zwischen zwei Seiten kann sich der Datenbestand ändern. Für deterministische Imports einen Snapshot-Mechanismus einbauen (z. B. Filter auf erstellt_am < SNAPSHOT_TIME).
Wann lohnt sich eine eigene OnOffice-Integration?
Wer als Maklerbüro nur Standard-Workflows abdecken will, kommt mit OnOffice-Bordmitteln (Portal-Anbindungen, Document-Builder, integriertes Marketing) weit. Eine eigene API-Integration lohnt sich, wenn:
- du Daten aus mehreren Quellsystemen (Excel, Altbestand, anderes CRM) konsolidieren willst,
- du eine Multi-Mandanten-SaaS für mehrere Maklerbüros baust,
- du spezielle Reportings, BI-Pipelines oder externe Portale bedienst, die nicht über die Standard-Schnittstellen abgedeckt sind,
- du eine Migration zu oder von OnOffice planst — typisch beim Wechsel von/zu Propstack oder FlowFact.
Wir bei DevNest bauen solche Integrationen für Maklerbüros aller Größen — von der Single-Office-Migration bis zur Bridge zwischen mehreren CRM-Mandanten. Fragen oder konkretes Projekt? Projekt anfragen.
Weitere Themen: ImmoScout24-API anbinden · Propstack-Webhooks zu Portalen pushen · Propstack-Entwicklung · CRM für Immobilien