# Métodos de la API

Referencia completa de todos los métodos de la API REST con parámetros, ejemplos y respuestas.

## Métodos de la API

Referencia completa de todos los métodos expuestos por la API REST de Formstep. Cada método muestra sus parámetros, ejemplos de solicitud y la forma de las respuestas.

> ℹ️ **Un único endpoint, muchos métodos**
> <p>
>     Todos los métodos son <code>POST https://api.formstep.io/api/v1</code> con un cuerpo JSON{' '}
>     <code>{`{"method": "...", "params": {...}}`}</code> y una cabecera <code>Authorization: Bearer fb_...</code>. Consulta{' '}
>     <a href="/es/developers/overview">Descripción general de la API</a> para autenticación y manejo de errores, y{' '}
>     <a href="/es/developers/api-tokens">tokens de API</a> para el token en sí.
>   </p>

<h2 id="conventions">Convenciones</h2>

<ul>
  <li>
    <code>params</code> puede omitirse; su valor por defecto es <code>{`{}`}</code>. Un método desconocido es{' '}
    <code>404 METHOD_NOT_FOUND</code>.
  </li>
  <li>
    Un token está vinculado a <strong>un espacio de trabajo</strong>. Nombrar otro espacio de trabajo, o un formulario de otro, es{' '}
    <code>403 FORBIDDEN</code> incluso si perteneces a ambos.
  </li>
  <li>
    <strong>Paginación.</strong> Los métodos de listado devuelven <code>{`{ items, nextCursor, hasMore }`}</code>; la mayoría también
    devuelve <code>canPaginate</code>, que es <code>false</code> cuando <code>hasMore</code> es true pero ningún cursor puede continuar
    (búsqueda aproximada). Pasa <code>nextCursor</code> de vuelta como <code>cursor</code>. <code>limit</code> va de 1 a 100, con 20 por
    defecto — salvo <code>requests.list</code>, cuyo valor por defecto es 25.
  </li>
  <li>
    <strong>Límites de velocidad.</strong> 120 llamadas por minuto por token, compartidas con el{' '}
    <a href="/es/developers/mcp-server">servidor MCP</a>; <code>requests.create</code> tiene su propio límite de 60 por minuto. La
    autenticación fallida se limita por separado, 30 cada 15 minutos por IP, tras lo cual los tokens incorrectos ven{' '}
    <code>RATE_LIMITED</code> en lugar de <code>UNAUTHORIZED</code>.
  </li>
  <li>
    <strong>Tamaño del cuerpo.</strong> 1 MiB. Los cuerpos más grandes se rechazan con <code>VALIDATION_ERROR</code>.
  </li>
  <li>
    <strong>Versionado.</strong> La ruta lleva la versión. Los cambios que rompen compatibilidad se publican como <code>/api/v2</code>; los
    métodos nuevos y los campos de respuesta nuevos no.
  </li>
</ul>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="forms">Formularios</h2>

{/* ── forms.list ──────────────────────────────────────────────────────────── */}

Lista los formularios de un espacio de trabajo. Admite paginación por cursor y búsqueda difusa opcional por nombre.

  
    ID del espacio de trabajo.
  
  
    Filtrar por carpeta. Pasa <code>null</code> para obtener solo los formularios en la raíz. Omite para listar todos.
  
  
    Búsqueda difusa por nombre. Los resultados están limitados por <code>limit</code>; no se pagina por cursor.
  
  
    Tamaño de página (1–100).
  
  
    Cursor de paginación de una respuesta anterior.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.list",
    "params": { "workspaceId": "ws_abc123" }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "frm_abc123",
        "name": "Contact Form",
        "folderId": null,
        "workspaceId": "ws_abc123",
        "isPublished": true,
        "publishedAt": 1714041851000,
        "unpublishedAt": null,
        "createdAt": 1714041800000,
        "lastEditedAt": 1714042000000,
        "emoji": null
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── forms.get ───────────────────────────────────────────────────────────── */}

Obtiene los detalles completos de un formulario, incluidas las preguntas, la portada, el logotipo y una URL de vista previa.

  
    ID del formulario.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.get",
    "params": { "formId": "frm_abc123" }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "id": "frm_abc123",
    "name": "Event Feedback",
    "folderId": null,
    "workspaceId": "ws_abc123",
    "isPublished": true,
    "publishedAt": 1714041851000,
    "unpublishedAt": null,
    "createdAt": 1714041800000,
    "lastEditedAt": 1714042000000,
    "emoji": null,
    "questions": [
      {
        "id": "q_1",
        "type": "email-input",
        "inputType": "email",
        "title": "Your email",
        "metadata": null
      },
      {
        "id": "q_2",
        "type": "rating-input",
        "inputType": "rating",
        "title": "Overall experience",
        "metadata": { "kind": "rating", "maxStars": 5 }
      }
    ],
    "hasContent": true,
    "cover": null,
    "logo": null,
    "isDeleted": false,
    "trashedAt": null,
    "previewUrl": "https://formstep.io/preview/abc..."
  }
}
```

  

{/* ── forms.create ────────────────────────────────────────────────────────── */}

Crea un nuevo formulario vacío. Devuelve el formulario y una URL de vista previa.

  
    Nombre del formulario (1–255 caracteres).
  
  
    ID del espacio de trabajo.
  
  
    Coloca el formulario en una carpeta. Omite para crearlo en la raíz del espacio de trabajo.
  

  
    
      
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.create",
    "params": {
      "name": "Contact",
      "workspaceId": "ws_abc123"
    }
  }'
```

    
    
      
```
const res = await fetch('https://api.formstep.io/api/v1', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FORMSTEP_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    method: 'forms.create',
    params: { name: 'Contact', workspaceId: 'ws_abc123' },
  }),
})
const { ok, data } = await res.json()
```

    
    
      
```
import os, requests
res = requests.post(
  "https://api.formstep.io/api/v1",
  headers={"Authorization": f"Bearer {os.environ['FORMSTEP_TOKEN']}"},
  json={
    "method": "forms.create",
    "params": {
      "name": "Contact",
      "workspaceId": "ws_abc123",
    },
  },
)
data = res.json()
```

    
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "frm_new123",
    "name": "Contact",
    "workspaceId": "ws_abc123",
    "folderId": null,
    "isPublished": false,
    "createdAt": 1714041851000,
    "previewUrl": "https://formstep.io/preview/abc..."
  }
}
```

  

{/* ── forms.update ────────────────────────────────────────────────────────── */}

Actualiza los metadatos del formulario: nombre, carpeta, emoji, portada o logotipo. No actualiza el contenido del formulario (usa las herramientas del editor para eso).

  
    ID del formulario.
  
  
    Nuevo nombre del formulario (1–255 caracteres).
  
  
    Mueve el formulario a una carpeta. Pasa <code>null</code> para moverlo a la raíz del espacio de trabajo.
  
  
    Emoji del formulario (máx. 10 caracteres). Pasa <code>null</code> para eliminarlo.
  
  
    Portada. <code>{`{"type": "color", "color": "#ffffff"}`}</code>, <code>{`{"type": "image", "url": "https://...", "offsetY": 50}`}</code>{' '}
    (<code>offsetY</code> 0–100, 50 por defecto), o <code>{`{"type": "none"}`}</code> para eliminarla. Las URLs de imagen deben ser{' '}
    <code>http(s)</code> o una URI <code>data:image</code>.
  
  
    Logotipo. <code>{`{"type": "icon", "name": "HeartIcon"}`}</code>, <code>{`{"type": "image", "url": "https://..."}`}</code>, o{' '}
    <code>{`{"type": "none"}`}</code> para eliminarlo. Los nombres de icono son fijos: <code>QuestionMarkIcon</code>,{' '}
    <code>ListBulletsIcon</code>, <code>ChartBarIcon</code>, <code>ClockCountdownIcon</code>, <code>HeartIcon</code>,{' '}
    <code>LightbulbIcon</code>, <code>CheckCircleIcon</code>, <code>MagnifyingGlassIcon</code>, <code>TrendUpIcon</code>,{' '}
    <code>EnvelopeIcon</code>, <code>PhoneIcon</code>, <code>CalendarIcon</code>, <code>LinkIcon</code>, <code>UsersIcon</code>.
  

<p>
  Pasa al menos uno de los cinco campos actualizables. No cambia el contenido del formulario — usa las herramientas del editor de MCP para
  eso.
</p>

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.update",
    "params": {
      "formId": "frm_abc123",
      "name": "Updated Name",
      "emoji": "📋"
    }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "id": "frm_abc123",
    "name": "Updated Name",
    "folderId": null,
    "emoji": "📋"
  }
}
```

  

