Skip to content

Formularios

Lee la definición de un formulario de Marketing para renderizarlo en tu sitio y envía las respuestas de vuelta a Finova. Cada respuesta dispara las post-acciones configuradas en Finova (crear/actualizar lead, asociar a cliente, enviar email, notificar al equipo) en background.

Los formularios son multi-idioma: un mismo formulario puede declararse en varios idiomas (es, en) y la API te entrega los textos ya resueltos para el idioma que pidas, además de todas las traducciones para que armes tu propio selector.

Solo lectura de la definición

La definición del formulario (GET /forms, GET /forms/:id) es read-only desde la API: se diseña dentro de Finova. Lo que sí escribes desde afuera son las respuestas (POST /forms/:form_id/submissions).

El objeto Form

json
{
  "id":                "65a4b5c6d7e8f9012345abcd",
  "name":              "Contacto",
  "slug":              "contacto",
  "locale":            "es",
  "locales":           ["es", "en"],
  "default_locale":    "es",
  "title":             "Contáctanos",
  "description":       "Te respondemos en menos de 24 h.",
  "title_translations":       { "es": "Contáctanos", "en": "Contact us" },
  "description_translations": { "es": "Te respondemos en menos de 24 h.", "en": "We reply within 24 h." },
  "active":            true,
  "submissions_count": 128,
  "fields": [
    {
      "key":         "email",
      "label":       "Tu correo electrónico",
      "placeholder": "tu@empresa.com",
      "options":     [],
      "label_translations":       { "es": "Tu correo electrónico", "en": "Your email" },
      "placeholder_translations": { "es": "tu@empresa.com", "en": "you@company.com" },
      "options_translations":     {},
      "help_text":   null,
      "field_type":  "email",
      "required":    true,
      "maps_to":     "lead.email",
      "position":    0
    }
  ],
  "created_at":        "2026-06-09T18:21:09Z",
  "updated_at":        "2026-06-09T18:21:09Z"
}

Campos

CampoTipoDescripción
idstringObjectId.
namestringNombre interno del formulario (no se traduce; úsalo solo como referencia).
slugstringIdentificador legible y estable. Puedes usarlo en lugar del id en las rutas.
localestringIdioma con el que se resolvieron los textos de esta respuesta (= el ?locale= pedido, o el default_locale).
localesstring[]Idiomas que soporta el formulario. Subconjunto de es, en.
default_localestringIdioma por defecto. Se usa cuando no pides ?locale= o pides uno no soportado.
title / descriptionstringTítulo y descripción públicos ya resueltos para locale.
title_translations / description_translationsobjectMapa { <locale>: texto } con todos los idiomas. Útil para armar tu propio selector de idioma.
activeboolSi false, el formulario no acepta respuestas (POST devuelve 422 form_inactive).
submissions_countintTotal de respuestas recibidas.
fieldsarrayDefinición de los campos, ordenados por position. Ver abajo.
created_at / updated_atstringISO 8601 UTC.

El objeto Field

CampoTipoDescripción
keystringClave del campo. Es la llave que usas en answers al enviar la respuesta.
label / placeholderstringEtiqueta y placeholder ya resueltos para locale.
optionsstring[]Opciones ya resueltas para locale (solo en select/radio/checkbox).
label_translations / placeholder_translations / options_translationsobjectMapa { <locale>: ... } con todos los idiomas.
help_textstringTexto de ayuda (no se traduce).
field_typeenumtext, email, phone, textarea, number, date, select, checkbox, radio, file.
fileobjectSolo cuando field_type es file: restricciones de subida del campo. Ver Campos de archivo.
requiredboolSi el campo es obligatorio. La validación se aplica al enviar la respuesta.
maps_tostringA qué dato de contacto mapea el campo (siempre null en campos file): lead.email, lead.name, lead.phone, lead.company_name, lead.job_title, customer.email, customer.name, customer.phone. null si no mapea.
positionintOrden de aparición.

La key es tu contrato

Renderiza usando label/placeholder/options para mostrar, pero construye el payload de answers con la key de cada campo. Las key son estables aunque cambien las etiquetas o el idioma.

Campos de archivo

Un campo con field_type: "file" acepta que el visitante suba uno o varios archivos. La definición trae un objeto file con las restricciones configuradas en Finova, para que puedas construir el <input type="file"> y validar del lado del cliente:

