API 계약

이 문서의 「인증 없음」은 더 이상 사실이 아닙니다 (2026-09-16) 아래 본문은 09-03 설계입니다. 지금은 구글 로그인이 필요하고 과잉 사용 방지 허들이 붙었습니다. 엔드포인트 경로도 본문과 다릅니다. 정본은 개발 저장소의 docs/spec/api_contract.md입니다.

현행 — 인증과 허들 (2026-09-16)

  • 구글 로그인 필수. 판을 만들고 말하고 답을 내는 쓰기 요청은 전부 로그인이 필요합니다. 결과 조회 같은 읽기 요청은 로그인 없이 열립니다
  • 판을 만든 사용자 기준으로 사용량을 셉니다. 하루 판 수 제한과 전역 정원이 이 기준으로 걸립니다
HTTP 뜻 화면
401 로그인 필요 로그인 안내
403 daily_attempt_limit 이 사용자의 오늘 판 수 소진 (사용자당 하루 5판) 「오늘은 여기까지」
429 요청이 너무 잦음 — Retry-After 헤더로 대기 시간 안내 잠시 후 재시도
503 daily_cap 오늘 전역 정원 마감 「오늘 정원이 마감됐다」
413 / 422 요청이 너무 크거나 입력 상한 초과 (발화·질문은 200자) 입력 오류

세부 한도와 배포 설정은 공개 문서에 적지 않고 운영 환경에서 관리합니다.

계약이 나오는 순간이 일정의 분기점입니다 완벽하지 않아도 됩니다. 바뀔 수 있다는 걸 알리고 일단 내는 게 낫습니다.

기본 규약

항목 값
Base URL (local) http://localhost:8300
버전 /api/v1/...
인증 없음. 익명 세션 키를 X-Session-Key 헤더로 전달 → 구글 OAuth + rd_session 쿠키 (09-16, 위 「현행」 참고)
요청/응답 application/json; charset=utf-8
스트리밍 text/event-stream (SSE)
시각 ISO 8601 UTC
식별자 응답에는 항상 public_id
네이밍 snake_case

세션 키는 첫 진입 시 서버가 발급하고 클라이언트가 로컬에 보관합니다. 로그인 화면도, 회원가입도, 토큰 갱신도 없습니다 (12.4).

응답 형태

// 실패 — 형태가 절대 안 바뀐다
{ "error": { "code": "BUDGET_EXHAUSTED", "message": "오늘 더 말할 수 없다.", "detail": null } }

클라이언트는 항상 code로 분기합니다. message는 문구가 바뀝니다.

code HTTP 클라이언트가 할 일
SESSION_REQUIRED 401 세션 발급 후 재시도
BUDGET_EXHAUSTED 409 발화 입력 비활성, 하루 종료 안내
LOOP_ALREADY_ENDED 409 아침 화면으로
VALIDATION_FAILED 400 입력 오류 표시
RATE_LIMITED 429 잠시 후 재시도
AI_UNAVAILABLE 503 게임 진행 불가 안내 + 재시도
INTERNAL_ERROR 500 오류 화면 + 재시도

엔드포인트

세션 · 루프

메서드 경로 설명
POST /api/v1/sessions 익명 세션 발급. domain 지정 (game·audit·career)
GET /api/v1/sessions/me 배선 검증용. 세션 상태 반환
POST /api/v1/loops 새 루프 시작 → 아침 화면 데이터
GET /api/v1/loops/{id} 루프 상태 (남은 발화 예산, 현재 비트, NPC 목록)
POST /api/v1/loops/{id}/advance 다음 비트로
POST /api/v1/loops/{id}/skip 아무 말 없이 루프 흘려보내기 → 예산 회복 (6.1)

POST /loops 응답에 아침 문장이 들어갑니다.

{
  "public_id": "…", "loop_no": 3, "utterance_budget": 5,
  "morning": {
    "fixed": "7시 12분. 눈을 뜬다.",      // 항상 동일
    "lines": ["천장이 조금 낮아 보인다."]  // 손상 1층이 여기서 작동
  }
}

루프 카운터는 화면에 노출하지 않습니다 loop_no는 계약에 있지만 UI에 그리지 않습니다. 유저가 세게 만들고, 노트가 그 역할을 합니다 (4.9).

