# 작동 방식

> 제출이 거치는 모든 검사를 순서대로, 각 검사가 반환할 수 있는 응답과 함께 설명합니다.

`/f/<formId>`로 오는 모든 POST는 같은 파이프라인을 거칩니다. 비용이 적은 검사가 먼저 실행되므로, 대량 요청이나 알 수 없는 폼은 거의 비용이 들지 않습니다. 처음 실패한 검사가 응답을 결정합니다.

파이프라인 순서:

1. IP 플러드 가드: 폼을 조회하기 전에, 모든 폼을 합쳐 IP 하나당 분당 최대 600건의 POST만 허용합니다. 실패하면 `429 rate_limited`.
2. 폼 조회: 알 수 없거나, 삭제되었거나, 비활성화된 폼은 `404 not_found`로 실패합니다.
3. 폼 레이트 리밋: 폼의 프리셋으로, 방문자 IP별과 폼별로 셉니다. 실패하면 `429 rate_limited`.
4. 일시 중지된 폼은 `403 form_paused`로 실패합니다.
5. 본문 파싱: urlencoded, multipart, JSON이며 최대 256 KB입니다. 실패하면 `415`, `400 invalid_body`, `413 payload_too_large`, `422 too_deep` 또는 `422 files_not_supported`.
6. API 키: `Authorization` 헤더를 보낸 경우에 확인합니다. 실패하면 `403 forbidden` 또는 `403 origin_not_allowed`.
7. 허용 도메인: 다른 사이트에서 온 POST는 스팸으로 저장되거나 `403 origin_not_allowed`로 실패합니다.
8. 사람 확인: `403 challenge_required` 또는 `403 captcha_failed`로 실패합니다. 브라우저 폼 POST에는 대신 확인 페이지를 보여 줍니다.
9. 필드 검증: `422 validation_failed`로 실패합니다.
10. 스팸 점수: 5점 이상이면 스팸으로 저장되지만 응답은 여전히 성공입니다.
11. 멱등성: 같은 키로 다시 보내면 첫 응답을 그대로 돌려주거나 `409` 또는 `422`로 실패합니다.
12. 월간 사용량: 한도를 크게 넘으면 `429 quota_exceeded`로 실패합니다.
13. 저장: 제출 ID와 함께 `201`, 브라우저 POST에는 `303` 리디렉션. 이어서 웹훅, 알림 이메일, 실시간 대시보드 업데이트가 진행됩니다.

## 단계별 동작

### 무엇보다 먼저 레이트 리밋

