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 로 한정된다.
응답:
이후 모든 호출에 Authorization: Bearer <access_token> 를 실는다. scope 는 설치의 AccessGrant.effectiveScopes 기준이며, 부족하면 403 insufficient_scope 다.
OAuth client(client_id/client_secret)는 앱 등록 후 콘솔에서 1회 발급된다. secret 은 그 시점에만 노출되므로 안전하게 보관하라. installation_id 는 테넌트가 앱을 설치·동의한 뒤 installation.activated webhook 으로 전달된다. webhook 외에도, 설치 완료 후 앱의 redirect URI 로 전달되는 handoff query parameter 의 1회용 code 를 POST /oauth/handoffclient_id/client_secret 과 함께 교환하면 {installation_id, accommodation_id, app_id} 를 얻는다 (code 는 1회용이며 10분 후 만료). 토큰 검증·폐기는 POST /oauth/introspect / POST /oauth/revoke (둘 다 client_id/client_secret 인증).

2. 멱등성 (명령형 쓰기 필수)

명령형 쓰기(예약·결제 — /api/v1/reservations*·/api/v1/payments*POST/PATCH/DELETE)는 Idempotency-Key 헤더가 필수다. 없으면 400 invalid_request. 같은 키의 재요청은 최초 명령의 결과를 그대로 재응답하므로, 채널 매니저의 네트워크 재시도가 중복 예약/결제를 만들지 않는다. 그 밖의 쓰기 (dataset/document 푸시, setup-ready, action-run/upgrade 완료 보고 등)는 이 헤더를 받지 않는다 — upsert·상태 가드로 자체 멱등인 엔드포인트가 대부분이지만, POST /api/v1/events 는 중복 제거가 없으므로 재시도 시 이벤트가 중복 적재될 수 있다.
키는 의미 있는 자연 키를 쓰라 (예: <app>:resv:<외부예약번호>). 랜덤 UUID 를 매 재시도마다 새로 만들면 멱등성이 깨진다.
명령형 쓰기는 선택적으로 Ordering-Key 헤더(200자 이하, 초과 시 400 invalid_request)를 함께 보낼 수 있다. 같은 키의 지연(outbox) 명령은 설치 내에서 생성 순서대로 직렬 실행되고, 키가 다르면 병렬로 처리된다 (예: 같은 예약에 대한 수정 연쇄). 미지정 시 payload 의 id/reservation_id, 그것도 없으면 Idempotency-Key 기준으로 자동 직렬화된다.

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

쓰기는 hybrid 로 처리된다: 요청을 durable 하게 journaling 한 뒤 인라인으로 내부 백엔드에 시도하고, 실패 시 워커가 at-least-once 로 재시도한다. 202 를 받으면 최종 확정은 지연 webhook(예: reservation.created / reservation.failed) 으로 전달되며, 동일 outbox_id 로 요청과 상관관계를 맺는다. 완료 이벤트의 리소스 envelope 는 동기 성공 응답과 동일한 형태다. 단, 백엔드가 명령을 정책상 거부하면 (예: 감사 마감 데이터 수정 — ERR_AUDITTED_DATA) 재시도 없이 SKIPPED 로 종결되며 webhook 이 발행되지 않는다. 202 응답의 최종 상태는 GET /api/v1/commands?prefix=<키 prefix> (prefix 최소 8자)로 조회하라.

PMS 내부 변경 이벤트 (pms.reservation.*)

앱 명령 결과(reservation.*)와 별개로, 프런트데스크 등 VPMS 내부에서 발생한 예약 변경pms.reservation.* webhook 으로 구독 중인 설치에 전달된다 (채널매니저류 앱의 upstream 동기화용): payload 는 식별자만 담는다({ reservation_id, actor, reason, occurred_at }) — 앱은 GET /api/v1/reservations/:id 로 전체 상태를 다시 읽는다(read-after-notify). actor 로 자기 쓰기 에코를 억제할 수 있으나, 정밀 억제는 앱이 자기 쓰기 예약 id 를 추적하는 것을 권장한다.

4. 예약 (Reservations)

POST /api/v1/reservations

reservations:write · Idempotency-Key 필수.
nightly_rates 지정 시 합계가 예약 총액이 되고, 요금제는 분류(리포팅) 용도로만 쓰인다.

그 밖의 예약 엔드포인트

5. 결제 (Payments)

POST /api/v1/payments

folios:write · Idempotency-Key 필수. 결제는 folio 에 귀속되므로 folio_id 또는 reservation_id(→folio 해석) 중 하나가 필요하다.
method: "platform" 은 VPMS 의 대외후불이다 — 예약에 연결된 채널(거래처)의 미수로 처리된다. 따라서 platform 결제는 예약이 channel_id(거래처 연결 채널)를 가질 때 의미가 있다.

6. 조회 (Reads)

7. UI Surface 데이터 (매핑·문서)

UI surface(manifest.surfaces) 를 뒷받침하는 dataset·매핑·문서 API. 자세한 블록 선언은 UI Kit Manifest 가이드 참조.

PUT /api/v1/datasets/

key^[a-z0-9][a-z0-9_-]{0,63}$. 항목은 최대 5,000개.

GET /api/v1/mappings

?vpms_entity=<entity>&dataset=<key> 로 필터.

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

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

PUT /api/v1/documents

설치 내 (ref_type, data_id) 기준 upsert. body: ref_type · external_ref · data_id (각 ^[a-zA-Z0-9._:-]{1,128}$), content_type(application/xml | application/json | text/plain), content(UTF-8 최대 65,536 bytes), title(선택, 최대 200자). external_ref 는 키가 아니라 예약 등 대상 리소스를 가리키는 갱신 데이터다. 응답에는 content 가 에코되지 않는다(id, ref_type, external_ref, data_id, size_bytes, updated_at).

8. 설치 업그레이드

버전 업그레이드가 실행되면 항상 installation.upgraded webhook ({upgrade_id, from_version, to_version, app_migration, effective_scopes})이 발행된다. manifest 의 upgrade 블록에 appMigration: "required" 인 홉이 포함된 업그레이드는 AWAITING_APP 상태로 유지되며(app_migration: true), 앱이 자체 마이그레이션을 마친 뒤 이 엔드포인트로 ack 해야 완료된다. 대상 업그레이드가 AWAITING_APP 이 아니면(이미 완료됐거나 다른 설치 소유) 404 not_found. upgrade 블록이 없거나 appMigrationnone 이면 자동 완료된다(app_migration: false).

9. 사용량 / 컨텍스트

10. 에러 형식