API propform (REST)

L'API propform vous permet de gérer vos formulaires, vos soumissions, vos statistiques, vos modèles PDF, vos papiers à en-tête, vos collaborateurs et les paramètres de votre compte à partir d'applications externes, par exemple à partir de vos propres outils, scripts ou plateformes d'automatisation telles que Zapier, Make ou n8n.

> URL de base : https://propform.io/api/v1 > > Toutes les requêtes et réponses sont au format JSON (Content-Type: application/json). Les horodatages sont au format ISO-8601 (par exemple, 2026-07-07T08:30:00+00:00).

Démarrage rapide en 3 étapes

  1. Créer une clé API : Rendez-vous dans Paramètres → Clés API, cliquez sur Créer une clé API et copiez la clé affichée (format pf_live_…). Pour des raisons de sécurité, elle ne s'affichera qu'une seule fois.

  2. Tester la connexion : Remplacez pf_live_DEIN_KEY et accédez à :

    curl https://propform.io/api/v1/me \
      -H "Authorization: Bearer pf_live_DEIN_KEY"
    

    Réponse :

    { "data": { "id": 42, "name": "Max Makler", "email": "max@makler.de" } }
    
  3. Première requête réelle – lister vos formulaires :

   curl https://propform.io/api/v1/forms \
     -H "Authorization: Bearer pf_live_DEIN_KEY"

Authentification

L’API utilise des clés API comme jetons « bearer ». Chaque requête doit comporter la clé dans l’en-tête Authorization (Authorization: Bearer pf_live_…). En l’absence de clé ou si celle-ci n’est pas valide, l’API répond par 401.

  • Vous gérez vos clés sous Paramètres → Clés API : vous pouvez en créer, les nommer et les révoquer à tout moment.
  • Une clé révoquée perd immédiatement son accès.
  • Chaque compte peut disposer d’un maximum de 10 clés API.
  • Traitez la clé comme un mot de passe : elle donne un accès complet à vos formulaires et à vos soumissions. Ne la divulguez pas dans l’interface utilisateur du navigateur ni dans des dépôts publics.

Limites de débit

  • 60 requêtes par minute et par clé.
  • En outre, 300 requêtes par minute et par adresse IP (protection contre la surcharge).

En cas de dépassement, l’API renvoie 429 Too Many Requests et un en-tête Retry-After (nombre de secondes jusqu’à la prochaine tentative autorisée). Intégrez un court délai d’attente dans vos automatisations lorsque vous constatez ce statut.

Format de réponse, pagination et erreurs

Les réponses réussies fournissent les données dans le champ data.

Les listes sont paginées. Contrôlez-les via per_page (par défaut 25, max. 100) et page. La réponse contient également les champs links et 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 }
}

Les erreurs comportent toujours un champ message ; en cas d’erreurs de validation (422), les détails par champ figurent dans errors :

