Solicitudes
Crear un formulario y una solicitud en una sola llamada
Un agente que habla por teléfono con una clienta descubre que aún tiene que subir dos nóminas, pagar una tasa de 40 EUR y reservar una llamada. Envía el formulario y la solicitud en la misma llamada, y Formstep se encarga del resto.
Cuándo usarlo
Normalmente, una solicitud apunta a un formulario que alguien creó en el editor y publicó. Cuando las preguntas dependen del caso, el
llamante las envía con la solicitud: requests.create acepta form en lugar de formId. Formstep crea
un formulario normal a partir de los bloques, lo publica y crea la solicitud en un solo paso, así que existen los dos o ninguno. Lo
llamamos formulario integrado.
Sigue usando formId para un formulario que envías a muchas personas con la misma forma y quieres diseñar a mano en el editor.
Usa form cuando una automatización decide qué preguntar.
La llamada
curl -X POST https://api.formstep.io/api/v1 \
-H "Authorization: Bearer $FORMSTEP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"method":"requests.create","params":{
"form":{
"name":"Documents and fee for your mortgage application",
"logo":{"type":"image","url":"https://acme.example/logo.png"},
"cover":{"type":"color","color":"#0f4c81"},
"emoji":"🏦",
"blocks":[
{"type":"paragraph","content":"Upload the two documents and pay the processing fee."},
{"type":"file","key":"payslips","title":"Your last two payslips","maxFiles":2},
{"type":"payment","key":"fee","title":"Processing fee","amount":40,"currency":"EUR"},
{"type":"scheduleAppointment","key":"call","title":"Pick a time for our call","eventType":"15 min call"}]},
"recipient":{"email":"ada@example.com","name":"Ada"},
"delivery":"email",
"idempotencyKey":"call-8841"}}'Todo lo demás de la solicitud conserva su significado: recipient, prefill, context,
readonly, delivery, los recordatorios, la caducidad, callbackUrl, test e
idempotencyKey se comprueban contra el nuevo formulario igual que contra uno creado a mano. La respuesta añade el formulario,
con las claves con las que vuelven sus respuestas:
{
"id": "kd7...",
"status": "pending",
"url": "https://form.formstep.io/r/rq_...",
"form": {
"id": "j58...",
"name": "Documents and fee for your mortgage application",
"reused": false,
"fields": [
{ "key": "payslips", "type": "file" },
{ "key": "fee", "type": "payment" },
{ "key": "call", "type": "schedule-appointment" }
]
}
}Para precompletar una pregunta de elección en la misma llamada, envía claves de opción, como con cualquier formulario. La clave de una
opción se deriva de su etiqueta, así que Self-employed pasa a ser self_employed. Una etiqueta en lugar de una
clave falla con INVALID_PREFILL_VALUE, que lista las claves válidas.
El objeto form
| Campo | Qué hace |
|---|---|
| name | Obligatorio, de 1 a 100 caracteres. El nombre en el panel y en la pestaña del navegador, el mismo para cada destinatario. Personaliza con prefill y context, nunca con el nombre. |
| blocks | Obligatorio, de 1 a 200 bloques, en el orden en que los muestra el formulario. Los bloques de las columnas de una fila y los campos de un grupo repetible también cuentan. |
| language | El idioma de la interfaz del formulario. Por defecto, inglés. |
| logo, cover, emoji | Tu marca, enviada con cada solicitud. logo: { "type": "icon", "name": "HeartIcon" } o { "type": "image", "url": "https://…" }. cover: { "type": "color", "color": "#0f4c81" } o { "type": "image", "url": "https://…", "offsetY": 50 }. emoji: un emoji junto al nombre. Consulta más abajo. |
| theme | Un aspecto por modo de color: { "light": "solarized-light", "dark": "dracula" }. Cada modo acepta el id de un preajuste, gratis en todos los planes, o un objeto de tema completo como el que acepta la herramienta MCP formTheme_set, que requiere el plan Pro. |
| settings | El comportamiento del formulario que fija la página de ajustes: la marca, un dominio de correo verificado (emailDomainId), los correos al propietario y al encuestado con su texto, el calendario de recordatorios, el correo de invitación, la redirección y la retención. Campos por clave: respondentNotificationTo es la clave de una pregunta de correo, redirectQueryParams asigna un parámetro a una clave de campo. El texto de los correos es texto plano con marcadores {{key}}. No hay barrera de contraseña ni de inicio de sesión, ni ajustes del enlace público como la protección contra bots o las ediciones tras el envío: una solicitud lleva su propio enlace. Cualquier otra clave se rechaza. |
| translations | El formulario en más idiomas, con un selector de idioma para el destinatario: { "de": { "Your name": "Ihr Name" } }, cada texto del formulario con el texto tal como lo escribieron los bloques como clave. Consulta más abajo. |
Bloques
Un bloque acepta los parámetros de la herramienta editor_insert* correspondiente del
servidor MCP, sin formId ni after, más una key opcional.
Sin ella, la clave se deriva del título, así que “Full name” pasa a ser full_name. Un parámetro que el bloque no conoce se
rechaza en lugar de ignorarse.
| Tipo | Bloques |
|---|---|
| Preguntas | text, contact, number, date, time, radio, checkbox, select, switch, rating, linearScale, matrix, ranking, pictureChoice, file, signature, decision |
| Documentos | documents, para mostrar archivos que el destinatario lee o firma |
| Pagos y reservas | payment, scheduleAppointment |
| Contenido | header, paragraph, image (URL en src), embedded, table (cells la rellena), list, row (columns: los bloques de cada columna, de 2 a 6 columnas, y widths en porcentaje). Una key opcional permite que la lógica muestre u oculte el bloque. |
| Estructura | pageDivider, repeatingGroup (fields: las preguntas del grupo como bloques, cada una con su key), hiddenField (key obligatoria: es la clave de context), calculatedField (su name es su clave), logic |
El texto de header y paragraph, los elementos de lista y las celdas de tabla admiten bold, italic,
, struck<u>underlined</u>, , code$$equation$$,
text, <span color=“red” background=“yellow” font=“font-georgia” size=“1.25”>…</span>
con los colores, fuentes y tamaños de la barra de herramientas del editor, y {{key}}, que muestra el valor del campo con
esa clave. Las marcas se pueden anidar; un marcador sin pareja es texto. Una columna de una fila admite cualquier bloque excepto
pageDivider, logic, hiddenField, calculatedField, repeatingGroup, row y scheduleAppointment. Un bloque decision acepta su
title y sus labels para aprobar, rechazar y pedir cambios en el idioma del formulario.
Los bloques se refieren unos a otros por clave, nunca por id de elemento: un bloque logic acepta su rule como lo
hace editor_insertLogic, con claves de campo donde pide ids, etiquetas de opción como valores de condición y nombres de
página para setNextPage.
Pagos
Un bloque payment acepta amount y currency, o amountFromField: la clave de un campo
oculto o de una pregunta numérica. Con un campo oculto, cada solicitud pasa su propio importe en context, así que un solo
formulario cobra a cada cliente su propio importe:
{
"form": {
"name": "Pay your invoice",
"blocks": [
{ "type": "hiddenField", "key": "invoice_total" },
{ "type": "payment", "key": "invoice", "title": "Invoice", "currency": "EUR", "amountFromField": "invoice_total" }
]
},
"recipient": { "email": "ada@example.com" },
"context": { "invoice_total": 1200 }
}Una pregunta numérica que fija el importe debe ser obligatoria y tener un min mayor que 0, para que nadie llegue al checkout
con un importe vacío, cero o negativo.
El formulario cobra a través de la cuenta de Stripe predeterminada del espacio de trabajo, o de la que indique connection.
Una persona conecta Stripe una vez para el espacio de trabajo; una
automatización no puede. Sin una cuenta utilizable, la llamada falla y te dice dónde conectarla.
Reservas
Un bloque scheduleAppointment acepta eventType, el título o el id de un tipo de evento de la conexión de
Cal.com del espacio de trabajo, y connection cuando el espacio de trabajo tiene
varias. Formstep busca el tipo de evento al crear la solicitud. prefill: { name, email } indica las preguntas cuyas
respuestas rellenan la reserva.
Tu marca
Una automatización no puede inventarse tu imagen, así que se la das una vez y la envía con cada solicitud: logo,
cover y emoji junto a los bloques, y theme para los colores y la tipografía.
| Campo | Acepta |
|---|---|
| logo | Un icono del editor, { "type": "icon", "name": "HeartIcon" }, o tu propia imagen, { "type": "image", "url": "https://…/logo.png" }. |
| cover | Un color sólido, { "type": "color", "color": "#0f4c81" }, o una imagen, { "type": "image", "url": "https://…/cover.jpg", "offsetY": 50 }, donde offsetY, de 0 a 100, elige la parte visible de una imagen alta. |
| emoji | Un emoji, que se muestra junto al nombre del formulario. |
Cada imagen, incluido el src de un bloque image, debe ser un JPEG, PNG o WebP público. Formstep descarga cada una una vez al
crear el formulario y guarda su propia copia, así que el formulario y los PDF de sus envíos sobreviven al original. Una URL que no se
puede descargar hace fallar la llamada con INLINE_FORM_IMAGE_UNREACHABLE, que indica el campo. Sin marca, el formulario se ve
como uno nuevo creado desde el panel.
Traducciones
translations publica el formulario en más idiomas a la vez, y el destinatario recibe el selector de idioma que tiene un
formulario traducido:
{ "de": { "Your name": "Ihr Name", "Order for **{{customer}}**": "Bestellung für **{{customer}}**" } }. Debe estar cada
texto del formulario, con el texto exacto que escribieron los bloques como clave: títulos de preguntas, opciones, marcadores de posición,
encabezados, párrafos, elementos de lista, celdas de tabla, nombres de grupo y etiquetas de botones. El marcado y {{key}}
se mantienen como en el original. Si falta un texto, la llamada falla con INLINE_FORM_TRANSLATION_MISSING, que lista los
textos que hay que añadir. El texto de los correos no se traduce.
Una definición, un formulario
Una llamada posterior con el mismo objeto form reutiliza el mismo formulario, y la respuesta indica
“reused”: true. Cien solicitudes con una misma definición crean un solo formulario, con un solo conjunto de respuestas que
exportar. Cambiar un bloque, el nombre, la marca o una traducción crea un formulario nuevo; las solicitudes ya enviadas conservan el
formulario con el que se crearon. Un formulario cerrado o despublicado no se reutiliza: la siguiente llamada con la misma definición crea
uno nuevo.
Personaliza con prefill y context
Pon el nombre del cliente, el número de factura o el importe en prefill y context, nunca en el nombre del
formulario, en un título o en un importe fijo. Una definición que cambia con cada cliente crea un formulario nuevo por cliente. Pon una
key en cada pregunta: una clave derivada sigue al título, así que una pregunta con otro título responde con una clave
nueva. Un espacio de trabajo crea como máximo 300 formularios inline nuevos al día; a partir de ahí, una llamada que crearía un
formulario nuevo falla con RATE_LIMITED, mientras que las que reutilizan un formulario siguen funcionando.
Cuando una llamada crea un formulario nuevo aunque un formulario integrado abierto tenga los mismos tipos de bloque en el mismo orden, la
respuesta nombra ese formulario en form.similarForm. Normalmente se debe a un nombre o un título cambiados. Para leer todas
las respuestas de un formulario integrado, lista sus envíos en lugar de leer cada solicitud.
En el panel
Un formulario integrado pertenece a su solicitud, así que vive en la página de la solicitud y no en la página Formularios. La página lista
las respuestas por clave de campo, y Vista previa abre el formulario tal como lo vio el destinatario, rellenado con sus
respuestas. Un enlace al editor o a los envíos de un formulario integrado abre en su lugar la página Solicitudes filtrada por ese
formulario. Los agentes siguen viendo los formularios integrados en form_list, y la API se niega a modificarlos.
Un formulario integrado no tiene enlace público y no se le puede añadir uno: sus solicitudes son la única forma de entrar, y el callback lleva todas las respuestas. Tampoco tiene integraciones.
Como no tiene una página propia, un formulario integrado nunca se elimina a mano. Se va con su última solicitud: cuando eliminas una solicitud, o el período de retención del formulario se la lleva, y ninguna otra solicitud sigue usando el formulario, el formulario también se elimina.
Cuando se rechaza la llamada
Cada rechazo es un VALIDATION_ERROR con un details.reason según el cual tu automatización puede decidir qué
hacer. No se crea nada.
| Motivo | Qué hacer |
|---|---|
| FORM_SOURCE_MISSING / FORM_SOURCE_AMBIGUOUS | Envía exactamente uno de formId y form. |
| INLINE_FORM_INVALID | Un bloque o un campo de form no cumple su esquema. details.issues indica cada uno. |
| INLINE_FORM_DUPLICATE_KEY | Dos bloques dan la misma clave. Asigna a uno de ellos su propia key. |
| INLINE_FORM_UNKNOWN_KEY | Una regla de lógica, amountFromField o el prefill de una reserva indica una clave que ningún bloque tiene. |
| INLINE_FORM_AMOUNT_MISSING | Pasa un número positivo para el campo del importe en context. |
| INLINE_FORM_CONNECTION_MISSING | Stripe o Cal.com no está conectado. details.connectUrl es la página que una persona abre una vez para conectarlo. |
| INLINE_FORM_EVENT_TYPE_NOT_FOUND | Ningún tipo de evento de Cal.com coincide. El error enumera los que hay. |
| INLINE_FORM_IMAGE_UNREACHABLE | Una URL de imagen no es un JPEG, PNG o WebP público. details.field la indica. Usa una URL que cualquiera pueda abrir, o quita la imagen. |
| INLINE_FORM_TRANSLATION_MISSING | A una traducción le faltan textos que tiene el formulario. details.missing los lista, tal como están escritos en los bloques. |
| INLINE_FORM_PUBLISH_ISSUES | La publicación rechazó el formulario, por ejemplo por un tipo de evento que no se puede reservar desde un formulario. |
Desde un agente
En el servidor MCP, request_create acepta el mismo form. Antes de crear un formulario con un pago o una reserva,
un agente consulta workspaceConnection_list: muestra las cuentas de Stripe y Cal.com conectadas y, cuando falta una, el
enlace que una persona debe abrir una vez. scheduler_listEventTypes enumera los tipos de evento que puede indicar una
reserva, y paymentConnection_setDefault cambia la cuenta de Stripe predeterminada. En la API REST, esas mismas tres son
connections.list, scheduler.listEventTypes y paymentConnections.setDefault.