json
{
  "key":        "curriculum",
  "label":      "Adjunta tu CV",
  "field_type": "file",
  "required":   true,
  "maps_to":    null,
  "position":   3,
  "file": {
    "max_files":             2,
    "max_file_size_mb":      5,
    "max_file_size_bytes":   5242880,
    "allowed_kinds":         ["pdf", "documents"],
    "allowed_extensions":    ["pdf", "doc", "docx", "odt", "rtf", "txt"],
    "allowed_content_types": ["application/pdf", "application/msword", "..."],
    "accept":                ".pdf,.doc,.docx,.odt,.rtf,.txt"
  }
}
Campo de fileTipoDescripción
max_filesintMáximo de archivos que acepta el campo (1–10).
max_file_size_mb / max_file_size_bytesintTamaño máximo por archivo.
allowed_kindsstring[]Familias habilitadas: images, pdf, documents, spreadsheets, presentations, videos, audio.
allowed_extensionsstring[]Extensiones aceptadas, sin punto.
allowed_content_typesstring[]MIME types aceptados.
acceptstringListo para el atributo accept de un <input type="file">.

La validación del cliente es solo UX

El servidor revalida todo: tamaño, cantidad, extensión y — sobre todo — el contenido real del archivo (magic bytes), no el Content-Type que declares. Un archivo cuyo contenido no corresponde a su extensión se rechaza con validation_failed.

Formatos bloqueados siempre

Sin importar cómo esté configurado el campo, nunca se aceptan ejecutables, scripts, instaladores, comprimidos, documentos de Office con macros, HTML/SVG ni archivos de credenciales — entre otros: .exe, .dll, .msi, .bat, .cmd, .sh, .ps1, .js, .jar, .apk, .dmg, .deb, .php, .py, .rb, .html, .svg, .docm, .xlsm, .zip, .rar, .7z, .iso, .p12, .pem.

La verificación aplica a todos los segmentos de extensión del nombre, así que cv.pdf.exe y cv.exe.pdf se rechazan igual.


Idiomas: cómo pedir un locale

Pasa ?locale= en cualquier GET para recibir los textos resueltos en ese idioma. Si lo omites (o pides un idioma que el formulario no soporta), se usa el default_locale.

bash
# Textos en inglés
curl https://developers.fi-nova.com/api/v1/forms/contacto?locale=en \
  -H 'Authorization: Bearer finova_sk_TU_SECRETO'

En todos los casos recibes también *_translations con todos los idiomas, así que con una sola petición puedes renderizar un formulario con selector de idioma del lado del cliente.


Listar formularios

GET/api/v1/formsread-forms

Query params: limit, starting_after, ending_before, include_total, page (igual que Productos) y locale. Ver la guía de paginación.


Obtener un formulario

GET/api/v1/forms/:idread-forms

:id acepta el ObjectId o el slug del formulario. Soporta ?locale=.


Listar respuestas de un formulario

GET/api/v1/forms/:form_id/submissionsread-forms

Devuelve las respuestas recibidas (paginadas). :form_id acepta id o slug.


Obtener una respuesta

GET/api/v1/forms/:form_id/submissions/:idread-forms

Enviar una respuesta

POST/api/v1/forms/:form_id/submissionscreate-form_submissions

El cuerpo lleva las respuestas en data.answers, donde cada llave es la key de un campo del formulario. Opcionalmente, data.locale (o el query param ?locale=) indica el idioma en que el visitante llenó el formulario: se guarda en la respuesta y determina, por ejemplo, qué plantilla de email se envía cuando el idioma tiene una configurada.

Campos aceptados en data: answers (objeto requerido), locale (opcional), files (opcional, ver abajo).

Enviar archivos

Un campo file se envía de dos maneras. Elige la que le acomode a tu integración:

a) multipart/form-data — manda el binario bajo la misma key del campo:

bash
curl -X POST https://developers.fi-nova.com/api/v1/forms/vacantes/submissions \
  -H 'Authorization: Bearer finova_sk_TU_SECRETO' \
  -F 'data[answers][email]=laura@example.com' \
  -F 'data[answers][curriculum]=@curriculum.pdf'

También se acepta plano (answers[curriculum]=@...) o en un bloque aparte (files[curriculum]=@...), que es útil cuando tu framework separa campos de archivos.

b) JSON + base64 — en lugar de un string, manda un objeto:

json
{
  "data": {
    "answers": {
      "email": "laura@example.com",
      "curriculum": {
        "filename":     "curriculum.pdf",
        "content_type": "application/pdf",
        "data":         "JVBERi0xLjQKJeLjz9M..."
      }
    }
  }
}

También se acepta una data URL completa ("data:application/pdf;base64,JVBERi0...") en data. Para varios archivos en un mismo campo, manda un array de objetos.

Dónde quedan los archivos

Cada respuesta con archivos crea una carpeta en el gestor de archivos de la empresa (/Formularios/<Formulario>/<fecha> - <correo>) y guarda ahí los archivos. Si el formulario tiene una post-acción de correo con adjuntos activada, los archivos se envían también como attachments (hasta 18 MB por correo; lo que no entre queda solo en el gestor de archivos).

