# Cara kerja

> Setiap pemeriksaan yang dilalui sebuah kiriman, secara berurutan, dan apa yang bisa dikembalikan masing-masing.

Setiap kiriman ke `/f/<formId>` melewati pipeline yang sama. Pemeriksaan yang ringan berjalan lebih dulu, jadi banjir request atau formulir yang tidak dikenal hampir tidak memakan biaya. Pemeriksaan pertama yang gagal menentukan responsnya.

Pipeline-nya, secara berurutan:

1. Proteksi flood IP: maksimal 600 kiriman per menit dari satu IP di semua formulir, sebelum formulir dicari. Gagal dengan `429 rate_limited`.
2. Pencarian formulir: formulir yang tidak dikenal, dihapus, dan dinonaktifkan gagal dengan `404 not_found`.
3. Rate limit formulir: preset formulir, dihitung per IP pengunjung dan per formulir. Gagal dengan `429 rate_limited`.
4. Formulir yang dijeda gagal dengan `403 form_paused`.
5. Parsing body: urlencoded, multipart, atau JSON, maksimal 256 KB. Gagal dengan `415`, `400 invalid_body`, `413 payload_too_large`, `422 too_deep`, atau `422 files_not_supported`.
6. Kunci API, jika header `Authorization` dikirim. Gagal dengan `403 forbidden` atau `403 origin_not_allowed`.
7. Domain yang diizinkan: kiriman dari situs lain disimpan sebagai spam, atau gagal dengan `403 origin_not_allowed`.
8. Pemeriksaan manusia: gagal dengan `403 challenge_required` atau `403 captcha_failed`. Kiriman formulir dari browser mendapat halaman pemeriksaan sebagai gantinya.
9. Validasi field: gagal dengan `422 validation_failed`.
10. Penilaian spam: skor 5 atau lebih disimpan sebagai spam tetapi tetap dijawab sebagai sukses.
11. Idempotensi: key yang sama mengembalikan respons pertama lagi, atau gagal dengan `409` atau `422`.
12. Pemakaian bulanan: jauh melewati batas gagal dengan `429 quota_exceeded`.
13. Simpan: `201` dengan ID kiriman, atau redirect `303` untuk kiriman dari browser. Setelah itu webhook, email notifikasi, dan pembaruan dashboard langsung menyusul.

## Apa yang dilakukan setiap tahap

### Rate limit sebelum yang lain

Rate limit berjalan sebelum body dibaca. Proteksi flood (600 per menit per IP) bahkan berjalan sebelum pencarian formulir, jadi banjir request tidak pernah sampai ke database. Aturan milik formulir berjalan tepat setelah pencarian. Request yang ditolak tidak memakai jatah. Angka pastinya ada di [Rate limit](https://ikiform.com/id/docs/rate-limits.md).

### Parsing

Body diurai sebagai urlencoded, multipart, atau JSON. Teks di-decode sebagai UTF-8, dinormalisasi ke NFC, CRLF menjadi LF, dan karakter kontrol dihapus. Nama yang diawali `_` adalah instruksi reserved, seperti `_redirect` dan `_subject`, dan tidak pernah disimpan sebagai jawaban. Lihat [Formulir HTML](https://ikiform.com/id/docs/html.md#reserved-fields).

### Siapa pengirimnya

Header `Authorization: Bearer` bersifat opsional. Secret key (`ik_sk_`) dengan scope `submissions:write` melewati pemeriksaan domain yang diizinkan dan pemeriksaan manusia Ikiform. Publishable key (`ik_pub_`) hanya berfungsi dari domain yang tercantum di key tersebut. Tanpa key, kiriman dinilai berdasarkan `Origin`-nya.

### Spam

Spam tidak pernah menjadi error. Kiriman yang gagal kebijakan domain yang diizinkan dalam mode spam, atau mendapat skor 5 atau lebih, disimpan dengan status `spam` dan mendapat respons sukses yang sama seperti kiriman asli, jadi bot tidak belajar apa pun. Spam tidak mengirim webhook atau email dan tidak dihitung dalam pemakaian. Lihat [Perlindungan spam](https://ikiform.com/id/docs/spam.md).

### Simpan dan kirim

Kiriman, pengiriman webhook-nya, dan email notifikasinya ditulis dalam satu batch database, lalu diserahkan ke antrean. Pengiriman webhook dan email tidak pernah memperlambat respons, dan kiriman tidak pernah disimpan tanpa pengirimannya.

## JSON atau redirect

Endpoint yang sama menjawab browser dengan halaman dan kode dengan JSON. Ikiform memutuskannya per request:

| Request | Jawaban |
| --- | --- |
| `Accept` mencakup `application/json` | JSON |
| Body-nya JSON | JSON |
| `Sec-Fetch-Mode: navigate` (kiriman formulir biasa) | Redirect atau halaman hosted |
| `Sec-Fetch-Mode` lain, seperti `cors` dari `fetch` | JSON |
| Tanpa `Sec-Fetch-Mode`, dan `Accept` mencakup `text/html` | Redirect atau halaman hosted |
| Selain itu, seperti curl | JSON |

Dalam mode JSON, sukses berarti `201 { "ok": true, "id": "sub_..." }`. Dalam mode halaman, sukses berupa redirect `303`, dan error menampilkan halaman error hosted yang mencantumkan masalah field berdasarkan label.

## Setelah kiriman disimpan

| Apa | Kapan | Detail |
| --- | --- | --- |
| Webhook | Beberapa detik kemudian, dicoba ulang selama sekitar 3 hari | [Webhook](https://ikiform.com/id/docs/webhooks.md) |
| Email notifikasi | Beberapa detik kemudian, jika notifikasi aktif untuk formulir tersebut | Dibatasi 50 email per formulir per jam (10 untuk workspace yang berumur kurang dari 7 hari), lalu satu ringkasan per jam |
| Dashboard | Langsung, real-time | Spam muncul di filter spam |
| REST API | Langsung | [REST API](https://ikiform.com/id/docs/api.md) |

> Formulir yang kirimannya berisi nama field bergaya kata sandi, kartu, atau seed phrase akan ditandai dan email notifikasinya ditahan untuk ditinjau. Jangan kumpulkan kredensial dengan backend formulir.

## ID

| ID | Bentuknya | Catatan |
| --- | --- | --- |
| Formulir | `frm_xxxxxxxxxxxxxxxx` | `frm_` ditambah 16 karakter acak. Publik: ada di URL action formulir Anda. |
| Kiriman | `sub_01k7v2c9t3e8m5q4x6z0a1b2c3` | `sub_` ditambah 26 karakter, berurutan menurut waktu. |
| Pengiriman webhook | `dlv_...` | Dikirim sebagai `webhook-id`; tetap sama di setiap percobaan ulang. |
| Request | `req_...` | Setiap respons punya header `X-Request-Id`. Sertakan saat Anda menghubungi dukungan. |

**201** (`Sukses JSON`)

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

**303** (`Sukses browser`)

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