# Crear una solicitud

Descubre las claves de campo de un formulario y luego crea una solicitud con valores precompletados, campos bloqueados y contexto.

## Crear una solicitud

Dos llamadas a la API: pregunta al formulario qué se le puede indicar, y luego asígnalo a una persona con los valores que ya conoces.

<h2 id="start-in-the-share-sheet">Empieza en la hoja de compartir</h2>

<p>
  Abre tu formulario publicado, haz clic en <strong>Compartir</strong> y elige la pestaña <strong>Solicitudes</strong>. La tarjeta que
  aparece ahí te da todo lo que necesitas para hacer la primera llamada:
</p>

<ul>
  <li>
    El <strong>id del formulario</strong>, con un botón de copiar.
  </li>
  <li>
    Un fragmento de <strong>curl</strong> y un prompt de <strong>MCP</strong>, ambos construidos con las claves de campo reales de tu
    formulario — así el ejemplo ya se dirige a los campos que este formulario tiene de verdad.
  </li>
  <li>
    Una pestaña <strong>Manual</strong> que crea una solicitud a mano, y <strong>Pruébalo tú mismo</strong>, que convierte lo que rellenaste
    ahí en una solicitud en <a href="#test-mode">modo de prueba</a> y te da su enlace.
  </li>
  <li>
    Un enlace directo a la <a href="/es/requests/managing-requests">página de Solicitudes</a>, filtrado a este formulario.
  </li>
</ul>

> ⚠️ **Publica primero**
> <p>
>     Un formulario sin publicar no puede recibir solicitudes, y los fragmentos de código permanecen desactivados hasta que lo publiques. Las
>     claves de campo se congelan en la primera publicación — eso es lo que permite que tu automatización siga dirigiéndose a{' '}
>     <code>company_name</code> un año después. Consulta <a href="/es/requests/field-keys">Claves de campo</a>.
>   </p>

<h2 id="discover-fields">Paso 1 — Descubre los campos</h2>

<p>
  <code>fields.list</code> devuelve todos los campos de la versión publicada actual del formulario, con la clave para dirigirte a cada uno,
  la forma de valor que espera y a qué grupo pertenece.
</p>

```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer $FORMSTEP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"method":"fields.list","params":{"formId":"j57..."}}'
```

```
{
  "ok": true,
  "data": {
    "published": true,
    "items": [
      { "key": "company_name", "type": "text", "title": "Company", "required": true, "prefillable": true },
      {
        "key": "company_size", "type": "select", "title": "Company size", "required": false, "prefillable": true,
        "options": [
          { "key": "1_50", "label": "1–50" },
          { "key": "51_200", "label": "51–200" }
        ]
      },
      { "key": "case_id", "type": "hidden", "title": "Case", "required": false, "prefillable": false, "context": true },
      { "key": "total", "type": "number", "title": "Total", "required": false, "prefillable": false, "calculated": true },
      { "key": "signature", "type": "signature", "title": "Sign here", "required": true, "prefillable": false }
    ],
    "hasMore": false
  }
}
```

<ul>
  <li>
    <code>context: true</code> marca un campo oculto. Su valor va en <code>context</code>, nunca en <code>prefill</code>; la clave de un
    campo oculto en <code>prefill</code> se rechaza con <code>UNKNOWN_FIELD_KEY</code>.
  </li>
  <li>
    <code>calculated: true</code> marca un campo calculado. El formulario calcula su valor, así que nadie puede enviarlo; lo lees de vuelta
    bajo su clave en <code>answers</code>.
  </li>
  <li>
    <code>prefillable: false</code> marca un campo para el que nadie puede suministrar un valor: subida de archivo, firma, pago, reserva de
    cita y bloques de Documentos. Las preguntas las rellena el propio destinatario. Los campos ocultos y los campos calculados también
    muestran <code>prefillable: false</code>: los campos ocultos reciben su valor mediante <code>context</code>, y los campos calculados no
    reciben nada.
  </li>
  <li>
    <code>options</code> lista las opciones de una pregunta de elección. Envía la <strong>clave</strong> de la opción, no su etiqueta; la
    etiqueta solo está ahí para que puedas relacionar la opción que conoces con su clave. Una matriz lista sus <code>rows</code> y{' '}
    <code>columns</code> de la misma forma.
  </li>
  <li>
    Los grupos repetibles vuelven como una única entrada con <code>type: "group"</code>, <code>repeating: true</code> y una lista de{' '}
    <code>members</code>.
  </li>
</ul>

<h2 id="create">Paso 2 — Crea la solicitud</h2>