<p>
  <code>cover</code> y <code>logo</code> solo vuelven cuando los enviaste. Un payload que coincidía con el estado actual en todos los campos
  escalares añade <code>noChange: true</code>.
</p>

{/* ── forms.publish ───────────────────────────────────────────────────────── */}

Publica un formulario para que pueda recibir respuestas, y congela sus [claves de campo](/es/requests/field-keys) en una nueva instantánea.
Idempotente: un formulario ya publicado devuelve éxito con `alreadyPublished: true`, y un formulario despublicado se vuelve a publicar
desde su última instantánea.

  
    ID del formulario.
  

<p>
  Un formulario con bloques de contenido pero sin preguntas se publica con una advertencia. Un formulario sin ningún contenido no se puede
  publicar. Publicar no crea una URL pública — llama a <a href="#share-links-create">shareLinks.create</a> para eso.
</p>

{/* ── forms.unpublish ─────────────────────────────────────────────────────── */}

Pone un formulario fuera de línea. Los respondentes ya no pueden abrirlo. Idempotente — un formulario que no está publicado devuelve
`alreadyUnpublished: true`. Reversible con `forms.publish`.

  
    ID del formulario.
  

{/* ── forms.delete ────────────────────────────────────────────────────────── */}

Mueve un formulario a la papelera. Sus enlaces para compartir activos se revocan, así que sus URLs públicas dejan de funcionar.

  
    ID del formulario.
  

> ⚠️ **Restaurar no recupera los enlaces**
> <p>
>     <code>forms.restore</code> devuelve el formulario, pero los enlaces para compartir que revocó siguen revocados. Genera otros nuevos con{' '}
>     <code>shareLinks.create</code>. Un formulario que ya está en la papelera devuelve <code>alreadyTrashed: true</code> y conserva su fecha
>     original de envío a la papelera.
>   </p>

{/* ── forms.restore ───────────────────────────────────────────────────────── */}

Restaura un formulario desde la papelera.

  
    ID del formulario.
  
  
    Dónde restaurarlo. Omite para su carpeta original, <code>null</code> para la raíz del espacio de trabajo, o un ID de carpeta.
  

<p>
  Un formulario que no está en la papelera devuelve <code>alreadyRestored: true</code>.
</p>

{/* ── formSettings.get ────────────────────────────────────────────────────── */}

Lee los ajustes de comportamiento de un formulario.

  
    ID del formulario.
  

<p>
  Devuelve <code>{`{ settings, isDefault, availableEmailDomains, defaultFromAddress, payment }`}</code>. <code>isDefault</code> es true
  cuando el formulario aún no tiene una fila de ajustes guardada y estás viendo los valores por defecto. <code>availableEmailDomains</code>{' '}
  contiene los ids de dominio verificados que puedes pasar como <code>emailDomainId</code>, y <code>payment</code> indica si Stripe está
  conectado (conectarlo es un paso desde el panel).
</p>

{/* ── formSettings.update ─────────────────────────────────────────────────── */}

Actualiza los ajustes de comportamiento de un formulario. Una actualización parcial: solo se escriben los campos que envías.

  
    ID del formulario.
  
  
    <code>language</code> (BCP-47, por defecto <code>"en"</code>), <code>requireAuthentication</code>, <code>showBranding</code>,{' '}
    <code>captchaEnabled</code>, <code>passwordEnabled</code>, <code>password</code> (4 caracteres o más; una cadena implica{' '}
    <code>passwordEnabled: true</code>, <code>null</code> elimina el bloqueo).
  
  
    <code>notifyOnSubmission</code>, <code>notificationEmails</code> (array), <code>selfNotificationSubject</code>,{' '}
    <code>selfNotificationEmailBody</code>, <code>pdfGenerationEnabled</code>, <code>showViewSubmissionButton</code>. Los correos del
    propietario no son traducibles — escríbelos en el idioma que quieras.
  
  
    <code>respondentNotificationEnabled</code>, <code>respondentNotificationTo</code> (el id de campo de una pregunta de correo, o{' '}
    <code>null</code>), <code>respondentNotificationSubject</code>, <code>respondentNotificationBody</code>,{' '}
    <code>respondentNotificationPdfEnabled</code>.
  
  
    <code>respondentReminderEnabled</code>, <code>respondentReminderTo</code>, <code>respondentReminderSubject</code>,{' '}
    <code>respondentReminderBody</code>, <code>respondentReminderRequiredFieldIds</code>, y <code>reminderSteps</code> — desfases de
    inactividad como <code>["1d","3d","1w"]</code>, como máximo 5, ordenados y deduplicados al guardar, <code>[]</code> para ninguno. El
    calendario se aplica tanto a respuestas de enlace público abandonadas como a solicitudes. Pro.
  
  
    <code>redirectUrl</code> (<code>http(s)</code>; <code>null</code> o <code>""</code> lo elimina), <code>redirectQueryParams</code> (
    <code>{`[{ paramName, fieldId }]`}</code>), <code>allowAnotherResponse</code> (mutuamente excluyente con una redirección),{' '}
    <code>maxSubmissionsPerRespondent</code> (0 = sin límite, máx. 1000), <code>editAfterSubmit</code>, <code>maxEdits</code> (máx. 3; 0
    significa sin límite en Pro y Business, 3 en Free).
  
  
    <code>draftRetentionDays</code> y <code>submissionRetentionDays</code> (0–36500, <code>null</code> vuelve al valor por defecto). La
    retención de respuestas es Business, y establecerla elimina cualquier fecha de eliminación fija configurada en el editor.
  
  
    Un id de dominio de correo verificado de <code>formSettings.get</code>, para una dirección De personalizada. <code>null</code>{' '}
    restablece el remitente por defecto.
  

