Skip to main content
마켓플레이스 앱이 VPMS 데이터에 접근하는 외부 REST 평면이다. GraphQL 과 별개의 평면이며, OAuth2 bearer 토큰으로 인증되고 설치(Installation)의 승인된 scope 로 인가된다. 내부 API 의 raw mirror 가 아닌 public contract 로 설계된다. Base URL 은 API 게이트웨이 도메인의 /marketplace namespace 다 (예: https://staging-api.vpms.io/marketplace). 게이트웨이가 prefix 를 벗겨 marketplace-svc 로 프록시하므로, 이 문서의 모든 경로는 base URL 뒤에 그대로 붙인다 — 예: 토큰 발급은 POST {baseUrl}/oauth/token.

1. 인증 (OAuth2 client_credentials)

앱은 설치별 access token 을 발급받는다. 표준 client_credentialsinstallation_id 파라미터로 확장한 흐름이다 — 토큰은 해당 설치의 accommodation 컨텍스트와 승인 scope 로 한정된다.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=mp_client_xxx
&client_secret=mp_secret_xxx
&installation_id=01KXD...
응답:
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "reservations:write folios:write",
  "installation_id": "01KXD..."
}
이후 모든 호출에 Authorization: Bearer <access_token> 를 실는다. scope 는 설치의 AccessGrant.effectiveScopes 기준이며, 부족하면 403 forbidden 이다.
OAuth client(client_id/client_secret)는 앱 등록 후 콘솔에서 1회 발급된다. secret 은 그 시점에만 노출되므로 안전하게 보관하라. installation_id 는 테넌트가 앱을 설치·동의한 뒤 installation.activated webhook 으로 전달된다.

2. 멱등성 (쓰기 필수)

모든 쓰기(POST/PATCH/DELETE)는 Idempotency-Key 헤더가 필수다. 없으면 400 invalid_request. 같은 키의 재요청은 최초 명령의 결과를 그대로 재응답하므로, 채널 매니저의 네트워크 재시도가 중복 예약/결제를 만들지 않는다.
POST /api/v1/reservations
Authorization: Bearer <token>
Idempotency-Key: your-app:resv:12345
Content-Type: application/json
키는 의미 있는 자연 키를 쓰라 (예: <app>:resv:<외부예약번호>). 랜덤 UUID 를 매 재시도마다 새로 만들면 멱등성이 깨진다.

3. 쓰기 모델 — command outbox (동기 확정 + WAL)

쓰기는 hybrid 로 처리된다: 요청을 durable 하게 journaling 한 뒤 인라인으로 내부 백엔드에 시도하고, 실패 시 워커가 at-least-once 로 재시도한다.
결과HTTP본문
인라인 성공200/201리소스 envelope + outbox_id
인라인 실패(재시도 대기)202{ "accepted": true, "outbox_id": "…" }
검증 실패400{ "error": "invalid_request", "error_description": "…" }
scope 부족403
내부 백엔드 오류502{ "error": "upstream_error" }
202 를 받으면 최종 확정은 지연 webhook(예: reservation.created / reservation.failed) 으로 전달되며, 동일 outbox_id 로 요청과 상관관계를 맺는다. 완료 이벤트의 리소스 envelope 는 동기 성공 응답과 동일한 형태다.

4. 예약 (Reservations)

POST /api/v1/reservations

reservations:write · Idempotency-Key 필수.
필드필수설명
room_type_idVPMS 객실 타입 id
typelodge(숙박) 또는 rent(대실)
start_date체크인 YYYY-MM-DD
end_date체크아웃 YYYY-MM-DD (rent 는 = start_date)
check_in_at / check_out_at실제 입실/퇴실 시각 (ISO datetime). 미지정 시 날짜 기준 생성 — 시각이 있는 원천 데이터는 반드시 함께 보내라
nightly_rates박별 가격 배열. 길이 = 박수(rent 는 1). 지정 시 요금제 자동가격을 우회해 원 단위 패리티
rate_plan_id미지정 시 VPMS 가 자동 해석(최저가 플랜)
channel_id판매 채널(거래처) id. 채널에 거래처가 연결돼 있으면 대외후불 연동
statusINQUIRY/TENTATIVE/DEFINITE… (기본 DEFINITE)
sleeps / child_sleeps인원
ota_code외부 예약번호
memo메모
guest{ name, phone, email, country, vehicle_number }
{
  "room_type_id": "01K...",
  "type": "lodge",
  "start_date": "2026-08-02",
  "end_date": "2026-08-04",
  "nightly_rates": [75000, 75000],
  "channel_id": 12,
  "status": "DEFINITE",
  "ota_code": "123944",
  "guest": { "name": "홍길동", "phone": "010-0000-0000" }
}
nightly_rates 지정 시 합계가 예약 총액이 되고, 요금제는 분류(리포팅) 용도로만 쓰인다.

