API de propform (REST)

Con la API de propform puedes gestionar tus formularios, envíos, estadísticas, plantillas PDF, papeles de carta, empleados y la configuración de tu cuenta desde aplicaciones externas, por ejemplo, desde tus propias herramientas, scripts o plataformas de automatización como Zapier, Make o n8n.

> URL base: https://propform.io/api/v1 > > Todas las solicitudes y respuestas están en formato JSON (Content-Type: application/json). Las marcas de tiempo están en formato ISO-8601 (p. ej., 2026-07-07T08:30:00+00:00).

Inicio rápido en 3 pasos

  1. Crear una clave API: Ve a Configuración → Claves API, haz clic en Crear clave API y copia la clave que se muestra (formato pf_live_…). Por motivos de seguridad, solo se te mostrará esta única vez.

  2. Probar la conexión: Sustituye pf_live_DEIN_KEY y accede a:

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

    Respuesta:

    { "data": { "id": 42, "name": "Max Makler", "email": "max@makler.de" } }
    
  3. Primera consulta real: listar tus formularios:

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

Autenticación

La API utiliza claves API como tokens de portador. Cada solicitud debe incluir la clave en el encabezado Authorization (Authorization: Bearer pf_live_…). Si no se incluye la clave o esta no es válida, la API responde con 401.

  • Puedes gestionar las claves en Configuración → Claves API: crearlas, nombrarlas y revocarlas en cualquier momento.
  • Una clave revocada pierde el acceso de inmediato.
  • Se permite un máximo de 10 claves API por cuenta.
  • Trata la clave como si fuera una contraseña: tiene acceso completo a tus formularios y envíos. No la reveles en la interfaz del navegador ni en repositorios públicos.

Límites de frecuencia

  • 60 solicitudes por minuto y por clave.
  • Además, 300 solicitudes por minuto y dirección IP (protección contra sobrecargas).

Si se supera este límite, la API responde con 429 Too Many Requests y un encabezado Retry-After (segundos hasta el siguiente intento permitido). Incorpora una breve espera en las automatizaciones cuando veas este estado.

Formato de respuesta, paginación y errores

Las respuestas correctas proporcionan los datos en el campo data.

Las listas están paginadas. Contrólalas mediante per_page (por defecto 25, máx. 100) y page. La respuesta incluye además links y 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 }
}

Los errores siempre tienen un campo message; en el caso de los errores de validación (422), los detalles de cada campo se encuentran en errors:

{
  "message": "The url field is required.",
  "errors": { "url": ["The url field is required."] }
}
Estado Significado
200 / 201 Éxito (201 = recién creado)
204 Éxito, sin datos de respuesta (p. ej., eliminación)
401 Clave API inexistente o no válida
403 El registro no pertenece a tu cuenta (o la función no está incluida en el plan)
404 El registro no existe
409 Acción no disponible actualmente (p. ej., onOffice no está conectado)
422 Error de validación: detalles en el campo errors
429 Límite de rate alcanzado: ten en cuenta Retry-After

Formularios

Método Punto final Descripción
GET /forms Mostrar una lista de formularios propios. Filtro: active (0/1), template (0/1), updated_since (fecha), folder (ID de carpeta o none), per_page, page
POST /forms Crear un nuevo formulario: solo se necesita internal_title; se inicia inactivo
GET /forms/{id} Leer el formulario completo (incluidos los campos, la configuración del correo electrónico y de ChatGPT)
PATCH /forms/{id} Configurar formulario: solo se modifican los campos enviados
POST /forms/{id}/copy Copiar el formulario (incluidos los campos, las reglas y la configuración del correo electrónico); la copia se inicia inactiva

El formulario es un recurso en la API: también puedes leer y escribir los ajustes de envío de correo electrónico y de la integración con ChatGPT directamente en el formulario. Los nombres de los campos coinciden exactamente con los ajustes del editor de formularios.

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

