フォーム ID

SDK リファレンス

@ikiform/sdk パッケージ: submit、challenge、getSchema、validate、createClient、verifyWebhook。

@ikiform/sdk は、フォームの送信、REST API からのデータの読み取り、Webhook の検証を行う小さな TypeScript パッケージです。依存関係はなく、fetch、FormData、Web Crypto だけを使うので、ブラウザー、Node 20 以降、Bun、Deno、Cloudflare Workers で動きます。型宣言付きの ESM として提供しています。

ターミナル
npm install @ikiform/sdk
インポートインポート元実行環境
submit@ikiform/sdkブラウザーまたはサーバー
challenge, ChallengeError@ikiform/sdkブラウザー
getSchema, validate, validateField@ikiform/sdkブラウザーまたはサーバー
createClient, IkiformError@ikiform/sdkサーバーのみ
verifyWebhook, WebhookVerificationError@ikiform/sdk/webhooksサーバー

submit

submit(formId, data, options?) は /f/:formId に送信し、結果に解決されます。例外は投げません。HTTP エラーではエラーの情報を、ネットワークエラーではコード network_error を、中断では aborted を返します。

SubmitResult
type SubmitResult =
  | { ok: true; id: string }
  | {
      ok: false;
      error: { code: string; message: string; fields?: Record<string, string[]> };
      status: number | null;
      requestId: string | null;
    };
data送信形式
テキストだけの FormDataapplication/x-www-form-urlencoded
File を含む FormDatamultipart/form-data。アップロードが実装されるまで Ikiform は拒否します
URLSearchParamsapplication/x-www-form-urlencoded
単純なオブジェクトapplication/json
  • React の $ACTION_ フィールドは FormData から除外されます。
  • 戻り値は React のフォームアクションの型も満たすので、action={(data) => submit(id, data)} と書けます。
  • submit は Accept: application/json を送ります。Authorization ヘッダーは送らないので、API キーを付けて送信するには fetch を使ってください。
オプション説明
baseUrlデフォルトは https://ikiform.com。
signalリクエストをキャンセルするための AbortSignal。
idempotencyKeyIdempotency-Key ヘッダーとして送られます。
turnstileTokenどのボディの形式でも、cf-turnstile-response として送られます。
fetch独自の fetch の実装。
submit.ts
import { submit } from "@ikiform/sdk";

const result = await submit("frm_xxxxxxxxxxxxxxxx", { email: "ada@example.com" }, {
  idempotencyKey: crypto.randomUUID(),
  signal: AbortSignal.timeout(10_000),
});

if (!result.ok) console.error(result.error.code, result.error.fields);

challenge

スパムチェックが Ikiform のフォーム向けです。challenge(formId, options?) は幅 300 ピクセルの ikiform.com のフレームをマウントします。フレームは Turnstile がクリックを必要としない限り高さ 0 のままで、トークンで resolve されるとフレームは削除されます。トークンは 1 回限りで有効期間は 300 秒なので、送信ごとに 1 回呼び出してください。

オプション説明
containerフレームをマウントする場所。デフォルトは document.body。
mode"frame"(デフォルト)または "popup"。フレームをブロックするブラウザー向けです。ポップアップはクリックから開く必要があります。
timeoutMsこのミリ秒数が経過すると reject します。デフォルトではタイムアウトなし。
signalAbortSignal。
baseUrlデフォルトは https://ikiform.com。

code が challenge_failed、challenge_timeout、aborted のいずれかの ChallengeError で reject されます。

challenge.ts
import { challenge, ChallengeError, submit } from "@ikiform/sdk";

async function send(form) {
  try {
    const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx", { timeoutMs: 60_000 });
    return submit("frm_xxxxxxxxxxxxxxxx", new FormData(form), { turnstileToken });
  } catch (error) {
    if (!(error instanceof ChallengeError) || error.code !== "challenge_failed") throw error;
    const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx", { mode: "popup" });
    return submit("frm_xxxxxxxxxxxxxxxx", new FormData(form), { turnstileToken });
  }
}

getSchema

