# Exportar envíos

> Descarga los envíos en CSV desde el panel, o exporta todos los envíos con la API REST.

## CSV desde el panel

Abre **Submissions**, o la pestaña de envíos de un formulario, aplica los filtros que quieras y elige **Export CSV**. El archivo respeta los filtros de la pantalla.

| Filtro | Valores |
| --- | --- |
| Formulario | Un formulario o todos |
| Estado | Recibidos, spam o ambos |
| Rango de fechas | Últimas 24 horas, últimos 7 días, últimos 30 días o todo |

- Las columnas son `id`, `created_at`, `form`, `status` y luego una columna por campo. Para un solo formulario, primero van sus campos definidos y después cualquier otro nombre de campo encontrado en las filas exportadas.
- Los arrays de valores simples se unen con una coma y un espacio. Los objetos se escriben como JSON.
- Los valores que empiezan con `=`, `+`, `-`, `@`, un tabulador o un retorno de carro reciben un `'` delante, para que las hojas de cálculo no los ejecuten como fórmulas.
- El archivo es UTF-8 con marca de orden de bytes, para que Excel abra bien los acentos.
- Una exportación incluye hasta 100 000 filas y 500 columnas de campos. Acota los filtros en espacios de trabajo más grandes, o usa la API.

## Todo con la API REST

Para una copia de seguridad completa, una sincronización programada o más de 100 000 filas, recorre las páginas de la [API REST](https://ikiform.com/es/docs/api.md#list-submissions) con una clave secreta que tenga `submissions:read`. Las listas van de más reciente a más antiguo y se mantienen estables mientras llegan envíos nuevos.

**Node** (`export.ts`)

```ts
import { createWriteStream } from "node:fs";
import { createClient } from "@ikiform/sdk";

const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY! });
const out = createWriteStream("submissions.jsonl");

for await (const submission of ikiform.submissions.iterate({
  formId: "frm_xxxxxxxxxxxxxxxx",
  status: "received",
  limit: 100,
})) {
  out.write(`${JSON.stringify(submission)}\n`);
}

out.end();
```

**Python** (`export.py`)

```python
import csv
import os

import requests

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['IKIFORM_SECRET_KEY']}"
params = {"form_id": "frm_xxxxxxxxxxxxxxxx", "status": "received", "limit": 100}

with open("submissions.csv", "w", newline="", encoding="utf-8") as file:
    writer = None
    while True:
        page = session.get("https://ikiform.com/api/v1/submissions", params=params, timeout=30).json()
        for submission in page["data"]:
            row = {"id": submission["id"], "created_at": submission["created_at"], **submission["fields"]}
            if writer is None:
                writer = csv.DictWriter(file, fieldnames=list(row), extrasaction="ignore")
                writer.writeheader()
            writer.writerow(row)
        if not page["has_more"]:
            break
        params["cursor"] = page["next_cursor"]
```

> Cada clave puede hacer 100 peticiones por minuto. Con `limit: 100`, son hasta 10 000 envíos por minuto. El iterador del SDK lanza un `IkiformError` con `retryAfter` ante un `429`; espera esos segundos y continúa desde el último cursor.
