# Exporting submissions

> Download submissions as CSV from the dashboard, or export every submission with the REST API.

## CSV from the dashboard

Open **Submissions**, or a form's own submissions tab, set the filters you want and choose **Export CSV**. The file follows the filters on screen.

| Filter | Values |
| --- | --- |
| Form | One form, or all forms |
| Status | Received, spam, or both |
| Date range | Last 24 hours, last 7 days, last 30 days, or all time |

- Columns are `id`, `created_at`, `form`, `status`, then one column per field. For a single form, its defined fields come first, followed by any other field names found in the exported rows.
- Arrays of plain values are joined with a comma and a space. Objects are written as JSON.
- Values that start with `=`, `+`, `-`, `@`, a tab or a carriage return get a leading `'`, so spreadsheet apps don't run them as formulas.
- The file is UTF-8 with a byte order mark, so Excel opens accents correctly.
- One export holds up to 100,000 rows and 500 field columns. Narrow the filters for larger workspaces, or use the API.

## Everything with the REST API

For a full backup, a scheduled sync or more than 100,000 rows, page through the [REST API](https://ikiform.com/docs/api.md#list-submissions) with a secret key that has `submissions:read`. Lists are newest first and stable while new submissions arrive.

**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"]
```

> Each key can make 100 requests a minute. With `limit: 100`, that is up to 10,000 submissions a minute. The SDK's iterator throws an `IkiformError` with `retryAfter` on `429`; wait that many seconds and continue from the last cursor.