```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer $FORMSTEP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"method":"requests.create","params":{
        "formId":"j57...",
        "recipient":{"email":"ada@acme.com","name":"Ada"},
        "context":{"case_id":"CASE-9"},
        "prefill":{"company_name":"Acme","company_size":"51_200"},
        "readonly":["company_name"],
        "delivery":"email",
        "externalId":"run-42",
        "callbackUrl":"https://automation.example/webhook/resume-abc",
        "idempotencyKey":"run-42"}}'
```

```
{
  "ok": true,
  "data": {
    "id": "kd7...",
    "status": "pending",
    "url": "https://form.formstep.io/r/rq_...",
    "deliveryStatus": "queued",
    "expiresAt": 1794787200000,
    "createdAt": 1789379200000,
    "externalId": "run-42",
    "deduplicated": false
  }
}
```

<p>
  <code>deliveryStatus</code> es <code>"queued"</code> cuando Formstep envía la invitación por email, y <code>"not_requested"</code> cuando
  entregas el enlace tú mismo.
</p>

<h2 id="three-buckets">Precompletado, campos bloqueados y contexto</h2>

<p>Hay tres cosas distintas que se pueden adjuntar a una solicitud, y confundirlas es el error inicial más habitual.</p>

<h3 id="prefill">Precompletado</h3>

<p>
  Respuestas iniciales para las preguntas visibles, para que el destinatario revise y corrija en lugar de escribir desde cero. Todo lo que
  ya sepas sobre esa persona pertenece aquí — el nombre de la empresa desde tu CRM, el importe de la factura, las respuestas del año pasado.
</p>

<h3 id="locked-fields">Campos bloqueados</h3>

<p>
  Incluye una clave precompletada en <code>readonly</code> y el destinatario verá el valor pero no podrá cambiarlo. Úsalo para los datos que
  está confirmando en lugar de proporcionando — el número de contrato, el precio acordado. El bloqueo es por solicitud: el formulario en sí
  no se toca, y el mismo campo queda libremente editable en la siguiente solicitud.
</p>

<p>
  Toda clave bloqueada también debe estar precompletada, y un campo bloqueado <em>obligatorio</em> debe precompletarse con algo no vacío —
  de lo contrario el destinatario se enfrentaría a un formulario que nunca podría enviar, y Formstep rechaza la llamada en lugar de crear
  esa trampa.
</p>

<h3 id="context">Contexto</h3>

<p>
  Valores de confianza para los <a href="/es/building-forms/hidden-fields">campos ocultos</a> del formulario — un número de caso, un id de
  ejecución de flujo, un importe. El contexto alimenta variables, lógica condicional, campos calculados y copys de correo, vuelve sin
  cambios en el callback, y el destinatario no puede alterarlo. Esa última parte es la diferencia frente a sembrar un campo oculto mediante
  una URL en un enlace público, donde cualquiera puede editar la cadena de consulta; los enlaces de solicitud ignoran por completo los
  parámetros de consulta de la URL. Los valores de contexto deben ser una cadena, un número o un booleano.
</p>

<p>
  El contexto no es de formato libre: cada clave debe ser un campo oculto en la versión publicada del formulario, y cualquier otra clave se
  rechaza con <code>UNKNOWN_FIELD_KEY</code>. La contabilidad interna que no tiene un campo oculto, como un id de ejecución, pertenece a{' '}
  <a href="#metadata">metadata</a>.
</p>

<p>
  Los campos ocultos no se muestran en el formulario, pero un valor de contexto no es secreto para el destinatario. Lo ve donde sea que el
  formulario o la invitación lo muestren: una <a href="/es/building-forms/answer-piping">mención</a> en el contenido del formulario o en el
  texto del correo, o una pregunta visible que usa ese campo oculto como su{' '}
  <a href="/es/building-forms/field-configuration#default-values">valor predeterminado</a>. En ese último caso, el destinatario ve el valor
  de contexto precompletado en esa pregunta y puede editar la respuesta. El valor de contexto en sí permanece sin cambios. Un{' '}
  <code>prefill</code> para la clave propia de esa pregunta tiene prioridad sobre el valor predeterminado.
</p>

<h3 id="metadata">Metadatos</h3>

<p>
  Tu propia contabilidad interna — un id de ejecución, un id de registro del CRM. Nunca llega al formulario, así que no se puede insertar en
  el texto ni leer por la lógica; solo viaja junto a la solicitud y vuelve en cada callback y lectura de estado.
</p>

<h2 id="value-shapes">Formas de valor</h2>