{
  "message": "The url field is required.",
  "errors": { "url": ["The url field is required."] }
}
Statut Signification
200 / 201 Succès (201 = nouvellement créé)
204 Succès, aucune donnée de réponse (par ex. suppression)
401 Clé API manquante ou non valide
403 L'enregistrement n'appartient pas à votre compte (ou la fonctionnalité n'est pas incluse dans votre forfait)
404 L'enregistrement n'existe pas
409 Action actuellement impossible (par ex. onOffice non connecté)
422 Erreur de validation – détails dans le champ errors
429 Limite de requêtes atteinte – voir Retry-After

Formulaires

Méthode Point de terminaison Description
GET /forms Lister ses propres formulaires. Filtre : active (0/1), template (0/1), updated_since (date), folder (ID de dossier ou none), per_page, page
POST /forms Créer un nouveau formulaire – seul internal_title est nécessaire, le formulaire est initialement inactif
GET /forms/{id} Lire le formulaire dans son intégralité (y compris les champs, la configuration de l'e-mail et de ChatGPT)
PATCH /forms/{id} Configurer le formulaire – seuls les champs envoyés sont modifiés
POST /forms/{id}/copy Copier le formulaire (y compris les champs, les règles et la configuration des e-mails) ; la copie est désactivée au démarrage

Dans l’API, le formulaire est une ressource : vous pouvez également lire et modifier les paramètres d’envoi d’e-mails et d’intégration de ChatGPT directement dans le formulaire. Les noms des champs correspondent exactement aux paramètres de l’éditeur de formulaire.

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}'

Particularités importantes :

  • Sémantique PATCH : les champs que vous n’incluez pas dans la requête restent inchangés. Vous pouvez donc modifier certains paramètres en toute sécurité.
  • La fonction Activer ("active": true) ne fonctionne que tant que la limite de formulaires actifs de votre forfait n’est pas atteinte (sinon, utilisez 422). La désactivation est toujours possible.
  • Les formulaires ne peuvent pas être supprimés via l’API – cela n’est délibérément possible que dans le tableau de bord.
  • Les éléments non modifiables sont notamment slug, les compteurs et les indicateurs de modèle. Les mots de passe (par exemple, le mot de passe du formulaire) sont modifiables, mais n’apparaissent jamais dans les réponses.
  • La liste contient tous vos formulaires – y compris ceux partagés comme modèle à copier. Le champ en lecture seule form_template indique ce partage ; le filtre template=0/1 permet de filtrer selon ce critère.
  • Chaque réponse au formulaire contient le url public du formulaire.

Paramètres de formulaire fréquemment utilisés

Un formulaire dispose de plus de 100 paramètres (tous ceux proposés par l’éditeur). Les plus importants :

Champ Signification
internal_title Nom interne (visible uniquement dans le tableau de bord)
title Titre public du formulaire
custom_slug Partie descriptive de l’URL (doit être unique dans votre compte)
active Formulaire en ligne (true) ou hors ligne (false)
description Texte d'introduction au-dessus du formulaire
submit_button_label Libellé du bouton d'envoi
thankyou_headline / thankyou_text Titre et texte de la page de remerciement
redirect URL de redirection après l'envoi (à la place de la page de remerciement)
background_color / accent_color Couleurs de mise en page (hexadécimal, par ex. #0d6efd)
subject / body / receiver Objet, texte et destinataire de l'e-mail de confirmation
send_email_via_onoffice Envoyer l'e-mail de confirmation via onOffice

Vous pouvez consulter à tout moment l'objet complet via GET /forms/{id}.

👉 Vous trouverez la liste complète de tous les champs et formats dans la référence des champs de l'API.

Champs

Méthode Point de terminaison Description
GET /forms/{id}/fields Champs d’un formulaire (triés par position)
POST /forms/{id}/fields Créer un champ – onoffice_module est obligatoire, la position est automatiquement ajoutée à la fin
GET /fields/{id} Lire un champ individuel
PATCH /fields/{id} Modifier un champ (propriétés envoyées uniquement)
DELETE /fields/{id} Supprimer un champ
PATCH /forms/{id}/fields/reorder Réorganiser les champs – field_ids doit contenir tous les identifiants de champ du formulaire dans l'ordre souhaité
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}'

Propriétés de champ fréquemment utilisées

Champ Signification
onoffice_module Module de destination : address, estate, task, calendar, agentsLog – ou relationlist pour le Champ de lien (obligatoire lors de la création)
onoffice_field_name Nom du champ cible dans onOffice (voir Configuration des champs onOffice)
label Libellé du champ dans le formulaire
hint / placeholder Texte d'aide ou espace réservé dans le champ de saisie
required Champ obligatoire (true/false)
hidden / disabled / read_only Masqué / désactivé / en lecture seule
default_value Valeur prédéfinie
half_size Afficher le champ en demi-largeur sur les grands écrans
datalist Valeurs de sélection sous forme de liste séparée par des points-virgules (Rot;Grün;Blau)
display_variant Variante de présentation du champ (voir ci-dessous)
select_option_meta Données supplémentaires par clé de sélection pour les présentations cartes/icônes/images (chaîne JSON, voir ci-dessous)

Variantes de présentation via l'API

Avec display_variant, vous contrôlez comment un champ est présenté dans le formulaire — la valeur enregistrée et la connexion onOffice restent identiques. null ou vide = présentation standard. Valeurs valides selon le type de champ :

Type de champ onOffice Valeurs
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
Critère de recherche de/à (integer/float dans le module searchcriteria) rangeslider

Les valeurs inconnues ou ne correspondant pas au type de champ sont ignorées lors du rendu (présentation standard). Pour cards, icons et images, select_option_meta fournit les données supplémentaires par valeur-clé onOffice — une chaîne JSON de la forme :

{"haus": {"icon": "home", "description": "Individuelle ou mitoyenne"}, "wohnung": {"image": "https://…/wohnung.jpg"}}

icon est le nom d'une icône du set intégré Tabler Icons (uniquement minuscules/chiffres/tirets, p. ex. home, building-estate, currency-euro), image une URL d'image http(s) (idéalement issue de la bibliothèque de médias), description un texte sous le titre de l'option (cartes, tuiles icônes et images). Les icônes apparaissent sur les tuiles d'icônes, devant le libellé de l'option dans le groupe de boutons et au-dessus des boutons de l'échelle de Likert. Les entrées vides ou inconnues sont ignorées. Pour le curseur, la valeur minimale/maximale et l'intervalle proviennent de min_value/max_value/step (également pour le rangeslider de/à) ; pour la notation, max_value détermine le nombre de niveaux (2–10, 5 par défaut).

Options de réglage selon la présentation (toutes facultatives ; les valeurs non adaptées sont ignorées lors du rendu) :

Propriété S'applique à Signification
variant_axis_labels slider Libellés personnalisés sous le curseur, séparés par des points-virgules (p. ex. abordable;moyen;cher) ; priorité sur datalist
slider_allow_input slider, rangeslider true = la valeur peut aussi être saisie dans un champ numérique
range_default_von / range_default_bis rangeslider + de/à standard Préremplit « de » et « à » séparément (priorité sur default_value, qui remplit les deux côtés de la même manière) ; sur le curseur, les valeurs prédéfinies — contrairement aux poignées non touchées — sont envoyées
scale_label_left / scale_label_right stars, nps, likert Libellés des extrémités de l'échelle
rating_symbol stars stars (standard), hearts, smileys (échelle d'humeur, 2–5 niveaux ; au-delà, retour aux étoiles) ou custom (icône personnalisée via rating_custom_icon)
rating_custom_icon stars Nom d'icône Tabler pour rating_symbol: "custom" (invalide/vide ⇒ étoiles)
variant_tile_size cards, icons, images compact ou large (vide = normal)
variant_image_ratio images square ou 16x9 (vide = 4:3)
variant_image_fit images contain = ajuster l'image entière (vide = remplir/recadrer)
multiselect_min_selected / multiselect_max_selected présentations à sélection multiple Nombre minimal/maximal d'options sélectionnables (imposé côté navigateur)
calendar_date_mode calendar future (à partir d'aujourd'hui uniquement) ou past (jusqu'à aujourd'hui uniquement)
calendar_min_date / calendar_max_date calendar Date minimale/maximale fixe (YYYY-MM-DD) ; priorité sur calendar_date_mode
calendar_disable_weekends calendar true = bloquer les week-ends
yesno_yes_label / yesno_no_label yesno Libellés de boutons personnalisés (vide = « Oui »/« Non » dans la langue du compte)
yesno_icons yesno true = icônes sur les boutons (coche/croix par défaut)
yesno_yes_icon / yesno_no_icon yesno Icônes Tabler personnalisées au lieu de coche/croix (n'agissent qu'avec yesno_icons: true)
variant_columns radio, checkboxes Colonnes de liste : 13 (toujours une seule colonne sur mobile)
variant_centered boutons/tuiles/chips/yesno/stars/nps/calendar/curseurs true = centré au lieu d'aligné à gauche (pour les curseurs, le champ de saisie)
variant_icon_color icons, yesno, buttons, likert Couleur d'icône personnalisée en hexadécimal (#RRGGBB) ; vide = hérite de la couleur du texte

Remarque sélection multiple : Les présentations en tuiles/listes affichent les options à plat ; il n'y a pas d'automatisme parent-enfant à cet endroit. Le multiselect_mode enregistré reste inchangé lorsque tu définis une variante de présentation et s'applique de nouveau dès que tu reviens à la présentation standard.

Remarque curseur : Le curseur simple — comme le curseur de/à — n'envoie une valeur qu'après une interaction ; un curseur non touché se comporte comme un champ vide.

Remarque : L'ancien range-slider via special_field: "range" continue de fonctionner sans changement (legacy). Pour les nouveaux curseurs, nous recommandons display_variant: "slider" — si un champ a les deux configurés, display_variant l'emporte.

Champ de lien via l'API

Le Champ de lien (afficher, lier, créer, modifier et dissocier des enregistrements onOffice liés) se crée avec onoffice_module: "relationlist". Le type de relation se compose de relation_type (p. ex. estate:address:owner) plus relation_anchor_module (estate ou address – quel côté correspond à l'enregistrement chargé dans le formulaire) ; s'y ajoutent les commutateurs relation_show_existing, relation_allow_search, relation_allow_create, relation_allow_edit, relation_allow_remove, le contrôle des doublons (relation_duplicate_check + relation_duplicate_check_fields) et la liste des éléments du mini-formulaire relation_subform_elements (tableau de {type: "field", name, required, half} ou d'éléments de mise en page {type: "headline"|"description"|"dividingline"|"collapsible", text}). Pour la création s'ajoutent relation_advisor_mode (anchor = reprendre le responsable de l'enregistrement chargé, fixed = utilisateur fixe de relation_advisor_user_id) et relation_auto_open_create (ouvrir automatiquement le mini-formulaire lorsqu'aucune connexion n'est affichée). Toutes les valeurs et limites figurent dans la référence des champs.

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": "Propriétaire",
    "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}
    ]
  }'

Tri : pour reorder, l'ordre dans le tableau field_ids détermine la nouvelle position. Le tableau doit contenir exactement tous identifiants de champ du formulaire (s’il en manque un ou s’il y en a un qui n’appartient pas au formulaire → 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]}'

Configuration des champs onOffice

GET /onoffice/fields fournit la configuration des champs de votre compte onOffice – par module (address, estate, task, calendar, agentsLog, file, project) tous les champs avec leur libellé, leur type, leur longueur et les valeurs autorisées. Cela te permet de trouver les valeurs valides pour onoffice_field_name. Avec ?module=address, tu peux filtrer par module.

{
  "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" } }
    }
  }
}

