JavaScript y fetch
El endpoint de envíos en detalle: formatos de cuerpo, cabeceras, CORS, respuestas e idempotencia.
Cualquier código que pueda enviar una petición HTTP puede enviar a Ikiform. Envía JSON, FormData o datos urlencoded a https://ikiform.com/f/<formId> y lee una respuesta JSON. El SDK lo envuelve en una función, pero es opcional. Para una configuración paso a paso en un stack concreto, consulta Guías por plataforma.
Con fetch
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({
name: "Ada Lovelace",
email: "ada@example.com",
message: "Hello",
}),
});
const result = await response.json();
if (!result.ok) console.error(result.error.code, result.error.fields);Pruébalo
curl https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx \
-H "Content-Type: application/json" \
-d '{"name":"Ada Lovelace","email":"ada@example.com","message":"Hello from the Ikiform docs."}'Escribe el ID de tu formulario para enviarlo como una petición real.
Endpoints
| Método | Ruta | Qué hace |
|---|---|---|
POST | /f/<formId> | Crea un envío |
OPTIONS | /f/<formId> | Preflight de CORS, siempre 204 |
GET | /f/<formId>/schema | La definición pública de los campos del formulario, consulta shadcn/ui |
GET | /f/<formId>/thanks | La página de agradecimiento alojada |
GET | /f/<formId>/challenge | El frame de verificación humana de Ikiform, que usa el SDK |
Abrir /f/<formId> en una pestaña del navegador es un GET, que responde 404 not_found. Es lo esperado: el endpoint solo acepta envíos.
Formatos de cuerpo
| Content-Type | Forma | Notas |
|---|---|---|
application/json y tipos +json | Un objeto JSON | Los valores pueden ser cadenas, números, booleanos, arrays y objetos, con un anidamiento máximo de 3 niveles contando el objeto principal |
application/x-www-form-urlencoded | Pares de nombre y valor | Los nombres repetidos y name[] se convierten en arrays |
multipart/form-data | Pares de nombre y valor | Igual que urlencoded; los archivos reales se rechazan |
| Cualquier otro | 415 unsupported_media_type |
Los cuerpos deben ser UTF-8. Un charset distinto de utf-8 recibe 415 unsupported_charset, y los bytes que no son UTF-8 válido reciben el mismo código. Un array JSON o un cuerpo mal formado recibe 400 invalid_body.
Cabeceras
Cabeceras de la petición
| Cabecera | Obligatoria | Efecto |
|---|---|---|
Content-Type | Sí | Elige el parser del cuerpo, consulta arriba |
Accept | No | application/json fuerza una respuesta JSON, consulta JSON o una redirección |
Idempotency-Key | No | Hace que los reintentos sean seguros, hasta 255 caracteres, consulta Idempotencia |
Authorization | No | Bearer ik_sk_... o Bearer ik_pub_..., consulta Desde un servidor |
Origin, Referer | Los envían los navegadores | Se comparan con los dominios permitidos del formulario |
Cabeceras de la respuesta
| Cabecera | Cuándo | Valor |
|---|---|---|
X-Request-Id | Cada respuesta | req_..., repetido como request_id en los cuerpos de error |
Retry-After | 429 | Segundos hasta que se permite el siguiente envío |
Location | 303 éxito en el navegador | Tu redirección o la página de agradecimiento alojada |
Access-Control-Allow-Origin | Cuando la petición tiene un Origin | El origen de la propia petición |
Access-Control-Expose-Headers | Con CORS | X-Request-Id, Retry-After, para que el código del navegador pueda leer ambas |
Cache-Control | Éxito | no-store |
CORS
El endpoint de envíos acepta envíos cross-origin desde cualquier sitio. Los preflights siempre tienen éxito y devuelven el origen de la petición. No se usan cookies ni credenciales, así que CORS no es lo que protege tu formulario: lo protegen los dominios permitidos. Con dominios permitidos configurados, un envío desde otro sitio se guarda como spam o se rechaza, según el ajuste del formulario. Consulta Dominios permitidos.
OPTIONS /f/frm_xxxxxxxxxxxxxxxx HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Accept, Idempotency-Key, Authorization
Access-Control-Max-Age: 86400
Vary: Origin/api/v1 es distinta: no envía cabeceras CORS y responde a los preflights del navegador con 405, así que una clave secreta nunca se puede usar desde una página web.Respuestas
El éxito es 201 con el id del nuevo envío. Los errores usan el mismo formato en todas partes, con los errores de campo indexados por id de campo cuando falla la validación. Todos los códigos están en Errores.
{ "ok": true, "id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3" }201. El spam se guarda aparte y nunca llega a tus webhooks ni a tu bandeja de entrada.Idempotencia
Envía una cabecera Idempotency-Key, o un campo _idempotency, para que los reintentos sean seguros. Las claves tienen hasta 255 caracteres y valen para el formulario durante 24 horas. Si se envían ambas, gana la cabecera.
| Reintento con | Resultado |
|---|---|
| La misma clave y los mismos campos | Se repite la primera respuesta. No se crea un segundo envío. |
| La misma clave y campos distintos | 422 idempotency_mismatch |
| La misma clave mientras la primera petición sigue en curso | 409 idempotency_in_progress |
Los envíos que fallan antes de guardarse, como los errores de validación, no conservan la clave, así que puedes corregir los datos y reintentar con la misma clave. Un bucle de reintentos seguro:
const key = crypto.randomUUID();
async function send(attempt = 1) {
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: { "Content-Type": "application/json", "Idempotency-Key": key },
body: JSON.stringify({ email: "ada@example.com" }),
}).catch(() => null);
if (response && response.status < 500 && response.status !== 429) return response.json();
if (attempt === 3) throw new Error("Ikiform is unreachable");
const wait = Number(response?.headers.get("Retry-After")) || attempt * 2;
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return send(attempt + 1);
}Guías por plataforma
Frameworks
- Next.jsUn componente de cliente o una server action.
- ReactEstados de carga, error y éxito.
- VueUn componente de archivo único.
- NuxtEnvía antes de la hidratación y con
$fetchdespués. - SvelteUn componente de Svelte 5 y una server action.
- SvelteKitUna página prerenderizada con mejora progresiva.
- AstroUn formulario sin JavaScript, opcionalmente mejorado.
- GatsbyUn formulario estático o una Gatsby Function.
- JavaScript puro
fetchcon errores en línea.
Generadores de sitios estáticos
- HugoUn partial y un shortcode para los archivos de contenido.
- JekyllUn include y la configuración de GitHub Pages.
- EleventyUn shortcode para Nunjucks, Liquid y Markdown.