<p>
  Envía los valores en la forma que pida el <code>type</code> de <code>fields.list</code>. Una forma incorrecta vuelve como un error de
  validación que nombra la clave, el tipo esperado y — para preguntas de elección — los valores que se habrían aceptado.
</p>

<h2 id="documents">Documentos</h2>

<p>
  Un <a href="/es/building-forms/documents-block">bloque de documentos</a> le entrega archivos al respondente. Sus documentos creados por ti
  son los mismos para todos y siempre permanecen; una solicitud añade archivos para su único destinatario debajo de ellos — el propio
  contrato de alquiler del cliente, una copia del DNI para revisar. Los bytes nunca viajan por la propia llamada a la API: primero se sube,
  y luego se referencia.
</p>

```
"documents": [
  { "documentId": "kn7...", "name": "Your lease contract" },
  { "documentId": "kn8..." }
]
```

<p>
  <code>name</code> sustituye el nombre visible guardado con la subida. Si el formulario tiene más de un bloque de documentos, indica el
  destino con <code>field</code>, la clave de campo del bloque (<code>fields.list</code> la muestra, junto con los documentos de autor que
  ya reciben todos los encuestados). Los archivos aparecen debajo de esos documentos de autor: una solicitud añade archivos, nunca sustituye
  ninguno. Una misma subida se puede referenciar desde tantas solicitudes como quieras — una lista de precios subida una vez sirve a
  quinientas solicitudes.
</p>

<p>
  Límites: solo PDF e imágenes, 25 MB por documento, 100 MB por solicitud (<code>DOCUMENTS_TOO_LARGE</code>), y como máximo 20 documentos
  por bloque contando los documentos de autor (<code>DOCUMENTS_TOO_MANY</code>). Los archivos cuentan contra el almacenamiento de tu espacio
  de trabajo y se liberan en cuanto las solicitudes que los referencian salen de la ventana de retención del formulario. La respuesta
  registra la lista que vio el destinatario bajo la clave de campo del bloque, así que el callback te dice exactamente qué archivos recibió
  esa persona.
</p>

<h2 id="options">El resto de las opciones</h2>

<h3 id="test-mode">Modo de prueba</h3>

<p>
  Pasa <code>test: true</code> para poner a prueba todo el circuito antes de una ejecución real. Una solicitud de prueba es real en todo lo
  que importa para la integración: el enlace se abre y se puede completar, el <a href="/es/requests/callbacks">callback</a> se dispara como
  siempre, y <code>requests.get</code> devuelve las respuestas. Lo que nunca hace es llegar a nadie ni a nada que después tengas que
  limpiar:
</p>

<ul>
  <li>
    No se envía ninguna invitación ni ningún recordatorio, sea lo que sea lo que diga <code>delivery</code>.{' '}
    <strong>Enviar recordatorio</strong> se rechaza en ella, y no gasta nada de tu asignación mensual.
  </li>
  <li>
    El callback lleva <code>"test": true</code>, para que tu flujo de trabajo pueda bifurcar o ignorar el evento.
  </li>
  <li>
    La respuesta se almacena pero no cuenta: ni contra tu cuota mensual (una prueba se completa incluso con la cuota agotada), y nunca
    aparece en los recuentos de respuestas del formulario, en la pestaña de respuestas, en las exportaciones, ni en tus integraciones. Nadie
    recibe notificación.
  </li>
  <li>
    La solicitud queda oculta en la <a href="/es/requests/managing-requests">página de Solicitudes</a> detrás de{' '}
    <strong>Mostrar solicitudes de prueba</strong>, se deja fuera del embudo de solicitudes en Analytics, y se deja fuera de{' '}
    <code>requests.list</code> salvo que pases <code>includeTest: true</code>.
  </li>
  <li>
    El enlace se cierra en un plazo de 24 horas, incluso cuando <code>expiresAt</code> pide más; el <code>expiresAt</code> de la respuesta
    indica cuándo. En Free, un workspace puede crear 10 solicitudes de prueba al día. La siguiente falla con <code>RATE_LIMITED</code> y el
    motivo <code>TEST_REQUEST_LIMIT_REACHED</code>, y <code>retryAfterMs</code> indica cuándo puedes volver a intentarlo. Pro y Business no
    tienen límite diario.
  </li>
</ul>

<p>
  <strong>Pruébalo tú mismo</strong> en la hoja de compartir es este modo con un solo clic: toma el borrador de la pestaña Manual, deja
  fuera el destinatario y la entrega, y te da el enlace para abrirlo tú mismo.