Los archivos no son públicos: se consultan desde Finova, no por URL abierta. La API solo devuelve su metadata en files.

Límite práctico de tamaño

Aunque el campo permita hasta 1 GB, la infraestructura HTTP impone su propio límite de tamaño de petición (típicamente ~32 MB). Para archivos grandes valida antes con tu propio hosting y considera enviar solo el enlace en un campo de texto.

El servidor valida answers contra la definición del formulario (campos requeridos y tipos: email, number, date). Los datos de contacto (email, name, phone) se extraen automáticamente de los campos con maps_to.

Idempotency-Key recomendado

Los formularios web generan doble-submit cuando el usuario hace doble-click o la red reintenta. Manda un Idempotency-Key derivado del contenido (ej. hash del email + timestamp) para evitar respuestas duplicadas.

bash
curl -X POST https://developers.fi-nova.com/api/v1/forms/contacto/submissions \
  -H 'Authorization: Bearer finova_sk_TU_SECRETO' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: form-contacto-abc-123' \
  -d '{
    "data": {
      "locale": "en",
      "answers": {
        "email": "laura@example.com",
        "name":  "Laura Méndez",
        "phone": "+52 656 1234567"
      }
    }
  }'
javascript
await fetch('https://developers.fi-nova.com/api/v1/forms/contacto/submissions', {
  method: 'POST',
  headers: {
    Authorization:     'Bearer finova_sk_TU_SECRETO',
    'Content-Type':    'application/json',
    'Idempotency-Key': 'form-contacto-abc-123',
  },
  body: JSON.stringify({
    data: {
      locale: 'en',
      answers: {
        email: 'laura@example.com',
        name:  'Laura Méndez',
        phone: '+52 656 1234567',
      },
    },
  }),
});

El objeto Submission

json
{
  "id":           "65a4b5c6d7e8f9012345ffff",
  "form_id":      "65a4b5c6d7e8f9012345abcd",
  "locale":       "en",
  "answers":      { "email": "laura@example.com", "name": "Laura Méndez", "phone": "+52 656 1234567" },
  "email":        "laura@example.com",
  "name":         "Laura Méndez",
  "phone":        "+52 656 1234567",
  "contact_type": "lead",
  "contact_id":   "65a4b5c6d7e8f9012345eeee",
  "source":       "developers_api",
  "status":       "received",
  "files": [
    {
      "field_key":    "curriculum",
      "filename":     "curriculum.pdf",
      "content_type": "application/pdf",
      "size_bytes":   184320,
      "asset_id":     "65a4b5c6d7e8f9012345aaaa",
      "folder_path":  "/Formularios/Vacantes/2026-08-10 1432 - laura@example.com"
    }
  ],
  "files_error":  null,
  "referrer":     "https://tusitio.com/contacto",
  "created_at":   "2026-06-09T18:21:09Z",
  "updated_at":   "2026-06-09T18:21:10Z"
}
CampoTipoDescripción
idstringObjectId de la respuesta.
form_idstringFormulario al que pertenece.
localestringIdioma en que se envió.
answersobjectLo que mandaste, por key.
email / name / phonestringDatos de contacto extraídos de los campos con maps_to.
contact_typeenumlead, customer o none. Cómo se resolvió el contacto al procesar.
contact_idstringObjectId del Lead/Customer resuelto (si aplica).
sourcestringOrigen. developers_api cuando entra por esta API.
statusenumreceived (recién creada), processed, failed (todas las post-acciones fallaron).
filesarrayArchivos recibidos: field_key, filename, content_type, size_bytes, asset_id (id en el gestor de archivos) y folder_path. Vacío si el formulario no tiene campos de archivo.
files_errorstringMotivo por el que los archivos no se pudieron guardar (p. ej. la empresa agotó su almacenamiento). La respuesta se conserva igual para no perder el contacto. null cuando todo salió bien.
referrerstringReferer de la petición, si lo hubo.
created_at / updated_atstringISO 8601 UTC.

Procesamiento asíncrono

Al crear la respuesta recibes status: "received" de inmediato. Las post-acciones (lead/cliente, email, notificación) corren en background, así que el status final (processed/failed) y el contact_id aparecen al volver a leer la respuesta unos segundos después.


Errores específicos

errorCuándo
validation_failed (422)Falta un campo requerido o un valor no pasa la validación de tipo (email/number/date). El message indica qué campo.
validation_failed (422)Un archivo excede el tamaño o la cantidad permitida del campo, tiene una extensión no permitida o bloqueada, o su contenido real no coincide con su extensión.
form_inactive (422)El formulario tiene active: false y no acepta respuestas.
not_found (404)El form_id (o id) no existe o pertenece a otra empresa.
insufficient_scope (403)Falta el scope read-forms (lectura) o create-form_submissions (envío).

Hecho con cuidado por Finova.