> Une connexion API onOffice opérationnelle est requise ; dans le cas contraire, le point de terminaison renvoie 409.

POST /onoffice/fields/refresh recharge la configuration des champs depuis onOffice, par exemple après avoir créé un champ ou modifié des valeurs autorisées dans onOffice. L'actualisation s'exécute en arrière-plan : la réponse est 202 avec data.status = pending ; quelques secondes plus tard, GET /onoffice/fields renvoie le nouvel état. Les deux points de terminaison renvoient un objet meta contenant la date de la dernière génération (updated_at), la langue des libellés de champs (locale) et refresh_pending (true tant qu'une actualisation déclenchée via l'API est encore en cours). Sans cet appel, propform actualise la configuration une fois par jour et à chaque ouverture de l'aperçu des formulaires. Limite : 120 appels par heure.

{
  "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 }
}

Conditions (règles)

Méthode Point de terminaison Description
GET /forms/{id}/rules Règles d’un formulaire (triées par ordre de priorité)
PUT /forms/{id}/rules Remplacer toutes les règles du formulaire (tableau vide = tout supprimer)

Le point de terminaison d’écriture fonctionne comme l’éditeur de conditions dans le tableau de bord : toutes les règles sont toujours enregistrées en une seule fois (pas d’ajout individuel). Commencez donc par lire avec GET, modifiez la liste, puis renvoyez-la dans son intégralité via PUT. Une règle se compose de clauses (conditions « si », liées par mode = all/any) et actions (actions « alors ») :

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"}]
  }]}'
  • Opérateurs : equals, not_equals, empty, not_empty, starts_with, not_starts_with, contains, not_contains
  • Promotions : show, 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
  • Outre les identifiants de champ, les destinations virtuelles sont également autorisées : __form_title, __form_description, __submit, __submit_spinner, __multistep_back, __multistep_next (dans ce cas, uniquement set_label, show, hide ; pour le spinner, uniquement set_label).
  • Tous les identifiants de champ référencés doivent appartenir au formulaire.
  • Limites par requête : 200 règles au maximum, 50 clauses/actions chacune.
  • Comme dans le tableau de bord, les conditions ne sont disponibles qu’à partir du forfait comprenant 10 formulaires actifs (sinon 403).

