베타 운영 중·유료 거래는 2026년 12월 17일 정식 오픈 후 시작됩니다. 지금은 상품을 미리 등록하고 둘러보실 수 있습니다.

SDK 레퍼런스

@saemol/sdk — Node.js 18+ 서버용 클라이언트. 플랫폼 API 래퍼와 웹훅 서명 검증, 새몰 계정 로그인(SSO/OAuth 2.0) 헬퍼를 제공합니다. 런타임 의존성 없음, TypeScript 타입 내장. GitHub에서 소스 보기 ↗

클라이언트 생성

import { Saemol } from '@saemol/sdk'

const saemol = new Saemol({
  secret: process.env.SAEMOL_SECRET!,   // 필수 — 연동 정보 카드의 연동 시크릿
})
옵션기본값설명
secret(필수)상품별 연동 시크릿 — X-Saemol-Secret 헤더로 전송
baseUrlhttps://saemol.com플랫폼 주소 (개발 환경에서 변경)
timeoutMs10000요청당 타임아웃 (ms)
maxRetries2멱등 호출(health·verify)의 최대 재시도 — reportUsage는 재시도 안 함
retryDelayMs300재시도 대기 (시도마다 2배 증가)
fetch전역 fetch테스트·폴리필용 주입

메서드

메서드HTTP API용도
health()GET /api/v1/health연동 테스트
verifyLicense({ licenseKey, deviceId?, version? })POST /api/v1/licenses/verify1회성 라이선스·인앱 토큰 검증
verifyEntitlement({ apiKey, version? })POST /api/v1/entitlements/verify구독/API/사용량/화이트라벨 권한 확인
reportUsage({ apiKey, quantity, metadata? })POST /api/v1/usage/report사용량 집계
checkUpdate({ currentVersion? })POST /api/v1/updates/check최신 버전·강제 업데이트 확인
currentUser({ licenseKey? | apiKey? })POST /api/v1/users/current키 소유 구매자 컨텍스트 조회

응답 필드는 HTTP API 응답과 1:1입니다. 대표 응답 형태:

verifyLicense 응답 (판별 유니언 — valid로 분기)
// valid: true
{ valid: true, status: 'active', kind: 'license', plan_name: 'Pro',
  sku: 'com.myapp.pro' | null,
  devices: { registered: 2, limit: 3 | null },
  expires_at: '2026-08-05T…' | null,   // 기간제·trial 만료 (null=무기한)
  is_trial: false }

// valid: false
{ valid: false, reason: 'revoked' | 'expired' | 'suspended'
        | 'device_limit_exceeded' | 'version_not_allowed' | …,
  status?, devices?, allowed_versions?, expires_at?, is_trial? }
verifyEntitlement 응답
// 키가 조회되면 상태와 무관하게 전체 응답 (valid만 false일 수 있음)
{ valid: boolean, status: 'active' | 'expired' | 'revoked' | …,
  kind: 'subscription' | 'usage' | 'api' | …, plan_name: 'Pro' | null,
  plan_features: ['core', 'advanced'] | null, rate_limit_per_min: 60 | null,
  usage: { unit, used, limit } | null,        // kind가 usage일 때만
  current_period_end: '…' | null, expires_at: '…' | null, is_trial: boolean }

// 버전 미허용 등 거부 응답 (reason 존재 여부로 판별)
{ valid: false, reason: 'version_not_allowed', allowed_versions: ['1.0'], … }
reportUsage 응답
{ accepted: true,
  allowed: boolean,          // false = 초과 정책 block → 사용 차단
  usage: { unit, used, limit, remaining },
  overage_policy: 'notify' | 'block' | …,
  over_limit: boolean }
checkUpdate 응답 — 상품 수정 페이지 '업데이트 정보' 카드에서 설정
{ product_id, latest_version: '1.4.0' | null,
  update_available: boolean,   // currentVersion < latest
  update_required: boolean,    // currentVersion < min_version → 강제 업데이트
  min_version: '1.0.0' | null, download_url: '…' | null, release_notes: '…' | null }
currentUser 응답 (판별 유니언 — found로 분기)
// found: true — 키 소유 구매자 컨텍스트
{ found: true, entitlement_id, kind, status: 'active' | 'expired' | …,
  plan_name: 'Pro' | null, is_trial: boolean, expires_at: '…' | null,
  user: { id, name, email } }