그 밖의 예약 엔드포인트

메서드경로scope비고
GET/api/v1/reservations/:idreservations:read동기 조회
PATCH/api/v1/reservations/:idreservations:write기간 변경 시 요금제 재해석
DELETE/api/v1/reservations/:idreservations:write취소 (cancel_payment 옵션)
POST/api/v1/reservations/:id/purgereservations:write물리 삭제 — 예약+folio+청구+결제 행 제거 (취소·soft-delete 아님). 예약 1건짜리 단독 folio 만 허용. 멱등
GET/api/v1/commands/:keyreservations:read이 설치가 특정 Idempotency-Key 로 만든 명령의 저장 결과 조회 (부수효과 없음)

5. 결제 (Payments)

POST /api/v1/payments

folios:write · Idempotency-Key 필수. 결제는 folio 에 귀속되므로 folio_id 또는 reservation_id(→folio 해석) 중 하나가 필요하다.
필드필수설명
reservation_id 또는 folio_id귀속 folio
amount금액
methodaccount cash creditCard easy etc mileage platform
status결제 상태 (예: APPROVED)
transaction_id / approval_id거래·승인 번호
tax_type과세 유형
memo메모
method: "platform" 은 VPMS 의 대외후불이다 — 예약에 연결된 채널(거래처)의 미수로 처리된다. 따라서 platform 결제는 예약이 channel_id(거래처 연결 채널)를 가질 때 의미가 있다.
메서드경로scope
PATCH/api/v1/payments/:idfolios:write
DELETE/api/v1/payments/:idfolios:write

6. 조회 (Reads)

메서드경로scope설명
GET/api/v1/accommodation(bearer)설치 숙소 정보
GET/api/v1/room-typesrooms:read객실 타입 목록
GET/api/v1/room-types/:idrooms:read객실 타입 상세
GET/api/v1/ratesrates:read일별 요금
GET/api/v1/availabilityinventory:read재고/가용성

7. UI Surface 데이터 (매핑)

설정 화면(manifest.surfaces) 을 뒷받침하는 dataset/매핑 API. 자세한 블록 선언은 UI Kit Manifest 가이드 참조.
메서드경로scope설명
PUT/api/v1/datasets/{key}marketplace:datasets:write매핑 후보 항목 푸시(upsert)
GET/api/v1/datasets/{key}marketplace:datasets:write푸시한 dataset 조회
GET/api/v1/mappingsmarketplace:mappings:read호텔이 확정한 매핑 조회
POST/api/v1/installation/setup-readymarketplace:datasets:writeexplicit 준비 신호
POST/api/v1/action-runs/{run_id}/complete(bearer)action(completion=event) 완료 보고

PUT /api/v1/datasets/

key^[a-z0-9][a-z0-9_-]{0,63}$. 항목은 최대 5,000개.
{ "items": [ { "id": "AGODA", "label": "아고다", "meta": {} } ] }

GET /api/v1/mappings

?vpms_entity=<entity>&dataset=<key> 로 필터.
{
  "mappings": [
    { "vpms_entity": "roomType", "vpms_entity_id": "01K...",
      "dataset": "ext_room_types", "external_id": "RT-1",
      "external_label": "스탠다드", "updated_at": "…" }
  ]
}

POST /api/v1/action-runs//complete

{ "status": "completed" | "failed", "message": "27건 동기화" } — 이미 종결된 run 은 현재 상태를 멱등 재응답한다.

8. 사용량 / 컨텍스트

메서드경로scope설명
GET/api/v1/me(bearer)호출 주체(설치·client·scope)
GET/api/v1/installation(bearer)설치 상세 — setup_status, config(configForm 값) 포함
GET/api/v1/usagemarketplace:usage:readendpoint family 별 호출량
POST/api/v1/eventsmarketplace:events:writeRobust 이벤트 적재(202, outbox)

9. 에러 형식

{ "error": "invalid_request", "error_description": "room_type_id is required" }
HTTPerror의미
400invalid_request검증 실패 / 필수 헤더 누락
401invalid_client / invalid_token인증 실패
403forbiddenscope 부족
404not_found리소스 없음
502upstream_error내부 백엔드 오류 (재시도 가능)