<p>
  Los asuntos y cuerpos son texto plano y aceptan marcadores <code>{`{{variable}}`}</code>; los saltos de línea se convierten en párrafos.
  Personalizar un asunto o cuerpo de respondente lo hace traducible, así que sus claves aparecen de inmediato en{' '}
  <code>translations.listEntries</code>.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="submissions" class="border-t border-border pt-8">
  Respuestas
</h2>

{/* ── submissions.list ────────────────────────────────────────────────────── */}

Lista las respuestas de un formulario, la página más reciente primero, con paginación por cursor.

  
    ID del formulario.
  
  
    Incluye respuestas que se empezaron pero nunca se enviaron. Los borradores son una función Pro: en Free solo se listan los envíos
    completados.
  
  
    Adjunta las traducciones de IA guardadas de las respuestas bajo <code>items[].translation.display</code>, con las mismas claves que{' '}
    <code>display</code>. <code>items[].answers</code> y <code>items[].display</code> siempre conservan el original.
  
  
    Tamaño de página (1–100).
  
  
    Cursor de paginación de una respuesta anterior.
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "formName": "Event Feedback",
    "items": [
      {
        "id": "sub_xyz789",
        "submittedAt": "2026-05-18 18:02:28",
        "isCompleted": true,
        "createdAt": "2026-05-18 18:02:19",
        "answers": {
          "email": "user@example.com",
          "plan": "pro",
          "contacts": [{ "name": "Ada" }, { "name": "Grace" }]
        },
        "display": {
          "email": "user@example.com",
          "plan": "Pro",
          "contacts": "Ada, Grace"
        }
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

> ℹ️ **Las mismas respuestas que webhooks y callbacks**
> <p>
>     Cada elemento lleva <code>answers</code> indexado por <a href="/es/requests/field-keys">clave de campo</a> y <code>display</code> con
>     las mismas claves, en texto legible — la forma que llevan un <a href="/es/developers/webhooks-reference">payload de webhook</a>, un{' '}
>     <a href="/es/requests/callbacks">callback de solicitud</a> y <code>requests.get</code>. Una respuesta de elección es su clave de opción,
>     un grupo repetitivo un array de instancias. Llama a <code>fields.list</code> para el título y las etiquetas de opción de cada clave.
>     Este método no devuelve totales.
>   </p>

{/* ── submissions.pdf ─────────────────────────────────────────────────────── */}

Obtiene un enlace al PDF de una respuesta. Construido para el conector de Zapier: solo devuelve un resultado cuando el formulario tiene una
integración de Zapier activa configurada para incluir el PDF, y el PDF se conservó.

  
    ID del formulario.
  
  
    ID de la respuesta. Debe pertenecer a ese formulario y estar completada.
  

  
    
```
{
  "ok": true,
  "data": {
    "url": "https://api.formstep.io/api/storage/...",
    "filename": "formstep-submission-sub_xyz789.pdf",
    "contentType": "application/pdf",
    "byteLength": 148213
  }
}
```

  

{/* ── submissions.sample ──────────────────────────────────────────────────── */}

Construye un payload de respuesta de muestra para un formulario, sin ningún dato real. Es exactamente la forma que lleva una entrega de
respuesta de enlace público, así que los conectores lo usan para descubrir campos; una respuesta nacida de una solicitud llega a una
suscripción como <code>request.completed</code> en su lugar, muestreada por{' '}

<a href="#requests-sample">requests.sample</a>. <code>data.form.snapshotId</code> es la versión publicada actual del formulario, el mismo id
que llevan los eventos en vivo, o <code>null</code> mientras el formulario no esté publicado.

  
    ID del formulario.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "evt_example000000000000",
    "type": "submission.completed",
    "createdAt": "2026-05-18T19:00:00.000Z",
    "apiVersion": "2026-09-24",
    "test": true,
    "data": {
      "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" },
      "submission": {
        "id": "sub_example000000000000",
        "respondentEmail": "respondent@example.com",
        "submittedAt": "2026-05-18T19:00:00.000Z",
        "updatedAt": null,
        "editCount": 0,
        "pdfUrl": null,
        "language": "en"
      },
      "answers": { "your_email": "john@example.com" },
      "display": { "your_email": "john@example.com" }
    }
  }
}
```

  

<p>
  La semántica de los campos y del payload está documentada una sola vez, en la{' '}
  <a href="/es/developers/webhooks-reference#payload">referencia de webhooks</a>.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="share-links" class="border-t border-border pt-8">
  Enlaces compartidos
</h2>

{/* ── shareLinks.list ─────────────────────────────────────────────────────── */}

Lista los enlaces compartidos de un formulario.

  
    ID del formulario.
  
  
    Incluye los enlaces revocados en el resultado.
  
  
    Tamaño de página (1–100).
  
  
    Cursor de paginación.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "sl_abc123",
        "code": "RPjNes52",
        "url": "https://formstep.io/RPjNes52",
        "customDomainUrl": null,
        "formId": "frm_abc123",
        "createdAt": 1714041851000,
        "expiresAt": null,
        "maxClaims": null,
        "claimedCount": 7,
        "isRevoked": false,
        "revokedAt": null,
        "customDomainId": null,
        "customSlug": null
      }
    ],
    "availableCustomDomains": [],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── shareLinks.create ───────────────────────────────────────────────────── */}

Crea un enlace compartido para un formulario. El formulario debe estar publicado primero.

  
    ID del formulario. Un formulario despublicado, o que se publicó y luego se despublicó, se rechaza — llama antes a{' '}
    <code>forms.publish</code>.
  
  
    Vencimiento como marca de tiempo Unix futura en milisegundos. A diferencia de la actualización, aquí no se acepta <code>0</code>.
  
  
    Número máximo de veces que se puede usar este enlace. Debe ser positivo; usa <code>shareLinks.update</code> para eliminarlo después.
  

<p>
  La respuesta es el enlace compartido (con la misma forma que un elemento de <code>shareLinks.list</code>) más{' '}
  <code>availableCustomDomains</code>, para que puedas continuar con <code>shareLinks.update</code> y asociar uno.
</p>

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "shareLinks.create",
    "params": {
      "formId": "frm_abc123",
      "maxClaims": 100
    }
  }'
```

{/* ── shareLinks.update ───────────────────────────────────────────────────── */}

Actualiza un enlace compartido. Puedes cambiar el vencimiento, el número máximo de usos, el dominio personalizado, el slug, o revocar el enlace.

  
    ID del enlace compartido.
  
  
    Nueva marca de tiempo de vencimiento en milisegundos. Pasa <code>0</code> para eliminar el vencimiento.
  
  
    Nuevo número máximo de usos. Pasa <code>-1</code> para eliminar el límite.
  
  
    Asocia un dominio personalizado. Pasa <code>null</code> para desasociarlo.
  
  
    Slug de URL personalizado (3–64 caracteres, alfanumérico en minúsculas y guiones). Obligatorio junto con <code>customDomainId</code>;
    pasa ambos como <code>null</code> para desasociar. <code>login</code>, <code>auth-callback</code>, <code>preview</code>,{' '}
    <code>payment</code>, <code>api</code>, <code>admin</code> y <code>health</code> están reservados.
  
  
    Establece en <code>true</code> para revocar el enlace de forma permanente. No se puede combinar con otros campos, y no se puede deshacer
    — esta es la única vía para eliminar un enlace compartido.
  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="fields" class="border-t border-border pt-8">
  Campos