Vous trouverez plus de détails sur le fonctionnement des règles sous Conditions, règles et calculs.

Soumissions et statistiques

Méthode Point de terminaison Description
GET /forms/{id}/submissions Soumissions d’un formulaire. Filtres : created_after, created_before, sort (asc/desc), per_page
GET /submissions/{id} Soumission unique
GET /forms/{id}/stats Visites, soumissions et taux de conversion ; en option from/to (date)

Le contenu d'un envoi (content) est regroupé par modules onOffice :

{
  "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"
  }
}

Si vous avez désactivé l'enregistrement des données de soumission dans les paramètres de confidentialité, content correspond toujours à null – mais le formulaire est tout de même pris en compte dans les statistiques. La réponse stats contient views, submissions et conversion_rate.

Webhooks (nouvelles soumissions en temps réel)

Au lieu d'interroger régulièrement les soumissions, tu peux te faire notifier via un webhook : propform envoie immédiatement un POST à ton URL à chaque nouvelle soumission.

💡 Vous pouvez également gérer les webhooks dans l'interface, sans clé API : dans les paramètres, sous « Webhooks » (pour tous les formulaires) ou directement dans l'éditeur de formulaires, sous « Webhooks (Zapier, n8n, Make …) ».

Méthode Point de terminaison Description
GET /webhooks Lister vos abonnements aux webhooks
POST /webhooks Créer un abonnement : url (obligatoire), form_id (facultatif – si non spécifié, l'abonnement s'applique à tous vos formulaires), payload_template (facultatif – voir ci-dessous), message_key (facultatif – nom du champ du message résolu, par défaut text)
DELETE /webhooks/{id} Supprimer un abonnement

