フォーム ID

JavaScript と fetch

送信エンドポイントの詳細: ボディの形式、ヘッダー、CORS、レスポンス、冪等性。

HTTP リクエストを送れるコードなら何でも Ikiform に送信できます。JSON、FormData、urlencoded のデータを https://ikiform.com/f/<formId> に POST し、JSON の応答を読み取ります。SDK はこれを 1 つの関数にまとめていますが、使うかどうかは任意です。特定のスタックでの手順はプラットフォーム別ガイドを参照してください。

fetch を使う

const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
  method: "POST",
  headers: { "Content-Type": "application/json", Accept: "application/json" },
  body: JSON.stringify({
    name: "Ada Lovelace",
    email: "ada@example.com",
    message: "Hello",
  }),
});

const result = await response.json();
if (!result.ok) console.error(result.error.code, result.error.fields);

試してみる

フォーム ID
フィールド
ボディ
冪等キー
curl https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx \
  -H "Content-Type: application/json" \
  -d '{"name":"Ada Lovelace","email":"ada@example.com","message":"Hello from the Ikiform docs."}'

自分のフォーム ID を入力すると、実際のリクエストとして送信できます。

エンドポイント

メソッドパス内容
POST/f/<formId>送信データを作成
OPTIONS/f/<formId>CORS のプリフライト。常に 204
GET/f/<formId>/schemaフォームの公開フィールド定義。shadcn/ui を参照
GET/f/<formId>/thanksホストされたサンクスページ
GET/f/<formId>/challengeSDK が使う、Ikiform の人間確認フレーム

ブラウザーのタブで /f/<formId> そのものを開くと GET になり、404 not_found が返ります。これは想定どおりの動作で、エンドポイントは POST しか受け付けません。

ボディの形式

Content-Type形式メモ
application/json と +json 系のタイプ1 つの JSON オブジェクト値には文字列、数値、真偽値、配列、オブジェクトを使えます。ネストはトップレベルのオブジェクトを含めて最大 3 階層です
application/x-www-form-urlencoded名前と値のペア繰り返される名前と name[] は配列になります
multipart/form-data名前と値のペアurlencoded と同じ。実際のファイルは拒否されます
それ以外415 unsupported_media_type

ボディは UTF-8 である必要があります。utf-8 以外の charset は 415 unsupported_charset になり、UTF-8 として無効なバイトも同じコードになります。JSON の配列や不正な形式のボディは 400 invalid_body になります。

ヘッダー

リクエストヘッダー

ヘッダー必須効果
Content-Typeはいボディのパーサーを決めます。上記を参照
Acceptいいえapplication/json で JSON の応答を強制します。JSON かリダイレクトかを参照
Idempotency-Keyいいえリトライを安全にします。最大 255 文字。冪等性を参照
AuthorizationいいえBearer ik_sk_... または Bearer ik_pub_...。サーバーから送信するを参照
Origin, Refererブラウザーが送信フォームの許可ドメインと照合されます

レスポンスヘッダー

ヘッダータイミング値
X-Request-Idすべてのレスポンスreq_...。エラーのボディでは request_id として繰り返されます
Retry-After429次の送信が許可されるまでの秒数
Locationブラウザーでの成功時の 303リダイレクト先、またはホストされたサンクスページ
Access-Control-Allow-Originリクエストに Origin がある場合リクエスト自身のオリジン
Access-Control-Expose-HeadersCORS の場合X-Request-Id, Retry-After。ブラウザーのコードから両方を読み取れます
Cache-Control成功時no-store

CORS

送信エンドポイントは、どのサイトからのクロスオリジンの送信も受け付けます。プリフライトは常に成功し、リクエストのオリジンをそのまま返します。Cookie や認証情報は使わないため、フォームを守るのは CORS ではなく Allowed domains(許可ドメイン)です。許可ドメインを設定すると、他のサイトからの送信はフォームの設定に応じてスパムとして保存されるか、拒否されます。許可ドメインを参照してください。

プリフライト
OPTIONS /f/frm_xxxxxxxxxxxxxxxx HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Accept, Idempotency-Key, Authorization
Access-Control-Max-Age: 86400
Vary: Origin
/api/v1 以下の REST API は異なります。CORS ヘッダーを送らず、ブラウザーのプリフライトには 405 で応答するため、シークレットキーが Web ページから使われることはありません。

レスポンス

成功すると、新しい送信 ID 付きの 201 を返します。エラーはどこでも同じ形式を使い、バリデーションに失敗した場合はフィールド ID ごとにフィールドエラーを返します。すべてのコードはエラーにあります。

{ "ok": true, "id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3" }
スパムと判定された送信にも 201 を返します。スパムは別に保存され、Webhook や受信トレイに届くことはありません。

冪等性

リトライを安全にするには、Idempotency-Key ヘッダーか _idempotency フィールドを送ります。キーは最大 255 文字で、フォーム単位で 24 時間有効です。両方送った場合はヘッダーが優先されます。

リトライの内容結果
同じキーで同じフィールド最初の応答を再送します。2 件目の送信データは作成されません。
同じキーで異なるフィールド422 idempotency_mismatch
最初のリクエストの処理中に同じキー409 idempotency_in_progress

バリデーションエラーなど、送信データが保存される前に失敗した送信はキーを保持しないので、データを直して同じキーでリトライできます。安全なリトライのループ:

retry.js
const key = crypto.randomUUID();

async function send(attempt = 1) {
  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: { "Content-Type": "application/json", "Idempotency-Key": key },
    body: JSON.stringify({ email: "ada@example.com" }),
  }).catch(() => null);
  if (response && response.status < 500 && response.status !== 429) return response.json();
  if (attempt === 3) throw new Error("Ikiform is unreachable");
  const wait = Number(response?.headers.get("Retry-After")) || attempt * 2;
  await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  return send(attempt + 1);
}

プラットフォーム別ガイド

フレームワーク

静的サイトジェネレーター

サイトビルダー