# PayRi Docs > 페이리(PayRi) 공식 문서예요. 기능 소개와 셀러 가이드, REST API·웹훅·라이선스·SDK 개발자 문서를 담고 있어요. - 사이트: https://docs.payri.kr - API base URL: `https://api.payri.kr` (모든 호출은 HTTPS, `Authorization: Bearer pyr_live_…` 또는 `pyr_test_…` 인증) - OpenAPI 명세: https://docs.payri.kr/openapi/v1.json (운영 API 에서는 https://api.payri.kr/v1/openapi.json) - 전체 문서 단일 파일: https://docs.payri.kr/llms-full.txt - 언어: 한국어 / 대상: PayRi 연동 개발자 ## 셀러 가이드 - [셀러 가이드](https://docs.payri.kr/guide) - [파일·링크 팔기](https://docs.payri.kr/guide/sell-files): 파일, 비공개 링크, 접근권을 결제 링크 하나로 판매할 수 있어요. - [내 사이트에 결제 붙이기](https://docs.payri.kr/guide/connect-site): 내 서비스에 결제를 연결하고 결제 결과에 따라 이용 권한을 줄 수 있어요. - [구독](https://docs.payri.kr/guide/subscriptions): 월·연 플랜을 판매하고 구독 갱신과 변경·해지를 운영할 수 있어요. - [라이선스 키](https://docs.payri.kr/guide/licenses): 결제 뒤 라이선스 키를 전달하고 앱에서 확인해 기기 이용을 관리할 수 있어요. - [상품 관리](https://docs.payri.kr/guide/products) - [결제와 주문](https://docs.payri.kr/guide/payments) - [환불](https://docs.payri.kr/guide/refunds) - [정산](https://docs.payri.kr/guide/settlements) - [스토어 운영](https://docs.payri.kr/guide/store) ## 계약 요약 - Base URL은 `https://api.payri.kr`이고 경로는 `/v1/...`예요. 인증은 `Authorization: Bearer pyr_live_…` 또는 `pyr_test_…`를 사용해요. API 키는 `pyr_live_`·`pyr_test_` 접두사 뒤 base62 38자(본문 32자·체크섬 6자)가 붙는 서버 비밀키이고, mode 없는 `key_…` ID는 콘솔·로그 식별용이에요. 키 회전 유예는 24시간이며 유예 키 호출에는 `Payri-Key-Deprecated: <만료 ISO 8601>` 응답 헤더가 오고, 웹훅 시크릿은 엔드포인트별 `whsec_…`예요. SDK 패키지는 `@payri/sdk` 1.0.0이에요. - 권한은 `products:read`, `checkout:read`, `checkout:write`, `orders:read`, `refunds:read`, `refunds:write`, `subscriptions:read`, `subscriptions:write`, `licenses:read` 9종이며 기본 선택은 없어요. - 라이선스 앱 API를 제외한 모든 v1 POST에는 `Idempotency-Key`가 필수예요. 키는 1~255자이고 누락하면 400 `VALIDATION_FAILED`의 `details.fields`에 필드 `Idempotency-Key`가 와요. 키는 store·mode 범위에서 메서드·경로·본문 해시와 함께 24시간 보관돼요. 재생 응답에는 `Idempotent-Replayed: true`가 오며, 본문 충돌과 처리 중 요청은 각각 409 `IDEMPOTENCY_KEY_CONFLICT`, `IDEMPOTENCY_IN_PROGRESS`예요. 검증 400과 5xx 응답은 저장하지 않아 같은 키와 요청으로 다시 보내면 다시 처리해요. - 오류 코드와 HTTP 상태의 원천은 OpenAPI의 `x-payri-error-catalog`예요. 오류 형식은 `{ statusCode, code, message, details }`예요. `details`는 항상 객체예요. `VALIDATION_FAILED`는 400이고 `details.fields`는 `{ field, message }` 객체 배열이에요. `API_KEY_INVALID`는 헤더 누락·키 형식·체크섬 오류·미등록·유예 만료를 포함한 401, `API_KEY_IP_NOT_ALLOWED`와 `API_KEY_SCOPE_MISSING`은 403이고 후자는 `details.required`(string[])에 필요한 권한이 와요. 404 코드는 `PRODUCT_NOT_FOUND`, `CHECKOUT_SESSION_NOT_FOUND`, `ORDER_NOT_FOUND`, `REFUND_REQUEST_NOT_FOUND`, `SUBSCRIPTION_NOT_FOUND`, `LICENSE_NOT_FOUND`예요. 모르는 필드도 400 `VALIDATION_FAILED`이며 API 키당 분당 300회 제한 초과는 429 `RATE_LIMITED`와 `Retry-After`를 반환해요. - 모든 객체 응답에는 `mode`(`live` 또는 `test`)가 있고 없는 값은 `null`로 내려와요. 금액은 정수와 `currency`로 표현하고 목록은 `{ data, total, page, limit }`예요. `page` 기본값은 1, `limit` 기본값은 20이고 최대 100이에요. `sort`·`order`를 보내면 400으로 거절돼요. - 웹훅 요청 본문은 `{ id, type, createdAt, mode, data }`이고 본문과 `data`의 `mode`는 항상 포함돼요. `webhook-id`는 이벤트 ID와 같고 재시도·콘솔 재전송에도 같으며, 콘솔 재전송은 새 `webhook-timestamp`로 서명을 다시 계산해요. `webhook-signature` 형식은 `v1,`이고 시크릿 회전 유예 24시간에는 `v1,<새 서명> v1,<이전 서명>`이 전송돼요. 타임스탬프 허용 오차는 5분이며 수신 엔드포인트는 10초 안에 응답해야 해요. 전달 순서는 보장되지 않으므로 ID로 중복을 제거하고 순서가 필요하면 `createdAt`으로 정렬해요. 2xx만 성공으로 처리하고 재시도는 즉시·1분·5분·30분·2시간·6시간·12시간·24시간 순서로 8회, 약 45시간 동안 이어져요. - 이벤트는 `order.paid`(최초·갱신·차액은 `data.subscriptionEvent`의 `initial | renewal | proration`으로 구분하고 비구독 주문은 `null`), `order.refunded`(주문과 `refund: { id, amount, currency, reason, refundedAt }`), `subscription.created`(`data.change.kind`: `created`), `subscription.updated`(`data.change.kind`: `plan_changed | plan_change_scheduled | billing_interval_changed | billing_interval_change_scheduled | cancel_scheduled | cancel_resumed | period_renewed | period_extended`, change 필드: `kind, from, to, prorationAmount, orderId, effectiveAt, extensionDays, amount, currency`), `subscription.ended`(`endReason`: `canceled | refunded | payment_failed | product_unavailable | plan_unavailable | pg_canceled`), `subscription.payment_failed`(`attempts, amount, currency`), `refund.requested`, `refund.approved`, `refund.rejected`, `refund.on_hold`, `refund.withdrawn`, `license.issue`, `webhook.test`예요. `webhook.test`는 `live`·`test` 엔드포인트 모두로 보낼 수 있어요. `license.issue`의 data는 `{ licenseId, orderId, productId, planId, interval, buyer: { email, name }, maxActivations, expiresAt }`이고 응답은 `{ key, expiresAt?, maxActivations? }`예요. `key`는 1~256자, 선택한 `expiresAt`은 ISO 8601, `maxActivations`는 0 이상의 정수예요. 응답 본문은 64KB 이하예요. 2xx가 아니거나 응답 검증에 실패하거나 크기를 넘으면 라이선스를 pending으로 두고 재시도해요. 10초 안에 응답하고 같은 이벤트 ID에는 같은 키를 반환해요. - `/v1`은 기본적으로 서버 전용(CORS 미허용)이지만, 브라우저 CORS는 API 키 없는 라이선스 앱 `POST /v1/licenses/verify`, `activate`, `deactivate`와 키 교체 확인용 `GET /v1/licenses/signing-keys`를 허용해요. signing-keys는 API 키 없이 호출할 수 있고 모든 origin에 CORS를 허용하며 캐시할 수 있어요. 라이선스 앱 API에는 `productId`와 base64 또는 hex nonce(디코딩 후 16바이트 이상, 권장 형식은 32바이트를 hex 64자로 인코딩)가 필요하고, `Payri-Signature: kid=, ed25519=`로 원문 응답을 검증한 뒤 nonce와 5분 이내 `issuedAt`도 확인해요. 앱 API는 항상 HTTP 200이며 `status`는 `active | expired | revoked | pending | not_found`예요. 응답은 `{ status, expiresAt, maxActivations, activationCount, mode, activation, nonce, issuedAt }`이고 `maxActivations: null`은 무제한, `activation`은 활성화 객체 또는 `null`이에요. IP당 분당 60회와 라이선스 키당 분당 30회 제한 초과는 429 `LICENSE_RATE_LIMITED`예요. 같은 IP에서 10분 안에 `not_found`가 10회 나오면 해당 IP를 15분 차단하고 429 `LICENSE_LOOKUP_BLOCKED`를 반환해요. 두 429 모두 `Retry-After`를 반환해요. 400·429 오류에는 서명이 없고 404는 반환하지 않아요. 현재·다음 공개 키를 SDK 내장 테이블과 개발자 문서에 함께 게시하고 앱에 모두 내장해요. 고정된 키 목록에 없는 `kid`는 검증 실패로 처리하고 서버에서 키를 자동으로 받아오지 않아요. signing-keys 응답은 `{ keys: [{ kid, publicKey, createdAt }] }`이며 서명 키 교체 여부를 확인하는 용도예요. 다음 `kid` 공개 키를 먼저 게시한 뒤 SDK 배포와 앱 업데이트가 퍼질 때까지 수개월 기다리고, 앱 업데이트 보급을 기준으로 서버 서명을 새 `kid`로 전환해요. SDK 내장 테이블을 쓰면 SDK를 최신 버전으로 유지하고, 새 다음 키가 게시되면 앱을 업데이트해 미리 포함해요. 서명 전환 후 이전 키는 30일 동안 `signing-keys` 목록에 남아요. 이 30일은 앱 내장 키를 갱신할 유예 기간이 아니에요. 자동 발급 키는 `PAYRI-XXXXX-XXXXX-XXXXX-XXXXX`, test 키는 `PAYRI-TEST-XXXXX-XXXXX-XXXXX-XXXXX` 형식이에요. - test API 키로 만든 세션만 test 결제이며 공개 결제 링크는 live예요. 구독 세션은 `planId`와 `interval`을 필수로 보내고, `interval`은 `monthly` 또는 `yearly`예요. `planId`는 `GET /v1/products` 응답의 `pricing.plans[].id`에서 골라요. test 세션의 리턴 URL은 localhost와 127.0.0.1을 허용하고 live는 등록 호스트만 허용해요. 미등록 호스트는 400 `RETURN_URL_NOT_ALLOWED`, 미인증 공개 연락처는 409 `PUBLIC_CONTACT_VERIFICATION_REQUIRED`예요. PG 호출은 없고 테스트 화면에서 성공·실패·구독 갱신 실패를 선택해요. 전달물은 live와 같지만 test 데이터는 live 매출·재고·통계에 합산되지 않아요. 상품·쿠폰·스토어 설정은 공유되며 test 키가 노출된 공개 환경에서는 실제 상품이 무료 제공될 수 있어요. - 퀵스타트: 콘솔에서 API 키·웹훅·리디렉트 도메인을 설정하고 `PAYRI_API_KEY`·`PAYRI_WEBHOOK_SECRET`을 저장한 뒤 test 세션 생성 → 서명 검증과 이벤트 처리 → 라이브 키 전환 순서로 연동해요. ## 개발자 문서 - [개발자 문서](https://docs.payri.kr/developers): PayRi 외부 연동의 인증·결제·웹훅·라이선스 흐름을 안내해요. - [퀵스타트](https://docs.payri.kr/developers/quickstart): Test 키로 결제 세션을 만들고 웹훅으로 결제 결과를 받는 데까지 진행해요. - [test 모드](https://docs.payri.kr/developers/test-mode): Test와 Live의 차이를 한눈에 확인해요. ### 인증과 API 키 - [인증과 API 키](https://docs.payri.kr/developers/api-keys): PayRi REST API 키 발급·인증·회전과 보안 규칙을 안내해요. - [권한](https://docs.payri.kr/developers/api-keys/scopes): API 키의 9개 권한과 허용 엔드포인트를 확인해요. - [IP · 상품 제한](https://docs.payri.kr/developers/api-keys/restrictions): API 키를 허용 IP, 상품 범위, mode에 따라 제한하는 방법을 안내해요. ### REST API 개요 - [REST API 개요](https://docs.payri.kr/developers/rest-api): 페이리 REST API의 Base URL, 인증, 공통 응답·요청 규칙과 엔드포인트. - [Checkout](https://docs.payri.kr/developers/rest-api/checkout): 상품·플랜으로 Checkout Session을 만들고 조회하는 REST API. - [Products](https://docs.payri.kr/developers/rest-api/products): 상품과 플랜별·결제 주기별 가격을 조회하는 REST API. - [Orders](https://docs.payri.kr/developers/rest-api/orders): 주문 목록과 단건 주문 및 구독 결제 구분 필드를 조회하는 REST API. - [Refunds](https://docs.payri.kr/developers/rest-api/refunds): 주문 환불과 환불 요청 조회·승인·거절·보류 REST API. - [Subscriptions](https://docs.payri.kr/developers/rest-api/subscriptions): 구독 목록·단건 조회, 해지 미리보기와 해지 REST API. - [Portal Sessions](https://docs.payri.kr/developers/rest-api/portal-sessions): 구매자 포털 세션을 발급하는 REST API. - [Licenses](https://docs.payri.kr/developers/rest-api/licenses): 구매자 정보와 발급된 라이선스를 서버에서 조회하는 REST API. - [에러 코드](https://docs.payri.kr/developers/rest-api/errors): REST API 에러 응답 형식과 공통·리소스별 코드. ### 웹훅 - [웹훅](https://docs.payri.kr/developers/webhooks): 외부 연동에서 웹훅 엔드포인트를 등록하고 이벤트를 안전하게 처리하는 방법. - [이벤트 레퍼런스](https://docs.payri.kr/developers/webhooks/events): 결제·환불·구독·라이선스 웹훅 이벤트와 data 객체를 확인해요. - [서명 검증](https://docs.payri.kr/developers/webhooks/signature): Standard Webhooks 규격에 따라 원문 본문과 시크릿으로 웹훅 서명을 검증해요. - [재시도와 재전송](https://docs.payri.kr/developers/webhooks/retry): 웹훅 재시도 간격, 발송 이력, 콘솔 재전송과 멱등 처리 방법. ### 라이선스 - [라이선스](https://docs.payri.kr/developers/licensing): 상품의 라이선스 발급 방식과 키 형식, 앱 연동 API 검증 대상을 확인해요. - [앱 연동 API](https://docs.payri.kr/developers/licensing/app-api): 라이선스 키를 앱에서 확인하고 기기를 활성화·비활성화하는 API와 응답 서명 검증 방법. - [내 서버에서 발급](https://docs.payri.kr/developers/licensing/issue-webhook): license.issue 웹훅을 받아 셀러 서버가 라이선스 키를 발급하는 방법. ### SDK - [SDK](https://docs.payri.kr/developers/sdk): JavaScript·TypeScript 공식 SDK 개요. 서버 REST 클라이언트, 웹훅 검증과 처리, 라이선스 앱 클라이언트의 기능과 공통 규칙을 안내해요. - [JavaScript SDK](https://docs.payri.kr/developers/sdk/js): Payri JavaScript·TypeScript SDK의 서버 API, 웹훅, 라이선스 앱 연동 예제예요. - [운영 전환 체크리스트](https://docs.payri.kr/developers/go-live): Live API 키와 웹훅, 리디렉트 도메인, 라이선스 흐름을 배포 전에 점검해요. - [변경 이력](https://docs.payri.kr/developers/changelog): PayRi 외부 연동 계약 개편과 개발자 문서 정정 내역이에요.