Send the user to approve
javascript
// 1. A verifier, its S256 challenge, and the redirect.
function base64url(bytes) {
return btoa(String.fromCharCode(...new Uint8Array(bytes)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = base64url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
sessionStorage.setItem("pkce_verifier", verifier);
const url = new URL("https://uplinkswitch.ai/auth");
url.searchParams.set("callback_url", "https://yourapp.com/callback");
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
url.searchParams.set("key_label", "Your App"); // optional
url.searchParams.set("limit", "10"); // optional, USD
url.searchParams.set("state", crypto.randomUUID()); // optional, echoed back
location.href = url.toString();Trade the code for a key
javascript
// 2. On https://yourapp.com/callback?code=...&state=...
const code = new URLSearchParams(location.search).get("code");
const res = await fetch("https://api.uplinkswitch.com/v1/auth/keys", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code,
code_verifier: sessionStorage.getItem("pkce_verifier"),
code_challenge_method: "S256",
}),
});
const { key } = await res.json(); // sk-uplink-live-…Use it
javascript
// 3. Call models with the user's key.
const answer = await fetch("https://api.uplinkswitch.com/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify({ model: "MODEL_ID", messages: [{ role: "user", content: "Hello" }] }),
});Rules
callback_urlhttps, or http on localhost, 127.0.0.1 or [::1] (any port). No user name, password or #fragment.code_challengebase64url SHA-256 of the verifier, 43 characters. code_challenge_method must be S256.key_labelThe key name the user sees, up to 64 characters. App: <host> unless set.limitThe key’s budget in USD. Once spent, the key answers 402 key_budget_exhausted.stateReturned on the callback unchanged; check it against the one you sent.codeSingle use, 10 minutes. The first exchange attempt spends it, matched or not.callback_url on exchangeOptional; when sent it must equal the approved one exactly.error=access_deniedOn the callback when the user denies.