</h2>

{/* ── fields.list ─────────────────────────────────────────────────────────── */}

Lista todos los campos de la versión publicada actual de un formulario, con la clave para dirigirte a cada uno. Llama a esto antes de
`requests.create` en lugar de codificar las claves a mano. Consulta [Claves de campo](/es/requests/field-keys).

  
    ID del formulario. Un formulario que nunca se ha publicado todavía no tiene claves de campo y responde con <code>published: false</code>{' '}
    sin elementos.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -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": "satisfaction", "type": "matrix", "title": "How did we do?", "required": false, "prefillable": true,
        "rows": [{ "key": "delivery_speed", "label": "Delivery speed" }],
        "columns": [{ "key": "very_good", "label": "Very good" }, { "key": "poor", "label": "Poor" }]
      },
      { "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 }
    ],
    "hasMore": false
  }
}
```

  

> ℹ️ **Cómo leer los indicadores**
> <p>
>     <code>context: true</code> es un campo oculto — su valor va en <code>context</code>, nunca en <code>prefill</code>.{' '}
>     <code>prefillable: false</code> marca un campo al que nadie puede darle un valor (archivo, firma, pago, cita, documentos). Para una
>     pregunta de elección, envía la <strong>clave</strong> de la opción, no su etiqueta; una matriz lista sus <code>rows</code> y{' '}
>     <code>columns</code> del mismo modo y toma <code>{'{ "row_key": "column_key" }'}</code>. <code>calculated: true</code> es un campo
>     calculado: el formulario calcula su valor, lo vuelves a leer en <code>answers</code>, y nada puede enviarlo.
>   </p>

<p>
  Un grupo repetitivo es <code>type: "group"</code> con <code>repeating: true</code> y un array <code>members</code>. Un bloque de
  Documentos es <code>type: "documents"</code> y lleva <code>documents: [{`{ name }`}]</code>, los archivos de autor que todo respondente ya
  ve.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="requests" class="border-t border-border pt-8">
  Solicitudes
</h2>

Una solicitud asigna un formulario publicado a una persona y te llama de vuelta cuando termina. La guía conceptual está en
[Crear una solicitud](/es/requests/creating-requests); esta es la lista de parámetros.

{/* ── requests.create ─────────────────────────────────────────────────────── */}

Crea una solicitud. Gasta una unidad del cupo mensual del espacio de trabajo, responda o no el destinatario.

  
    El formulario publicado a asignar.
  
  
    <code>{`{ email?, name? }`}</code>. Un correo es obligatorio cuando <code>delivery</code> es <code>"email"</code>; en caso contrario
    solo identifica a la persona en la página de Solicitudes y en sus respuestas.
  
  
    Respuestas iniciales por clave de campo. El destinatario las ve y puede cambiarlas.
  
  
    Claves precompletadas que el destinatario no puede cambiar. Cada clave aquí también debe aparecer en <code>prefill</code>, y un campo
    obligatorio bloqueado debe estar precompletado con un valor no vacío.
  
  
    Valores para los campos ocultos del formulario, por clave de campo. De confianza, inmutables, y devueltos en el callback. Una clave
    desconocida se rechaza con <code>UNKNOWN_FIELD_KEY</code>.
  
  
    Tu propia contabilidad. Nunca llega al formulario; vuelve en los callbacks y las lecturas.
  
  
    Uno de los idiomas publicados del formulario. Por defecto, el idioma predeterminado del formulario.
  
  
    <code>"email"</code> para que Formstep envíe la invitación (necesita un correo del destinatario, y Pro o Business o una de las 10
    invitaciones gratis de una cuenta Free), o <code>"none"</code> para entregar tú mismo el enlace.
  
  
    Anula el calendario de recordatorios del formulario para esta solicitud. Un array vacío desactiva los recordatorios.
  
  
    Milisegundos desde epoch. Por defecto, 30 días; 365 días es el máximo.
  
  
    Adónde envía Formstep el callback en POST cuando la solicitud termina. Solo HTTPS, y el host debe resolver a una dirección pública.
  
  
    Tu propio id para esta solicitud. Filtrable en <code>requests.list</code>.
  
  
    Repetirla con el mismo cuerpo devuelve la solicitud original con <code>deduplicated: true</code>. Un cuerpo distinto se rechaza. Las
    claves viven 30 días.
  
  
    Genera el enlace en uno de tus dominios personalizados. Solo en la API REST.
  
  
    <code>{`[{ documentId, field?, name? }]`}</code> — archivos entregados a este destinatario, subidos antes con{' '}
    <a href="#documents-create">documents.create</a>.
  
  
    Un ensayo: no se envía nada por correo, el callback lleva <code>"test": true</code>, y el envío no cuenta en ningún sitio. El enlace se
    cierra en un plazo de 24 horas, y en Free un workspace puede crear 10 solicitudes de prueba al día.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "requests.create",
    "params": {
      "formId": "j57...",
      "recipient": { "email": "ada@acme.com", "name": "Ada" },
      "prefill": { "company_name": "Acme" },
      "readonly": ["company_name"],
      "context": { "case_id": "CASE-9" },
      "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
  }
}
```

  

> ⚠️ **Guarda la url**
> <p>
>     <code>url</code> lleva el token de un solo uso. <code>requests.get</code> normalmente puede reconstruirla, pero vuelve como{' '}
>     <code>null</code> para una solicitud creada antes de que el despliegue tuviera una clave de token de solicitud. Si entregas el enlace tú
>     mismo, guárdalo al crearlo.
>   </p>

<p>
  <code>deliveryStatus</code> es <code>not_requested</code> hasta que se pone en cola una invitación, luego <code>queued</code> →{' '}
  <code>sent</code> o <code>failed</code>, y <code>bounced</code> en cuanto el proveedor de correo reporta un rebote definitivo o una queja.
</p>

{/* ── requests.get ────────────────────────────────────────────────────────── */}

Obtén una solicitud completa: estado, resultado, lo que se precompletó, su cronología y — una vez completada — <code>answers</code> y{' '}

