Form ID

JavaScript와 fetch

제출 엔드포인트 자세히 보기: 본문 형식, 헤더, CORS, 응답, 멱등성.

HTTP 요청을 보낼 수 있는 코드라면 무엇이든 Ikiform에 제출할 수 있습니다. JSON, FormData, urlencoded 데이터를 https://ikiform.com/f/<formId>로 POST하고 JSON 응답을 읽으세요. SDK는 이 과정을 함수 하나로 감싸지만 꼭 필요하지는 않습니다. 특정 스택의 단계별 설정은 플랫폼별 가이드를 참고하세요.

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);

직접 해 보기

Form 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 타입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다음 POST가 허용될 때까지 남은 초
Location브라우저 성공 시 303리디렉션 주소 또는 호스팅 감사 페이지
Access-Control-Allow-Origin요청에 Origin이 있을 때요청 자신의 출처
Access-Control-Expose-HeadersCORS 사용 시X-Request-Id, Retry-After, 브라우저 코드가 둘 다 읽을 수 있도록
Cache-Control성공 시no-store

CORS

제출 엔드포인트는 어떤 사이트에서 오는 교차 출처 POST든 받습니다. 프리플라이트는 항상 성공하며 요청의 출처를 그대로 돌려줍니다. 쿠키나 자격 증명을 쓰지 않으므로, 폼을 보호하는 것은 CORS가 아니라 허용 도메인입니다. 허용 도메인을 설정하면 다른 사이트에서 온 POST는 폼 설정에 따라 스팸으로 저장되거나 거부됩니다. 허용 도메인을 참고하세요.

Preflight
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로 응답하므로, 시크릿 키는 웹 페이지에서 절대 사용할 수 없습니다.

응답

성공하면 새 제출 ID와 함께 201을 반환합니다. 오류는 어디서나 같은 형식을 사용하며, 검증에 실패하면 필드 ID를 키로 필드 오류를 담습니다. 모든 코드는 오류에 정리되어 있습니다.

{ "ok": true, "id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3" }
스팸 점수를 받은 POST도 201을 받습니다. 스팸은 따로 저장되며 웹훅이나 받은편지함에 닿지 않습니다.

멱등성

재시도를 안전하게 하려면 Idempotency-Key 헤더나 _idempotency 필드를 보내세요. 키는 최대 255자이며 24시간 동안 폼 단위로 적용됩니다. 둘 다 보내면 헤더가 우선합니다.

재시도 조건결과
같은 키, 같은 필드첫 응답을 그대로 돌려줍니다. 두 번째 제출은 만들어지지 않습니다.
같은 키, 다른 필드422 idempotency_mismatch
첫 요청이 아직 처리 중일 때 같은 키409 idempotency_in_progress

검증 오류처럼 제출이 저장되기 전에 실패한 POST는 키를 보관하지 않으므로, 데이터를 고친 뒤 같은 키로 다시 시도할 수 있습니다. 안전한 재시도 루프:

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);
}

플랫폼별 가이드

프레임워크

정적 사이트 생성기

사이트 빌더