Características importantes:

  • Semántica PATCH: los campos que no se incluyan en la solicitud permanecerán sin cambios. Por lo tanto, puedes modificar ajustes concretos sin ningún riesgo.
  • Activar ("active": true) solo funciona mientras no se haya alcanzado el límite de formularios activos de tu plan (en caso contrario, 422). Desactivar siempre es posible.
  • Los formularios no se pueden eliminar a través de la API; esto solo es posible, de forma deliberada, en el panel de control.
  • No se pueden modificar, entre otros, slug, los contadores y los indicadores de plantilla. Las contraseñas (por ejemplo, la contraseña del formulario) son modificables, pero nunca aparecen en las respuestas.
  • La lista contiene todos tus formularios, incluidos los compartidos como plantilla para copiar. El campo de solo lectura form_template indica esa liberación; con el filtro template=0/1 puedes filtrar por ello.
  • Cada respuesta del formulario contiene el url público del formulario.

Ajustes de formulario más utilizados

Un formulario tiene más de 100 configuraciones (todas las que ofrece también el editor). Las más importantes:

Campo Significado
internal_title Nombre interno (visible solo en el panel de control)
title Título público del formulario
custom_slug Parte descriptiva de la URL (debe ser única en tu cuenta)
active Formulario en línea (true) o fuera de línea (false)
description Texto introductorio encima del formulario
submit_button_label Texto del botón de envío
thankyou_headline / thankyou_text Título y texto de la página de agradecimiento
redirect URL de redireccionamiento tras el envío (en lugar de la página de agradecimiento)
background_color / accent_color Colores de diseño (hexadecimal, p. ej., #0d6efd)
subject / body / receiver Asunto, texto y destinatario del correo electrónico de confirmación
send_email_via_onoffice Enviar el correo electrónico de confirmación a través de onOffice

Puedes ver el objeto completo en cualquier momento a través de GET /forms/{id}.

👉 Encontrarás la lista completa de todos los campos y formatos en la referencia de campos de la API.

Campos

Método Punto final Descripción
GET /forms/{id}/fields Campos de un formulario (ordenados por posición)
POST /forms/{id}/fields Crear campo – onoffice_module es obligatorio; la posición se coloca automáticamente al final
GET /fields/{id} Leer un campo concreto
PATCH /fields/{id} Modificar campo (solo propiedades enviadas)
DELETE /fields/{id} Eliminar campo
PATCH /forms/{id}/fields/reorder Reordenar campos: field_ids debe contener todos los ID de campo del formulario en el orden deseado
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}'

Propiedades de campo de uso frecuente

Campo Significado
onoffice_module Módulo de destino: address, estate, task, calendar, agentsLog – o relationlist para el Campo de vínculo (obligatorio al crear el campo)
onoffice_field_name Nombre del campo de destino en onOffice (véase Configuración de campos de onOffice)
label Etiqueta del campo en el formulario
hint / placeholder Texto de ayuda o marcador de posición en el campo de entrada
required Campo obligatorio (true/false)
hidden / disabled / read_only Oculto / desactivado / solo de lectura
default_value Valor predefinido
half_size Mostrar el campo a medio ancho en pantallas grandes
datalist Valores de selección como lista separada por punto y coma (Rot;Grün;Blau)
display_variant Variante de presentación del campo (véase más abajo)
select_option_meta Datos adicionales por clave de selección para las presentaciones de tarjetas/iconos/imágenes (cadena JSON, véase más abajo)

Variantes de presentación por API

Con display_variant controlas cómo se presenta un campo en el formulario — el valor guardado y la conexión con onOffice permanecen idénticos. null o vacío = presentación estándar. Valores válidos según el tipo de campo:

Tipo de campo onOffice Valores
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
Criterio de búsqueda de/a (integer/float en el módulo searchcriteria) rangeslider

Los valores desconocidos o que no corresponden al tipo de campo se ignoran al renderizar (presentación estándar). Para cards, icons e images, select_option_meta aporta los datos adicionales por valor clave de onOffice — una cadena JSON con la forma:

{"haus": {"icon": "home", "description": "Independiente o adosada"}, "wohnung": {"image": "https://…/wohnung.jpg"}}