<code>display</code> indexados por clave de campo, los mismos dos mapas que lleva el callback.

  
    ID de la solicitud.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "kd7...",
    "formId": "j57...",
    "status": "completed",
    "outcome": "approve",
    "isTest": false,
    "recipient": { "email": "ada@acme.com", "name": "Ada" },
    "language": "en",
    "externalId": "run-42",
    "metadata": null,
    "context": { "case_id": "CASE-9" },
    "prefill": { "company_name": "Acme" },
    "readonlyKeys": ["company_name"],
    "delivery": "email",
    "deliveryStatus": "sent",
    "hasCallback": true,
    "callbackFailedAt": null,
    "submissionId": "kp2...",
    "url": "https://form.formstep.io/r/rq_...",
    "answers": { "company_name": "Acme", "decision": "approve" },
    "display": { "company_name": "Acme", "decision": "Approve" },
    "timeline": [
      { "id": "kd7...:created", "type": "created", "at": 1789379200000 },
      { "id": "kd7...:completed", "type": "completed", "at": 1789465600000 }
    ],
    "expiresAt": 1794787200000,
    "completedAt": 1789465600000
  }
}
```

  

> ℹ️ **outcome frente a status**
> <p>
>     <code>status</code> dice si la solicitud terminó; <code>outcome</code> dice qué decidió el destinatario — <code>approve</code>,{' '}
>     <code>decline</code>, <code>changes</code>, o <code>null</code> en cualquier caso que no sea una solicitud completada cuyo destinatario
>     eligió una de las tres — incluido un formulario sin <a href="/es/requests/decisions-and-approvals">pregunta de decisión</a>. La URL del
>     callback nunca se devuelve; <code>hasCallback</code> solo dice si hay una configurada.
>   </p>

<p>
  El ejemplo anterior está recortado. Una respuesta completa también lleva <code>workspaceId</code>, <code>formSnapshotId</code>,{' '}
  <code>createdVia</code>, <code>documents</code>, <code>reminderStep</code>, <code>remindersSent</code>, <code>reminderDueAt</code>,{' '}
  <code>dataPurgedAt</code>, y el resto de las marcas de tiempo (<code>updatedAt</code>, <code>openedAt</code>, <code>startedAt</code>,{' '}
  <code>lastActivityAt</code>, <code>expiredAt</code>, <code>canceledAt</code>, <code>canceledBy</code>, <code>cancelReason</code>).
</p>
<p>
  Dos campos te dicen cuándo la copia que tienes delante es la única copia. <code>callbackFailedAt</code> está establecido mientras el
  callback de esta solicitud se ha quedado sin intentos, y se limpia en cuanto uno llega o lo repites. <code>dataPurgedAt</code> se
  establece una vez que la retención elimina la solicitud: <code>context</code>, <code>prefill</code> y <code>metadata</code> vuelven
  vacíos, <code>readonlyKeys</code> y <code>documents</code> son <code>[]</code>, y <code>submissionId</code>, <code>answers</code> y{' '}
  <code>display</code> son <code>null</code>.
</p>
<p>
  <code>timeline</code> se deriva, del más antiguo al más reciente. Cada entrada tiene un <code>id</code>, un <code>at</code>, y un{' '}
  <code>type</code> — <code>created</code>, <code>invitation</code>, <code>reminder</code>, <code>opened</code>, <code>started</code>,{' '}
  <code>completed</code>, <code>expired</code>, <code>canceled</code>, <code>callback</code>. Las entradas de entrega añaden{' '}
  <code>deliveryStatus</code> y <code>attemptCount</code>, y los callbacks añaden <code>eventType</code>. Las filas de entrega se conservan
  30 días, así que las cronologías más antiguas se reducen a las marcas de tiempo.
</p>

{/* ── requests.list ───────────────────────────────────────────────────────── */}

Lista las solicitudes de un espacio de trabajo o de un formulario, las más recientes primero. Las solicitudes de prueba se omiten a menos
que las pidas.

  
    Limita a un espacio de trabajo. Da esto o <code>formId</code>.
  
  
    Limita a un formulario.
  
  
    <code>pending</code>, <code>completed</code>, <code>expired</code>, o <code>canceled</code>.
  
  
    <code>approve</code>, <code>decline</code>, o <code>changes</code>. Implica solo solicitudes completadas.
  
  
    Tu propio id, para encontrar la solicitud que creó una ejecución.
  
  
    Incluye las solicitudes creadas con <code>test: true</code>.
  
  
    Tamaño de página (1–100).
  
  
    Cursor de paginación de una respuesta anterior.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "kd7...",
        "formId": "j57...",
        "status": "pending",
        "outcome": null,
        "recipient": { "email": "ada@acme.com", "name": "Ada" },
        "externalId": "run-42",
        "deliveryStatus": "sent",
        "expiresAt": 1794787200000,
        "createdAt": 1789379200000
      }
    ],
    "nextCursor": null,
    "hasMore": false
  }
}
```

  

<p>
  Los elementos de la lista llevan los mismos campos que <code>requests.get</code> salvo <code>url</code>, <code>answers</code>,{' '}
  <code>display</code> y <code>timeline</code>, y cada uno lleva <code>isTest</code>. Da <code>workspaceId</code> o <code>formId</code> — no
  dar ninguno es <code>400 VALIDATION_ERROR</code> con motivo <code>SCOPE_REQUIRED</code>. <code>outcome</code> tiene prioridad sobre{' '}
  <code>status</code>, ya que solo una solicitud completada tiene un veredicto.
</p>

{/* ── requests.cancel ─────────────────────────────────────────────────────── */}

Retira una solicitud pendiente. El enlace deja de funcionar, el destinatario ve un aviso de retirada, y se dispara un callback
`request.canceled`.

  
    ID de la solicitud.
  
  
    Tu nota sobre el motivo, guardada en la solicitud y enviada en el callback.
  

{/* ── requests.remind ─────────────────────────────────────────────────────── */}

Envía un correo al destinatario ahora, sin tocar el calendario de recordatorios. Necesita un correo del destinatario y un plan Pro o
Business.

  
    ID de la solicitud. Debe seguir pendiente, y no ser una solicitud de prueba.
  

<p>
  Se aplican dos límites: al menos 10 minutos entre recordatorios manuales, y como máximo 8 recordatorios por solicitud en total, manuales y
  programados juntos. El calendario automático no se toca — <code>reminderStep</code> y <code>reminderDueAt</code> quedan como estaban.
</p>

{/* ── requests.replayCallback ─────────────────────────────────────────────── */}

Reenvía el callback que disparó una solicitud al terminar — mismo payload, mismo id de evento, para que un receptor que ya lo procesó
pueda deduplicarlo. Úsalo tras arreglar un endpoint que fallaba.

  
    ID de la solicitud. Debe estar completada, expirada o cancelada.
  

  
    
```
{
  "ok": true,
  "data": { "dispatchId": "kf9...", "eventId": "evt_kf9..." }
}
```

  

{/* ── requests.sample ─────────────────────────────────────────────────────── */}

Construye un evento de solicitud de muestra para un formulario, sin ninguna solicitud real. Es exactamente el envoltorio que recibe una
suscripción a <code>request\_\*</code> creada con <a href="#webhooks-create">webhooks.create</a>, así que los conectores lo usan para
descubrir campos. Una muestra completada lleva las mismas respuestas de ejemplo que muestra{' '}

