# 제출 내보내기

> 대시보드에서 제출을 CSV로 다운로드하거나, REST API로 모든 제출을 내보내세요.

## 대시보드에서 CSV로

**Submissions** 또는 폼의 제출 탭을 열고 원하는 필터를 설정한 뒤 **Export CSV**를 고르세요. 파일은 화면의 필터를 따릅니다.

| 필터 | 값 |
| --- | --- |
| 폼 | 폼 하나 또는 모든 폼 |
| 상태 | 수신, 스팸 또는 둘 다 |
| 기간 | 최근 24시간, 최근 7일, 최근 30일 또는 전체 기간 |

- 열은 `id`, `created_at`, `form`, `status` 순이며, 그다음 필드마다 열이 하나씩 붙습니다. 폼 하나를 내보내면 정의된 필드가 먼저 오고, 내보낸 행에서 발견된 다른 필드 이름이 뒤따릅니다.
- 일반 값의 배열은 쉼표와 공백으로 이어 붙입니다. 객체는 JSON으로 기록합니다.
- `=`, `+`, `-`, `@`, 탭, 캐리지 리턴으로 시작하는 값에는 앞에 `'`를 붙여, 스프레드시트 앱이 수식으로 실행하지 않게 합니다.
- 파일은 바이트 순서 표시(BOM)가 있는 UTF-8이므로 Excel에서 악센트 문자가 올바르게 열립니다.
- 한 번에 최대 100,000행, 필드 열 500개까지 내보낼 수 있습니다. 큰 워크스페이스는 필터를 좁히거나 API를 사용하세요.

## REST API로 전부 내보내기

전체 백업, 예약 동기화, 100,000행 이상의 데이터에는 `submissions:read`가 있는 시크릿 키로 [REST API](https://ikiform.com/ko/docs/api.md#list-submissions)를 페이지 단위로 조회하세요. 목록은 최신순이며 새 제출이 들어와도 안정적으로 유지됩니다.

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

> 키마다 분당 100건의 요청을 보낼 수 있습니다. `limit: 100`이면 분당 최대 10,000건의 제출입니다. SDK의 이터레이터는 `429`에서 `retryAfter`가 담긴 `IkiformError`를 던집니다. 그 초만큼 기다린 뒤 마지막 커서부터 계속하세요.
