API REFERENCE / V1
하나의 Core API.
브라우저 게임은 같은 origin의 절대 경로를 사용합니다. 가격, 잔액, 영수증 상태는 항상 KOISCORE 서버 응답을 권위값으로 취급하세요.BASE / AUTH
주소와 인증
Base URL은 https://koiscore.com입니다. koiscore.com의 브라우저 게임은 HttpOnly same-site 쿠키를 사용하고, 승인된 별도 origin 런타임은 발급된 Bearer 세션 방식을 사용합니다.
const response = await fetch('/api/profile/me', {
credentials: 'same-origin',
headers: { accept: 'application/json' },
});CORE / ENDPOINTS
공개 게임 연동 엔드포인트
인증 없는 공개 게임 카탈로그. CORS와 캐시를 지원합니다.
최근 7일 플레이 지표와 게임 귀속 매출 가산점으로 계산한 포털 노출 순위입니다.
게스트 프로필과 세션을 생성합니다.
현재 로그인 또는 게스트 프로필을 조회합니다.
표시 이름, 프로필 이미지와 언어 설정을 수정합니다.
Google OAuth 로그인을 시작하고 현재 게스트 기록에 연결합니다.
현재 세션의 게임별 playerId와 단기 서명 assertion을 발급합니다.
게임 서버가 identity assertion을 검증할 Ed25519 공개키입니다.
활성화된 게임의 등록 환율로 젬을 게임 재화로 교환합니다.
서버 상품 카탈로그를 기준으로 구매 영수증을 발급합니다.
발급된 영수증의 현재 상태를 조회합니다.
게임 지급 어댑터가 영수증을 멱등 커밋합니다.
젬 교환과 구매 API는 게임 ID, SKU, 지급 어댑터가 서버에서 활성화된 뒤 사용할 수 있습니다. 등록만 된 draft 게임은 결제를 호출할 수 없습니다.
CATALOG / EXAMPLE
공개 게임 목록
GET /api/public/games
{
"schema": "koiscore.public-games.v1",
"updatedAt": "2026-08-03",
"count": 1,
"games": [{
"id": "example-game",
"portalId": "example-game",
"title": { "ko": "예제 게임", "ja": "サンプル", "en": "Example Game" },
"genres": ["puzzle"],
"runtimeUrl": "https://example.com/game",
"locales": ["ko", "ja", "en"],
"localeAware": true,
"status": "live"
}]
}PROFILE / GOOGLE
로그인과 프로필은 Core가 관리
게임은 Google SDK나 OAuth 비밀값을 직접 포함하지 않습니다. KOISCORE가 로그인을 관리하고 게임에는 게임별 playerId와 단기 서명 assertion만 전달합니다. 세이브·점수·인벤토리는 각 게임 저장소가 playerId 기준으로 관리합니다.
// 로그인 시작
location.href = '/api/profile/connect/google?returnTo=/profile/connect';
// 현재 사용자 조회
const { profile } = await fetch('/api/profile/me', {
credentials: 'same-origin'
}).then(response => response.json());
// 프로필 설정 저장
await fetch('/api/profile/me', {
method: 'PATCH',
credentials: 'same-origin',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ displayName: 'Koi Player', locale: 'ko' })
});- 첫 Google 연결은 현재 게스트의 게임 기록과 지갑을 유지한 채 계정을 연결합니다.
- 이후 같은 Google 계정으로 로그인하면 기존 KOISCORE 프로필 세션으로 전환합니다.
- 게임에는 OAuth access token, Google client secret 또는 Google 이메일을 전달하지 않습니다.
- 전역 profileId는 게임 데이터 키로 사용하지 않으며, assertion은 JWKS의 Ed25519 공개키로 검증합니다.
WALLET / RECEIPTS
젬 구매는 영수증으로
POST /api/rewards/receipts/issue
Content-Type: application/json
{
"gameId": "example-game",
"requestKey": "issue_01K2ABCDEF12",
"source": "in_game_shop",
"items": [{ "sku": "example-game.coin-pack-1", "quantity": 1 }]
}requestKey는 사용자 작업마다 새 값을 만들고 재시도에는 같은 값을 사용합니다.- 가격과 지급량은 서버 SKU 카탈로그가 계산하며 클라이언트 값을 신뢰하지 않습니다.
- 게임 서버는
purchase/commit을 멱등 처리해 같은 영수증을 두 번 지급하지 않습니다.
PLAYER / POSTMESSAGE
공통 플레이어 브리지
웹 포털과 앱인토스용 페이지는 같은 KOISCORE 플레이어 셸을 사용합니다. 두 채널 모두 상단에 현재 게임 타이틀과 공용 젬 잔액을 표시하고 같은 메시지 계약으로 게임 프레임을 제어합니다.
| 방향 | TYPE | 역할 |
|---|---|---|
| 게임 → 포털 | game:ready | 리스너 준비 완료와 초기 상태 요청 |
| 포털 → 게임 | portal:init | locale, shell, 초기 음소거 동기화 |
| 포털 → 게임 | portal:audio | 실행 중 마스터 음소거 변경 |
| 게임 → 포털 | game:shell | 제목과 설명 등 공통 셸 갱신 |
| 게임 → 포털 | game:wallet | 교환 후 포털 젬 잔액 갱신 |
window.parent.postMessage({
protocol: 'koiscore.portal.v1',
type: 'game:ready',
gameId: 'example-game'
}, 'https://koiscore.com');수신 측은 event.origin, event.source, protocol, gameId를 모두 검증해야 합니다. postMessage('*')는 사용하지 않습니다.
appintoss이면 웹 광고를 로드하지 않고 Apps in Toss 광고 SDK를 사용합니다. 타이틀, 젬 지갑, 게임 프레임 계약은 웹과 동일합니다.