<a href="#submissions-sample">submissions.sample</a>; una muestra expirada o cancelada lleva solo el bloque request.

  
    ID del formulario.
  
  
    Qué final muestrear, en la grafía de <code>webhooks.create</code>. El <code>type</code> del envoltorio es la forma con puntos.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "evt_example000000000000",
    "type": "request.completed",
    "createdAt": "2026-05-18T19:00:00.000Z",
    "apiVersion": "2026-09-24",
    "test": true,
    "data": {
      "request": {
        "id": "req_example000000000000",
        "externalId": "order-1234",
        "status": "completed",
        "language": "en",
        "recipient": { "email": "recipient@example.com", "name": "Sample Recipient" },
        "metadata": { "source": "sample" },
        "context": {},
        "createdAt": "2026-05-18T18:00:00.000Z",
        "completedAt": "2026-05-18T19:00:00.000Z"
      },
      "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" },
      "submission": {
        "id": "sub_example000000000000",
        "respondentEmail": "respondent@example.com",
        "submittedAt": "2026-05-18T19:00:00.000Z",
        "updatedAt": null,
        "editCount": 0,
        "pdfUrl": null,
        "language": "en"
      },
      "answers": { "your_email": "john@example.com" },
      "display": { "your_email": "john@example.com" }
    }
  }
}
```

  

<p>
  El bloque request y el resultado están documentados en la <a href="/es/requests/callbacks#payload">página de callbacks</a>; la mitad de la
  respuesta en la <a href="/es/developers/webhooks-reference#payload">referencia de webhooks</a>. Los ids de muestra son los marcadores
  fijos que se muestran arriba y <code>test</code> es <code>true</code>, así que un receptor puede distinguir una muestra de un evento real.
</p>

{/* ── documents.create ────────────────────────────────────────────────────── */}

Reserva una subida para un archivo que entregarás a un destinatario a través del [bloque de Documentos](/es/building-forms/documents-block)
del formulario. Los bytes nunca pasan por esta API: obtienes un `PUT` prefirmado, subes el archivo, y `requests.create` verifica el objeto
antes de que la solicitud exista.

  
    El formulario cuyo bloque de Documentos mostrará el archivo. Limita la subida a ese espacio de trabajo.
  
  
    Nombre visible que ve el destinatario (1–200 caracteres). Se puede anular por solicitud.
  
  
    <code>application/pdf</code> o un tipo de imagen: <code>image/png</code>, <code>image/jpeg</code>, <code>image/webp</code>,{' '}
    <code>image/gif</code>, <code>image/svg+xml</code>, <code>image/avif</code>, <code>image/bmp</code>, <code>image/tiff</code>. No se
    aceptan documentos de Office.
  
  
    Longitud exacta en bytes. Máximo 25 MB (26.214.400).
  
  
    Digest hexadecimal de los bytes. Se verifica después de subir el archivo, cuando se proporciona.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "kn7...",
    "name": "Lease contract draft",
    "contentType": "application/pdf",
    "size": 412000,
    "uploadUrl": "https://...",
    "expiresAt": 1792198800000
  }
}
```

  

<p>
  Haz un <code>PUT</code> de los bytes en bruto a <code>uploadUrl</code> antes de que pase una hora, con <code>Content-Type</code> ajustado
  al tipo que declaraste, y luego referencia el id desde <code>requests.create</code>:
</p>

```
{
  "method": "requests.create",
  "params": {
    "formId": "j57...",
    "documents": [
      { "documentId": "kn7...", "name": "Your lease contract" },
      { "documentId": "kn8...", "field": "attachments" }
    ]
  }
}
```

<ul>
  <li>
    <code>field</code> es la clave de campo del bloque de Documentos. Opcional cuando el formulario tiene exactamente un bloque; obligatorio
    con dos o más.
  </li>
  <li>Los documentos de autor del bloque permanecen; los tuyos aparecen debajo de ellos, solo para este destinatario.</li>
  <li>Límites: 25 MB por documento, 100 MB de documentos por solicitud, 20 documentos mostrados por bloque incluyendo los de autor.</li>
  <li>
    Una subida puede ser referenciada por cualquier número de solicitudes. Una subida que nadie referencia caduca. Los bytes cuentan contra
    el almacenamiento del propietario del espacio de trabajo hasta que la retención elimina la última solicitud que los referencia.
  </li>
</ul>

<p>
  Todo fallo aquí es <code>400 VALIDATION_ERROR</code> con un <code>details.reason</code>: <code>DOCUMENT_TYPE_NOT_ALLOWED</code>,{' '}
  <code>DOCUMENT_TOO_LARGE</code>, <code>INVALID_DOCUMENT_NAME</code> o <code>INVALID_DOCUMENT_SHA256</code> de este método, y{' '}
  <code>DOCUMENT_NOT_FOUND</code>, <code>DOCUMENT_NOT_UPLOADED</code> (te saltaste el <code>PUT</code>), <code>DOCUMENT_INVALID</code>,{' '}
  <code>INVALID_DOCUMENT_TARGET</code>, <code>DOCUMENTS_TOO_LARGE</code> o <code>DOCUMENTS_TOO_MANY</code> de <code>requests.create</code>.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="webhooks" class="border-t border-border pt-8">
  Webhooks
</h2>

{/* ── webhooks.list ───────────────────────────────────────────────────────── */}

Lista las suscripciones de webhook de un formulario.

  
    ID del formulario.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "subscriptionId": "int_abc123",
        "formId": "frm_abc123",
        "provider": "zapier",
        "targetUrl": "https://hooks.zapier.com/...",
        "eventType": "submission_created",
        "status": "active",
        "createdAt": 1714041851000
      }
    ],
    "hasMore": false
  }
}
```

  

{/* ── webhooks.create ─────────────────────────────────────────────────────── */}

Suscribe una URL a eventos del formulario: respuestas nuevas o abandonadas, o solicitudes del formulario al terminar. La URL debe usar
HTTPS.

  
    ID del formulario.
  
  
    URL HTTPS para recibir los payloads del webhook.
  
  
    A qué herramienta pertenece la suscripción. Es una etiqueta para tu propia contabilidad — no hay ninguna app de marketplace que
    instalar, y todos los proveedores se comportan igual.
  
  
    Tipo de evento al que suscribirse. Los tres tipos <code>submission_*</code> entregan el payload de la respuesta:{' '}
    <code>submission_created</code> una primera respuesta, <code>submission_updated</code> una edición del respondente, y{' '}
    <code>submission_abandoned</code> un borrador inactivo. Los tres tipos <code>request_*</code> entregan el{' '}
    <a href="/es/requests/callbacks#payload">evento de solicitud</a> correspondiente cada vez que una solicitud del formulario termina de
    ese modo, firmado con el secreto de esta suscripción; las solicitudes de prueba no llegan a ninguna suscripción.
  
  
    Obligatorio cuando <code>eventType</code> es <code>submission_abandoned</code>; rechazado para cualquier otro tipo.
  
  
    Secreto de firma HMAC opcional, 32–255 caracteres. Cuando se proporciona, las entregas incluyen <code>X-Formstep-Signature</code>. El
    secreto se guarda pero la API nunca lo devuelve.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "webhooks.create",
    "params": {
      "formId": "frm_abc123",
      "targetUrl": "https://hooks.zapier.com/hooks/catch/...",
      "provider": "zapier",
      "eventType": "submission_created",
      "signingSecret": "whsec_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "subscriptionId": "int_new123",
    "formId": "frm_abc123",
    "provider": "zapier",
    "targetUrl": "https://hooks.zapier.com/hooks/catch/...",
    "eventType": "submission_created"
  }
}
```

  

<p>
  Las suscripciones de respuestas abandonadas devuelven el <code>idleWindow</code> elegido tanto en <code>webhooks.create</code> como en{' '}
  <code>webhooks.list</code>. Cualquier otra suscripción lo omite.
</p>

