Mit der propform-API kannst du deine Formulare, Einsendungen, Statistiken, PDF-Vorlagen, Briefpapiere, Mitarbeiter und Konto-Einstellungen aus externen Anwendungen heraus verwalten – zum Beispiel aus eigenen Tools, Skripten oder Automatisierungsplattformen wie Zapier, Make oder n8n.
Basis-URL:
https://propform.io/api/v1Alle Anfragen und Antworten sind JSON (
Content-Type: application/json). Zeitstempel sind im ISO-8601-Format (z.B.2026-07-07T08:30:00+00:00).
API-Key erstellen: Gehe zu Einstellungen → API-Keys, klicke auf API-Key erstellen und kopiere den angezeigten Key (Format pf_live_…). Er wird dir aus Sicherheitsgründen nur dieses eine Mal angezeigt.
Verbindung testen: Ersetze pf_live_DEIN_KEY und rufe auf:
curl https://propform.io/api/v1/me \
-H "Authorization: Bearer pf_live_DEIN_KEY"
Antwort:
{ "data": { "id": 42, "name": "Max Makler", "email": "max@makler.de" } }
Erste echte Abfrage – deine Formulare auflisten:
curl https://propform.io/api/v1/forms \
-H "Authorization: Bearer pf_live_DEIN_KEY"
Die API nutzt API-Keys als Bearer-Token. Jede Anfrage braucht den Key im Authorization-Header (Authorization: Bearer pf_live_…). Ohne oder mit ungültigem Key antwortet die API mit 401.
Bei Überschreitung antwortet die API mit 429 Too Many Requests und einem Retry-After-Header (Sekunden bis zum nächsten erlaubten Versuch). Baue in Automatisierungen ein kurzes Warten ein, wenn du diesen Status siehst.
Erfolgreiche Antworten liefern die Daten im Feld data.
Listen sind paginiert. Steuere sie über per_page (Standard 25, max. 100) und page. Die Antwort enthält zusätzlich links und meta:
{
"data": [ { "id": 14, "internal_title": "Kontaktformular", "active": true } ],
"links": { "first": "...", "last": "...", "prev": null, "next": "...?page=2" },
"meta": { "current_page": 1, "per_page": 25, "total": 63, "last_page": 3 }
}
Fehler haben immer ein message-Feld; bei Validierungsfehlern (422) stehen die Details pro Feld in errors:
{
"message": "The url field is required.",
"errors": { "url": ["The url field is required."] }
}
| Status | Bedeutung |
|---|---|
200 / 201 |
Erfolg (201 = neu angelegt) |
204 |
Erfolg, keine Antwort-Daten (z.B. Löschen) |
401 |
Kein oder ungültiger API-Key |
403 |
Der Datensatz gehört nicht zu deinem Account (oder Funktion nicht im Tarif) |
404 |
Datensatz existiert nicht |
409 |
Aktion aktuell nicht möglich (z.B. onOffice nicht verbunden) |
422 |
Validierungsfehler – Details im Feld errors |
429 |
Rate-Limit erreicht – Retry-After beachten |
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/forms |
Eigene Formulare auflisten. Filter: active (0/1), template (0/1), updated_since (Datum), folder (Ordner-ID oder none), per_page, page |
POST |
/forms |
Neues Formular anlegen – nur internal_title nötig, startet inaktiv |
GET |
/forms/{id} |
Formular komplett lesen (inkl. Felder, E-Mail- und ChatGPT-Konfiguration) |
PATCH |
/forms/{id} |
Formular konfigurieren – es werden nur die gesendeten Felder geändert |
POST |
/forms/{id}/copy |
Formular kopieren (inkl. Felder, Regeln, E-Mail-Konfiguration); die Kopie startet inaktiv |
Das Formular ist in der API eine Ressource: Auch die Einstellungen des E-Mail-Versands und der ChatGPT-Integration liest und schreibst du direkt am Formular. Die Feldnamen entsprechen exakt den Einstellungen im Formular-Editor.
curl -X PATCH https://propform.io/api/v1/forms/123 \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Neuer Titel", "active": true}'
Wichtige Besonderheiten:
"active": true) funktioniert nur, solange dein Tarif-Limit aktiver Formulare nicht erreicht ist (sonst 422). Deaktivieren geht immer.slug, Zähler und Vorlagen-Flags. Passwörter (z.B. Formular-Passwort) sind schreibbar, tauchen aber nie in Antworten auf.form_template (nur lesbar) zeigt die Freigabe an; mit dem Filter template=0/1 kannst du danach filtern.url des Formulars.Ein Formular hat über 100 Einstellungen (alle, die auch der Editor bietet). Die wichtigsten:
| Feld | Bedeutung |
|---|---|
internal_title |
Interner Name (nur im Dashboard sichtbar) |
title |
Öffentliche Überschrift des Formulars |
custom_slug |
Sprechender URL-Teil (muss in deinem Account eindeutig sein) |
active |
Formular online (true) oder offline (false) |
description |
Einleitungstext über dem Formular |
submit_button_label |
Beschriftung des Absende-Buttons |
thankyou_headline / thankyou_text |
Überschrift und Text der Danke-Seite |
redirect |
Weiterleitungs-URL nach dem Absenden (statt Danke-Seite) |
background_color / accent_color |
Design-Farben (Hex, z.B. #0d6efd) |
subject / body / receiver |
Betreff, Text und Empfänger der Bestätigungs-E-Mail |
send_email_via_onoffice |
Bestätigungs-E-Mail über onOffice versenden |
Das vollständige Objekt siehst du jederzeit über GET /forms/{id}.
👉 Die vollständige Liste aller Felder und Formate findest du in der API-Feldreferenz.
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/forms/{id}/fields |
Felder eines Formulars (sortiert nach Position) |
POST |
/forms/{id}/fields |
Feld anlegen – onoffice_module ist Pflicht, Position automatisch am Ende |
GET |
/fields/{id} |
Einzelnes Feld lesen |
PATCH |
/fields/{id} |
Feld ändern (nur gesendete Eigenschaften) |
DELETE |
/fields/{id} |
Feld löschen |
PATCH |
/forms/{id}/fields/reorder |
Felder neu sortieren – field_ids muss alle Feld-IDs des Formulars in der gewünschten Reihenfolge enthalten |
curl -X POST https://propform.io/api/v1/forms/123/fields \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-H "Content-Type: application/json" \
-d '{"onoffice_module": "address", "onoffice_field_name": "Vorname", "label": "Vorname", "required": true}'
| Feld | Bedeutung |
|---|---|
onoffice_module |
Zielmodul: address, estate, task, calendar, agentsLog – oder relationlist für das Verknüpfungs-Feld (Pflicht beim Anlegen) |
onoffice_field_name |
Ziel-Feldname in onOffice (siehe onOffice-Feldkonfiguration) |
label |
Beschriftung des Felds im Formular |
hint / placeholder |
Hilfetext bzw. Platzhalter im Eingabefeld |
required |
Pflichtfeld (true/false) |
hidden / disabled / read_only |
Versteckt / deaktiviert / nur lesbar |
default_value |
Vorbelegter Wert |
half_size |
Feld auf großen Bildschirmen halbbreit darstellen |
datalist |
Auswahlwerte als Semikolon-Liste (Rot;Grün;Blau) |
display_variant |
Darstellungsvariante des Felds (siehe unten) |
select_option_meta |
Zusatzangaben pro Auswahl-Schlüssel für Karten-/Icon-/Bild-Darstellungen (JSON-String, siehe unten) |
Mit display_variant steuerst du, wie ein Feld im Formular dargestellt wird — der gespeicherte Wert und die onOffice-Anbindung bleiben identisch. null bzw. leer = Standard-Darstellung. Gültige Werte je Feldtyp:
| onOffice-Feldtyp | Werte |
|---|---|
singleselect / user |
radio, buttons, cards, icons, images, likert |
multiselect |
checkboxes, chips, icons, images |
boolean |
checkbox, yesno |
integer |
slider, stars, nps |
float |
slider, stars, nps |
date |
calendar |
Von/Bis-Suchkriterium (integer/float im Modul searchcriteria) |
rangeslider |
Unbekannte oder zum Feldtyp unpassende Werte werden beim Rendern ignoriert (Standard-Darstellung). Für cards, icons und images liefert select_option_meta die Zusatzangaben pro onOffice-Schlüsselwert — ein JSON-String der Form:
{"haus": {"icon": "home", "description": "Freistehend oder Reihenhaus"}, "wohnung": {"image": "https://…/wohnung.jpg"}}
icon ist ein Icon-Name aus dem integrierten Tabler-Icons-Set (nur Kleinbuchstaben/Ziffern/Bindestriche, z.B. home, building-estate, currency-euro), image eine http(s)-Bild-URL (idealerweise aus der Medienbibliothek), description ein Text unter dem Options-Titel (Karten, Icon- und Bild-Kacheln). Icons erscheinen bei Icon-Kacheln, vor dem Options-Label der Button-Gruppe und über den Buttons der Likert-Skala. Leere bzw. unbekannte Angaben werden ignoriert. Beim Schieberegler kommen Minimal-/Maximal-Wert und Intervall aus min_value/max_value/step (auch beim Von/Bis-rangeslider); bei der Bewertung bestimmt max_value die Anzahl der Stufen (2–10, Standard 5).
Einstellungsoptionen je Darstellung (alle optional, unpassende Werte werden beim Rendern ignoriert):
| Eigenschaft | Wirkt bei | Bedeutung |
|---|---|---|
variant_axis_labels |
slider |
Eigene Beschriftungen unter dem Regler, Semikolon-getrennt (z.B. günstig;mittel;teuer); Vorrang vor datalist |
slider_allow_input |
slider, rangeslider |
true = Wert zusätzlich als Zahlenfeld eintippbar |
range_default_von / range_default_bis |
rangeslider + Standard-Von/Bis |
Belegt Von und Bis getrennt vor (Vorrang vor default_value, der beide Seiten gleich füllt); beim Slider werden Voreinstellungen — anders als unberührte Regler — mitgesendet |
scale_label_left / scale_label_right |
stars, nps, likert |
Beschriftung der Skalen-Endpunkte |
rating_symbol |
stars |
stars (Standard), hearts, smileys (Stimmungs-Skala, 2–5 Stufen; darüber Fallback auf Sterne) oder custom (eigenes Icon via rating_custom_icon) |
rating_custom_icon |
stars |
Tabler-Icon-Name für rating_symbol: "custom" (ungültig/leer ⇒ Sterne) |
variant_tile_size |
cards, icons, images |
compact oder large (leer = normal) |
variant_image_ratio |
images |
square oder 16x9 (leer = 4:3) |
variant_image_fit |
images |
contain = ganzes Bild einpassen (leer = füllen/zuschneiden) |
multiselect_min_selected / multiselect_max_selected |
Mehrfachauswahl-Darstellungen | Mindest-/Höchstanzahl wählbarer Optionen (Browser-seitig erzwungen) |
calendar_date_mode |
calendar |
future (nur ab heute) oder past (nur bis heute) |
calendar_min_date / calendar_max_date |
calendar |
Festes frühestes/spätestes Datum (YYYY-MM-DD); Vorrang vor calendar_date_mode |
calendar_disable_weekends |
calendar |
true = Wochenenden sperren |
yesno_yes_label / yesno_no_label |
yesno |
Eigene Button-Beschriftungen (leer = „Ja"/„Nein" in der Account-Sprache) |
yesno_icons |
yesno |
true = Icons auf den Buttons (Standard Haken/Kreuz) |
yesno_yes_icon / yesno_no_icon |
yesno |
Eigene Tabler-Icons statt Haken/Kreuz (wirken nur mit yesno_icons: true) |
variant_columns |
radio, checkboxes |
Listen-Spalten: 1–3 (mobil immer einspaltig) |
variant_centered |
Buttons/Kacheln/Chips/yesno/stars/nps/calendar/Slider |
true = zentriert statt linksbündig (bei Slidern das Eintipp-Feld) |
variant_icon_color |
icons, yesno, buttons, likert |
Eigene Icon-Farbe als Hex (#RRGGBB), leer = erbt die Textfarbe |
Hinweis Mehrfachauswahl: Die Kachel-/Listen-Darstellungen zeigen die Optionen flach, eine Eltern-Kind-Automatik gibt es dort nicht. Der gespeicherte
multiselect_modebleibt beim Setzen einer Darstellungsvariante unverändert und wirkt wieder, sobald du auf die Standard-Darstellung zurückstellst.
Hinweis Slider: Der einfache Schieberegler sendet — wie der Von/Bis-Slider — erst nach einer Interaktion einen Wert; ein unberührter Slider verhält sich wie ein leeres Feld.
Hinweis: Der ältere Range-Slider über
special_field: "range"funktioniert unverändert weiter (Legacy). Für neue Slider empfehlen wirdisplay_variant: "slider"— hat ein Feld beides gesetzt, gewinntdisplay_variant.
Das Verknüpfungs-Feld (verbundene onOffice-Datensätze anzeigen, verbinden, anlegen, bearbeiten, trennen) legst du mit onoffice_module: "relationlist" an. Der Relationstyp besteht aus relation_type (z.B. estate:address:owner) plus relation_anchor_module (estate oder address – welche Seite ist der geladene Formular-Datensatz); dazu kommen die Schalter relation_show_existing, relation_allow_search, relation_allow_create, relation_allow_edit, relation_allow_remove, der Dublettencheck (relation_duplicate_check + relation_duplicate_check_fields) und die Miniformular-Elementliste relation_subform_elements (Array aus {type: "field", name, required, half} bzw. Layout-Elementen {type: "headline"|"description"|"dividingline"|"collapsible", text}). Für die Neuanlage zusätzlich relation_advisor_mode (anchor = Betreuer des geladenen Datensatzes übernehmen, fixed = fester Benutzer aus relation_advisor_user_id) und relation_auto_open_create (Miniformular ohne angezeigte Verbindungen automatisch aufklappen). Alle Werte und Grenzen stehen in der Feld-Referenz.
curl -X POST https://propform.io/api/v1/forms/123/fields \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"onoffice_module": "relationlist",
"relation_type": "estate:address:owner",
"relation_anchor_module": "estate",
"label": "Eigentümer",
"relation_show_existing": true,
"relation_allow_create": true,
"relation_subform_elements": [
{"type": "field", "name": "Vorname", "half": true},
{"type": "field", "name": "Name", "required": true, "half": true},
{"type": "field", "name": "Email", "required": true}
]
}'
Sortieren: Beim reorder bestimmt die Reihenfolge im field_ids-Array die neue Position. Das Array muss exakt alle Feld-IDs des Formulars enthalten (fehlt eine oder ist eine fremde dabei → 422):
curl -X PATCH https://propform.io/api/v1/forms/123/fields/reorder \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-H "Content-Type: application/json" \
-d '{"field_ids": [4712, 4711, 4713]}'
GET /onoffice/fields liefert die Feldkonfiguration deines onOffice-Accounts – pro Modul (address, estate, task, calendar, agentsLog, file, project) alle Felder mit Label, Typ, Länge und erlaubten Werten. So findest du die gültigen Werte für onoffice_field_name. Mit ?module=address filterst du auf ein Modul.
{
"data": {
"address": {
"Vorname": { "label": "Vorname", "type": "varchar", "length": 50, "default": null, "permittedvalues": null },
"Anrede": { "label": "Anrede", "type": "singleselect", "length": null, "default": null,
"permittedvalues": { "HERR": "Herr", "FRAU": "Frau" } }
}
}
}
Voraussetzung ist eine funktionierende onOffice-API-Anbindung; andernfalls antwortet der Endpunkt mit
409.
POST /onoffice/fields/refresh lädt die Feldkonfiguration neu von onOffice – z.B. nachdem du in onOffice ein Feld angelegt oder Auswahlwerte geändert hast. Der Abruf läuft im Hintergrund: Die Antwort ist 202 mit data.status = pending, wenige Sekunden später liefert GET /onoffice/fields den neuen Stand. Beide Endpunkte geben in meta den Zeitpunkt des letzten Aufbaus (updated_at), die Sprache der Feldlabels (locale) und refresh_pending zurück (true, solange ein per API angestoßener Abruf noch läuft). Ohne diesen Aufruf aktualisiert propform die Konfiguration einmal täglich sowie bei jedem Öffnen der Formularübersicht. Limit: 120 Aufrufe pro Stunde.
{
"message": "…",
"data": { "status": "pending", "already_pending": false },
"meta": { "updated_at": "2026-09-08T06:12:41+02:00", "locale": "de", "refresh_pending": true, "last_refresh_failed": false }
}
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/forms/{id}/rules |
Regeln eines Formulars (sortiert nach Priorität) |
PUT |
/forms/{id}/rules |
Alle Regeln des Formulars ersetzen (leeres Array = alle löschen) |
Der Schreib-Endpunkt arbeitet wie der Bedingungs-Editor im Dashboard: Es werden immer alle Regeln auf einmal gespeichert (kein einzelnes Hinzufügen). Lies also erst mit GET, ändere die Liste und schicke sie komplett per PUT zurück. Eine Regel besteht aus clauses (Wenn-Bedingungen, verknüpft mit mode = all/any) und actions (Dann-Aktionen):
curl -X PUT https://propform.io/api/v1/forms/123/rules \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-H "Content-Type: application/json" \
-d '{"rules": [{
"title": "Zusatzfeld einblenden",
"mode": "all", "priority": 10, "is_enabled": 1, "run_on_init": 1,
"clauses": [{"when_field_id": "4711", "operator": "equals", "value": "Ja"}],
"actions": [{"order": 10, "action": "show", "target_field_id": "4712"}]
}]}'
equals, not_equals, empty, not_empty, starts_with, not_starts_with, contains, not_containsshow, hide, enable, disable, require, optional, hide_option, unhide_option, set_value, set_label, make_readonly, unmake_readonly, enable_rules_by_tag, disable_rules_by_tag__form_title, __form_description, __submit, __submit_spinner, __multistep_back, __multistep_next (dort nur set_label, show, hide; beim Spinner nur set_label).403).Details zur Funktionsweise der Regeln findest du unter Bedingungen, Regeln & Berechnungen.
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/forms/{id}/submissions |
Einsendungen eines Formulars. Filter: created_after, created_before, sort (asc/desc), per_page |
GET |
/submissions/{id} |
Einzelne Einsendung |
GET |
/forms/{id}/stats |
Aufrufe, Einsendungen und Conversion-Rate; optional from/to (Datum) |
Der Inhalt einer Einsendung (content) ist nach onOffice-Modulen gruppiert:
{
"data": {
"id": 456, "form_id": 123, "index": 42,
"content": { "address": { "Vorname": "Max", "Name": "Mustermann" }, "estate": { "Id": "131" } },
"ip_address": "203.0.113.7",
"created_at": "2026-07-07T08:30:00+00:00"
}
}
Hast du in den Datenschutz-Einstellungen das Speichern von Einsendungsdaten deaktiviert, ist content immer null – die Einsendung zählt aber trotzdem für die Statistik. Die stats-Antwort enthält views, submissions und conversion_rate.
Statt Einsendungen regelmäßig abzufragen, kannst du dich per Webhook benachrichtigen lassen: propform schickt bei jeder neuen Einsendung sofort einen POST an deine URL.
💡 Webhooks kannst du auch ohne API-Key im Frontend verwalten: in den Einstellungen unter „Webhooks" (für alle Formulare) oder direkt im Formular-Editor unter „Webhooks (Zapier, n8n, Make …)".
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/webhooks |
Deine Webhook-Abos auflisten |
POST |
/webhooks |
Abo anlegen: url (Pflicht), form_id (optional – ohne Angabe gilt das Abo für alle deine Formulare), payload_template (optional – siehe unten), message_key (optional – Feldname der aufgelösten Nachricht, Standard text) |
DELETE |
/webhooks/{id} |
Abo löschen |
Anforderungen an die URL: Sie muss öffentlich erreichbar sein und http:// oder https:// verwenden. Interne oder lokale Adressen (z.B. localhost, 127.0.0.1, private Netz-IPs) werden aus Sicherheitsgründen abgelehnt (422). Da die Einsendungsdaten übertragen werden, verwende möglichst https://.
Die Antwort beim Anlegen enthält das secret, mit dem jede Zustellung signiert wird (du kannst es später jederzeit über GET /webhooks wieder abrufen):
{
"data": {
"id": 7, "event": "submission.created",
"url": "https://example.com/hook", "form_id": 123,
"secret": "a1b2c3…", "payload_template": null, "message_key": "text", "is_active": true,
"failure_count": 0, "last_success_at": null, "last_failure_at": null,
"created_at": "2026-07-07T08:30:00+00:00"
}
}
Jede Zustellung enthält zwei Header:
X-Propform-Event – der Event-Typ (aktuell immer submission.created)X-Propform-Signature – sha256=<HMAC-SHA256 des Request-Bodys mit deinem secret>Beispiel-Payload:
{
"event": "submission.created",
"created_at": "2026-07-07T08:30:00+00:00",
"form": { "id": 123, "slug": "aB3x…", "custom_slug": "kontakt", "title": "…", "internal_title": "…" },
"submission": { "id": 456, "index": 42, "content": { "address": { "Vorname": "Max" } } },
"onoffice_ids": { "address": { "ID": "789" } }
}
payload_template)Optional kannst du pro Abo ein Freitext-Template hinterlegen. propform löst darin nach jeder Formularabsendung onOffice-Makros auf (gegen die Datensätze, die die Einsendung angelegt oder bearbeitet hat) und schickt das Ergebnis als eigenes Nachrichtenfeld im Payload mit — ideal als fertige Nachricht für Slack & Co., ohne dass du im Empfänger-Workflow etwas zusammenbauen musst. Der Feldname ist standardmäßig text (siehe unten).
Beispiel-Template:
Neue Anfrage von _Vorname _Name zu _objekttitel → _getEstateLink
wird im Payload zu:
"text": "Neue Anfrage von Max Mustermann zu Traumwohnung am Stadtpark → https://smart.onoffice.de/…"
Feldname der Nachricht (message_key): Standardmäßig heißt das Feld text – damit verstehen Slack, Microsoft Teams und Google Chat den Payload direkt als Nachricht. Discord erwartet content. Für Zapier/n8n/Make ist der Name beliebig. Du kannst ihn pro Webhook über den Parameter message_key (im Frontend über „Feldname der Nachricht") anpassen.
Zu beachten:
_getAddressLink, _getEstateLink, …)._objekttitel eine verknüpfte Immobilie). Ohne Kontext bleibt das Makro unaufgelöst im Text stehen.Prüfe die Signatur, um sicherzustellen, dass die Anfrage wirklich von propform stammt. Berechne den HMAC über den rohen Request-Body (nicht über das geparste JSON) und vergleiche ihn zeitkonstant.
PHP:
$secret = 'DEIN_WEBHOOK_SECRET';
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PROPFORM_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit('Ungültige Signatur');
}
// ab hier: $body ist echt – json_decode($body, true) verarbeiten
Node.js (Express):
const crypto = require('crypto');
// WICHTIG: express.raw() nutzen, damit der Body unverändert bleibt
app.post('/hook', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.PROPFORM_WEBHOOK_SECRET;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(req.body).digest('hex');
const got = req.header('X-Propform-Signature') || '';
if (expected.length !== got.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got))) {
return res.status(403).send('Ungültige Signatur');
}
const payload = JSON.parse(req.body.toString());
res.sendStatus(200);
});
2xx-Status antworten. Bei Fehlern versucht propform die Zustellung bis zu 3-mal (mit Wartezeit).is_active: false, sichtbar über GET /webhooks). Lege es danach einfach neu an.Ordner strukturieren die Formularübersicht im Dashboard. Jedes Formular liegt in maximal einem Ordner (form_folder_id, null = „Ohne Ordner"). Ordner können eine Unterebene haben (parent_id, maximal zwei Ebenen). Nicht zu verwechseln mit den Formular-Gruppen, die die Kopier-Freigabe steuern.
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/form-folders |
Deine Ordner mit IDs und Formular-Anzahl |
POST |
/form-folders |
Ordner anlegen – name nötig (max. 100 Zeichen), optional parent_id (ID eines Hauptordners → legt einen Unterordner an) |
PATCH |
/form-folders/{id} |
Ordner umbenennen (name) und/oder umhängen (parent_id: ID eines Hauptordners oder null = Hauptordner) |
DELETE |
/form-folders/{id} |
Ordner löschen – die Formulare bleiben bestehen und landen wieder unter „Ohne Ordner", Unterordner werden zu Hauptordnern |
Die Zuordnung setzt du im Formular-PATCH über form_folder_id (ID eines eigenen Ordners oder null). In der Formular-Liste filterst du mit ?folder=<id> bzw. ?folder=none.
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/form-groups |
Deine Gruppen mit IDs, copy-Flag und Formular-Anzahl |
POST |
/form-groups |
Gruppe anlegen – nur name nötig |
PATCH |
/form-groups/{id} |
name und/oder copy ändern (Kopierbarkeit der Formulare dieser Gruppe) |
DELETE |
/form-groups/{id} |
Gruppe löschen – die Formulare bleiben bestehen und verlieren nur die Gruppenzuordnung |
Die Gruppen-IDs weist du im Formular-PATCH als form_groups (Array) zu.
propform kann aus jeder Einsendung ein eigenes PDF erzeugen (siehe PDF-Generator). Die Vorlage eines Formulars – Blockliste, Design und Einstellungen – liest und schreibst du komplett über die API:
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/forms/{id}/pdf-template |
Vorlage lesen (enabled, name, blocks, design, settings); existiert noch keine, wird sie automatisch aus den Formularfeldern vorgeschlagen |
PATCH |
/forms/{id}/pdf-template |
Teilupdate – nur die gesendeten Teile werden geändert |
POST |
/forms/{id}/pdf-template/regenerate |
Blockliste aus dem aktuellen Feldbestand neu erzeugen (Design, Einstellungen und Aktiv-Status bleiben) |
GET |
/forms/{id}/pdf-template/preview |
Echte PDF-Vorschau mit Beispieldaten (application/pdf; max. 10 Abrufe/Minute) |
GET |
/account/pdf-design |
Kontoweites Design-Preset lesen (design + settings) |
PATCH |
/account/pdf-design |
Design-Preset ändern – neue Formular-Vorlagen erben es als Startpunkt |
curl -X PATCH https://propform.io/api/v1/forms/123/pdf-template \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true, "design": {"accent_color": "#0d6efd"}}'
Wichtige Besonderheiten:
regenerate (ersetzt eigene Block-Anpassungen!) oder ergänze die Blöcke gezielt per PATCH.settings.document_signing.enabled ein, sorgt propform automatisch für einen Dokument-Unterschriftsblock in der Vorlage (falls keiner vorhanden ist).blocks ist ein Array aus Blöcken. Jeder Block hat type, eine automatisch vergebene id und typabhängige Eigenschaften (max. 300 Blöcke pro Vorlage):
| Typ | Eigenschaften |
|---|---|
heading |
text (max. 500 Zeichen), level (1, 2 oder 3) |
paragraph |
text (max. 5.000 Zeichen) |
field |
Einzelnes Feld mit Wert: field_id (Feld dieses Formulars) oder onoffice (Referenz, siehe unten), optional label |
fieldlist |
Beschriftungs-Wert-Liste: field_ids = Array aus Feld-IDs und/oder onOffice-Referenzen |
columns |
Zweispaltig: left und right sind jeweils Block-Arrays (eine Ebene tief) |
image |
url (http/https), width_percent (10–100), align (left/center/right), caption |
divider |
style: line (Linie) oder space (Abstand) |
pagebreak |
Seitenumbruch, keine Eigenschaften |
signature |
field_id = Unterschriften-Feld des Formulars oder null = Dokument-Unterschrift nach dem Absenden; label; required (nur bei Dokument-Unterschrift) |
onOffice-Referenzen: In field/fieldlist kannst du statt einer Feld-ID auch ein Feld des verknüpften onOffice-Datensatzes ausgeben: {"module": "address", "name": "Telefon1", "label": "Telefon"} (Module address und estate; das Feld muss in deiner onOffice-Feldkonfiguration existieren).
Beispiel:
{
"blocks": [
{"type": "heading", "text": "Mieterselbstauskunft", "level": 1},
{"type": "paragraph", "text": "Angaben des Interessenten:"},
{"type": "fieldlist", "field_ids": [4711, 4712, {"module": "estate", "name": "objekttitel", "label": "Objekt"}]},
{"type": "pagebreak"},
{"type": "signature", "field_id": null, "label": "Ort, Datum, Unterschrift", "required": true}
]
}
Design-Schlüssel (design): font, heading_font, font_size (7–14), text_color/heading_color/accent_color (Hex), logo_url, logo_position (left/center/right), logo_width_mm (10–90), label_column_width (20–60), header_text, footer_text, show_page_numbers, letterhead_id (siehe Briefpapiere) sowie letterhead_margin_top/right/bottom/left (0–100 mm, null = Ränder des Briefpapiers).
Einstellungs-Schlüssel (settings): empty_fields (hide/dash/blank), embed_uploaded_images, attach_uploaded_pdfs, embed_existing_files, embedded_image_size (small/column/full), document_signing.enabled.
Eigenes Briefpapier (PDF oder Bild) wird als Hintergrund unter jede Seite des erzeugten PDFs gelegt – mit optional eigenem Folgebogen ab Seite 2:
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/letterheads |
Deine Briefpapiere; mit ?form_id= genau die in diesem Formular wählbaren |
POST |
/letterheads |
Hochladen (multipart/form-data, siehe unten) |
PATCH |
/letterheads/{id} |
letterhead_name und Ränder ändern |
DELETE |
/letterheads/{id} |
Löschen – Vorlagen, die es nutzen, rendern danach wieder im Standard-Layout |
Upload-Parameter (POST, multipart): letterhead_name (Pflicht), letterhead_first (Pflicht, PDF/PNG/JPG bis 10 MB – der Erstbogen), letterhead_continuation (optional – der Folgebogen, gleicher Dateityp), letterhead_margin_top/right/bottom/left (optional, mm), form_id (optional – damit wird das Briefpapier formprivat, ohne landet es in der Konto-Bibliothek).
curl -X POST https://propform.io/api/v1/letterheads \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-F "letterhead_name=Firmenbogen" \
-F "letterhead_first=@briefbogen.pdf" \
-F "letterhead_continuation=@folgebogen.pdf"
422 abgelehnt. Bilder mit niedriger Auflösung werden angenommen, die Antwort enthält dann aber einen Hinweis im Feld warning.design.letterhead_id im PDF-Vorlagen-PATCH setzen (Konto-Briefpapiere überall, formprivate nur im eigenen Formular).| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/account |
Konto-Einstellungen gruppiert lesen |
PATCH |
/account |
Teilupdate – nur die gesendeten Gruppen/Felder werden geändert |
Die Antwort ist nach den Einstellungs-Bereichen des Dashboards gruppiert:
| Gruppe | Inhalt | Schreibbar |
|---|---|---|
profile |
name (Profil-/Firmenname) |
✅ |
styling |
Alle default_*-Design-Standards für neue Formulare (Farben, Fonts, Logo, Karten-Optik …) |
✅ |
imprint |
Standard-Impressum: default_privacypolicy_url, default_imprint_url, default_homepage_url, default_imprint_line_1 bis _5 |
✅ |
notifications |
Bis zu 3 Empfänger technischer Benachrichtigungen (notification_email_1–3) + notification_self |
✅ |
statistics |
save_submission_data, save_ips (Datenschutz) |
✅ |
domain |
username (Subdomain), externaldomain, externaldomain_active |
❌ nur lesbar |
staff_area |
enabled, session_days (1–90), seat_limit + active_members (nur lesbar) |
teilweise |
curl -X PATCH https://propform.io/api/v1/account \
-H "Authorization: Bearer pf_live_DEIN_KEY" \
-H "Content-Type: application/json" \
-d '{"styling": {"default_accent_color": "#0d6efd"}, "statistics": {"save_ips": false}}'
domain ist bewusst nur lesbar: Ein Wechsel der Subdomain oder Domain würde alle bereits eingebetteten Formular-Links brechen – das geht nur im Dashboard.staff_area.enabled lässt sich nur aktivieren, wenn dein Tarif Mitarbeiter-Plätze enthält (seat_limit > 0, sonst 422). seat_limit selbst ergibt sich aus dem Tarif und ist nicht schreibbar.notifications werden als kompakte Liste gespeichert: Lücken rücken nach dem Speichern auf.| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/account/onoffice |
Verbindungsstatus und Einstellungen der onOffice-Anbindung |
PATCH |
/account/onoffice |
Einstellungen ändern (max. 5 Anfragen/Minute) |
Die Antwort enthält u.a. api_type (normal/marketplace_api), connection_ok, credentials_set, email_onoffice_api_user (+ _is_functional), field_config_locale und reason_cancellation. API-Token und -Secret tauchen aus Sicherheitsgründen in keiner Antwort auf.
Per PATCH änderbar:
| Feld | Bedeutung |
|---|---|
api_type |
normal (eigene onOffice-API-Zugangsdaten) oder marketplace_api (Marketplace-Anbindung; erfordert einen verbundenen Marketplace-Benutzer) |
email_onoffice_api_user |
E-Mail-Identitäten des API-Benutzers als Array (max. 10) – jede Adresse wird per Testversand geprüft |
email_onoffice_marketplace_api_user |
dito für die Marketplace-Anbindung |
field_config_locale |
Sprache der onOffice-Feldlabels (de, en, …) – der Feldkonfigurations-Cache wird danach im Hintergrund neu aufgebaut |
reason_cancellation |
Eigene Absagegründe als Array (max. 50 Einträge, je max. 40 Zeichen; leer = Standardliste) |
Formulare mit aktiviertem Mitarbeiter-Zugangsschutz sind nur nach Login per Magic-Link erreichbar. Die Mitarbeiter und Gruppen dahinter verwaltest du per API:
| Methode | Endpunkt | Beschreibung |
|---|---|---|
GET |
/staff-members |
Mitarbeiter inkl. Gruppenzuordnung, source (onoffice/manual) und is_active |
POST |
/staff-members |
Externen Mitarbeiter anlegen: name, email, optional groups (Array von Gruppen-IDs) |
PATCH |
/staff-members/{id} |
Teilupdate: name, email (nur manuelle), groups, is_active |
DELETE |
/staff-members/{id} |
Mitarbeiter löschen (beendet auch laufende Sessions) |
POST |
/staff-members/import-onoffice |
onOffice-Benutzer importieren: onoffice_user_ids (Array, optional – ohne Angabe alle importierbaren). Antwort: created, updated |
GET |
/staff-groups |
Gruppen inkl. Mitgliederzahl (onoffice_group_id gesetzt = aus onOffice gespiegelt) |
POST |
/staff-groups |
Lokale Gruppe anlegen (name) |
DELETE |
/staff-groups/{id} |
Gruppe löschen |
staff_area.seat_limit unter /account) wird beim Anlegen, Aktivieren und Importieren hart durchgesetzt (422). Nur aktive Mitarbeiter belegen einen Platz.is_active: false) beendet sofort alle laufenden Sessions des Mitarbeiters.staff_only_enabled, staff_allowed_member_ids und staff_allowed_group_ids im Formular-PATCH; die Konto-Schalter (staff_area.enabled, session_days) liegen unter /account.GET /forms/{id}/submissions?sort=desc und die höchste bereits verarbeitete id merken.GET /me.GET /forms?updated_since=….429 ein kurzes Warten einbauen und den Retry-After-Header respektieren.Die aktuelle Version ist v1 und steht fest im Pfad (/api/v1). Wir fügen abwärtskompatibel neue Felder und Endpunkte hinzu, ohne die Version zu ändern – dein Client sollte unbekannte Felder in Antworten daher einfach ignorieren. Grundlegende, nicht abwärtskompatible Änderungen würden unter einer neuen Version (/api/v2) erscheinen.