</p>

<h3 id="allowance">Lo que cuesta una solicitud</h3>

<p>
  Todo plan tiene una <strong>asignación mensual</strong> compartida por ambos canales: un envío por enlace público gasta una unidad, y
  también lo hace cada solicitud que creas — la responda el destinatario, la ignore, o la cancele tú. La respuesta que recopila una
  solicitud ya está pagada y no cuenta en ningún sitio. Free incluye 1.000 unidades al mes, Pro y Business 50.000; el contador se reinicia
  el día 1 de cada mes, UTC. Al llegar al tope, <code>requests.create</code> falla con <code>UPGRADE_REQUIRED</code> y motivo{' '}
  <code>MONTHLY_ALLOWANCE_REACHED</code>; las solicitudes que ya habías creado siguen pudiéndose responder.
</p>

<p>
  En Free, una solicitud creada con <code>"delivery": "email"</code> también gasta una de las{' '}
  <a href="/es/subscription-billing/limits-quotas#free-invitations">10 invitaciones gratis</a> de la cuenta. No se reinician nunca; cuando
  se agotan, el envío por correo falla con <code>UPGRADE_REQUIRED</code> y motivo <code>FREE_INVITATIONS_USED</code>.
</p>

<h3 id="idempotency">Idempotencia</h3>

<p>
  Pasa la misma <code>idempotencyKey</code> con el mismo cuerpo y recibes la solicitud original de vuelta, con{' '}
  <code>deduplicated: true</code> y el enlace original — sin segunda solicitud, sin segundo correo. Reutiliza la clave con un cuerpo{' '}
  <em>distinto</em> y Formstep lo rechaza con <code>IDEMPOTENCY_CONFLICT</code> en lugar de adivinar cuál querías. Las claves están
  limitadas al espacio de trabajo y se respetan durante 30 días; pasado ese tiempo, la misma clave inicia una solicitud nueva.
</p>

<p>
  En una herramienta de flujos de trabajo, el id de ejecución es la clave natural: una ejecución reintentada tras un fallo de red recupera
  la solicitud que ya había creado.
</p>

<h3 id="rate-limit">Límite de frecuencia</h3>

<p>
  <code>requests.create</code> y <code>documents.create</code> comparten un presupuesto de <strong>60 llamadas por minuto</strong>, contado
  por token de API (o por usuario, para una llamada hecha sin uno). Un backlog que estás vaciando debería ir a su propio ritmo; una ráfaga
  que supere el presupuesto se rechaza y se puede reintentar.
</p>

<h3 id="custom-domains">Dominios personalizados</h3>

<p>
  Si el formulario ya está publicado en uno de tus <a href="/es/branding-domains/custom-domains">dominios personalizados</a>, los enlaces de
  solicitud se generan ahí automáticamente — <code>https://forms.tuempresa.com/r/rq_…</code>. Especifica <code>domainId</code>{' '}
  explícitamente cuando el formulario esté publicado en más de uno. El dominio debe pertenecer al mismo espacio de trabajo que el
  formulario.
</p>

<h2 id="what-the-recipient-sees">Lo que ve el destinatario</h2>

<p>
  Exactamente el formulario que tú creaste — mismo tema, mismo logo, mismo idioma — con sus valores en su sitio, los campos bloqueados en
  solo lectura, y sin captcha que resolver. Cuando lo envía, ve tu página de agradecimiento. Si vuelve al enlace después, ve la página de
  resultado en lugar de un formulario en blanco.
</p>

<p>
  No hay ningún mensaje de tu automatización en la página. Todo lo que el destinatario necesite saber debe ir en el propio formulario, donde
  puedes personalizarlo <a href="/es/building-forms/answer-piping">mencionando</a> un valor de contexto o un campo precompletado.
</p>

<p>
  Un agente de IA ejecuta los mismos dos pasos que <code>fields_list</code> y <code>request_create</code>, con las mismas opciones —{' '}
  documents y domainId incluidos. Consulta <a href="/es/developers/mcp-server#requests">Solicitudes en el servidor MCP</a>.
</p>

<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Claves de campo](/es/requests/field-keys) — De dónde vienen esas claves y cómo mantenerlas estables.
  - [Callbacks y firma](/es/requests/callbacks) — Qué llega cuando el destinatario termina.
  - [Solución de problemas](/es/requests/troubleshooting) — Todos los motivos de rechazo y qué hacer al respecto.
  - [Referencia de la API](/es/developers/rest-api) — Lista completa de parámetros para cada método de solicitud.
</div>
