BazaarLinkBazaarLink
로그인
문서API 레퍼런스SDK 레퍼런스에이전트 사용법AI 스킬
목차

OAuth PKCE

OAuth PKCE

OAuth 2.0 PKCE (Proof Key for Code Exchange)는 공개 클라이언트 앱(브라우저 SPA, 모바일 앱)이 클라이언트 측에 장기 서버 시크릿을 저장하지 않고도 개별 사용자를 대신하여 API 호출을 할 수 있게 합니다.

OAuth PKCE 사용 시기

  • 사용자별 과금이 필요한 브라우저 SPA 또는 모바일 앱 구축 시
  • 멀티테넌트 SaaS — 각 최종 사용자가 자체 API 사용량을 지불
  • 클라이언트 측에 공유 시크릿을 저장할 수 없는 경우 (공개 클라이언트)
  • 단일 공유 API 키 대신 사용자별로 독립적이고 폐기 가능한 키(사용자가 폐기할 때까지 유효)가 필요한 경우

사용자에게 보이는 화면

자체 로그인 화면에서 다음 단계를 안내하세요:

  1. 앱에서 "BazaarLink로 로그인"(또는 원하는 버튼 이름)을 선택합니다.
  2. 브라우저에 BazaarLink 승인 페이지가 열립니다. 아직 로그인하지 않았다면 먼저 로그인하고 자동으로 이 페이지로 돌아옵니다.
  3. 페이지에 앱 주소와 허용했을 때 일어나는 일이 표시됩니다: 앱 전용 새 API 키가 생성되고, 사용량은 BazaarLink 잔액에서 과금되며, 언제든 키를 폐기할 수 있습니다. 키 이름과 월 지출 한도를 지정할 수 있습니다(둘 다 선택 사항).
  4. "허용"을 클릭하면 앱으로 돌아갑니다. "거부"를 클릭하면 키가 생성되지 않고 앱에도 알리지 않으며 "접근이 거부되었습니다" 페이지가 표시됩니다.
  5. 나중에 접근을 취소하려면 API 키 페이지(https://bazaarlink.ai/keys)를 열어 앱 이름이 붙은 키를 비활성화하거나 삭제하세요.

동작 방식

단계 1 — 인증 시작 — 랜덤 code_verifier(긴 랜덤 문자열)와 그 SHA-256 해시를 code_challenge로 생성한 다음, 사용자를 BazaarLink 인증 페이지로 리디렉션합니다.

https://bazaarlink.ai/auth?callback_url=https://yourapp.com/callback&code_challenge=CODE_CHALLENGE&code_challenge_method=S256

인증 URL 매개변수

  • callback_url (필수) — 사용자가 허용한 뒤 돌아올 주소. 규칙은 아래를 참고하세요.
  • code_challenge (필수) — code_verifier의 SHA-256을 base64url로 인코딩한 값(43자, 패딩 없음).
  • code_challenge_method (필수) — 반드시 S256이어야 합니다.
  • state (선택) — 자체 불투명 CSRF 값으로, 출력 가능한 ASCII 문자 1~256자. BazaarLink는 code 바로 뒤에 &state=…로 그대로 돌려줍니다. 형식이 잘못된 state는 승인 페이지에 "잘못된 승인 요청"이 표시되게 합니다.
  • key_label (선택) — 최대 100자. 동의 페이지의 키 이름을 미리 채워 줍니다(사용자가 변경할 수 있음). 너무 길거나 빈 라벨은 무시됩니다.
  • Headless tools (CLI / desktop, no callback_url) — omit callback_url. After the user allows, the page shows a one-time code (valid 5 minutes, single use) for them to enter into your tool; exchange it exactly like a redirected code. The page cannot show which app is asking, so tell users to enter the code only in a tool they started themselves.

지원하지 않음

  • workspace_id와 required_workspace_id — 지원하지 않습니다. 키는 항상 사용자 본인 계정에 속합니다.
  • code_challenge_method=plain — 지원하지 않습니다(S256만 가능).

callback_url 규칙

  • https://여야 합니다. http://는 localhost, 127.0.0.1, [::1](모든 포트)에만 허용되며, 이 경우 동의 페이지에 로컬 개발 경고가 표시됩니다.
  • 프래그먼트(#), URL 내 사용자 이름·비밀번호는 허용되지 않으며 최대 2048자입니다.
  • 동의 페이지에 callback 호스트 이름이 표시되어 사용자가 어떤 앱이 요청하는지 알 수 있습니다.
  • 사용자가 허용하면 브라우저가 code=…가 덧붙은 callback_url로 이동합니다(state를 보냈다면 state=…도 덧붙으며, 기존 쿼리 문자열은 유지됩니다). code는 10분간 유효하며 한 번만 사용할 수 있습니다.

단계 2 — 코드를 API 키로 교환 — 사용자가 요청을 승인하면 BazaarLink이 일회용 code와 함께 callback_url로 리디렉션합니다. code는 10분간 유효하며 한 번만 사용할 수 있고, code_verifier가 틀리면 code가 폐기됩니다. code, code_verifier, code_challenge_method(세 가지 모두 필수)를 POST하면 해당 사용자의 API 키를 받을 수 있습니다.

curl https://bazaarlink.ai/api/v1/auth/keys \
-H "Content-Type: application/json" \
-d '{
  "code": "<CODE_FROM_CALLBACK_URL>",
  "code_verifier": "<CODE_VERIFIER>",
  "code_challenge_method": "S256"
}'

# Response
# { "key": "sk-bl-...", "user_id": "..." }

단계 3 — 사용자 범위 키 사용 — 반환된 키는 승인한 사용자에게 속한 일반 BazaarLink API 키입니다. 사용량은 귀하가 아니라 해당 사용자의 잔액에서 과금됩니다.

curl https://bazaarlink.ai/api/v1/chat/completions \
-H "Authorization: Bearer sk-bl-..." \
-H "Content-Type: application/json" \
-d '{"model": "google/gemini-3.5-flash", "messages": [{"role": "user", "content": "Hello"}]}'

JavaScript 예제 (브라우저)

// 1) Create a verifier + S256 challenge, then send the user to BazaarLink
const b64url = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = b64url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
sessionStorage.setItem("bl_verifier", verifier);
// "state" is optional: BazaarLink hands it back unchanged next to the code.
const state = b64url(crypto.getRandomValues(new Uint8Array(16)));
sessionStorage.setItem("bl_state", state);
location.href =
"https://bazaarlink.ai/auth?callback_url=" + encodeURIComponent(location.origin + "/callback") +
"&code_challenge=" + challenge + "&code_challenge_method=S256&state=" + state;

// 2) On /callback: check your own state, then exchange the one-time code for a user key
const params = new URLSearchParams(location.search);
if (params.get("state") !== sessionStorage.getItem("bl_state")) throw new Error("state mismatch");
const res = await fetch("https://bazaarlink.ai/api/v1/auth/keys", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  code: params.get("code"),
  code_verifier: sessionStorage.getItem("bl_verifier"),
  code_challenge_method: "S256",
}),
});
if (!res.ok) throw new Error((await res.json()).error?.message ?? "exchange failed"); // 403 invalid_code: ask the user to authorize again
const { key } = await res.json(); // use as: Authorization: Bearer <key>
보안: S256만 지원
code_challenge_method를 S256 (SHA-256 해시)으로 설정하세요. S256만 지원합니다. plain 이거나 code_challenge_method가 없는 요청은 HTTP 400으로 거부됩니다.
참고
사용자가 거부하면 BazaarLink는 callback_url로 리디렉션하지 않으며 앱에도 알리지 않습니다. "접근이 거부되었습니다" 페이지만 표시하므로 /auth가 오픈 리디렉션이 되지 않습니다. 앱 안에서 미승인 상태를 처리하세요(예: 타임아웃 또는 재시도 버튼).