icon es el nombre de un icono del set integrado Tabler Icons (solo minúsculas/dígitos/guiones, p. ej. home, building-estate, currency-euro), image una URL de imagen http(s) (idealmente de la biblioteca de medios), description un texto debajo del título de la opción (tarjetas, mosaicos de iconos e imágenes). Los iconos aparecen en los mosaicos de iconos, delante de la etiqueta de la opción en el grupo de botones y sobre los botones de la escala Likert. Las entradas vacías o desconocidas se ignoran. En el control deslizante, el valor mínimo/máximo y el intervalo provienen de min_value/max_value/step (también en el rangeslider de/a); en la valoración, max_value determina el número de niveles (2–10, por defecto 5).

Opciones de configuración según la presentación (todas opcionales; los valores no adecuados se ignoran al renderizar):

Propiedad Se aplica a Significado
variant_axis_labels slider Etiquetas propias debajo del deslizador, separadas por punto y coma (p. ej. económico;medio;caro); tiene prioridad sobre datalist
slider_allow_input slider, rangeslider true = el valor también se puede escribir en un campo numérico
range_default_von / range_default_bis rangeslider + de/a estándar Preasigna «de» y «a» por separado (tiene prioridad sobre default_value, que rellena ambos lados por igual); en el deslizador, los valores preestablecidos — a diferencia de los controles sin tocar — sí se envían
scale_label_left / scale_label_right stars, nps, likert Etiquetas de los extremos de la escala
rating_symbol stars stars (estándar), hearts, smileys (escala de estado de ánimo, 2–5 niveles; por encima se vuelve a las estrellas) o custom (icono propio mediante rating_custom_icon)
rating_custom_icon stars Nombre de icono Tabler para rating_symbol: "custom" (no válido/vacío ⇒ estrellas)
variant_tile_size cards, icons, images compact o large (vacío = normal)
variant_image_ratio images square o 16x9 (vacío = 4:3)
variant_image_fit images contain = ajustar la imagen completa (vacío = rellenar/recortar)
multiselect_min_selected / multiselect_max_selected presentaciones de selección múltiple Número mínimo/máximo de opciones seleccionables (se impone en el navegador)
calendar_date_mode calendar future (solo desde hoy) o past (solo hasta hoy)
calendar_min_date / calendar_max_date calendar Fecha mínima/máxima fija (YYYY-MM-DD); tiene prioridad sobre calendar_date_mode
calendar_disable_weekends calendar true = bloquear fines de semana
yesno_yes_label / yesno_no_label yesno Etiquetas propias de los botones (vacío = «Sí»/«No» en el idioma de la cuenta)
yesno_icons yesno true = iconos en los botones (estándar: marca de verificación/cruz)
yesno_yes_icon / yesno_no_icon yesno Iconos Tabler propios en lugar de marca de verificación/cruz (solo surten efecto con yesno_icons: true)
variant_columns radio, checkboxes Columnas de la lista: 13 (en móvil siempre una columna)
variant_centered botones/mosaicos/chips/yesno/stars/nps/calendar/deslizadores true = centrado en lugar de alineado a la izquierda (en los deslizadores, el campo de entrada)
variant_icon_color icons, yesno, buttons, likert Color de icono propio en hexadecimal (#RRGGBB); vacío = hereda el color del texto

Nota sobre selección múltiple: Las presentaciones de mosaicos/listas muestran las opciones de forma plana; allí no existe la automática padre-hijo. El multiselect_mode guardado no se modifica al establecer una variante de presentación y vuelve a aplicarse en cuanto regresas a la presentación estándar.

Nota sobre el deslizador: El deslizador simple — igual que el deslizador de/a — solo envía un valor tras una interacción; un deslizador sin tocar se comporta como un campo vacío.

Nota: El range-slider antiguo mediante special_field: "range" sigue funcionando sin cambios (legacy). Para nuevos deslizadores recomendamos display_variant: "slider" — si un campo tiene ambos configurados, gana display_variant.

Campo de vínculo por API

El Campo de vínculo (mostrar, vincular, crear, editar y desvincular registros de onOffice conectados) se crea con onoffice_module: "relationlist". El tipo de relación se compone de relation_type (p. ej. estate:address:owner) más relation_anchor_module (estate o address – qué lado es el registro cargado en el formulario); a esto se añaden los interruptores relation_show_existing, relation_allow_search, relation_allow_create, relation_allow_edit, relation_allow_remove, la comprobación de duplicados (relation_duplicate_check + relation_duplicate_check_fields) y la lista de elementos del miniformulario relation_subform_elements (matriz de {type: "field", name, required, half} o elementos de diseño {type: "headline"|"description"|"dividingline"|"collapsible", text}). Para la creación existen además relation_advisor_mode (anchor = adoptar el responsable del registro cargado, fixed = usuario fijo de relation_advisor_user_id) y relation_auto_open_create (abrir automáticamente el miniformulario cuando no se muestran conexiones). Todos los valores y límites figuran en la referencia de campos.

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": "Propietario",
    "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}
    ]
  }'