Exigences relatives à l'URL : elle doit être accessible au public et utiliser http:// ou https://. Les adresses internes ou locales (par exemple localhost, 127.0.0.1, adresses IP de réseaux privés) sont refusées pour des raisons de sécurité (422). Étant donné que les données d'envoi sont transmises, utilisez si possible https://.

La réponse lors de la création contient le secret, avec lequel chaque envoi est signé (tu peux le récupérer à tout moment par la suite via GET /webhooks) :

{
  "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"
  }
}

Chaque envoi contient deux en-têtes :

  • X-Propform-Event – le type d’événement (actuellement toujours submission.created)
  • X-Propform-Signaturesha256=<HMAC-SHA256 des Request-Bodys mit deinem secret>

Exemple de charge utile :

{
  "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" } }
}

Texte libre / message avec macros onOffice (payload_template)

En option, vous pouvez enregistrer un modèle de texte libre pour chaque abonnement. Après chaque envoi de formulaire, propform y résout les macros onOffice (à partir des enregistrements créés ou modifiés par la soumission) et transmet le résultat dans la charge utile en tant que champ de message dédié — idéal comme message prêt à l'emploi pour Slack & co., sans rien devoir assembler dans votre workflow destinataire. Le champ s'appelle text par défaut (voir ci-dessous).

Exemple de modèle :

Nouvelle demande de _Vorname _Name concernant _objekttitel → _getEstateLink

devient dans la charge utile :

"text": "Nouvelle demande de Max Mustermann concernant Traumwohnung am Stadtpark → https://smart.onoffice.de/…"

Nom du champ du message (message_key) : Par défaut, le champ s'appelle text – ainsi Slack, Microsoft Teams et Google Chat interprètent la charge utile directement comme un message. Discord attend content. Pour Zapier/n8n/Make, le nom est libre. Vous pouvez le modifier par webhook via le paramètre message_key (dans l'interface, sous « Nom du champ du message »).

À noter :

  • Les mêmes macros que dans les modèles d'e-mails de propform fonctionnent (macros de champs onOffice, _getAddressLink, _getEstateLink, …).
  • Une macro ne peut être résolue que si la soumission dispose du contexte d'enregistrement correspondant (p. ex. _objekttitel nécessite un bien immobilier lié). Sans contexte, la macro reste non résolue dans le texte.
  • Les valeurs qui ne sont écrites qu'après l'envoi du webhook (téléversements de fichiers vers onOffice, champs à sélection multiple écrits via « étendre ») peuvent encore manquer dans le texte résolu.
  • Sans modèle de texte libre, aucun champ de message n'est envoyé.
  • 5 000 caractères au maximum.

Vérification de la signature (recommandée)

Vérifiez la signature pour vous assurer que la requête provient bien de propform. Calculez le HMAC à partir du corps de la requête brut (et non à partir du JSON analysé) et comparez-le en temps réel.

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);
});

Livraison, tentatives et limites

  • Votre point de terminaison doit répondre avec un statut 2xx. En cas d’erreur, propform tente la livraison jusqu’à 3 fois (avec un délai d’attente).
  • Si un abonnement échoue 20 fois de suite, il est automatiquement désactivé (is_active: false, visible via GET /webhooks). Il suffit ensuite de le recréer.
  • Chaque compte peut disposer d’un maximum de 20 abonnements Webhook.
  • Les pièces jointes ne font pas partie de la charge utile.

Dossiers de formulaires

