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.
requiredboolSi el campo es obligatorio. La validación se aplica al enviar la respuesta.
maps_tostringA qué dato de contacto mapea el campo: 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.


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

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