Ordenación: En el caso de reorder, el orden en la matriz field_ids determina la nueva posición. La matriz debe contener exactamente todos los ID de campo del formulario (si falta alguno o hay alguno que no pertenezca al formulario → 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]}'

Configuración de campos de onOffice

GET /onoffice/fields proporciona la configuración de campos de tu cuenta de onOffice, por módulo (address, estate, task, calendar, agentsLog, file, project) todos los campos con su etiqueta, tipo, longitud y valores permitidos. Así encontrarás los valores válidos para onoffice_field_name. Con ?module=address puedes filtrar por un módulo.

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

> Es necesario disponer de una conexión a la API de onOffice que funcione correctamente; de lo contrario, el punto final responderá con 409.

POST /onoffice/fields/refresh vuelve a cargar la configuración de campos desde onOffice, por ejemplo después de haber creado un campo o modificado los valores permitidos en onOffice. La actualización se ejecuta en segundo plano: la respuesta es 202 con data.status = pending; unos segundos después, GET /onoffice/fields devuelve el nuevo estado. Ambos puntos finales devuelven un objeto meta con la fecha de la última generación (updated_at), el idioma de las etiquetas de campo (locale) y refresh_pending (true mientras siga en curso una actualización iniciada a través de la API). Sin esta llamada, propform actualiza la configuración una vez al día y cada vez que se abre la vista general de formularios. Límite: 120 llamadas por hora.

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

Condiciones (reglas)

Método Punto final Descripción
GET /forms/{id}/rules Reglas de un formulario (ordenadas por prioridad)
PUT /forms/{id}/rules Reemplazar todas las reglas del formulario (matriz vacía = borrar todas)

El punto final de escritura funciona igual que el editor de condiciones del panel de control: Siempre se guardan todas las reglas de una sola vez (no se añaden de forma individual). Por lo tanto, primero lee con GET, modifica la lista y devuélvela completa mediante PUT. Una regla se compone de clauses (condiciones «Si», vinculadas a mode = all/any) y actions (acciones «entonces»):

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"}]
  }]}'
  • Operadores: equals, not_equals, empty, not_empty, starts_with, not_starts_with, contains, not_contains
  • Acciones: 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
  • Como destino, además de los ID de campo, también se permiten destinos virtuales: __form_title, __form_description, __submit, __submit_spinner, __multistep_back, __multistep_next (en este caso, solo set_label, show, hide; en el spinner, solo set_label).
  • Todos los ID de campo a los que se hace referencia deben pertenecer al formulario.
  • Límites por consulta: máx. 200 reglas, con 50 cláusulas/acciones cada una.
  • Las condiciones, al igual que en el panel de control, solo están disponibles a partir del plan con 10 formularios activos (en caso contrario, 403).

Encontrarás más detalles sobre el funcionamiento de las reglas en Condiciones, reglas y cálculos.

Envíos y estadísticas

Método Punto final Descripción
GET /forms/{id}/submissions Envíos de un formulario. Filtros: created_after, created_before, sort (asc/desc), per_page
GET /submissions/{id} Envío individual
GET /forms/{id}/stats Visitas, envíos y tasa de conversión; opcionalmente from/to (fecha)

El contenido de un envío (content) se agrupa por módulos de 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 has desactivado el almacenamiento de datos de envío en la configuración de privacidad, content siempre será null, pero el envío seguirá contando para las estadísticas. La respuesta stats contiene views, submissions y conversion_rate.

Webhooks (nuevas entradas en tiempo real)

En lugar de consultar las respuestas periódicamente, puedes recibir notificaciones mediante webhooks: propform envía inmediatamente un POST a tu URL cada vez que se recibe una nueva respuesta.