레이트 리밋은 본문을 읽기 전에 실행됩니다. 플러드 가드(IP당 분당 600건)는 폼 조회보다도 먼저 실행되므로, 대량 요청이 데이터베이스까지 닿지 않습니다. 폼 자체의 규칙은 조회 직후에 실행됩니다. 거부된 요청은 허용량을 소모하지 않습니다. 정확한 수치는 [레이트 리밋](https://ikiform.com/ko/docs/rate-limits.md)에 있습니다.

### 파싱

본문은 urlencoded, multipart, JSON으로 파싱합니다. 텍스트는 UTF-8로 디코딩하고 NFC로 정규화하며, CRLF는 LF로 바꾸고 제어 문자는 제거합니다. `_`로 시작하는 이름은 `_redirect`, `_subject` 같은 예약된 지시어이며 응답으로 저장되지 않습니다. [HTML 폼](https://ikiform.com/ko/docs/html.md#reserved-fields)을 참고하세요.

### 누가 보냈는지

`Authorization: Bearer` 헤더는 선택 사항입니다. `submissions:write` 스코프가 있는 시크릿 키(`ik_sk_`)는 허용 도메인 검사와 Ikiform 사람 확인을 건너뜁니다. 퍼블리셔블 키(`ik_pub_`)는 키에 등록된 도메인에서만 작동합니다. 키가 없으면 `Origin`으로 POST를 판단합니다.

### 스팸

스팸은 절대 오류가 아닙니다. 스팸 모드에서 허용 도메인 정책을 통과하지 못했거나 점수가 5점 이상인 POST는 `spam` 상태로 저장되고, 실제 제출과 같은 성공 응답을 받습니다. 그래서 봇은 아무것도 알 수 없습니다. 스팸은 웹훅이나 이메일을 보내지 않고 사용량에도 포함되지 않습니다. [스팸 방지](https://ikiform.com/ko/docs/spam.md)를 참고하세요.

### 저장과 전달

제출, 웹훅 전송, 알림 이메일은 하나의 데이터베이스 배치로 기록된 뒤 큐로 넘어갑니다. 웹훅 전송과 이메일이 응답을 늦추는 일은 없으며, 전송 정보 없이 제출만 저장되는 일도 없습니다.

## JSON 또는 리디렉션

같은 엔드포인트가 브라우저에는 페이지로, 코드에는 JSON으로 응답합니다. Ikiform은 요청마다 다음 기준으로 판단합니다.

| 요청 | 응답 |
| --- | --- |
| `Accept`에 `application/json` 포함 | JSON |
| 본문이 JSON | JSON |
| `Sec-Fetch-Mode: navigate` (일반 폼 POST) | 리디렉션 또는 호스팅 페이지 |
| 그 외 `Sec-Fetch-Mode` (예: `fetch`의 `cors`) | JSON |
| `Sec-Fetch-Mode` 없음, `Accept`에 `text/html` 포함 | 리디렉션 또는 호스팅 페이지 |
| 그 밖의 모든 경우 (예: curl) | JSON |

JSON 모드에서 성공 응답은 `201 { "ok": true, "id": "sub_..." }`입니다. 페이지 모드에서 성공 응답은 `303` 리디렉션이며, 오류가 나면 필드 문제를 레이블별로 보여 주는 호스팅 오류 페이지를 렌더링합니다.

## 제출이 저장된 뒤

| 항목 | 시점 | 자세히 |
| --- | --- | --- |
| 웹훅 | 몇 초 뒤, 약 3일간 재시도 | [웹훅](https://ikiform.com/ko/docs/webhooks.md) |
| 알림 이메일 | 몇 초 뒤, 폼의 알림이 켜져 있는 경우 | 폼당 시간당 50통까지(생성 7일 미만 워크스페이스는 10통), 이후 시간별 요약 한 통 |
| 대시보드 | 즉시, 실시간 | 스팸은 스팸 필터에서 표시 |
| REST API | 즉시 | [REST API](https://ikiform.com/ko/docs/api.md) |

> 제출에 비밀번호, 카드, 시드 문구처럼 보이는 필드 이름이 있는 폼은 표시되고, 알림 이메일은 검토를 위해 보류됩니다. 폼 백엔드로 자격 증명을 수집하지 마세요.

## ID

| ID | 형식 | 참고 |
| --- | --- | --- |
| 폼 | `frm_xxxxxxxxxxxxxxxx` | `frm_` 뒤에 무작위 문자 16자. 공개 정보이며 폼의 action URL에 들어 있습니다. |
| 제출 | `sub_01k7v2c9t3e8m5q4x6z0a1b2c3` | `sub_` 뒤에 26자, 시간순으로 정렬됩니다. |
| 웹훅 전송 | `dlv_...` | `webhook-id`로 전송되며, 재시도해도 바뀌지 않습니다. |
| 요청 | `req_...` | 모든 응답에 `X-Request-Id` 헤더가 있습니다. 지원팀에 문의할 때 알려 주세요. |

**201** (`JSON success`)

```json
{ "ok": true, "id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3" }
```

**303** (`Browser success`)

```http
HTTP/1.1 303 See Other
Location: https://example.com/thanks
X-Request-Id: req_01k7v2c9t3e8m5q4x6z0a1b2c3
```