/auth 페이지와 POST /api/v1/auth/keys는 https://bazaarlink.ai 에서 제공됩니다(api.bazaarlink.ai 아님). 발급받은 키는 https://bazaarlink.ai/api/v1 과 https://api.bazaarlink.ai/v1 두 기본 URL 모두에서 작동합니다.

사용자가 승인 시 지출 한도를 설정하면 그 한도는 월 한도이며 계정의 월간 주기에 맞춰 초기화됩니다.

오류

오류는 OpenAI 방식의 {"error":{"message","type","code"}} 형식입니다. POST /api/v1/auth/keys는 다음을 반환할 수 있습니다:

  • 400 invalid_code_challenge_method — code_challenge_method가 없거나 plain이거나 S256이 아닙니다.
  • 400 — JSON 본문이 올바르지 않거나 code / code_verifier가 없습니다.
  • 403 invalid_code — code를 알 수 없거나 만료되었거나 이미 사용되었거나, code_verifier가 틀렸거나, 계정이 더 이상 허용되지 않습니다. 호출자가 구분할 수 없도록 모두 의도적으로 같은 메시지를 반환합니다. code_verifier가 틀리면 code도 폐기되므로 사용자에게 다시 승인하도록 요청하세요.
  • 429 rate_limited — 한 IP에서 10분 동안 30회를 초과해 교환을 시도했습니다(Retry-After 헤더 참고).
  • 429 key_total_cap — 계정이 이미 최대 200개의 API 키를 보유하고 있습니다.
  • 405 — POST만 허용됩니다.

서버에서 인증 코드 생성하기 (관리 키)

