리셀러 하위 가맹점(셀러)을 만들고 읽고 고칩니다. 만들 때 첫 프로젝트와 결제수단까지 한 번에 붙일 수 있습니다.
| API | 하는 일 |
|---|---|
| 1. 가맹점 생성 | 하위 가맹점 생성(셀러 + 기본 프로젝트 + 첫 결제수단) |
| 2. 가맹점 목록 | 하위 가맹점 목록 |
| 3. 가맹점 조회 | 하위 가맹점 단건 조회 |
| 4. 가맹점 정보 수정 | 사업자 정보 수정(보낸 칸만) |
| 5. 테스트 데이터 삭제 | 테스트용 가맹점·프로젝트·권한 일괄 삭제(개발 환경 전용) |
인증·권한 오류는 각 표에 없어도 공통 오류 가 날 수 있습니다.
1가맹점 생성
리셀러 계정으로 하위 가맹점(셀러)을 만들고, 셀러의 기본 프로젝트와 첫 결제수단(PG·메서드) 까지 한 트랜잭션 안에서 같이 등록합니다.
동작 규칙
- 셀러를 식별할 외부 키
primary_key가 필수입니다. 본사 DB 의 회원 ID 와 묶어두면 동일 리셀러 안에서 중복도 자동으로 막힙니다. - 가맹점과 함께 프로젝트 하나를 만듭니다.
project로 이름·통화·상품 종류·시간대를 정하고, 안 보내면기본프로젝트·KRW·비실물·Asia/Seoul로 만듭니다. resources배열을 함께 보내면 그 프로젝트에 PG·결제수단까지 한 번에 붙습니다. 비워두면 빈 프로젝트만 만들어집니다.- 응답으로 받은
apps[].application_keys가 곧 셀러가 결제창(SDK) 에 쓸 키입니다.apps[].private_key는 프로젝트 단위 백엔드 키입니다. 둘 다 안전한 자리에 저장합니다. - 한 트랜잭션이라 중간에 어디가 실패하면 셀러·프로젝트 둘 다 만들어지지 않습니다. 같은 키로 다시 호출하면 됩니다.
API 정보
https://api.bootpay.co.kr/v2/reseller/sellerBasic Auth (리셀러 계정 키)1-1. 요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
primary_key |
String | 필수 | 본사가 관리하는 셀러 식별자. 동일 리셀러 안에서 유일해야 합니다 |
company_name |
String | 필수 | 셀러 회사명(사업자 명의) |
email |
String | 필수 | 셀러 대표 이메일. 이 이메일로 부트페이 계정이 함께 생성됩니다. 이미 있는 계정이면 새로 만들지 않고 그 계정에 프로젝트 권한을 줍니다 |
owner_name |
String | 선택 | 대표자명 |
manager_name |
String | 선택 | 담당자명 |
regist_no |
String | 선택 | 사업자등록번호. 하이픈은 무시됩니다 |
phone |
String | 선택 | 담당자/대표 연락처. 하이픈은 무시됩니다 |
homepage |
String | 선택 | 서비스 URL |
zip |
String | 선택 | 우편번호 |
address1 |
String | 선택 | 기본 주소 |
address2 |
String | 선택 | 상세 주소 |
send_email |
Boolean | 선택 | 가입 안내 메일 발송 플래그. 기본 false. 현재 구현에서는 이 값을 true 로 보내도 메일이 실제로 발송되지 않습니다 |
project |
Object | 선택 | 함께 만들 프로젝트. 형식은 아래 참고. 안 보내면 기본프로젝트·KRW·비실물·Asia/Seoul |
resources |
Array | 선택 | 첫 프로젝트에 함께 등록할 PG·결제수단 배열. 형식은 아래 참고 |
project 객체 형식
프로젝트 생성 과 같은 값·기본값을 사용합니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
name |
String | 선택 | 프로젝트명. 기본 기본프로젝트 |
unit |
String | 선택 | 결제 통화 KRW·USD. 기본 KRW |
real |
String | 선택 | 상품 종류 비실물(digital)·실물(real)·실물&비실물(both). 기본 비실물 |
timezone |
String | 선택 | 시간대(IANA 이름). 기본 Asia/Seoul |
desc |
String | 선택 | 프로젝트 설명 |
resources 배열 형식
각 원소는 PG · 결제수단 · 발급키를 한 묶음으로 가집니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
pg |
String | 필수 | PG 심볼 (예: nicepay, kcp, inicis, tosspayments). 전체 목록은 PG 코드 참고 |
method |
String | 필수 | 결제수단 심볼 (예: 카드, 계좌이체, 가상계좌, 카드자동). |
sandbox |
Boolean | 선택 | true 면 테스트 모드(부트페이 테스트 키로 결제)로 설정합니다 |
resource |
Object | 선택 | PG 가 발급한 가맹점 키 묶음. 키 이름은 PG·메서드마다 다릅니다. 연동 값 없이 테스트 모드로만 쓰려면 생략하고 sandbox: true 를 보냅니다 |
resource 안에 들어가는 키 이름은 PG 사가 정한 그대로입니다. 나이스페이먼츠 카드라면 mid, mkey(카드자동은 cancel_pwd 까지) 같은 식이고, KCP 라면 site_cd, site_key 같은 식입니다. 가맹점 발급 통보서에 있는 값을 그대로 넣으면 됩니다. 꼭 넣어야 하는 키는 PG·필수 연동 값 목록 에서 required: true 인 키입니다(부트페이 관리자 화면과 같은 기준) — 하나라도 비면 RESOURCE_KEY_BLANK 로 가맹점도 만들어지지 않습니다.
1-2. 코드 예제
import fetch from 'node-fetch'
const RESELLER_AUTH = 'Basic ' + Buffer
.from(`${process.env.BOOTPAY_RESELLER_CLIENT_KEY}:${process.env.BOOTPAY_RESELLER_SECRET_KEY}`)
.toString('base64')
const res = await fetch('https://api.bootpay.co.kr/v2/reseller/seller', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': RESELLER_AUTH
},
body: JSON.stringify({
primary_key: 'host-member-1024',
company_name: '한입가게',
email: 'owner@hanip.shop',
owner_name: '김대표',
manager_name: '박매니저',
regist_no: '1234567890',
phone: '01012345678',
homepage: 'https://hanip.shop',
zip: '06236',
address1: '서울 강남구 테헤란로 123',
address2: '4층',
send_email: true,
project: { name: '한입가게', real: '실물' }, // 안 보내면 기본프로젝트·KRW·비실물·Asia/Seoul
resources: [
{
pg: 'nicepay',
method: '카드',
resource: {
mid: 'nicepay00m',
mkey: 'EYzu8jGGMfqaDEp76gSckuvnaHHu+...='
}
}
]
})
})
const data = await res.json()
// data.provider_id, data.apps[0].application_keys 저장
console.log(data)javascriptimport base64
import os
import requests
auth_raw = f"{os.environ['BOOTPAY_RESELLER_CLIENT_KEY']}:{os.environ['BOOTPAY_RESELLER_SECRET_KEY']}"
auth = 'Basic ' + base64.b64encode(auth_raw.encode()).decode()
payload = {
'primary_key': 'host-member-1024',
'company_name': '한입가게',
'email': 'owner@hanip.shop',
'owner_name': '김대표',
'manager_name': '박매니저',
'regist_no': '1234567890',
'phone': '01012345678',
'homepage': 'https://hanip.shop',
'zip': '06236',
'address1': '서울 강남구 테헤란로 123',
'address2': '4층',
'send_email': True,
'project': {'name': '한입가게', 'real': '실물'},
'resources': [
{
'pg': 'nicepay',
'method': '카드',
'resource': {
'mid': 'nicepay00m',
'mkey': 'EYzu8jGGMfqaDEp76gSckuvnaHHu+...='
}
}
]
}
res = requests.post(
'https://api.bootpay.co.kr/v2/reseller/seller',
json=payload,
headers={'Authorization': auth}
)
print(res.json())python<?php
$auth = 'Basic ' . base64_encode($_ENV['BOOTPAY_RESELLER_CLIENT_KEY'] . ':' . $_ENV['BOOTPAY_RESELLER_SECRET_KEY']);
$payload = [
'primary_key' => 'host-member-1024',
'company_name' => '한입가게',
'email' => 'owner@hanip.shop',
'owner_name' => '김대표',
'manager_name' => '박매니저',
'regist_no' => '1234567890',
'phone' => '01012345678',
'homepage' => 'https://hanip.shop',
'zip' => '06236',
'address1' => '서울 강남구 테헤란로 123',
'address2' => '4층',
'send_email' => true,
'project' => ['name' => '한입가게', 'real' => '실물'],
'resources' => [
[
'pg' => 'nicepay',
'method' => '카드',
'resource' => [
'mid' => 'nicepay00m',
'mkey' => 'EYzu8jGGMfqaDEp76gSckuvnaHHu+...='
]
]
]
];
$ch = curl_init('https://api.bootpay.co.kr/v2/reseller/seller');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: ' . $auth],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE)
]);
$response = curl_exec($ch);
echo $response;phprequire 'base64'
require 'json'
require 'net/http'
auth = 'Basic ' + Base64.strict_encode64(
"#{ENV['BOOTPAY_RESELLER_CLIENT_KEY']}:#{ENV['BOOTPAY_RESELLER_SECRET_KEY']}"
)
uri = URI('https://api.bootpay.co.kr/v2/reseller/seller')
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json', 'Authorization' => auth)
req.body = {
primary_key: 'host-member-1024',
company_name: '한입가게',
email: 'owner@hanip.shop',
owner_name: '김대표',
manager_name: '박매니저',
regist_no: '1234567890',
phone: '01012345678',
homepage: 'https://hanip.shop',
zip: '06236',
address1: '서울 강남구 테헤란로 123',
address2: '4층',
send_email: true,
project: { name: '한입가게', real: '실물' },
resources: [
{ pg: 'nicepay', method: '카드',
resource: { mid: 'nicepay00m', mkey: 'EYzu8jGGMfqaDEp76gSckuvnaHHu+...=' } }
]
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyrubyRESELLER_AUTH=$(printf '%s' "$BOOTPAY_RESELLER_CLIENT_KEY:$BOOTPAY_RESELLER_SECRET_KEY" | base64)
curl -X POST 'https://api.bootpay.co.kr/v2/reseller/seller' \
-H "Authorization: Basic $RESELLER_AUTH" \
-H 'Content-Type: application/json' \
-d '{
"primary_key": "host-member-1024",
"company_name": "한입가게",
"email": "owner@hanip.shop",
"owner_name": "김대표",
"manager_name": "박매니저",
"regist_no": "1234567890",
"phone": "01012345678",
"homepage": "https://hanip.shop",
"zip": "06236",
"address1": "서울 강남구 테헤란로 123",
"address2": "4층",
"send_email": true,
"project": { "name": "한입가게", "real": "실물", "unit": "KRW", "timezone": "Asia/Seoul" },
"resources": [
{ "pg": "nicepay", "method": "카드",
"resource": { "mid": "nicepay00m", "mkey": "EYzu8jGGMfqaDEp76gSckuvnaHHu+...=" } }
]
}'bash1-3. 응답
생성된 셀러 정보와 함께 첫 프로젝트가 apps[] 안에 같이 담겨 옵니다.
{
"provider_id": "65a1c0aa8f1b5b00367a0001",
"company_alias": null,
"email": "owner@hanip.shop",
"company_name": "한입가게",
"owner_name": "김대표",
"manager_name": "박매니저",
"mail_order_sales_number": null,
"information_manager_name": null,
"biz_tel": null,
"biz_email": null,
"phone": "01012345678",
"homepage": "https://hanip.shop",
"lv": 1,
"regist_no": "1234567890",
"zip": "06236",
"address1": "서울 강남구 테헤란로 123",
"address2": "4층",
"apps": [
{
"app_id": "65a1c0ab8f1b5b00367a0002",
"app_name": "한입가게",
"real": { "name": "실물", "value": 1, "desc": "" },
"unit": "KRW",
"application_keys": [
{ "type": 1, "name": "WEB", "key": "65a1c0ab8f1b5b00367a0003" },
{ "type": 2, "name": "Android", "key": "65a1c0ab8f1b5b00367a0004" },
{ "type": 3, "name": "iOS", "key": "65a1c0ab8f1b5b00367a0005" },
{ "type": 4, "name": "REST", "key": "65a1c0ab8f1b5b00367a0006" }
],
"private_key": "kQ3f8vB2pN7sXq1wE5rT9yU0iO4aS6dF8gH2jK5lZ0c=",
"payment_apps": [
{
"payment_resource_id": "65a1c0ab8f1b5b00367a0007",
"pg_id": "6098a1b2c3d4e5f600000101",
"pg_name": "나이스페이먼츠",
"pg_alias": "nicepay",
"method_id": "6098a1b2c3d4e5f600000201",
"method_name": "카드",
"method_alias": "card",
"resource_mode": null,
"sandbox_mode": false,
"easypay": false,
"updated_at": "2024-01-12T18:04:11+09:00",
"status": 1,
"resource": {
"mid": "nicepay00m",
"mkey": "EYzu8jGGMfqaDEp76gSckuvnaHHu+..."
}
}
],
"status": 20,
"commerce": {
"status": "created",
"seller_id": "65a1c0ab8f1b5b00367a0008",
"project_id": "65a1c0ab8f1b5b00367a0009",
"client_key": "example_commerce_client_key",
"core_app_id": "65a1c0ab8f1b5b00367a0002",
"key_chain": {
"project_key_chain_id": "65a1c0ab8f1b5b00367a0020",
"name": "reseller-default",
"client_key": "example_commerce_server_client_key",
"secret_key": "example_commerce_server_secret_key",
"is_supervisor": true,
"expires_at": "2099-12-31T23:59:59+09:00"
}
}
}
]
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| provider_id | String | 생성된 셀러의 식별자. 이후 가맹점 조회·프로젝트 추가에 사용 |
| String | 등록된 셀러 이메일 | |
| company_name | String | 셀러 회사명 |
| lv | Number | 가맹점 레벨 (1 = 셀러) |
| apps | Array | 함께 생성된 프로젝트 배열 |
| apps[].app_id | String | 프로젝트(=app) 식별자 |
| apps[].app_name | String | 프로젝트명. project.name 이 없으면 기본프로젝트 |
| apps[].real | Object | 거래 상품 유형. { name, value, desc } 구조입니다 |
| apps[].unit | String | 결제 통화 |
| apps[].application_keys | Array | 플랫폼별 SDK 연동키 묶음. WEB / Android / iOS / REST 4 건이 내려옵니다 |
| apps[].application_keys[].type | Number | 플랫폼 코드. 1 = WEB, 2 = Android, 3 = iOS, 4 = REST |
| apps[].application_keys[].name | String | 플랫폼 이름 (WEB / Android / iOS / REST) |
| apps[].application_keys[].key | String | SDK 초기화에 넣는 application_id. Basic Auth 에 쓰는 client_key 와는 다른 값입니다 |
| apps[].private_key | String | 프로젝트 단위의 백엔드 키. 32바이트 난수를 Base64 로 만든 값입니다 |
| apps[].payment_apps | Array | resources 로 등록한 PG·결제수단 결과. 프로젝트를 만들 때 함께 붙는 부트페이 기본 결제수단도 나옵니다 |
| apps[].payment_apps[].payment_resource_id | String | 등록된 결제수단 리소스 식별자 |
| apps[].payment_apps[].pg_id / pg_name / pg_alias | String | 매칭된 PG 식별자·이름·심볼 |
| apps[].payment_apps[].method_id / method_name / method_alias | String | 매칭된 결제수단 식별자·이름·심볼 |
| apps[].payment_apps[].resource_mode | String / null | PG 연동 방식(일부 PG 만) |
| apps[].payment_apps[].sandbox_mode | Boolean | 테스트 모드 여부 |
| apps[].payment_apps[].easypay | Boolean | 간편결제 여부 |
| apps[].payment_apps[].updated_at | String | 리소스 갱신 시각 (ISO 8601) |
| apps[].payment_apps[].status | Number | 결제수단 상태 |
| apps[].payment_apps[].resource | Object | 등록된 PG 발급키 묶음. 요청에 보낸 값이 그대로 다시 내려옵니다 |
| apps[].status | Number | 프로젝트 상태. 기본값 20 (심사통과) |
| apps[].commerce | Object | 함께 만든 부트페이 커머스 프로젝트 정보 |
| apps[].commerce.status | String | created 새로 생성, exists 기존 프로젝트 연결, failed 커머스 생성 실패 (error_code·message 포함) |
| apps[].commerce.project_id | String | 커머스 프로젝트 식별자 |
| apps[].commerce.client_key | String | 커머스 공개키. 프론트 SDK 용이며 서버용 secret 은 키 생성·조회 에서 발급합니다 |
| apps[].commerce.core_app_id | String | 커머스 프로젝트에 연결된 결제 프로젝트(app_id) |
| apps[].commerce.key_chain | Object | 리셀러 전용 커머스 서버 키(이름 reseller-default, 전체 권한·만료 없음). 커머스 프로젝트를 이 호출에서 처음 만든 경우(created)에만 오고, secret_key 원문은 이 응답에서만 보입니다. 모양은 키 생성·조회 의 발급 응답과 같습니다. 잃어버리면 키 재발급 |
| apps[].commerce.key_error | String | 커머스 프로젝트는 만들었지만 서버 키 발급에 실패한 사유. 키 재발급 의 target: commerce 로 다시 받습니다 |
| apps[].commerce.error_code | String | failed 일 때 원인 코드 |
apps[].application_keys[].key 는 셀러가 자체적으로 결제창을 띄울 때 SDK 에 넣는 값입니다. 본사 DB 의 셀러 레코드에 묶어 안전하게 저장해두면 됩니다. 분실하면 프로젝트 조회 로 다시 확인하거나 콘솔에서 확인·재발급합니다.
commerce.status 가 failed 이면 커머스 쪽 생성만 실패한 것입니다. 결제(PG) 가맹점·프로젝트는 정상 생성돼 있으므로 같은 생성 API 를 다시 부르지 않습니다. PUT /v2/reseller/seller/app/commerce/:app_id 로 커머스만 다시 시도합니다. 자세한 내용은 커머스 연동 재시도 를 참고합니다.
커머스 프로젝트 생성까지 기다렸다가 응답하므로 10초 가까이 걸릴 수 있습니다. 호출 쪽 HTTP 타임아웃은 15초 이상으로 둡니다. 중간에 끊겼다면 같은 생성 API 를 다시 부르지 말고 커머스 연동 재시도 로 확인합니다(이미 만들어졌으면 exists).
apps[].payment_apps[].resource 에는 PG 가 발급한 가맹점 키가 평문으로 들어 있습니다. 이 응답을 그대로 로그·APM·에러 리포팅에 남기지 않습니다. 저장이 필요하면 resource 를 마스킹하거나 제거한 뒤 기록합니다.
1-4. 에러 코드
인증·권한 관련 에러는 에러 코드표를 참고합니다.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
API_ |
리셀러만 이용이 가능한 API입니다 | 리셀러 권한이 부여된 계정 키로 호출합니다 |
API_ (10000) |
허가된 API 권한이 아닙니다 (HTTP 401) | 계정에 이 API 호출 권한이 열려 있는지 부트페이 관리자에게 문의합니다 |
API_ (10001) |
허가된 API 권한이 아닙니다 (HTTP 401) | 계정에 이 API 호출 권한이 열려 있는지 부트페이 관리자에게 문의합니다 |
PROVIDER_ |
primary_key 가 비어 있습니다 | primary_key 를 본사 회원 ID 와 묶어 항상 보냅니다 |
PROVIDER_ |
동일 primary_key 가 이미 존재합니다 | 본사 DB 에서 기존 셀러를 먼저 조회합니다. 신규일 때만 호출합니다 |
PROVIDER_ |
회사명이 비어 있습니다 | company_name 을 채워서 보냅니다 |
PROVIDER_ |
이메일이 비어 있거나 형식이 맞지 않습니다 | RFC 5322 형식의 이메일을 보냅니다 |
PG_ |
resources[].pg 심볼을 찾지 못했습니다 |
PG 코드 에서 PG 심볼을 확인합니다 |
METHOD_ |
resources[].method 심볼을 찾지 못했습니다 |
PG 가 지원하는 결제수단 심볼인지 확인합니다 |
RESOURCE_ |
resources[].resource 에 필수 키(required: true)가 빠져 있습니다 (빠진 키 이름이 메시지에 나옵니다) |
PG·필수 연동 값 목록 에서 required: true 인 키를 채워서 보냅니다 |
PM_ (1101) |
sandbox: true 로 연동 값 없이 붙이려는 결제수단이 테스트 모드를 지원하지 않습니다 (어느 원소인지 메시지에 나옵니다) |
PG·필수 연동 값 목록 에서 sandbox_supported 를 확인하고, 지원하지 않으면 resource 와 함께 보냅니다 |
PARAMS_ |
project 가 객체가 아닙니다 |
project 는 { "name": … } 처럼 객체로 보내거나 뺍니다 |
APP_ |
project.unit 이 KRW·USD 가 아닙니다 |
빼면 KRW |
APP_ (221) |
project.real 이 정해진 값이 아닙니다 |
비실물·실물·실물&비실물 중 하나. 빼면 비실물 |
APP_ (223) |
project.timezone 이 모르는 시간대입니다 |
Asia/Seoul 같은 IANA 이름. 빼면 Asia/Seoul |
PROVIDER_ |
셀러 생성이 트랜잭션 도중 실패했습니다 | 같은 요청을 다시 호출합니다. 반복되면 부트페이에 문의 |
2가맹점 목록
리셀러 본인의 하위에 속한 셀러를 최근 생성 순으로 나열합니다. 본사 콘솔의 가맹점 목록 화면을 그릴 때 사용합니다.
동작 규칙
- 목록은 최근 생성 순입니다. 리셀러 본인은 목록에 포함되지 않습니다.
list의 각 원소는 가맹점 조회 의 셀러 객체(프로젝트 목록apps포함)에 목록 전용 세 필드(pg_wait·commerce_status·monthly_amount)가 더해진 형태입니다.pg_wait는 가장 최근 PG 가맹 신청서의 진행 상태입니다. 신청서가 없으면null. 자세한 내용은 등록 상태 조회 로 봅니다.monthly_amount는 이번 달 1일 0시부터 지금까지 결제 완료 금액에서 취소 금액을 뺀 값입니다. 하루 단위 통계로 모으므로 방금 난 결제는 늦게 반영될 수 있고, 통화를 나누지 않고 더합니다.commerce_status는 부트페이 커머스 쪽 판매자 계정이 있으면exists, 없으면none입니다. 커머스 쪽을 잠시 읽지 못하면 그 페이지 전부failed이고 목록은 그대로 옵니다.count는 페이지와 무관한 전체 하위 가맹점 수입니다.apps에는 프로젝트 기본 정보만 옵니다. 연동키와 커머스 정보는 프로젝트 조회 로 봅니다.
API 정보
https://api.bootpay.co.kr/v2/reseller/sellerBasic Auth (리셀러 계정 키)2-1. 요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
page |
Number | 선택 | 페이지 번호 (쿼리). 기본 1 |
limit |
Number | 선택 | 페이지당 건수 (쿼리). 기본 20, 최대 100 |
2-2. 코드 예제
import fetch from 'node-fetch'
const RESELLER_AUTH = 'Basic ' + Buffer
.from(`${process.env.BOOTPAY_RESELLER_CLIENT_KEY}:${process.env.BOOTPAY_RESELLER_SECRET_KEY}`)
.toString('base64')
const res = await fetch('https://api.bootpay.co.kr/v2/reseller/seller?page=1&limit=20', {
method: 'GET',
headers: {
'Authorization': RESELLER_AUTH
}
})
const data = await res.json()
console.log(data.count, data.list)javascriptimport base64, os, requests
auth = 'Basic ' + base64.b64encode(
f"{os.environ['BOOTPAY_RESELLER_CLIENT_KEY']}:{os.environ['BOOTPAY_RESELLER_SECRET_KEY']}".encode()
).decode()
res = requests.get(
'https://api.bootpay.co.kr/v2/reseller/seller',
params={'page': 1, 'limit': 20},
headers={'Authorization': auth}
)
print(res.json())pythonRESELLER_AUTH=$(printf '%s' "$BOOTPAY_RESELLER_CLIENT_KEY:$BOOTPAY_RESELLER_SECRET_KEY" | base64)
curl -X GET "https://api.bootpay.co.kr/v2/reseller/seller?page=1&limit=20" \
-H "Authorization: Basic $RESELLER_AUTH"bash2-3. 응답
{
"list": [
{
"provider_id": "65a1c0aa8f1b5b00367a0001",
"company_alias": null,
"email": "owner@hanip.shop",
"company_name": "한입가게",
"owner_name": "김대표",
"manager_name": "박매니저",
"mail_order_sales_number": null,
"information_manager_name": null,
"biz_tel": null,
"biz_email": null,
"phone": "01012345678",
"homepage": "https://hanip.shop",
"lv": 1,
"regist_no": "1234567890",
"zip": "06236",
"address1": "서울 강남구 테헤란로 123",
"address2": "4층",
"seller_name": "한입가게",
"pg_wait": {
"status": 1,
"stage": "doc_wait",
"due_at": "2026-10-30T18:00:00+09:00"
},
"commerce_status": "exists",
"monthly_amount": 1250000,
"apps": [
{
"app_id": "65a1c0ab8f1b5b00367a0010",
"provider_id": "65a1c0aa8f1b5b00367a0001",
"app_name": "기본프로젝트",
"real": { "name": "비실물", "value": 0, "desc": "" },
"desc": null,
"unit": "KRW",
"status": 20
}
]
}
],
"count": 37
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| list | Array | 하위 가맹점 배열. 원소 형식은 가맹점 조회 에 아래 세 필드가 더해집니다 |
| list[].pg_wait | Object / null | 가장 최근 PG 가맹 신청서. 없으면 null |
| list[].pg_wait.status | Number | 신청서 상태 번호(등록 상태 조회 의 status 와 같습니다) |
| list[].pg_wait.stage | String | 진행 단계(intake·doc_wait·doc_review·pg_submit·pg_review·done·closed) |
| list[].pg_wait.due_at | String / null | 그 단계 처리 기한 |
| list[].commerce_status | String | 커머스 판매자 계정 exists·none, 읽지 못했으면 failed |
| list[].monthly_amount | Number | 이번 달 결제 합계(완료 - 취소). 하루 단위 통계라 늦게 반영될 수 있고 통화를 나누지 않습니다 |
| list[].apps | Array | 그 가맹점의 프로젝트 목록(최신순, 없으면 빈 배열). 원소는 프로젝트 목록 과 같습니다(app_id·provider_id·app_name·real·desc·unit·status) |
| count | Number | 전체 하위 가맹점 수 |
2-4. 에러 코드
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
API_ |
리셀러만 이용이 가능한 API입니다 | 리셀러 권한 계정 키로 호출합니다 |
API_ |
허용되지 않은 API 권한의 키입니다 | 키에 이 API 의 리셀러 스코프 를 주거나 전체 권한(supervisor) 키로 호출합니다 |
3가맹점 조회
리셀러 본인의 하위에 속한 셀러를
provider_id로 단건 조회합니다. 본사 콘솔에서 셀러 상세 화면을 그릴 때, 또는 가입 직후 캐시 동기화 용도로 사용합니다.
동작 규칙
- 리셀러 본인의 하위에 묶이지 않은
provider_id를 넣으면PROVIDER_NOT_FOUND가 떨어집니다. 다른 리셀러의 셀러를 들여다보는 건 막혀 있습니다. - 리셀러가 아닌 계정으로 부르면
API_ONLY_RESELLER, 본사가 사용을 멈춘 리셀러면RESELLER_SUSPENDED로 막힙니다(목록·생성·수정과 같습니다). - 응답은 가맹점 생성 의 셀러 필드와 동일한 형태에
seller_name이 추가됩니다. - 그 셀러의 프로젝트 목록(
apps)이 함께 옵니다. 형식은 프로젝트 목록 과 같고 최신순입니다. 연동 키·결제수단·커머스 연결은 프로젝트 조회 로 봅니다.
API 정보
https://api.bootpay.co.kr/v2/reseller/seller/:provider_idBasic Auth (리셀러 계정 키)3-1. 요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
provider_id |
String | 필수 | 조회할 셀러의 provider_id (URL 경로) |
3-2. 코드 예제
import fetch from 'node-fetch'
const RESELLER_AUTH = 'Basic ' + Buffer
.from(`${process.env.BOOTPAY_RESELLER_CLIENT_KEY}:${process.env.BOOTPAY_RESELLER_SECRET_KEY}`)
.toString('base64')
const providerId = '65a1c0aa8f1b5b00367a0001'
const res = await fetch(`https://api.bootpay.co.kr/v2/reseller/seller/${providerId}`, {
method: 'GET',
headers: {
'Authorization': RESELLER_AUTH
}
})
const data = await res.json()
console.log(data)javascriptimport base64, os, requests
auth = 'Basic ' + base64.b64encode(
f"{os.environ['BOOTPAY_RESELLER_CLIENT_KEY']}:{os.environ['BOOTPAY_RESELLER_SECRET_KEY']}".encode()
).decode()
provider_id = '65a1c0aa8f1b5b00367a0001'
res = requests.get(
f'https://api.bootpay.co.kr/v2/reseller/seller/{provider_id}',
headers={'Authorization': auth}
)
print(res.json())pythonRESELLER_AUTH=$(printf '%s' "$BOOTPAY_RESELLER_CLIENT_KEY:$BOOTPAY_RESELLER_SECRET_KEY" | base64)
PROVIDER_ID='65a1c0aa8f1b5b00367a0001'
curl -X GET "https://api.bootpay.co.kr/v2/reseller/seller/$PROVIDER_ID" \
-H "Authorization: Basic $RESELLER_AUTH"bash3-3. 응답
{
"provider_id": "65a1c0aa8f1b5b00367a0001",
"company_alias": null,
"email": "owner@hanip.shop",
"company_name": "한입가게",
"owner_name": "김대표",
"manager_name": "박매니저",
"mail_order_sales_number": null,
"information_manager_name": null,
"biz_tel": null,
"biz_email": null,
"phone": "01012345678",
"homepage": "https://hanip.shop",
"lv": 1,
"regist_no": "1234567890",
"zip": "06236",
"address1": "서울 강남구 테헤란로 123",
"address2": "4층",
"seller_name": "한입가게",
"apps": [
{
"app_id": "65a1c0ab8f1b5b00367a0010",
"provider_id": "65a1c0aa8f1b5b00367a0001",
"app_name": "기본프로젝트",
"real": { "name": "비실물", "value": 0, "desc": "" },
"desc": null,
"unit": "KRW",
"status": 20
}
]
}json응답 파라미터
| 파라미터 | 타입 | 설명 |
|---|---|---|
| provider_id | String | 셀러 식별자 |
| String | 셀러 대표 이메일 | |
| company_name | String | 회사명 |
| owner_name | String | 대표자명 |
| manager_name | String | 담당자명 |
| phone | String | 연락처 |
| homepage | String | 서비스 URL |
| lv | Number | 가맹점 레벨 (1 = 셀러, 2 = 리셀러) |
| regist_no | String | 사업자등록번호 |
| zip / address1 / address2 | String | 주소 |
| seller_name | String | 콘솔 표시용 셀러명 |
| apps | Array | 이 셀러의 프로젝트 목록(최신순, 없으면 빈 배열). 원소는 프로젝트 목록 과 같습니다 |
| apps[].app_id · apps[].app_name | String | 프로젝트 식별자 · 이름 |
| apps[].real · apps[].unit · apps[].status | Object · String · Number | 상품 종류(name·value) · 통화 · 프로젝트 상태(20 심사통과) |
3-4. 에러 코드
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
API_ |
리셀러만 이용이 가능한 API입니다 | 리셀러 권한 계정 키로 호출합니다 |
RESELLER_ (343) |
본사가 리셀러 사용을 멈췄습니다 (HTTP 403) | 부트페이에 문의합니다 |
PROVIDER_ |
본인 하위에 해당 셀러가 없습니다 | provider_id 가 본인 리셀러 소속인지 확인합니다 |
4가맹점 정보 수정
본인 하위 가맹점의 사업자 정보를 고칩니다. 보낸 키만 바뀌고 보내지 않은 키는 그대로입니다. 응답은 가맹점 조회 와 같은 셀러 객체이고, 프로젝트 목록
apps만 없습니다.
동작 규칙
- 바꿀 수 있는 키는 아래 표에 있는 것뿐입니다. 표에 없는 키(
primary_key·lv등)는 보내도 무시됩니다. - 아무 키도 보내지 않으면
PARAMS_INVALID로 거부됩니다. regist_no·phone은 가맹점 생성 과 같이-를 빼고 저장합니다.- 값에
null이나 빈 문자열을 보내면 그 값을 비웁니다. 단company_name은 비울 수 없고email은 이메일 형식이어야 합니다. - 상호·사업자등록번호가 바뀌면 변경 이력이 부트페이 관리자 화면에 남습니다.
- 커머스 쪽 판매자 정보는 이 호출로 바뀌지 않습니다.
- 본인 하위가 아니거나 리셀러 자신이면
PROVIDER_NOT_FOUND입니다.
API 정보
https://api.bootpay.co.kr/v2/reseller/seller/:provider_idBasic Auth (리셀러 계정 키)PATCH 로 불러도 같습니다.
4-1. 요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
provider_id |
String | 필수 | 고칠 하위 가맹점의 provider_id (URL 경로) |
company_name |
String | 선택 | 상호. 빈 값 불가 |
email |
String | 선택 | 대표 이메일. 이메일 형식이어야 합니다 |
regist_no |
String | 선택 | 사업자등록번호. - 는 빼고 저장합니다 |
owner_name |
String | 선택 | 대표자 이름 |
manager_name |
String | 선택 | 담당자 이름 |
phone |
String | 선택 | 연락처. - 는 빼고 저장합니다 |
homepage |
String | 선택 | 홈페이지 주소 |
zip |
String | 선택 | 우편번호 |
address1 |
String | 선택 | 주소 |
address2 |
String | 선택 | 상세 주소 |
mail_order_sales_number |
String | 선택 | 통신판매업 신고번호 |
information_manager_name |
String | 선택 | 개인정보 관리 책임자 |
biz_tel |
String | 선택 | 대표 전화 |
biz_email |
String | 선택 | 대표 메일 |
4-2. 코드 예제
import fetch from 'node-fetch'
const RESELLER_AUTH = 'Basic ' + Buffer
.from(`${process.env.BOOTPAY_RESELLER_CLIENT_KEY}:${process.env.BOOTPAY_RESELLER_SECRET_KEY}`)
.toString('base64')
const providerId = '65a1c0aa8f1b5b00367a0001'
const res = await fetch(`https://api.bootpay.co.kr/v2/reseller/seller/${providerId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'Authorization': RESELLER_AUTH },
body: JSON.stringify({ company_name: '한입가게 본점', phone: '010-1234-5678', address2: '5층' })
})
console.log(await res.json())javascriptimport base64, os, requests
auth = 'Basic ' + base64.b64encode(
f"{os.environ['BOOTPAY_RESELLER_CLIENT_KEY']}:{os.environ['BOOTPAY_RESELLER_SECRET_KEY']}".encode()
).decode()
provider_id = '65a1c0aa8f1b5b00367a0001'
res = requests.put(
f'https://api.bootpay.co.kr/v2/reseller/seller/{provider_id}',
json={'company_name': '한입가게 본점', 'phone': '010-1234-5678', 'address2': '5층'},
headers={'Authorization': auth}
)
print(res.json())pythonRESELLER_AUTH=$(printf '%s' "$BOOTPAY_RESELLER_CLIENT_KEY:$BOOTPAY_RESELLER_SECRET_KEY" | base64)
PROVIDER_ID='65a1c0aa8f1b5b00367a0001'
curl -X PUT "https://api.bootpay.co.kr/v2/reseller/seller/$PROVIDER_ID" \
-H "Authorization: Basic $RESELLER_AUTH" \
-H 'Content-Type: application/json' \
-d '{"company_name":"한입가게 본점","phone":"010-1234-5678","address2":"5층"}'bash4-3. 응답
{
"provider_id": "65a1c0aa8f1b5b00367a0001",
"company_alias": null,
"email": "owner@hanip.shop",
"company_name": "한입가게 본점",
"owner_name": "김대표",
"manager_name": "박매니저",
"mail_order_sales_number": null,
"information_manager_name": null,
"biz_tel": null,
"biz_email": null,
"phone": "01012345678",
"homepage": "https://hanip.shop",
"lv": 1,
"regist_no": "1234567890",
"zip": "06236",
"address1": "서울 강남구 테헤란로 123",
"address2": "5층",
"seller_name": "한입가게"
}json응답 필드는 가맹점 조회 와 같습니다.
4-4. 에러 코드
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
API_ (601) |
리셀러만 이용이 가능한 API입니다 | 리셀러 권한 계정 키로 호출합니다 |
API_ |
허용되지 않은 API 권한의 키입니다 (HTTP 401) | 키에 이 API 의 리셀러 스코프 를 주거나 전체 권한(supervisor) 키로 호출합니다 |
API_ (10000) |
허가된 API 권한이 아닙니다 (HTTP 401) | 계정에 이 API 호출 권한이 열려 있는지 부트페이 관리자에게 문의합니다 |
API_ (10001) |
허가된 API 권한이 아닙니다 (HTTP 401) | 계정에 이 API 호출 권한이 열려 있는지 부트페이 관리자에게 문의합니다 |
PROVIDER_ (300) |
본인 하위 가맹점이 아닙니다 (HTTP 404) | provider_id 가 본인 리셀러 하위인지 확인합니다 |
PARAMS_ (-33) |
바꿀 값이 없거나 값이 문자열이 아닙니다 | 위 표의 키를 하나 이상, 문자열로 보냅니다 |
PROVIDER_ (322) |
상호가 비었습니다 | company_name 을 채워 보냅니다 |
PROVIDER_ (321) |
이메일 형식이 아닙니다 | email 을 올바른 형식으로 보냅니다 |
5테스트 데이터 삭제
리셀러 본인 하위에 만들어둔 테스트 셀러 한 곳을 삭제합니다. 셀러 자신, 셀러의 모든 프로젝트(app), 프로젝트에 발급된 SDK 연동키(AppKey), 프로젝트 멤버 권한(AppRole), 셀러 단위 멤버 권한(ProviderRole) 이 정리됩니다.
이 엔드포인트는 통합 테스트·CI 에서 만들어둔 데이터를 정리하기 위한 용도입니다. 운영 주소(api.bootpay.co.kr)에서는 막혀 있어 아무것도 지우지 않고 404 를 돌려줍니다. 개발 환경 주소·키는 부트페이에 요청합니다. 삭제된 셀러·프로젝트는 복구할 수 없습니다.
결제 내역(Receipt)·정산 데이터·회원(Member) 은 이 API 로 삭제되지 않고 그대로 남습니다. 삭제 대상은 아래 삭제 범위 표에 정리된 항목뿐입니다. 결제 내역이 있는 셀러를 삭제하면 소유자가 사라진 결제 데이터가 남게 되므로, 실제 결제가 발생한 셀러에는 호출하지 않습니다.
동작 규칙
- 리셀러 본인의 하위가 아닌 셀러는 삭제되지 않습니다.
provider_id가 다른 리셀러 소속이면PROVIDER_NOT_FOUND가 떨어집니다. - 본인(리셀러 자기 자신) 의
provider_id를 넣어도 거부됩니다. 리셀러 자체는 이 API 로 삭제할 수 없습니다. - 한 번에 한 셀러만 삭제됩니다. 대량 정리는 반복 호출이 필요합니다.
API 정보
https://{개발 환경 주소}/v2/reseller/test/seller/:provider_idBasic Auth (리셀러 계정 키)5-1. 요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
provider_id |
String | 필수 | 삭제할 테스트 셀러의 provider_id (URL 경로) |
5-2. 코드 예제
import fetch from 'node-fetch'
const RESELLER_AUTH = 'Basic ' + Buffer
.from(`${process.env.BOOTPAY_RESELLER_CLIENT_KEY}:${process.env.BOOTPAY_RESELLER_SECRET_KEY}`)
.toString('base64')
const providerId = '65a1c0aa8f1b5b00367a0001'
const BASE_URL = process.env.BOOTPAY_TEST_API_URL // 개발 환경 주소(운영 api.bootpay.co.kr 는 404)
const res = await fetch(`${BASE_URL}/v2/reseller/test/seller/${providerId}`, {
method: 'DELETE',
headers: { 'Authorization': RESELLER_AUTH }
})
console.log(res.status) // 200javascriptimport base64, os, requests
auth = 'Basic ' + base64.b64encode(
f"{os.environ['BOOTPAY_RESELLER_CLIENT_KEY']}:{os.environ['BOOTPAY_RESELLER_SECRET_KEY']}".encode()
).decode()
provider_id = '65a1c0aa8f1b5b00367a0001'
base_url = os.environ['BOOTPAY_TEST_API_URL'] # 개발 환경 주소(운영 api.bootpay.co.kr 는 404)
res = requests.delete(
f'{base_url}/v2/reseller/test/seller/{provider_id}',
headers={'Authorization': auth}
)
print(res.status_code)pythonRESELLER_AUTH=$(printf '%s' "$BOOTPAY_RESELLER_CLIENT_KEY:$BOOTPAY_RESELLER_SECRET_KEY" | base64)
PROVIDER_ID='65a1c0aa8f1b5b00367a0001'
# BOOTPAY_TEST_API_URL = 개발 환경 주소(운영 api.bootpay.co.kr 는 404)
curl -X DELETE "$BOOTPAY_TEST_API_URL/v2/reseller/test/seller/$PROVIDER_ID" \
-H "Authorization: Basic $RESELLER_AUTH"bash5-3. 삭제 범위
한 번의 호출로 아래가 정리됩니다.
| 대상 | 설명 |
|---|---|
| 셀러(provider) | 셀러 자체 레코드 |
| 프로젝트(app) | 셀러가 소유한 모든 프로젝트 |
| AppKey | 프로젝트에 발급된 SDK 용 application_id 만 삭제됩니다. Basic Auth 에 쓰는 client_key / secret_key (AppKeyChain) 는 그대로 남습니다 |
| ProviderRole | 셀러에 묶인 모든 팀 멤버 권한 |
| AppRole | 삭제되는 프로젝트들에 묶인 프로젝트 멤버 권한 |
하위 셀러를 만들 때 함께 만들어진 커머스(쇼핑몰) 쪽 Seller·Project 는 이 API 로 지워지지 않고 남습니다.
이 API 는 셀러와 프로젝트 레코드를 삭제하지만, 결제 내역·정산 데이터·회원 계정은 남깁니다. 실제 결제 내역이 있는 운영 셀러에는 호출하면 안 됩니다. 테스트 환경에서만 사용합니다.
5-4. 응답
성공 시 본문은 비어 있고, HTTP 200 으로 떨어집니다.
5-5. 에러 코드
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
API_ |
리셀러만 이용이 가능한 API입니다 | 리셀러 권한 계정 키로 호출합니다 |
API_ (1222) |
범위 키로는 부를 수 없습니다 (HTTP 401) | 전체 권한(supervisor) 키로 호출합니다 |
PROVIDER_ |
셀러가 본인 하위에 없거나 본인 자신입니다 | provider_id 가 본인 하위 셀러인지 확인합니다. 리셀러 자기 자신은 삭제 불가 |
NOT_ (HTTP 404) |
테스트 셀러 삭제는 운영 환경에서 쓸 수 없습니다 | 개발 환경 주소로 호출합니다 |
5-6. CI 에서 쓰는 패턴
테스트 흐름은 보통 셋업·검증·정리 세 단계로 묶입니다.
// 1) 셋업 — 테스트용 셀러 생성
const seller = await createTestSeller({
primary_key: `ci-${process.env.GITHUB_RUN_ID}-${Date.now()}`,
company_name: 'CI Sandbox',
email: `ci+${Date.now()}@example.com`
})
try {
// 2) 검증 — 결제 흐름 테스트
await runPaymentFlow(seller.apps[0].application_keys[0].key)
} finally {
// 3) 정리 — 어떤 결과든 셀러 일괄 삭제
await fetch(`${process.env.BOOTPAY_TEST_API_URL}/v2/reseller/test/seller/${seller.provider_id}`, {
method: 'DELETE',
headers: { 'Authorization': RESELLER_AUTH }
})
}javascriptprimary_key 에 CI 빌드 식별자를 박아두면 누구의 어떤 실행에서 만든 셀러인지 추적이 쉽습니다. 정리에서 누락된 잔여 셀러가 있어도 primary_key 패턴으로 찾아 일괄 처리할 수 있습니다.