💡 También puedes gestionar los webhooks en el frontend sin clave API: en la configuración, en «Webhooks» (para todos los formularios) o directamente en el editor de formularios, en «Webhooks (Zapier, n8n, Make …)».

Método Punto final Descripción
GET /webhooks Mostrar tus suscripciones a webhooks
POST /webhooks Crear suscripción: url (obligatorio), form_id (opcional; si no se especifica, la suscripción se aplica a todos tus formularios), payload_template (opcional; véase más abajo), message_key (opcional; nombre del campo del mensaje resuelto, por defecto text)
DELETE /webhooks/{id} Eliminar suscripción

Requisitos de la URL: debe ser de acceso público y utilizar http:// o https://. Las direcciones internas o locales (p. ej., localhost, 127.0.0.1, direcciones IP de redes privadas) se rechazan por motivos de seguridad (422). Dado que se transmiten los datos de envío, utiliza, en la medida de lo posible, https://.

La respuesta al crear el evento contiene el secret, con el que se firma cada entrega (puedes recuperarlo más tarde en cualquier momento mediante 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"
  }
}

Cada entrega contiene dos encabezados:

  • X-Propform-Event: el tipo de evento (actualmente siempre es submission.created)
  • X-Propform-Signaturesha256=<HMAC-SHA256 des Request-Bodys mit deinem secret>

Ejemplo de carga útil:

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

Texto libre / mensaje con macros de onOffice (payload_template)

Opcionalmente, puedes guardar una plantilla de texto libre para cada suscripción. Tras cada envío del formulario, propform resuelve en ella las macros de onOffice (con los registros que el envío ha creado o editado) e incluye el resultado como un campo de mensaje propio en la carga útil: ideal como mensaje listo para Slack y similares, sin tener que montar nada en el flujo de trabajo receptor. El nombre del campo es text por defecto (véase más abajo).

Ejemplo de plantilla:

Nueva solicitud de _Vorname _Name sobre _objekttitel → _getEstateLink

se convierte en la carga útil en:

"text": "Nueva solicitud de Max Mustermann sobre Traumwohnung am Stadtpark → https://smart.onoffice.de/…"

Nombre del campo del mensaje (message_key): Por defecto el campo se llama text, así Slack, Microsoft Teams y Google Chat interpretan la carga útil directamente como mensaje. Discord espera content. Para Zapier/n8n/Make el nombre es libre. Puedes cambiarlo por webhook mediante el parámetro message_key (en el frontend, en «Nombre del campo del mensaje»).

Ten en cuenta:

  • Funcionan las mismas macros que en las plantillas de correo electrónico de propform (macros de campos de onOffice, _getAddressLink, _getEstateLink, …).
  • Una macro solo puede resolverse si el envío tiene un contexto de registro adecuado (p. ej., _objekttitel necesita un inmueble vinculado). Sin contexto, la macro permanece sin resolver en el texto.
  • Los valores que se escriben después del envío del webhook (subidas de archivos a onOffice, campos de selección múltiple escritos mediante «ampliar») pueden faltar todavía en el texto resuelto.
  • Sin plantilla de texto libre no se envía ningún campo de mensaje.
  • Máximo 5.000 caracteres.

Comprobar la firma (recomendado)

Comprueba la firma para asegurarte de que la solicitud procede realmente de propform. Calcula el HMAC a partir del cuerpo de la solicitud en bruto (no a partir del JSON analizado) y compáralo en tiempo real.

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

Entrega, repetición y límites

  • Tu punto final debería responder con un estado 2xx. En caso de error, propform intentará la entrega hasta 3 veces (con un tiempo de espera).
  • Si una suscripción falla 20 veces seguidas, se desactivará automáticamente (is_active: false, visible a través de GET /webhooks). A continuación, solo tienes que volver a crearla.
  • Se permiten un máximo de 20 suscripciones a webhooks por cuenta.
  • Los archivos adjuntos no forman parte de la carga útil.

Carpetas de formularios

Las carpetas estructuran la vista general de formularios en el panel. Cada formulario está en una carpeta como máximo (form_folder_id, null = «Sin carpeta»). Las carpetas pueden tener un subnivel (parent_id, dos niveles como máximo). No confundir con los grupos de formularios, que controlan la autorización de copia.