개인 관리 키(Management Key)의 소유자는 브라우저 인증 페이지를 거치지 않고 POST /api/v1/auth/keys/code 로 직접 인증 코드를 생성할 수 있습니다(OpenRouter와 호환). 코드는 해당 관리 키의 소유자에게 묶이므로, 교환해서 받는 키는 그 소유자 계정의 표준 키이며 한도, 주기, 만료 시각, 이름은 여기에서 지정한 값이 적용됩니다. 자체 도구가 이미 관리 키를 보유하고 있고 앱에 제한된 키를 발급하려는 경우에 사용합니다. 교환 단계(POST /api/v1/auth/keys)는 위와 동일합니다.

  • callback_url (필수) 및 code_challenge + code_challenge_method=S256 (필수) — 인증 URL과 동일한 규칙입니다.
  • key_label (선택) — 최대 100자, 키 이름. 기본값: "App: <콜백 호스트>".
  • limit (선택) — USD 사용 한도, 0 이상. 0 또는 생략하면 한도 없음입니다.
  • usage_limit_type (선택) — daily, weekly, monthly: 한도가 초기화되는 주기. limit과 함께 사용할 때만 적용되며 기본값은 monthly입니다.
  • expires_at (선택) — 키 만료 시각: 미래의 ISO 8601 UTC 시각(초 단위 포함), 예: 2026-12-31T23:59:59Z. 분 단위까지만 있는 값은 거부됩니다. 생략하면 키는 폐기할 때까지 유효합니다.
  • workspace_id — 지원하지 않음 (400). spawn_agent 와 spawn_cloud 는 허용되지만 무시됩니다.
curl https://bazaarlink.ai/api/v1/auth/keys/code \
-H "Authorization: Bearer sk-bl-YOUR_MGMT_KEY" \
-H "Content-Type: application/json" \
-d '{
  "callback_url": "https://yourapp.com/callback",
  "code_challenge": "<CODE_CHALLENGE>",
  "code_challenge_method": "S256",
  "key_label": "My app",
  "limit": 10,
  "usage_limit_type": "monthly",
  "expires_at": "2026-12-31T23:59:59Z"
}'

# Response
# { "data": { "id": "<ONE_TIME_CODE>", "app_id": 123456789, "created_at": "2026-10-22T12:00:00.000Z" } }
# Then POST { "code": "<ONE_TIME_CODE>", "code_verifier": "<CODE_VERIFIER>", "code_challenge_method": "S256" } to /api/v1/auth/keys

응답: { "data": { "id", "app_id", "created_at" } }. id 는 교환에 사용하는 일회용 코드입니다(10분간 유효, 1회만 사용). BazaarLink에는 앱 등록 체계가 없으므로 app_id 는 콜백 주소에서 도출한 고정 정수입니다. 같은 주소는 항상 같은 app_id 를 받습니다.

  • 401 unauthorized — Bearer 키가 없거나 알 수 없는 키입니다.
  • 403 management_key_required — 개인 관리 키가 아닙니다(표준 키와 조직 관리 키는 거부됩니다).
  • 403 account_not_allowed — 키 소유자가 정지되었거나 삭제되었습니다.
  • 400 — invalid_callback_url, invalid_code_challenge_method, invalid_code_challenge, invalid_expires_at, workspace_not_supported 또는 잘못된 본문.
  • 429 rate_limited — 한 소유자가 시간당 30개 초과 또는 한 IP에서 시간당 120개 초과의 코드를 생성했습니다(Retry-After 참조).

발급되는 키

  • 표준 BazaarLink API 키(sk-bl-…)로, API 키 페이지에서 만든 키와 같은 종류입니다. 사용자가 폐기할 때까지 유효합니다.
  • 사용량은 사용자의 BazaarLink 잔액에서 과금됩니다. 사용자가 동의 페이지에서 지출 한도를 설정했다면 월 한도입니다.
  • 두 기본 URL 모두에서 작동합니다: https://bazaarlink.ai/api/v1 및 https://api.bazaarlink.ai/v1.
  • 계정의 키 한도 200개에 포함되며 사용자의 API 키 목록에 표시되어, 사용자가 언제든 비활성화하거나 삭제할 수 있습니다.
  • 정지되었거나 삭제된 계정은 앱을 승인할 수 없으며, 정지 전에 발급된 code는 교환 시 거부됩니다(403 invalid_code).

제공되는 것

  • 공개 클라이언트 애플리케이션을 위한 Authorization Code + PKCE 흐름
  • 사용자별 과금: 각 사용자가 자신의 잔액으로 자신의 사용량을 지불
  • 사용자마다 별도의 키가 발급되며, 사용자의 API 키 페이지에 표시되어 직접 폐기할 수 있음
Support
Hi! How can we help you?
Send a message and we'll get back to you soon.
OAuth PKCE | BazaarLink