시작하기
빠른 시작
테스트 키로 테스트 양도 코드를 만들고, 확인·접수·판매자 승인을 거쳐 구매자용 PIN 전달 링크를 열어 테스트 PIN을 확인하기까지 약 10분이면 따라 할 수 있습니다.
따라 하기 #
업체 신청하고 승인받기
테스트 키 발급하기
콘솔의 API 키 메뉴에서 테스트 키(pl_test_…)를 발급합니다. 키 전체 값은 발급 직후 한 번만 보이므로 바로 복사해 두세요.
# 발급받은 테스트 키를 서버 환경변수로 둡니다 (코드 저장소에 넣지 마세요)
export PINLOCK_API_KEY="pl_test_여기에_발급받은_키"키가 동작하는지 확인하기
GET /v1/me 로 키가 어느 업체·모드인지 확인합니다. mode 가 "test" 이면 준비 완료입니다.
curl https://api.pinlock.app/v1/me \
-H "Authorization: Bearer $PINLOCK_API_KEY"const res = await fetch("https://api.pinlock.app/v1/me", {
headers: { Authorization: `Bearer ${process.env.PINLOCK_API_KEY}` }
});
console.log(await res.json());import os, requests
res = requests.get(
"https://api.pinlock.app/v1/me",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
timeout=10,
)
print(res.json()){
"success": true,
"data": {
"partnerId": "ptn_…",
"partnerName": "예시상사",
"mode": "test",
"serverTime": "2026-09-23T09:00:00.000Z"
}
}테스트할 상품 ID 고르기
테스트 양도 코드를 만들려면 상품 ID가 필요합니다. GET /v1/products 로 상품 목록을 받아 아무 상품의 id 를 하나 고릅니다. 실제 서비스에서는
이 단계가 필요 없습니다(상품은 판매자의 핀락 주문에 이미 정해져 있습니다).
curl https://api.pinlock.app/v1/products \
-H "Authorization: Bearer $PINLOCK_API_KEY"const res = await fetch("https://api.pinlock.app/v1/products", {
headers: { Authorization: `Bearer ${process.env.PINLOCK_API_KEY}` }
});
const { data: products } = await res.json();
for (const p of products) console.log(p.id, p.name, p.faceValue);res = requests.get(
"https://api.pinlock.app/v1/products",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
timeout=10,
)
for p in res.json()["data"]:
print(p["id"], p["name"], p["faceValue"]){
"success": true,
"data": [
{
"id": "prd_…",
"name": "컬처랜드 문화상품권 1만원권",
"brand": { "slug": "cultureland", "name": "컬처랜드" },
"faceValue": 10000,
"…": "…"
}
]
}테스트 양도 코드 만들기
판매자가 핀락에서 상품권을 사고 양도 코드와 양도 비밀번호를 넘겨준 상황을 만듭니다. POST /v1/test/transfers 는 입금이 끝난 테스트 주문과 그 양도 코드를
만들어 transferCode 와 transferPassword 를 돌려줍니다. 테스트 코드의 양도 비밀번호는 항상 246813 입니다.
curl -X POST https://api.pinlock.app/v1/test/transfers \
-H "Authorization: Bearer $PINLOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "items": [{ "productId": "prd_여기에_상품_ID", "quantity": 1 }] }'const res = await fetch("https://api.pinlock.app/v1/test/transfers", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PINLOCK_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ items: [{ productId: "prd_여기에_상품_ID", quantity: 1 }] })
});
const { data } = await res.json();
// 판매자가 넘겨준 것과 같은 코드·비밀번호
const { transferCode, transferPassword } = data;
console.log(transferCode, transferPassword);res = requests.post(
"https://api.pinlock.app/v1/test/transfers",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
json={"items": [{"productId": "prd_여기에_상품_ID", "quantity": 1}]},
timeout=10,
)
data = res.json()["data"]
# 판매자가 넘겨준 것과 같은 코드·비밀번호
transfer_code, transfer_password = data["transferCode"], data["transferPassword"]
print(transfer_code, transfer_password){
"success": true,
"data": { "transferCode": "PLX-3HRT-8QZC-M2VK", "transferPassword": "246813" }
}코드 확인하기 (선점하지 않음)
판매자에게 코드를 받으면 먼저 GET /v1/transfers/code/{code} 로 상태를 봅니다. claimable 이 true 이고 passwordSet 이 true 이면 접수할 수 있습니다. 이 조회는 코드를 선점하지 않습니다.
curl https://api.pinlock.app/v1/transfers/code/PLX-3HRT-8QZC-M2VK \
-H "Authorization: Bearer $PINLOCK_API_KEY"const res = await fetch(`https://api.pinlock.app/v1/transfers/code/${encodeURIComponent(transferCode)}`, {
headers: { Authorization: `Bearer ${process.env.PINLOCK_API_KEY}` }
});
const { data: transfer } = await res.json();
console.log(transfer.status, transfer.claimable, transfer.passwordSet); // "available" true truefrom urllib.parse import quote
res = requests.get(
f"https://api.pinlock.app/v1/transfers/code/{quote(transfer_code)}",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
timeout=10,
)
transfer = res.json()["data"]
print(transfer["status"], transfer["claimable"], transfer["passwordSet"]) # available True True{
"success": true,
"data": {
"transferId": "trf_…",
"mode": "test",
"status": "available",
"claimable": true,
"passwordSet": true,
"items": [
{ "productName": "컬처랜드 문화상품권 1만원권", "faceValue": 10000, "quantity": 1 }
],
"faceTotal": 10000,
"paidAt": "2026-09-23T09:01:00.000Z",
"codeLast4": "M2VK"
}
}접수 요청하기
코드와 양도 비밀번호(숫자 6자리)로 POST /v1/transfers/claim 을 호출합니다. externalRef 에는 업체 쪽 매물 번호를 넣을 수 있습니다(선택).
성공하면 status 가 pending_approval(판매자 승인 대기)이고, 응답의 transferId 를 저장합니다.
curl -X POST https://api.pinlock.app/v1/transfers/claim \
-H "Authorization: Bearer $PINLOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "code": "PLX-3HRT-8QZC-M2VK", "password": "246813", "externalRef": "listing-1" }'const res = await fetch("https://api.pinlock.app/v1/transfers/claim", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PINLOCK_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ code: transferCode, password: transferPassword, externalRef: "listing-1" })
});
const { data: claimed } = await res.json();
// 이후 승인·링크·조회는 모두 transferId 로 합니다
const transferId = claimed.transferId;
console.log(transferId, claimed.status); // "trf_…" "pending_approval"res = requests.post(
"https://api.pinlock.app/v1/transfers/claim",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
json={"code": transfer_code, "password": transfer_password, "externalRef": "listing-1"},
timeout=10,
)
claimed = res.json()["data"]
# 이후 승인·링크·조회는 모두 transferId 로 합니다
transfer_id = claimed["transferId"]
print(transfer_id, claimed["status"]) # trf_… pending_approval{
"success": true,
"data": {
"transferId": "trf_…",
"mode": "test",
"status": "pending_approval",
"claimable": false,
"passwordSet": true,
"items": [{ "productName": "컬처랜드 문화상품권 1만원권", "faceValue": 10000, "quantity": 1 }],
"faceTotal": 10000,
"paidAt": "2026-09-23T09:01:00.000Z",
"codeLast4": "M2VK",
"externalRef": "listing-1",
"claimedAt": "2026-09-23T09:02:00.000Z",
"approval": {
"status": "pending",
"requestedAt": "2026-09-23T09:02:00.000Z",
"expiresAt": "2026-09-23T09:32:00.000Z",
"approvedAt": null
},
"claimExpiresAt": "2026-09-30T09:02:00.000Z",
"deliveredAt": null,
"share": null
}
}판매자 승인 처리하기 (테스트 전용)
운영에서는 판매자가 핀락 주문 화면에서 등록을 승인해야 하고, approval.expiresAt 까지 승인하지 않으면 접수가 자동 취소됩니다. 테스트 코드에는 승인해 줄
판매자가 없으므로 POST /v1/test/transfers/{transferId}/approve 로 승인된 것처럼 처리합니다. status 가 claimed_by_you 가 됩니다.
curl -X POST https://api.pinlock.app/v1/test/transfers/trf_여기에_transferId/approve \
-H "Authorization: Bearer $PINLOCK_API_KEY"const res = await fetch(`https://api.pinlock.app/v1/test/transfers/${transferId}/approve`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PINLOCK_API_KEY}` }
});
const { data } = await res.json();
console.log(data.status, data.approval.status); // "claimed_by_you" "approved"res = requests.post(
f"https://api.pinlock.app/v1/test/transfers/{transfer_id}/approve",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
timeout=10,
)
data = res.json()["data"]
print(data["status"], data["approval"]["status"]) # claimed_by_you approved구매자용 PIN 전달 링크 만들기
구매자가 업체에 결제를 마쳤다고 보고 POST /v1/transfers/{transferId}/share-link 로 링크를 만듭니다. sharePassword(6~20자)는 구매자가 링크를
열 때 입력할 비밀번호입니다. 링크는 만든 즉시 ready 이고, 24시간 동안 한 번 열 수 있습니다.
curl -X POST https://api.pinlock.app/v1/transfers/trf_여기에_transferId/share-link \
-H "Authorization: Bearer $PINLOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "sharePassword": "7K2m9Q" }'const res = await fetch(`https://api.pinlock.app/v1/transfers/${transferId}/share-link`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PINLOCK_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ sharePassword: "7K2m9Q" }) // 구매자가 링크를 열 때 넣을 비밀번호 (6~20자)
});
const { data } = await res.json();
// share.url 은 이 응답에서만 받을 수 있으니 바로 저장합니다
console.log(data.share.url, data.share.status); // "https://…/share/…" "ready"res = requests.post(
f"https://api.pinlock.app/v1/transfers/{transfer_id}/share-link",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
json={"sharePassword": "7K2m9Q"}, # 구매자가 링크를 열 때 넣을 비밀번호 (6~20자)
timeout=10,
)
data = res.json()["data"]
# share.url 은 이 응답에서만 받을 수 있으니 바로 저장합니다
print(data["share"]["url"], data["share"]["status"]) # https://…/share/… ready{
"success": true,
"data": {
"transferId": "trf_…",
"mode": "test",
"status": "claimed_by_you",
"…": "…",
"share": {
"status": "ready",
"url": "https://…/share/…",
"expiresAt": "2026-09-24T09:05:00.000Z",
"openedAt": null,
"failedAttempts": 0,
"createdAt": "2026-09-23T09:05:00.000Z"
}
}
}링크를 열고 전달 완료 확인하기
9단계에서 받은 share.url 을 브라우저에서 열고 sharePassword 로 넣은 비밀번호를 입력합니다. TEST-xxxx-xxxx-xxxx 형태의 테스트
PIN이 한 번 표시됩니다. 이어서 양도 건을 조회해 status 가 delivered 인지 확인하세요.
curl https://api.pinlock.app/v1/transfers/trf_여기에_transferId \
-H "Authorization: Bearer $PINLOCK_API_KEY"const res = await fetch(`https://api.pinlock.app/v1/transfers/${transferId}`, {
headers: { Authorization: `Bearer ${process.env.PINLOCK_API_KEY}` }
});
const { data: transfer } = await res.json();
console.log(transfer.status, transfer.deliveredAt, transfer.share.status); // "delivered" "2026-…" "opened"res = requests.get(
f"https://api.pinlock.app/v1/transfers/{transfer_id}",
headers={"Authorization": f"Bearer {os.environ['PINLOCK_API_KEY']}"},
timeout=10,
)
transfer = res.json()["data"]
print(transfer["status"], transfer["deliveredAt"], transfer["share"]["status"]) # delivered 2026-… opened실제 서비스에서는 링크를 만든 거래를 30~60초 간격으로 조회해 delivered 를 확인한 뒤 판매자에게 정산합니다. 대금 결제와 정산은 핀락을 거치지 않습니다.
더 시험해 보기 #
- 같은 코드로 접수를 한 번 더 보내면 같은 결과가 옵니다. 이미 접수된 코드를 다른 업체가 접수하려 하면 그쪽은
409 ALREADY_CLAIMED를 받습니다. - 비밀번호를 틀리게 보내면
401 WRONG_PASSWORD, 5번 틀리면 60분 동안423 LOCKED입니다. - 8단계를 건너뛰고 링크를 만들면
409 APPROVAL_PENDING입니다. 승인하지 않고 기다리면 승인 기한이 지나 접수가 취소되는 흐름도 볼 수 있습니다. - 링크를 열기 전에
POST /v1/transfers/{transferId}/release를 호출하면 선점이 풀리고 링크가 폐기됩니다.
각 API의 상세와 오류 목록은 양도 코드 연동 문서를 보세요.