Método Endpoint Descripción
GET /form-folders Tus carpetas con sus ID y el número de formularios
POST /form-folders Crear una carpeta: se necesita name (máx. 100 caracteres), opcionalmente parent_id (ID de una carpeta principal → crea una subcarpeta)
PATCH /form-folders/{id} Renombrar una carpeta (name) y/o moverla (parent_id: ID de una carpeta principal o null = carpeta principal)
DELETE /form-folders/{id} Eliminar una carpeta: los formularios se conservan y vuelven a aparecer en «Sin carpeta»; las subcarpetas pasan a ser carpetas principales

La asignación se establece en el PATCH del formulario mediante form_folder_id (ID de una carpeta propia o null). En la lista de formularios puedes filtrar con ?folder=<id> o ?folder=none.

Grupos de formularios

Método Punto final Descripción
GET /form-groups Tus grupos con sus ID, el indicador copy y el número de formularios
POST /form-groups Crear un grupo: solo se necesita name
PATCH /form-groups/{id} Modificar name y/o copy (posibilidad de copiar los formularios de este grupo)
DELETE /form-groups/{id} Eliminar el grupo: los formularios se conservan y solo pierden la asignación al grupo

Puedes asignar los ID de grupo en el PATCH del formulario como form_groups (matriz).

Plantillas PDF (generador de PDF)

propform puede generar un PDF propio a partir de cada envío (véase Generador de PDF). La plantilla de un formulario —lista de bloques, diseño y ajustes— se puede leer y escribir por completo a través de la API:

Método Punto final Descripción
GET /forms/{id}/pdf-template Leer la plantilla (enabled, name, blocks, design, settings); si todavía no existe, se propone automáticamente a partir de los campos del formulario
PATCH /forms/{id}/pdf-template Actualización parcial: solo se modifican las partes enviadas
POST /forms/{id}/pdf-template/regenerate Volver a generar la lista de bloques a partir del conjunto de campos actual (el diseño, los ajustes y el estado activo se conservan)
GET /forms/{id}/pdf-template/preview Vista previa real en PDF con datos de ejemplo (application/pdf; máx. 10 solicitudes/minuto)
GET /account/pdf-design Leer el preajuste de diseño de la cuenta (design + settings)
PATCH /account/pdf-design Modificar el preajuste de diseño: las nuevas plantillas de formulario lo heredan como punto de partida
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"}}'

Características importantes:

  • La respuesta es siempre el estado normalizado tras el guardado, exactamente lo que mostraría también el editor de plantillas. Las claves desconocidas y las referencias de campo no válidas se descartan silenciosamente: en caso de duda, compara la respuesta con lo que has enviado.
  • La lista de bloques es una instantánea: los campos que añadas por API después de crear la plantilla no se incluyen automáticamente en ella. Utiliza regenerate (¡sustituye tus propios ajustes de bloques!) o añade los bloques de forma selectiva mediante PATCH.
  • Si activas settings.document_signing.enabled, propform se encarga automáticamente de que haya un bloque de firma del documento en la plantilla (si no existe ninguno).

Referencia del modelo de bloques

blocks es una matriz de bloques. Cada bloque tiene type, un id asignado automáticamente y propiedades que dependen del tipo (máx. 300 bloques por plantilla):

Tipo Propiedades
heading text (máx. 500 caracteres), level (1, 2 o 3)
paragraph text (máx. 5.000 caracteres)
field Campo individual con valor: field_id (campo de este formulario) o onoffice (referencia, véase más abajo), label opcional
fieldlist Lista de etiqueta-valor: field_ids = matriz de ID de campo y/o referencias de onOffice
columns Dos columnas: left y right son sendas matrices de bloques (un nivel de profundidad)
image url (http/https), width_percent (10–100), align (left/center/right), caption
divider style: line (línea) o space (espacio)
pagebreak Salto de página, sin propiedades
signature field_id = campo de firma del formulario o null = firma del documento tras el envío; label; required (solo en la firma del documento)

Referencias de onOffice: en field/fieldlist, en lugar de un ID de campo, también puedes mostrar un campo del registro de onOffice vinculado: {"module": "address", "name": "Telefon1", "label": "Teléfono"} (módulos address y estate; el campo debe existir en tu configuración de campos de onOffice).