Les dossiers structurent la vue d'ensemble des formulaires dans le tableau de bord. Chaque formulaire se trouve dans un dossier au maximum (form_folder_id, null = « Sans dossier »). Les dossiers peuvent avoir un sous-niveau (parent_id, deux niveaux au maximum). À ne pas confondre avec les groupes de formulaires, qui contrôlent l'autorisation de copie.

Méthode Endpoint Description
GET /form-folders Vos dossiers avec leurs identifiants et le nombre de formulaires
POST /form-folders Créer un dossier – name est nécessaire (100 caractères max.), parent_id optionnel (ID d'un dossier principal → crée un sous-dossier)
PATCH /form-folders/{id} Renommer un dossier (name) et/ou le déplacer (parent_id : ID d'un dossier principal ou null = dossier principal)
DELETE /form-folders/{id} Supprimer un dossier – les formulaires sont conservés et réapparaissent sous « Sans dossier », les sous-dossiers deviennent des dossiers principaux

L'affectation se fait dans le PATCH du formulaire via form_folder_id (ID d'un de vos dossiers ou null). Dans la liste des formulaires, filtrez avec ?folder=<id> ou ?folder=none.

Groupes de formulaires

Méthode Point de terminaison Description
GET /form-groups Vos groupes avec leurs identifiants, l'indicateur copy et le nombre de formulaires
POST /form-groups Créer un groupe – seul name est nécessaire
PATCH /form-groups/{id} Modifier name et/ou copy (possibilité de copier les formulaires de ce groupe)
DELETE /form-groups/{id} Supprimer le groupe – les formulaires sont conservés et perdent uniquement leur affectation au groupe

Vous attribuez les identifiants de groupe dans le PATCH du formulaire sous la forme form_groups (tableau).

Modèles PDF (générateur de PDF)

propform peut générer son propre PDF à partir de chaque soumission (voir Générateur de PDF). Vous lisez et modifiez entièrement le modèle d'un formulaire – liste de blocs, design et paramètres – via l'API :

Méthode Point de terminaison Description
GET /forms/{id}/pdf-template Lire le modèle (enabled, name, blocks, design, settings) ; s'il n'en existe pas encore, il est automatiquement proposé à partir des champs du formulaire
PATCH /forms/{id}/pdf-template Mise à jour partielle – seules les parties envoyées sont modifiées
POST /forms/{id}/pdf-template/regenerate Régénérer la liste de blocs à partir des champs actuels du formulaire (le design, les paramètres et le statut actif sont conservés)
GET /forms/{id}/pdf-template/preview Véritable aperçu PDF avec des données d'exemple (application/pdf ; 10 requêtes/minute max.)
GET /account/pdf-design Lire le préréglage de design du compte (design + settings)
PATCH /account/pdf-design Modifier le préréglage de design – les nouveaux modèles de formulaire en héritent comme point de départ
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"}}'

Particularités importantes :

  • La réponse correspond toujours à l'état normalisé après l'enregistrement – exactement ce que l'éditeur de modèles afficherait. Les clés inconnues et les références de champ non valides y sont écartées silencieusement : en cas de doute, comparez la réponse avec ce que vous avez envoyé.
  • La liste de blocs est un instantané : les champs que vous ajoutez via l'API après la création du modèle n'y sont pas intégrés automatiquement. Utilisez regenerate (remplace vos propres adaptations de blocs !) ou complétez les blocs de manière ciblée via PATCH.
  • Si vous activez settings.document_signing.enabled, propform veille automatiquement à ce que le modèle contienne un bloc de signature de document (s'il n'en existe pas encore).

Référence du modèle de blocs

blocks est un tableau de blocs. Chaque bloc possède un type, un id attribué automatiquement et des propriétés dépendant du type (300 blocs au maximum par modèle) :

Type Propriétés
heading text (500 caractères max.), level (1, 2 ou 3)
paragraph text (5 000 caractères max.)
field Champ individuel avec sa valeur : field_id (champ de ce formulaire) ou onoffice (référence, voir ci-dessous), label facultatif
fieldlist Liste libellé-valeur : field_ids = tableau d'identifiants de champ et/ou de références onOffice
columns Deux colonnes : left et right sont chacun des tableaux de blocs (un seul niveau de profondeur)
image url (http/https), width_percent (10–100), align (left/center/right), caption
divider style : line (ligne) ou space (espacement)
pagebreak Saut de page, aucune propriété
signature field_id = champ de signature du formulaire ou null = signature du document après l'envoi ; label ; required (uniquement pour la signature de document)

Références onOffice : dans field/fieldlist, vous pouvez, au lieu d'un identifiant de champ, afficher un champ de l'enregistrement onOffice lié : {"module": "address", "name": "Telefon1", "label": "Téléphone"} (modules address et estate ; le champ doit exister dans votre configuration des champs onOffice).

Exemple :

{
  "blocks": [
    {"type": "heading", "text": "Déclaration du locataire", "level": 1},
    {"type": "paragraph", "text": "Informations fournies par le candidat :"},
    {"type": "fieldlist", "field_ids": [4711, 4712, {"module": "estate", "name": "objekttitel", "label": "Bien"}]},
    {"type": "pagebreak"},
    {"type": "signature", "field_id": null, "label": "Lieu, date, signature", "required": true}
  ]
}

Clés de design (design) : font, heading_font, font_size (7–14), text_color/heading_color/accent_color (hexadécimal), 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 (voir Papiers à en-tête) ainsi que letterhead_margin_top/right/bottom/left (0–100 mm, null = marges du papier à en-tête).

Clés de paramètres (settings) : empty_fields (hide/dash/blank), embed_uploaded_images, attach_uploaded_pdfs, embed_existing_files, embedded_image_size (small/column/full), document_signing.enabled.

Papiers à en-tête

Votre propre papier à en-tête (PDF ou image) est placé en arrière-plan sous chaque page du PDF généré – avec, en option, une feuille suivante distincte à partir de la page 2 :

Méthode Point de terminaison Description
GET /letterheads Vos papiers à en-tête ; avec ?form_id=, exactement ceux sélectionnables dans ce formulaire
POST /letterheads Téléverser (multipart/form-data, voir ci-dessous)
PATCH /letterheads/{id} Modifier letterhead_name et les marges
DELETE /letterheads/{id} Supprimer – les modèles qui l'utilisent sont ensuite à nouveau rendus dans la mise en page standard

Paramètres de téléversement (POST, multipart) : letterhead_name (obligatoire), letterhead_first (obligatoire, PDF/PNG/JPG jusqu'à 10 Mo – la première feuille), letterhead_continuation (facultatif – la feuille suivante, même type de fichier), letterhead_margin_top/right/bottom/left (facultatif, mm), form_id (facultatif – le papier à en-tête est alors réservé à ce formulaire ; sans indication, il rejoint la bibliothèque du compte).

curl -X POST https://propform.io/api/v1/letterheads \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -F "letterhead_name=Papier entreprise" \
  -F "letterhead_first=@briefbogen.pdf" \
  -F "letterhead_continuation=@folgebogen.pdf"
  • Les PDF chiffrés ou endommagés sont refusés avec 422. Les images en basse résolution sont acceptées, mais la réponse contient alors une remarque dans le champ warning.
  • Affectation au formulaire : définissez design.letterhead_id dans le PATCH du modèle PDF (les papiers à en-tête du compte partout, ceux réservés à un formulaire uniquement dans leur propre formulaire).

Paramètres du compte

Méthode Point de terminaison Description
GET /account Lire les paramètres du compte, regroupés par domaine
PATCH /account Mise à jour partielle – seuls les groupes/champs envoyés sont modifiés

La réponse est regroupée selon les sections de paramètres du tableau de bord :

Groupe Contenu Modifiable
profile name (nom de profil/d'entreprise)
styling Tous les standards de design default_* pour les nouveaux formulaires (couleurs, polices, logo, style de carte …)
imprint Mentions légales par défaut : default_privacypolicy_url, default_imprint_url, default_homepage_url, default_imprint_line_1 à _5
notifications Jusqu'à 3 destinataires des notifications techniques (notification_email_13) + notification_self
statistics save_submission_data, save_ips (protection des données)
domain username (sous-domaine), externaldomain, externaldomain_active ❌ lecture seule
staff_area enabled, session_days (1–90), seat_limit + active_members (lecture seule) en partie
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 est volontairement en lecture seule : un changement de sous-domaine ou de domaine casserait tous les liens de formulaire déjà intégrés – cela n'est possible que dans le tableau de bord.
  • staff_area.enabled ne peut être activé que si votre forfait comprend des places de collaborateurs (seat_limit > 0, sinon 422). seat_limit lui-même découle du forfait et n'est pas modifiable.
  • L'adresse e-mail (connexion), le mot de passe, les clés API, les certificats et les données d'abonnement/de paiement ne font pas partie de l'API.
  • Les emplacements d'e-mail dans notifications sont enregistrés sous forme de liste compacte : les emplacements vides sont resserrés après l'enregistrement.

Configuration onOffice

Méthode Point de terminaison Description
GET /account/onoffice Statut de connexion et paramètres de l'intégration onOffice
PATCH /account/onoffice Modifier les paramètres (5 requêtes/minute max.)

La réponse contient notamment api_type (normal/marketplace_api), connection_ok, credentials_set, email_onoffice_api_user (+ _is_functional), field_config_locale et reason_cancellation. Pour des raisons de sécurité, le token et le secret de l'API n'apparaissent dans aucune réponse.

Modifiable via PATCH :

Champ Signification
api_type normal (vos propres identifiants API onOffice) ou marketplace_api (intégration Marketplace ; nécessite un utilisateur Marketplace connecté)
email_onoffice_api_user Identités e-mail de l'utilisateur API sous forme de tableau (max. 10) – chaque adresse est vérifiée par un envoi de test
email_onoffice_marketplace_api_user idem pour l'intégration Marketplace
field_config_locale Langue des libellés de champs onOffice (de, en, …) – le cache de configuration des champs est ensuite reconstruit en arrière-plan
reason_cancellation Vos propres motifs de refus sous forme de tableau (50 entrées max., 40 caractères chacune ; vide = liste standard)

Collaborateurs (espace collaborateurs)

Les formulaires dont la protection d'accès collaborateurs est activée ne sont accessibles qu'après connexion via un lien e-mail (magic link). Vous gérez les collaborateurs et les groupes correspondants via l'API :

Méthode Point de terminaison Description
GET /staff-members Collaborateurs avec leur affectation aux groupes, source (onoffice/manual) et is_active
POST /staff-members Créer un collaborateur externe : name, email, facultativement groups (tableau d'identifiants de groupe)
PATCH /staff-members/{id} Mise à jour partielle : name, email (collaborateurs manuels uniquement), groups, is_active
DELETE /staff-members/{id} Supprimer un collaborateur (met également fin aux sessions en cours)
POST /staff-members/import-onoffice Importer des utilisateurs onOffice : onoffice_user_ids (tableau, facultatif – sans indication, tous les utilisateurs importables). Réponse : created, updated
GET /staff-groups Groupes avec leur nombre de membres (onoffice_group_id renseigné = miroir d'un groupe onOffice)
POST /staff-groups Créer un groupe local (name)
DELETE /staff-groups/{id} Supprimer un groupe
  • La limite de places de votre forfait (staff_area.seat_limit sous /account) est strictement appliquée lors de la création, de l'activation et de l'import (422). Seuls les collaborateurs actifs occupent une place.
  • Pour les collaborateurs onOffice, le nom, l'e-mail et les groupes proviennent de la synchronisation quotidienne avec onOffice – l'e-mail n'est donc pas modifiable via l'API.
  • Désactiver (is_active: false) met immédiatement fin à toutes les sessions en cours du collaborateur.
  • Si vous supprimez un groupe utilisé dans des partages de formulaires, il en est retiré ; s'il ne reste aucun partage, le formulaire n'est partagé avec personne (volontairement restrictif) – redéfinissez alors le partage.
  • Vous contrôlez quels formulaires sont protégés et qui peut les voir via les champs de formulaire staff_only_enabled, staff_allowed_member_ids et staff_allowed_group_ids dans le PATCH du formulaire ; les réglages au niveau du compte (staff_area.enabled, session_days) se trouvent sous /account.

Conseils pour Zapier, Make & Co.

  • Déclencheur « Nouvelle soumission » : créer un abonnement au webhook (voir ci-dessus) – plus fiable et plus rapide que les requêtes régulières.
  • Alternative à l’interrogation régulière : noter GET /forms/{id}/submissions?sort=desc et le id le plus élevé déjà traité.
  • Test de connexion : GET /me.
  • Détection de nouveaux formulaires : GET /forms?updated_since=….
  • En cas de 429, prévoir un court délai d’attente et respecter l’en-tête Retry-After.

Gestion des versions

La version actuelle est v1 et est définie de manière fixe dans le chemin d'accès (/api/v1). Nous ajoutons de nouveaux champs et points de terminaison de manière rétrocompatible sans modifier la version ; votre client doit donc simplement ignorer les champs inconnus dans les réponses. Les modifications fondamentales non rétrocompatibles apparaîtraient sous une nouvelle version (/api/v2).