<p>
  Una suscripción de solicitudes escucha los mismos eventos que un <a href="/es/requests/callbacks">callback</a>, pero como su propia
  entrega: su propio id de evento, su propia firma y su propio presupuesto de cinco reintentos, tras el cual la suscripción se pausa. Una
  solicitud creada con <code>callbackUrl</code> en un formulario con una suscripción <code>request_completed</code> se dispara entonces dos
  veces, una a cada receptor. <code>requests.replayCallback</code> reenvía solo el callback. Usa{' '}
  <a href="#requests-sample">requests.sample</a> para ver el payload antes de que ninguna solicitud haya terminado.
</p>

{/* ── webhooks.delete ─────────────────────────────────────────────────────── */}

Elimina una suscripción de webhook.

  
    ID de suscripción de <code>webhooks.list</code> o <code>webhooks.create</code>.
  

  
    
```
{
  "ok": true,
  "data": {
    "subscriptionId": "int_abc123",
    "deleted": true
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="analytics" class="border-t border-border pt-8">
  Analíticas
</h2>

{/* ── analytics.get ───────────────────────────────────────────────────────── */}

Obtiene métricas de analíticas agregadas de un formulario. Admite filtros por rango de fechas, dispositivo, fuente de tráfico y país.

Las analíticas son una función Pro, y la regla sigue el plan del **propietario** del espacio de trabajo, igual que la pestaña Analíticas del panel. Si el propietario no tiene Pro, la llamada devuelve `UPGRADE_REQUIRED` — también para el historial registrado cuando sí lo tenía. Un miembro Free en el espacio de un propietario Pro sí recibe los datos.

  
    ID del formulario.
  
  
    Inicio del rango de fechas como marca de tiempo Unix en milisegundos. Debe ser menor o igual que <code>to</code> cuando ambos están
    establecidos.
  
  
    Fin del rango de fechas como marca de tiempo Unix en milisegundos. Omite ambos para todo el historial — <code>period</code> vuelve
    entonces como <code>{`{ "from": null, "to": null }`}</code>.
  
  
    Filtrar por tipo de dispositivo.
  
  
    Filtrar por fuente de tráfico (p. ej. <code>"Direct"</code>, <code>"Google"</code>).
  
  
    Filtrar por código de país de 2 letras (p. ej. <code>"US"</code>, <code>"DE"</code>).
  
  
    Devuelve también los eventos de analíticas anonimizados detrás de las métricas, para tu propio análisis. Sin ids de visitante.
  

<p>
  Las tasas son números de 0 a 100, los recuentos son enteros, y <code>totalEvents</code> es el número de filas de eventos en bruto antes de
  la deduplicación en visitantes únicos.
</p>

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "period": { "from": null, "to": null },
    "totalEvents": 17,
    "metrics": {
      "views": 7,
      "uniqueVisitors": 7,
      "engaged": 6,
      "submissions": 4,
      "engagementRate": 86,
      "completionRate": 57,
      "bounceRate": 14
    },
    "breakdown": {
      "byBrowser": { "Chrome": 17 },
      "byCountry": { "US": 10, "DE": 7 },
      "byDevice": { "desktop": 14, "mobile": 3 },
      "bySource": { "Direct": 12, "Google": 5 }
    }
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="workspaces" class="border-t border-border pt-8">
  Espacios de trabajo
</h2>

{/* ── workspaces.list ─────────────────────────────────────────────────────── */}

Lista los espacios de trabajo a los que puede acceder tu token. Sin parámetros.

Un token de API está vinculado a un espacio de trabajo, así que esto devuelve exactamente ese — incluso si tu cuenta pertenece a varios.

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "ws_abc123",
        "name": "Acme Inc",
        "createdAt": 1714041800000,
        "role": "owner"
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── workspaces.createInvite ─────────────────────────────────────────────── */}

Crea un enlace de invitación para un espacio de trabajo.

  
    ID del espacio de trabajo.
  
  
    Vencimiento como marca de tiempo Unix futura en milisegundos.
  
  
    Número máximo de veces que se puede usar la invitación.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "inv_abc123",
    "workspaceId": "ws_abc123",
    "code": "aBcDeFgH",
    "expiresAt": null,
    "maxUses": null,
    "uses": 0,
    "createdAt": 1714041851000
  }
}
```

  

{/* ── workspaces.getInvite ────────────────────────────────────────────────── */}

Obtiene una invitación a un espacio de trabajo. Devuelve la misma forma que `workspaces.createInvite`.

  
    ID de la invitación.
  

{/* ── workspaces.updateInvite ─────────────────────────────────────────────── */}

Actualiza una invitación existente a un espacio de trabajo. Proporciona al menos uno de <code>expiresAt</code> o <code>maxUses</code>, o la
llamada se rechaza. Devuelve la invitación actualizada.

  
    ID de la invitación.
  
  
    Nueva marca de tiempo de vencimiento en milisegundos.
  
  
    Nuevo límite de usos máximos.
  

{/* ── workspaces.revokeInvite ─────────────────────────────────────────────── */}

Revoca permanentemente una invitación a un espacio de trabajo.

  
    ID de la invitación.
  

  
    
```
{
  "ok": true,
  "data": {
    "inviteId": "inv_abc123",
    "revoked": true
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="folders" class="border-t border-border pt-8">
  Carpetas
</h2>

{/* ── folders.list ────────────────────────────────────────────────────────── */}

Lista las carpetas de un espacio de trabajo.

  
    ID del espacio de trabajo.
  
  
    Tamaño de página (1–100).
  
  
    Cursor de paginación.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "fld_abc123",
        "name": "Customer Feedback",
        "workspaceId": "ws_abc123",
        "parentId": null,
        "createdAt": 1714041800000
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── folders.create ──────────────────────────────────────────────────────── */}

Crea una carpeta en un espacio de trabajo. Es idempotente: devuelve la carpeta existente si ya existe una con el mismo nombre.

  
    ID del espacio de trabajo.
  
  
    Nombre de la carpeta (1–255 caracteres).
  
  
    ID de la carpeta padre para anidar. Omite para el nivel raíz.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "fld_new123",
    "name": "Customer Feedback",
    "workspaceId": "ws_abc123",
    "parentId": null,
    "createdAt": 1714041851000,
    "alreadyExisted": false
  }
}
```

  

{/* ── folders.update ──────────────────────────────────────────────────────── */}

Renombra una carpeta o la mueve a un padre diferente.

  
    ID de la carpeta.
  
  
    Nuevo nombre de la carpeta (1–255 caracteres).
  
  
    Nueva carpeta padre. Pasa <code>null</code> para moverla a la raíz.
  

{/* ── folders.delete ──────────────────────────────────────────────────────── */}

Elimina permanentemente una carpeta y todo su contenido (subcarpetas y formularios).

  
    ID de la carpeta.
  

> ⚠️ **Operación destructiva**
> <p>Esto elimina permanentemente todas las subcarpetas y formularios dentro de la carpeta. Esta acción no se puede deshacer.</p>

  
    
```
{
  "ok": true,
  "data": {
    "folderId": "fld_abc123",
    "deletedFolderIds": ["fld_abc123"],
    "deletedFormIds": ["frm_in_folder"],
    "message": "Folder and 1 form deleted."
  }
}
```

  

<h2 id="translations" class="border-t border-border pt-8">
  Traducciones
</h2>

{/* ── translations.listLanguages ──────────────────────────────────────────── */}