// found: false (키 미존재 — 던지지 않고 반환)
{ found: false, reason: '…' }

새몰 계정 로그인 — SaemolOAuth

구독형 상품의 인증 연동 방식이 새몰 계정으로 로그인일 때 사용합니다. 구매자가 키를 복사·입력할 필요 없이 새몰 계정으로 로그인하고, 판매자는 같은 응답에서 구독 상태·플랜까지 받습니다. 자격증명은 상품 수정 페이지의 연동 정보 카드에서 확인합니다.

import { SaemolOAuth, isSubscriptionActive, hasPlanFeature } from '@saemol/sdk'

const oauth = new SaemolOAuth({
  clientId:     process.env.SAEMOL_OAUTH_CLIENT_ID!,
  clientSecret: process.env.SAEMOL_OAUTH_CLIENT_SECRET!,
  redirectUri:  'https://app.example.com/auth/callback',
})

// 1) 로그인 버튼 — state(CSRF)는 SDK가 생성해 반환
const { url, state } = oauth.createAuthorizationUrl()
session.oauthState = state
redirect(url)

// 2) 콜백 — 오류 확인·state 검증·토큰 교환·userinfo를 한 번에
const { user, tokens } = await oauth.handleCallback({
  url:           req.url,
  expectedState: session.oauthState,
})

if (!isSubscriptionActive(user)) return showPricing()
if (hasPlanFeature(user, 'advanced')) enableAdvanced()
메서드용도
createAuthorizationUrl()동의 화면 URL + state 생성
handleCallback()콜백 한 번에 처리 (아래 3개를 묶은 것)
exchangeCode()인가 코드 → 토큰
getUserInfo()액세스 토큰 → 구매자 정보·구독 상태
refresh()토큰 갱신 — 두 토큰 모두 회전
isSubscriptionActive()구독 개방 판정 (순수 함수)
hasPlanFeature()플랜별 기능 게이팅 (순수 함수)
redirectUri는 상품 설정의 OAuth Redirect URI와 정확히 일치해야 합니다 — 부분 일치·와일드카드는 허용되지 않습니다(오픈 리다이렉트 방지).
scope로 무엇을 받을지 정합니다 — identity(로그인만) 또는 entitlement(로그인 + 구독 상태·플랜). 생략하면 entitlement가 기본이며, 같은 상품에서 페이지마다 다르게 요청할 수 있습니다.

실패는 SaemolOAuthError로 던져지며 code로 구분합니다 — access_denied(구매자 거부) · state_mismatch(CSRF 차단) · invalid_client · invalid_grant(코드 재사용·만료) · invalid_token(갱신 필요) · missing_code.

에러 규약

상황SDK 동작
키가 유효하지 않음 (미존재·회수·만료·제한 초과)던지지 않음 — valid: false 응답 반환
연동 설정 문제 (시크릿 불일치 401 · 잘못된 요청 400)SaemolApiError
일시 서버 장애 (502·503·504)멱등 호출은 재시도 후 SaemolApiError
네트워크 실패·타임아웃SaemolNetworkError (timedOut 플래그)
웹훅 검증 실패SaemolWebhookError (code로 원인 구분)
import { SaemolApiError, SaemolNetworkError } from '@saemol/sdk'

try {
  const result = await saemol.verifyLicense({ licenseKey })
} catch (err) {
  if (err instanceof SaemolApiError)     log(err.status, err.reason)  // 설정 점검
  if (err instanceof SaemolNetworkError) retryLater(err.timedOut)
}
시그니처 동결 — 플랫폼 API 응답 필드는 추가만 되며 제거·의미 변경되지 않습니다. SDK 마이너 업데이트는 항상 하위 호환입니다 (semver).

SDK 없이 직접 호출

모든 요청에 X-Saemol-Secret 헤더를 붙여 호출하면 됩니다.

curl
curl -X POST https://saemol.com/api/v1/licenses/verify \
  -H 'Content-Type: application/json' \
  -H 'X-Saemol-Secret: <연동 시크릿>' \
  -d '{ "license_key": "MF-XXXX-XXXX-XXXX-XXXX", "device_id": "machine-abc" }'