토큰 발급
POST /v2/oauth-apps/token 으로 인가 코드를 연결된 앱의 액세스 토큰으로 바꾸는 요청 형식, 클라이언트 인증, 응답 필드와 오류를 설명해요.
POST /v2/oauth-apps/token
파트너 서버에서 인가 요청으로 받은 인가 코드를 액세스 토큰으로 바꿔요. 요청과 응답은 OAuth 2.0(RFC 6749) 형식이에요. 페이싱크 v2 API의 {code, data} 형식을 쓰지 않아요.
요청 본문
본문은 application/x-www-form-urlencoded예요.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
grant_type |
string | 예 | authorization_code로 보내요. |
code |
string | 예 | 리디렉션으로 받은 인가 코드예요. |
redirect_uri |
string | 예 | 인가 요청에 보낸 redirect_uri와 똑같은 값이에요. |
code_verifier |
string | 인가 요청의 code_challenge를 만든 원래 값이에요. 인가 요청에 code_challenge를 넣었다면 꼭 보내요. |
|
client_id |
string | 앱 ID(oap_...)예요. HTTP Basic 인증을 쓰지 않을 때 넣어요. |
|
client_secret |
string | 클라이언트 시크릿(oas_...)이에요. HTTP Basic 인증을 쓰지 않을 때 넣어요. |
클라이언트 인증
둘 중 하나로 해요.
Authorization: Basic <base64(client_id:client_secret)>헤더. 이 헤더가 있으면 본문의client_id,client_secret은 보지 않아요.- 본문의
client_id와client_secret
요청 예시
curl -X POST https://api.paysync.kr/v2/oauth-apps/token \
-u "$PAYSYNC_CLIENT_ID:$PAYSYNC_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
--data-urlencode redirect_uri=https://partner.example.com/paysync/callback \
-d code_verifier="$CODE_VERIFIER"const res = await fetch("https://api.paysync.kr/v2/oauth-apps/token", {
method: "POST",
headers: {
Authorization: "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64"),
},
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: "https://partner.example.com/paysync/callback",
code_verifier: codeVerifier,
}),
});
const token = await res.json();import os
import requests
res = requests.post(
"https://api.paysync.kr/v2/oauth-apps/token",
auth=(os.environ["PAYSYNC_CLIENT_ID"], os.environ["PAYSYNC_CLIENT_SECRET"]),
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": "https://partner.example.com/paysync/callback",
"code_verifier": code_verifier,
},
)
token = res.json()URLSearchParams 본문과 requests의 data는 Content-Type: application/x-www-form-urlencoded로 보내져요.
응답
성공하면 HTTP 200과 아래 JSON이 와요. 응답에는 Cache-Control: no-store 헤더가 붙어요.
{
"access_token": "oat_7v2m9q4k1x8c3n6b5z0p2w7r4t9y1u3i8o5a6s2d",
"token_type": "Bearer",
"scope": "invoices:read transactions:read",
"workspace_id": "ws_a1b2c3d4e5f6g7h8i9j0k1l2"
}
| 필드 | 타입 | 설명 |
|---|---|---|
access_token |
string | oat_로 시작하는 액세스 토큰이에요. |
token_type |
string | 항상 Bearer예요. |
scope |
string | 이 토큰에 허용된 스코프를 공백으로 구분한 값이에요. 요청한 스코프보다 좁을 수 있으니 이 값을 저장해 두세요. |
workspace_id |
string | 고객이 연결한 워크스페이스 ID예요. 같은 고객이 여러 워크스페이스를 연결하면 워크스페이스마다 토큰이 따로 있어요. |
액세스 토큰에는 만료가 없고, 리프레시 토큰도 없어요. 고객이 연결을 끊거나 다시 연결할 때까지 같은 토큰을 써요. 토큰은 고객의 API 키와 같은 무게로 다루세요. 서버의 비밀 저장소에 두고, 로그나 브라우저에 남기지 마세요.
같은 워크스페이스에서 다시 연결해 새 토큰을 받으면 이전 토큰은 바로 멈춰요. 자세한 내용은 연결이 끊기거나 다시 연결될 때를 참고하세요.
오류는 { "error": "..." } 형식이에요.
{
"error": "invalid_grant"
}
{
"error": "unsupported_grant_type"
}
{
"error": "invalid_request"
}
{
"error": "invalid_client"
}
오류
| HTTP | error |
발생 조건 |
|---|---|---|
| 400 | invalid_request |
본문을 application/x-www-form-urlencoded로 읽을 수 없어요. |
| 400 | unsupported_grant_type |
grant_type이 없거나 authorization_code가 아니에요. |
| 400 | invalid_grant |
인가 코드가 없거나, 이미 썼거나, 10분이 지났거나, 다른 앱의 코드예요. redirect_uri가 인가 요청과 다르거나, code_verifier가 맞지 않거나, 앱에 등록된 스코프가 바뀌어 허용할 스코프가 하나도 남지 않아도 이 오류예요. |
| 401 | invalid_client |
client_id나 client_secret이 없거나 틀렸어요. 이때 인가 코드는 쓰이지 않고 남아 있어요. |
invalid_grant가 오면 같은 코드로 다시 시도하지 말고 인가 요청부터 다시 시작하세요.