BazaarLinkBazaarLink
登入
文件API 參考SDK 參考Agent 應用AI Skills
章節導覽

OAuth PKCE

OAuth PKCE

OAuth 2.0 PKCE(Proof Key for Code Exchange)讓公開用戶端應用程式(瀏覽器 SPA、行動 App)能夠代表個別用戶呼叫 API,無需在用戶端儲存長效的伺服器密鑰。

何時使用 OAuth PKCE

  • 建構需要每位用戶計費的瀏覽器 SPA 或行動 App
  • 多租戶 SaaS — 每位終端用戶支付自己的 API 使用費用
  • 無法在用戶端儲存共用密鑰(公開用戶端)
  • 需要每位用戶各自獨立、可撤銷的金鑰(不會自動過期),而非單一共用 API 金鑰

你的用戶會看到什麼

請在你自己的登入畫面向用戶說明這些步驟:

  1. 在你的 App 內選擇「使用 BazaarLink 登入」(或你自己命名的按鈕)。
  2. 瀏覽器開啟 BazaarLink 授權頁。若尚未登入,會先登入,登入後自動回到這一頁。
  3. 頁面會顯示你的 App 網址,以及允許後會發生的事:為該 App 建立一把新的 API 金鑰、用量從 BazaarLink 餘額扣款、可隨時撤銷。用戶可替金鑰命名並設定每月額度上限(皆為選填)。
  4. 按「允許」後回到你的 App;按「拒絕」則不會建立金鑰、你的 App 也不會收到通知,用戶會看到「已拒絕」頁面。
  5. 之後若要撤銷,開啟「API 金鑰」頁面(https://bazaarlink.ai/keys),停用或刪除以你的 App 命名的那把金鑰。

運作方式

步驟 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

授權網址參數

  • callback_url(必填)— 用戶按允許後回到的網址,規則見下方。
  • code_challenge(必填)— code_verifier 的 SHA-256 再做 base64url 編碼(43 字元,不含 = 補位)。
  • code_challenge_method(必填)— 必須是 S256。
  • state(選填)— 你自己的不透明防 CSRF 值,1 到 256 個可見 ASCII 字元。BazaarLink 會原封不動地在授權碼之後以 &state=… 帶回。格式不正確的 state 會讓授權頁顯示「授權請求無效」。
  • key_label(選填)— 最多 100 個字元。會預填授權頁的金鑰名稱(用戶仍可修改)。過長或空白的標籤會被忽略。
  • 無回呼的工具(CLI/桌面程式)— 省略 callback_url。使用者允許後,頁面會顯示一次性授權碼(5 分鐘內有效、只能用一次),讓使用者輸入到你的工具;換 key 的方式與轉址取得的授權碼完全相同。頁面無法顯示是哪個應用在請求,請提醒使用者只在自己啟動的工具輸入。

不支援

  • workspace_id 與 required_workspace_id — 不支援。金鑰一律屬於用戶自己的帳號。
  • code_challenge_method=plain — 不支援(只收 S256)。

callback_url 規則

  • 必須是 https://。http:// 只接受 localhost、127.0.0.1 與 [::1](任何埠),此時授權頁會顯示本機開發警告。
  • 不可含片段(#),網址內不可含帳號密碼,最長 2048 字元。
  • 授權頁會顯示你的 callback 主機名稱,讓用戶看得出是哪個 App 在要求授權。
  • 用戶按允許後,瀏覽器會被導向你的 callback_url 並附上 code=…(若你有帶 state,也會附上 state=…;既有的查詢字串會保留)。授權碼 10 分鐘內有效且只能使用一次。

步驟 2 — 交換授權碼取得 API 金鑰 — 用戶授權後,BazaarLink 會將用戶重新導向至您的 callback_url 並附上一次性授權碼。授權碼 10 分鐘內有效且只能使用一次;code_verifier 錯誤會讓它立刻作廢。請 POST code、code_verifier 與 code_challenge_method(三者皆必填),即可取得該用戶的 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,也不會通知你的 App,只會顯示「已拒絕」頁面,這樣 /auth 不會變成開放轉址。請在你的 App 內處理「未授權」狀態(例如逾時或重試按鈕)。

/auth 頁面與 POST /api/v1/auth/keys 由 https://bazaarlink.ai 提供(不是 api.bazaarlink.ai)。取得的金鑰在 https://bazaarlink.ai/api/v1 與 https://api.bazaarlink.ai/v1 兩個 base 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_verifier 錯誤,或該帳號已不被允許。以上情況刻意回傳相同訊息,呼叫端無法區分。code_verifier 錯誤也會讓授權碼作廢:請用戶重新授權。
  • 429 rate_limited — 同一 IP 10 分鐘內交換超過 30 次(見 Retry-After 標頭)。
  • 429 key_total_cap — 該帳號已持有 200 把 API 金鑰的上限。
  • 405 — 只接受 POST。

從你的伺服器建立授權碼(Management 金鑰)

個人 Management 金鑰的擁有者可以不經過瀏覽器授權頁,直接用 POST /api/v1/auth/keys/code 建立授權碼(與 OpenRouter 相容)。授權碼會綁定該 Management 金鑰的擁有者,因此交換到的是該擁有者帳號下的標準金鑰,額度、週期、到期時間與名稱都由這裡決定。適用於你自己的工具已持有 Management 金鑰、想替某個 App 發一把受限金鑰的情況。交換步驟(POST /api/v1/auth/keys)與上面相同。

  • callback_url(必填)與 code_challenge + code_challenge_method=S256(必填)— 規則與授權網址相同。
  • key_label(選填)— 最多 100 字元,金鑰名稱。預設為「App: <你的 callback 主機名稱>」。
  • limit(選填)— 美元額度上限,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 分鐘內有效、只能用一次)。BazaarLink 沒有 App 註冊機制,app_id 是由你的 callback 位址推導出的固定整數:同一位址永遠得到相同的 app_id。

  • 401 unauthorized — 沒帶 Bearer 金鑰,或金鑰不存在。
  • 403 management_key_required — 該金鑰不是個人 Management 金鑰(標準金鑰與組織 Management 金鑰都會被拒絕)。
  • 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 餘額扣款。若用戶在授權頁設定額度上限,該上限為每月額度。
  • 兩個 base URL 都可使用:https://bazaarlink.ai/api/v1 與 https://api.bazaarlink.ai/v1。
  • 會計入帳號 200 把金鑰的上限,並出現在用戶的「API 金鑰」清單中,用戶可隨時停用或刪除。
  • 被停權或已刪除的帳號無法授權 App;停權前發出的授權碼在交換時會被拒絕(403 invalid_code)。

你會得到什麼

  • 授權碼 + PKCE 流程(公開用戶端應用程式)
  • 每用戶計費:各用戶用自己的餘額支付自己的用量
  • 每位用戶一把獨立金鑰,列在該用戶的「API 金鑰」頁面,用戶可隨時撤銷
客服
您好!有什麼可以協助?
請留下訊息,我們會盡快回覆。
OAuth PKCE | BazaarLink