Ejemplo:

{
  "blocks": [
    {"type": "heading", "text": "Autoevaluación del inquilino", "level": 1},
    {"type": "paragraph", "text": "Datos del interesado:"},
    {"type": "fieldlist", "field_ids": [4711, 4712, {"module": "estate", "name": "objekttitel", "label": "Inmueble"}]},
    {"type": "pagebreak"},
    {"type": "signature", "field_id": null, "label": "Lugar, fecha y firma", "required": true}
  ]
}

Claves de diseño (design): font, heading_font, font_size (7–14), text_color/heading_color/accent_color (hexadecimal), 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 (véase Papeles de carta), así como letterhead_margin_top/right/bottom/left (0–100 mm, null = márgenes del papel de carta).

Claves de ajustes (settings): empty_fields (hide/dash/blank), embed_uploaded_images, attach_uploaded_pdfs, embed_existing_files, embedded_image_size (small/column/full), document_signing.enabled.

Papeles de carta

El papel de carta propio (PDF o imagen) se coloca como fondo debajo de cada página del PDF generado, opcionalmente con una hoja de continuación propia a partir de la página 2:

Método Punto final Descripción
GET /letterheads Tus papeles de carta; con ?form_id= exactamente los seleccionables en ese formulario
POST /letterheads Subir (multipart/form-data, véase más abajo)
PATCH /letterheads/{id} Modificar letterhead_name y los márgenes
DELETE /letterheads/{id} Eliminar: las plantillas que lo utilizan vuelven a renderizarse después con el diseño estándar

Parámetros de subida (POST, multipart): letterhead_name (obligatorio), letterhead_first (obligatorio, PDF/PNG/JPG de hasta 10 MB: la primera hoja), letterhead_continuation (opcional: la hoja de continuación, mismo tipo de archivo), letterhead_margin_top/right/bottom/left (opcional, mm), form_id (opcional: con él, el papel de carta pasa a ser privado del formulario; sin él, se guarda en la biblioteca de la cuenta).

curl -X POST https://propform.io/api/v1/letterheads \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -F "letterhead_name=Papel corporativo" \
  -F "letterhead_first=@briefbogen.pdf" \
  -F "letterhead_continuation=@folgebogen.pdf"
  • Los PDF cifrados o dañados se rechazan con 422. Las imágenes de baja resolución se aceptan, pero la respuesta incluye entonces un aviso en el campo warning.
  • Asignar al formulario: establece design.letterhead_id en el PATCH de plantillas PDF (los papeles de carta de la cuenta en cualquier formulario, los privados solo en su propio formulario).

Configuración de la cuenta

Método Punto final Descripción
GET /account Leer la configuración de la cuenta agrupada
PATCH /account Actualización parcial: solo se modifican los grupos/campos enviados

La respuesta está agrupada según las secciones de configuración del panel de control:

Grupo Contenido Modificable
profile name (nombre del perfil o de la empresa)
styling Todos los valores de diseño predeterminados default_* para nuevos formularios (colores, fuentes, logotipo, aspecto de tarjetas…)
imprint Aviso legal estándar: default_privacypolicy_url, default_imprint_url, default_homepage_url, default_imprint_line_1 a _5
notifications Hasta 3 destinatarios de notificaciones técnicas (notification_email_13) + notification_self
statistics save_submission_data, save_ips (privacidad)
domain username (subdominio), externaldomain, externaldomain_active ❌ solo lectura
staff_area enabled, session_days (1–90), seat_limit + active_members (solo lectura) parcialmente
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 es deliberadamente de solo lectura: un cambio del subdominio o del dominio rompería todos los enlaces de formulario ya incrustados; esto solo es posible en el panel de control.
  • staff_area.enabled solo se puede activar si tu plan incluye plazas de empleados (seat_limit > 0; en caso contrario, 422). El propio seat_limit viene determinado por el plan y no es modificable.
  • La dirección de correo electrónico (inicio de sesión), la contraseña, las claves API, los certificados y los datos de suscripción/pago no forman parte de la API.
  • Las direcciones de correo en notifications se guardan como lista compacta: tras guardar, los huecos vacíos se compactan hacia delante.

