Contents
OAuth PKCE
OAuth PKCE
OAuth 2.0 PKCE (Proof Key for Code Exchange) lets public client apps (browser SPAs, mobile apps) make API calls on behalf of individual users without storing a long-lived server secret client-side.
When to Use OAuth PKCE
- Building a browser SPA or mobile app that needs per-user billing
- Multi-tenant SaaS — each end-user pays for their own API usage
- You cannot store a shared secret client-side (public client)
- You need a separate, revocable key per user (it does not expire by itself) instead of a single shared API key
What your users will see
Describe these steps on your own sign-in screen:
- In your app they choose "Sign in with BazaarLink" (or whatever you call the button).
- Their browser opens the BazaarLink authorization page. If they are not signed in yet, they sign in first and come back to this page automatically.
- The page shows your app's address and what happens if they allow: a new API key is created for the app, usage is billed to their BazaarLink balance, and they can revoke the key at any time. They may name the key and set a monthly spend limit (both optional).
- They click Allow and are sent back to your app. If they click Deny, no key is created, your app is not notified and they see a "denied" page.
- To revoke access later they open the API Keys page (https://bazaarlink.ai/keys) and disable or delete the key named after your app.
How it works
Step 1 — Initiate Authentication — Generate a random code_verifier (a long random string) and its SHA-256 hash as the code_challenge, then redirect the user to the BazaarLink auth page.
https://bazaarlink.ai/auth?callback_url=https://yourapp.com/callback&code_challenge=CODE_CHALLENGE&code_challenge_method=S256Authorization URL parameters
- callback_url (required for apps that can receive a redirect; omit it for headless tools, see the last item) — where the user returns after allowing. Rules below.
- code_challenge (required) — the base64url-encoded SHA-256 of your code_verifier (43 characters, no padding).
- code_challenge_method (required) — must be S256.
- state (optional) — your own opaque CSRF value, 1 to 256 printable ASCII characters. BazaarLink hands it back unchanged as &state=… right after the code. A malformed state makes the authorization page show "invalid request".
- key_label (optional) — up to 100 characters. Prefills the key name on the consent page (the user can still change it). Longer or blank labels are ignored.
- 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.
Not supported
- workspace_id and required_workspace_id — not supported. The key always belongs to the user's own account.
- code_challenge_method=plain — not supported (S256 only).
callback_url rules
- It must be https://. http:// is accepted only for localhost, 127.0.0.1 and [::1] (any port); the consent page then shows a local-development warning.
- No fragment (#), no username or password in the URL, at most 2048 characters.
- The consent page shows your callback host name so the user can see which app is asking.
- After the user allows, the browser is sent to your callback_url with code=… appended (and state=… when you sent one; an existing query string is kept). The code is valid for 10 minutes and works once.
Step 2 — Exchange Code for API Key — After the user authorizes the request, BazaarLink redirects to your callback_url with a one-time code. The code is valid for 10 minutes and can be used once; a wrong code_verifier burns it. POST code, code_verifier and code_challenge_method (all three required) to receive the user's API key.
Step 3 — Use the User-Scoped Key — The returned key is a normal BazaarLink API key that belongs to the user who authorized — usage is billed to that user's balance, not yours.
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 example (browser)
The /auth page and POST /api/v1/auth/keys are served from https://bazaarlink.ai (not api.bazaarlink.ai). The key you receive works on both https://bazaarlink.ai/api/v1 and https://api.bazaarlink.ai/v1.
If the user sets a spend limit when authorizing, it is a monthly limit and resets with the account's monthly period.
Errors
Errors use the OpenAI-style shape {"error":{"message","type","code"}}. POST /api/v1/auth/keys can return:
- 400 invalid_code_challenge_method — code_challenge_method is missing, plain, or anything other than S256.
- 400 — the JSON body is invalid, or code / code_verifier is missing.
- 403 invalid_code — the code is unknown, expired or already used, the code_verifier is wrong, or the account is no longer allowed. All of these return the same message on purpose, so a caller cannot tell them apart. A wrong code_verifier also burns the code: ask the user to authorize again.
- 429 rate_limited — more than 30 exchange attempts in 10 minutes from one IP (see the Retry-After header).
- 429 key_total_cap — the account already holds the maximum of 200 API keys.
- 405 — only POST is accepted.
Create the code from your server (Management key)
Instead of sending the user through the browser page, the owner of a personal Management key can create the authorization code directly with POST /api/v1/auth/keys/code (OpenRouter-compatible). The code is bound to the Management key's owner, so the key you exchange it for is a standard key on that owner's account, with the limit, period, expiry and label chosen here. Use it when your own tool already holds a Management key and wants to hand an app a limited key. The exchange (POST /api/v1/auth/keys) is the same as above.
- callback_url (required) and code_challenge + code_challenge_method=S256 (required) — same rules as the authorization URL.
- key_label (optional) — up to 100 characters; the key's name. Default: "App: <your callback host>".
- limit (optional) — spend limit in USD, 0 or more; 0 or missing means no limit.
- usage_limit_type (optional) — daily, weekly or monthly: how often the limit resets. Only used together with limit; the default is monthly.
- expires_at (optional) — when the key expires: ISO 8601 UTC time with seconds in the future, for example 2026-12-31T23:59:59Z. Minute-precision values are rejected. Missing means the key stays valid until it is revoked.
- workspace_id — not supported (400). spawn_agent and spawn_cloud are accepted and ignored.
Response: { "data": { "id", "app_id", "created_at" } }. id is the one-time code to exchange (valid 10 minutes, once). BazaarLink has no app registry, so app_id is a stable integer derived from your callback address: the same address always gets the same app_id.
- 401 unauthorized — no Bearer key, or the key is unknown.
- 403 management_key_required — the key is not a personal Management key (standard keys and organization Management keys are refused).
- 403 account_not_allowed — the key's owner is suspended or deleted.
- 400 — invalid_callback_url, invalid_code_challenge_method, invalid_code_challenge, invalid_expires_at, workspace_not_supported, or a malformed body.
- 429 rate_limited — more than 30 codes per hour for one owner or 120 per hour from one IP (see Retry-After).
The key you receive
- A standard BazaarLink API key (sk-bl-…), the same kind as a key created on the API Keys page. It does not expire by itself.
- Usage is billed to the user's BazaarLink balance. If the user set a spend limit on the consent page, it is a monthly limit.
- It works on both base URLs: https://bazaarlink.ai/api/v1 and https://api.bazaarlink.ai/v1.
- It counts toward the account's limit of 200 keys and shows up in the user's API Keys list, where they can disable or delete it at any time.
- Suspended or deleted accounts cannot authorize apps, and a code issued before a suspension is refused at exchange (403 invalid_code).
What you get
- Authorization Code + PKCE flow for public client applications
- Per-user billing: each user pays for their own usage from their own balance
- A separate key per user, listed on the user's API Keys page where they can revoke it