Lista todos los idiomas configurados en un formulario.

  
    ID del formulario.
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "items": [
      {
        "language": "es",
        "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 },
        "lastUpdatedAt": 1714041851000
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── translations.addLanguage ────────────────────────────────────────────── */}

Registra un idioma en un formulario. Cualquier otro método de traducción falla con `404 NOT_FOUND` hasta que lo hagas.

  
    ID del formulario.
  
  
    Etiqueta de idioma BCP-47 (p. ej. <code>"es"</code>, <code>"pt-BR"</code>).
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "language": "es",
    "rowId": "tl_new123"
  }
}
```

  

{/* ── translations.removeLanguage ─────────────────────────────────────────── */}

Elimina un idioma y todas sus traducciones de un formulario.

  
    ID del formulario.
  
  
    Etiqueta de idioma BCP-47.
  

{/* ── translations.listEntries ────────────────────────────────────────────── */}

Lista cada clave de origen para un idioma en un formulario, con su estado actual. Así es como descubres los valores de `key` que acepta
`translations.setEntry`.

  
    ID del formulario.
  
  
    Etiqueta de idioma BCP-47. Debe estar ya en el formulario.
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "language": "es",
    "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 },
    "items": [
      {
        "key": "block_q_1.title",
        "status": "current",
        "value": "[{\"text\":\"Tu correo electrónico\"}]",
        "sourceFragment": "[{\"text\":\"Your email\"}]",
        "updatedAt": 1714041851000,
        "block": { "id": "q_1", "type": "email-input", "label": "Your email" }
      }
    ],
    "hasMore": false
  }
}
```

  

<p>
  <code>status</code> es <code>missing</code> (nada guardado), <code>current</code>, <code>outdated</code> (el origen cambió desde
  entonces), o <code>suggested</code> (una sugerencia de IA preparada pero no aceptada). Las claves cubren el contenido del formulario (
  <code>block_&lt;id&gt;.*</code>) y, una vez que un autor las ha personalizado, los correos de confirmación y recordatorio del respondente
  (<code>email.confirmation.*</code>, <code>email.reminder.*</code>).
</p>

{/* ── translations.setEntry ───────────────────────────────────────────────── */}

Establece una entrada de traducción individual. El idioma debe haberse agregado previamente con <code>translations.addLanguage</code>.

  
    ID del formulario.
  
  
    Etiqueta de idioma BCP-47.
  
  
    Una clave de <code>translations.listEntries</code>. No la construyas a mano.
  
  
    El fragmento traducido, en formato JSON-string. Su estructura de marcas debe coincidir con la del fragmento de origen.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "translations.setEntry",
    "params": {
      "formId": "frm_abc123",
      "language": "es",
      "key": "block_q_1.title",
      "value": "[{\"text\":\"Tu correo electrónico\"}]"
    }
  }'
```

> ⚠️ **Aquí las escrituras son en vivo**
> <p>
>     La API no tiene un paso de borrador y luego publicación: un <code>setEntry</code> o <code>deleteEntry</code> llega a los respondentes de
>     inmediato. El panel y las herramientas de traducción de MCP usan un borrador en su lugar.
>   </p>

<p>
  Devuelve <code>{`{ formId, language, key }`}</code>.
</p>

{/* ── translations.deleteEntry ────────────────────────────────────────────── */}

Elimina una entrada de traducción individual, revirtiendo esa clave al idioma predeterminado del formulario. Idempotente. Cuando desaparece
la última entrada de un idioma, ese idioma deja de figurar entre los idiomas publicados del formulario.

  
    ID del formulario.
  
  
    Etiqueta de idioma BCP-47.
  
  
    Clave de traducción a eliminar.
  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="me" class="border-t border-border pt-8">
  Cuenta
</h2>

{/* ── me.get ──────────────────────────────────────────────────────────────── */}

Obtiene información sobre el usuario autenticado.

  
    
```
{
  "ok": true,
  "data": {
    "id": "usr_abc123",
    "email": "you@example.com",
    "name": "Jane Doe"
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="meta" class="border-t border-border pt-8">
  Meta
</h2>

Lista, ordenados, todos los nombres de métodos que sirve este despliegue. La respuesta autorizada cuando esta página y el servidor no
coinciden.

  
    
```
{
  "ok": true,
  "data": {
    "methods": [
      "methods.list",
      "analytics.get",
      "folders.create",
      "folders.delete",
      "folders.list",
      "folders.update",
      "formSettings.get",
      "formSettings.update",
      "forms.create",
      "forms.delete",
      "forms.get",
      "forms.list",
      "..."
    ]
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="error-reference" class="border-t border-border pt-8">
  Referencia de errores
</h2>

Cada respuesta de error tiene la misma forma. El conjunto de `code` de nivel superior es cerrado a propósito: un nuevo modo de fallo nunca
añade un código, añade un `reason`. Decide según `code` para el resultado a nivel HTTP y según `details.reason` para la solución.

```
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "No field with key \"company\" on this form.",
    "details": { "reason": "UNKNOWN_FIELD_KEY", "field": "prefill.company", "validKeys": ["company_name", "contacts"] }
  }
}
```

<p>
  <code>details</code> está presente siempre que el servidor pueda nombrar la causa. Además de <code>reason</code>, puede llevar{' '}
  <code>field</code> (el parámetro causante, con puntos para el anidamiento), <code>validKeys</code>, <code>validValues</code> (los{' '}
  <strong>values</strong> de opción que acepta una pregunta de elección), <code>expectedType</code>, <code>feature</code> (en{' '}
  <code>UPGRADE_REQUIRED</code>), y <code>retryAfterMs</code> (en una llamada limitada). Los motivos de la superficie de solicitudes se
  listan junto a cada método más arriba.
</p>

<p>Estos son todos los códigos:</p>

  
    Parámetros inválidos o ausentes en la solicitud.
  
  
    Token de API ausente o inválido.
  
  
    El token no tiene acceso al recurso solicitado.
  
  
    El recurso no existe.
  
  
    Nombre de método desconocido. Usa <code>methods.list</code> para ver los métodos disponibles.
  
  
    El recurso no está en un estado que permita esta llamada — una solicitud que ya no está pendiente, una clave de idempotencia reutilizada
    con un cuerpo distinto.
  
  
    Más de 120 llamadas por minuto con este token, más de 60 llamadas a <code>requests.create</code> por minuto, o demasiadas
    autenticaciones fallidas desde esta IP.
  
  
    La función requiere un nivel de suscripción superior, el espacio de trabajo ha gastado su asignación mensual (motivo{' '}
    <code>MONTHLY_ALLOWANCE_REACHED</code>), o una cuenta Free ha gastado sus 10 invitaciones gratis (motivo{' '}
    <code>FREE_INVITATIONS_USED</code>).
  
  
    Error inesperado del servidor. Inténtalo de nuevo más tarde.
  

<h2 id="next-steps">Próximos pasos</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Tokens de API](/es/developers/api-tokens) — Crea y gestiona tokens
  - [Servidor MCP](/es/developers/mcp-server) — Usa Formstep desde agentes de IA
  - [Referencia de webhooks](/es/developers/webhooks-reference) — Esquema de payload y firma
</div>