발화 — 이 게임의 유일한 행동

메서드 경로 설명
POST /api/v1/loops/{id}/utterances SSE 스트리밍. 발화 → NPC 판단 → 응답
event: advice          // 발화 직전 침묵 권고가 떴다면
data: {"recommendation":"침묵","rationale":"최근 3턴 의심도 +14, 신뢰 회복 이벤트 없음","advice_id":"…"}

event: delta
data: {"text":"그래? "}

event: tool
data: {"name":"ask_npc","target":"준","status":"running"}

event: done
data: {"utterance_id":"…","budget_after":3,"npc_visible_state":null}

event: error
data: {"code":"AI_UNAVAILABLE","message":"…"}

npc_visible_state는 항상 null입니다. 의심도·신뢰도는 유저에게 보이지 않습니다. 인스펙터에서만 열립니다 (8.8).

침묵 권고 — 측정 ②

메서드 경로 설명
POST /api/v1/advices/{id}/response accepted / ignored 기록

무시도 반드시 기록한다 유저가 권고를 무시하고 발화하면 클라이언트는 발화 요청 전에 ignored를 보냅니다. 이 한 줄이 없으면 11.2의 “옳았는데 무시당한 비율”을 낼 수 없습니다.

노트

메서드 경로 설명
GET /api/v1/sessions/{id}/notes 누적 노트 타임라인
GET /api/v1/sessions/{id}/notes/search?q= 자연어 검색 (search_notes)

⑧ 순위이므로 인터페이스만 먼저 뚫고 구현은 미룹니다. 초기엔 전량 반환으로 동작시키고, 루프가 쌓이면 임베딩 검색으로 교체합니다.

인스펙터 — 판정 로그 개봉

메서드 경로 설명
GET /api/v1/loops/{id}/inspector 그 루프의 판정 체인 전체
GET /api/v1/utterances/{id}/inspector 발화 1건의 판정 체인

응답은 8.8 출력 구조 그대로입니다 — 의심 판정 → 도구 호출 → 도구 결과 → 불일치 판정 → 신뢰 판정 → 재계획.

전이 도메인에서도 같은 엔드포인트가 열린다 10.3 시연의 전부가 “같은 인스펙터가 다른 도메인에서 열리는 장면”입니다. 그래서 인스펙터 API는 도메인 중립 필드명을 씁니다 — suspicion이 아니라 resistance, trust가 아니라 rapport로 매핑되는 계층을 둡니다.

루프 종료 · 엔딩

메서드 경로 설명
POST /api/v1/loops/{id}/end Evaluator 판정 → 원인 체인 서술 + 엔딩 타입

ending_type — repeat · isolation · hesitation · handover (7장).

계약 변경 절차

  1. 먼저 이 문서와 OpenAPI를 고친다. 코드가 먼저 나가면 프론트가 모른다
  2. 무엇이, 언제부터, 클라이언트가 뭘 해야 하는지를 변경 기록에 남긴다
  3. Day 9(9/11) 기능 동결 이후에는 계약을 바꾸지 않는다

결정 로그

날짜 결정 이유
2026-09-03 인증 엔드포인트 전부 제거, 익명 세션 키로 대체 12.4 로그인 없는 즉시 진입
2026-09-03 NPC 상태를 응답에서 항상 null 의심도·신뢰도는 유저에게 비노출. 인스펙터에서만 개봉
2026-09-03 권고 무시를 발화 전에 별도 기록 무시 로그가 없으면 측정 ②가 성립하지 않음
2026-09-03 인스펙터 필드를 도메인 중립으로 매핑 같은 화면이 감사·커리어에서도 열려야 함 (10.3)
2026-09-03 search_notes는 인터페이스만 선구현 12.3 ⑧ — 데모 범위에선 전량 컨텍스트로 충분
2026-09-15~16 구글 로그인 도입, 익명 진입 폐기 판을 만든 사용자(attempts.user_id)가 하루 판 수·전역 정원 집계의 기준
2026-09-16 과잉 사용 방지 허들 — 사용자당 하루 판 수 · 전역 정원 · 요청 빈도 · 입력 길이 판당 API 비용의 상한을 코드로 건다. Anthropic 콘솔 지출 한도와 함께 방어선