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 헤더로 전달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장).
계약 변경 절차
- 먼저 이 문서와 OpenAPI를 고친다. 코드가 먼저 나가면 프론트가 모른다
- 무엇이, 언제부터, 클라이언트가 뭘 해야 하는지를 변경 기록에 남긴다
- 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 콘솔 지출 한도와 함께 방어선 |