API de propform (REST)

Con la API de propform puedes gestionar tus formularios, envíos y estadísticas 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), updated_since (fecha), 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.
  • 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 (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)

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.

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.

Grupos de formularios

GET /form-groups muestra una lista de tus grupos de formularios con sus ID. Puedes asignar los ID en el PATCH del formulario como form_groups (matriz).

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).