Configuración de onOffice

Método Punto final Descripción
GET /account/onoffice Estado de conexión y ajustes de la conexión con onOffice
PATCH /account/onoffice Modificar ajustes (máx. 5 solicitudes/minuto)

La respuesta contiene, entre otros, api_type (normal/marketplace_api), connection_ok, credentials_set, email_onoffice_api_user (+ _is_functional), field_config_locale y reason_cancellation. Por motivos de seguridad, el token y el secret de la API no aparecen en ninguna respuesta.

Modificable mediante PATCH:

Campo Significado
api_type normal (credenciales propias de la API de onOffice) o marketplace_api (conexión Marketplace; requiere un usuario de Marketplace conectado)
email_onoffice_api_user Identidades de correo electrónico del usuario de la API como matriz (máx. 10); cada dirección se comprueba mediante un envío de prueba
email_onoffice_marketplace_api_user Ídem para la conexión Marketplace
field_config_locale Idioma de las etiquetas de campo de onOffice (de, en, …); la caché de configuración de campos se reconstruye después en segundo plano
reason_cancellation Motivos de rechazo propios como matriz (máx. 50 entradas, 40 caracteres cada una; vacío = lista estándar)

Empleados (Área de empleados)

Los formularios con la protección de acceso para empleados activada solo son accesibles tras iniciar sesión mediante un enlace mágico. Los empleados y grupos correspondientes se gestionan a través de la API:

Método Punto final Descripción
GET /staff-members Empleados, incluida la asignación a grupos, source (onoffice/manual) e is_active
POST /staff-members Crear un empleado externo: name, email, opcionalmente groups (matriz de ID de grupo)
PATCH /staff-members/{id} Actualización parcial: name, email (solo manuales), groups, is_active
DELETE /staff-members/{id} Eliminar empleado (también finaliza las sesiones en curso)
POST /staff-members/import-onoffice Importar usuarios de onOffice: onoffice_user_ids (matriz, opcional; si no se indica, todos los importables). Respuesta: created, updated
GET /staff-groups Grupos, incluido el número de miembros (onoffice_group_id establecido = reflejado desde onOffice)
POST /staff-groups Crear un grupo local (name)
DELETE /staff-groups/{id} Eliminar grupo
  • El límite de plazas de tu plan (staff_area.seat_limit en /account) se aplica de forma estricta al crear, activar e importar (422). Solo los empleados activos ocupan una plaza.
  • En los empleados de onOffice, el nombre, el correo electrónico y los grupos proceden de la sincronización diaria con onOffice; por eso, el correo electrónico no se puede modificar a través de la API.
  • Desactivar (is_active: false) finaliza de inmediato todas las sesiones en curso del empleado.
  • Si eliminas un grupo que se utiliza en habilitaciones de formularios, se elimina de ellas; si no queda ninguna habilitación, el formulario no está habilitado para nadie (deliberadamente restrictivo): en ese caso, vuelve a establecer la habilitación.
  • Qué formularios están protegidos y quién puede verlos se controla mediante los campos de formulario staff_only_enabled, staff_allowed_member_ids y staff_allowed_group_ids en el PATCH del formulario; los interruptores de la cuenta (staff_area.enabled, session_days) se encuentran en /account.

Consejos para Zapier, Make y similares

  • Desencadenante «Nuevo envío»: crea una suscripción a webhook (véase más arriba); es más fiable y rápido que las consultas periódicas.
  • Alternativa al sondeo: toma nota de GET /forms/{id}/submissions?sort=desc y del id más alto ya procesado.
  • Prueba de conexión: GET /me.
  • Detectar nuevos formularios: GET /forms?updated_since=….
  • En caso de 429, incorporar una breve espera y respetar el encabezado Retry-After.

Control de versiones

La versión actual es v1 y está fijada en la ruta (/api/v1). Añadimos nuevos campos y puntos finales de forma compatible con versiones anteriores sin cambiar la versión; por lo tanto, tu cliente debería simplemente ignorar los campos desconocidos en las respuestas. Los cambios fundamentales que no sean compatibles con versiones anteriores aparecerían bajo una nueva versión (/api/v2).