getSchema(formId, options?) はフォームの公開スキーマを取得します。公開済みのフィールド、表示設定、ハニーポットの名前、チャレンジのモードです。submit と同じく例外は投げず、{ ok: true, schema } かエラーの結果に解決されます。オプションは baseUrl、signal、fetch です。レスポンスは shadcn/ui のページで説明しています。

validate

validate(fields, data) は FormData または単純なオブジェクトをフィールド定義と照合し、無効なフィールドごとに 1 つのメッセージを持つ Record<string, string>、または空のオブジェクトを返します。12 種類すべてのフィールドタイプで、サーバーと同じルールとメッセージを使います。validateField(field, value) は 1 つの値を確認し、メッセージか undefined を返します。

validate.ts
import { getSchema, submit, validate } from "@ikiform/sdk";

const result = await getSchema("frm_xxxxxxxxxxxxxxxx");

if (result.ok) {
  const errors = validate(result.schema.fields, new FormData(form));
  if (Object.keys(errors).length === 0) {
    await submit("frm_xxxxxxxxxxxxxxxx", new FormData(form));
  }
}

createClient

createClient({ apiKey, baseUrl?, fetch? }) は REST API のクライアントを返します。キーがシークレットキーでない場合は TypeError を投げるので、サーバー上だけで使ってください。

メソッドリクエスト
forms.list({ limit, cursor })GET /forms
forms.get(formId)GET /forms/:formId
forms.iterate({ limit })カーソルをたどって、すべてのフォーム
submissions.list({ formId, status, limit, cursor })GET /submissions
submissions.get(id)GET /submissions/:id
submissions.iterate({ formId, status, limit })カーソルをたどって、すべての送信データ
submissions.markSpam(id)POST /submissions/:id/spam
submissions.markNotSpam(id)POST /submissions/:id/not-spam
submissions.delete(id)DELETE /submissions/:id

失敗すると、code、status、requestId、message に加え、ある場合は fields、429 の場合は秒数を示す retryAfter を持つ IkiformError を投げます。ネットワークエラーでは code: "network_error"、status: 0 で投げます。

client.ts
import { createClient, IkiformError } from "@ikiform/sdk";

const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY! });

const forms = await ikiform.forms.list({ limit: 10 });
const form = await ikiform.forms.get("frm_xxxxxxxxxxxxxxxx");

for await (const submission of ikiform.submissions.iterate({ formId: form.id, status: "received" })) {
  console.log(submission.id, submission.fields);
}

try {
  await ikiform.submissions.delete("sub_01k7v2c9t3e8m5q4x6z0a1b2c3");
} catch (error) {
  if (error instanceof IkiformError && error.status === 429) {
    console.log(`Retry in ${error.retryAfter} seconds`);
  }
}

verifyWebhook

verifyWebhook({ payload, headers, secret, toleranceSeconds? }) は Webhook の署名を検証し、解析済みのイベントを返します。生のボディの文字列を渡してください。デフォルトの許容範囲は前後 300 秒です。

webhook.ts
import { verifyWebhook, type WebhookEvent } from "@ikiform/sdk/webhooks";

const event = await verifyWebhook<WebhookEvent>({
  payload: rawBody,
  headers: request.headers,
  secret: process.env.IKIFORM_WEBHOOK_SECRET!,
  toleranceSeconds: 300,
});

検証エラー

reason意味
invalid_secretシークレットが whsec_ と base64 の形式ではない
missing_headerswebhook-id、webhook-timestamp、webhook-signature のいずれかがない
invalid_timestampwebhook-timestamp が整数の Unix 秒ではない
timestamp_too_oldtoleranceSeconds より前に署名された。リプレイされたリクエストなど
timestamp_too_new未来の時刻で署名された。サーバーの時計を確認してください
no_matching_signature一致する v1 署名がない。シークレットが間違っているか、ボディが変更された
invalid_payload署名は一致したが、ボディが JSON ではない
イベントの型は WebhookEvent、SubmissionCreatedEvent、EndpointTestEvent としてエクスポートされています。ペイロードの全体は Webhook を参照してください。