Appearance
Quickstart
The Platform API accepts Supabase user access tokens. Its URL, the Supabase/auth URL, signed storage URLs, and the docs URL are distinct. Replace placeholder URLs and the public anon key with your deployment's settings. Runtime service keys are never browser credentials.
Sign in with an email code
This example runs in a browser with the Supabase client already used by the portal:
ts
import { createClient } from "@supabase/supabase-js";
const apiBaseUrl = "https://api.example.com";
const supabase = createClient("https://auth.example.com", "<PUBLIC_ANON_KEY>");
const email = "player@example.com";
const sent = await supabase.auth.signInWithOtp({ email });
if (sent.error) throw sent.error;
// Ask the user for the code delivered by email; do not hardcode a real code.
const verified = await supabase.auth.verifyOtp({ email, token: enteredCode, type: "email" });
if (verified.error) throw verified.error;
if (!verified.data.session) throw new Error("Sign-in did not return a session");The Supabase deployment must support the configured email-code/template flow. A magic-link deployment completes its redirect flow instead. The resulting session's access_token is the Platform Bearer token, rather than the public anon key. See Authentication for accepted tokens and errors, or Device authorization for CLI/device sign-in.
A reusable request helper
The remaining workflow examples use apiRequest, apiBaseUrl, and the supabase instance above. Retain any /api proxy prefix in apiBaseUrl and omit its trailing slash.
ts
async function apiRequest(path: string, init: RequestInit = {}) {
const { data, error } = await supabase.auth.getSession();
if (error) throw error;
if (!data.session) throw new Error("Sign in before calling the Platform API");
const headers = new Headers(init.headers);
headers.set("Authorization", `Bearer ${data.session.access_token}`);
if (init.body && !(init.body instanceof FormData) && !headers.has("Content-Type")) {
headers.set("Content-Type", "application/json");
}
const response = await fetch(`${apiBaseUrl}${path}`, { ...init, headers });
const body = response.status === 204 || response.status === 205
? undefined
: response.headers.get("content-type")?.includes("json")
? await response.json()
: await response.text();
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}; request ${response.headers.get("x-request-id")}`);
}
return body;
}
const profile = await apiRequest("/v1/me");
console.log(profile.id, profile.display_name);The first read is getMyProfile. A failed response is checked before success fields are consumed. For example, an expired token returns HTTP 401 { "error": "invalid_token" }; refresh using supabase.auth.refreshSession() and retry the read, or sign in again if refresh fails. Do not automatically replay every failed write; use its documented retry/idempotency policy.
Try curl
bash
curl https://api.example.com/v1/me \
-H 'Authorization: Bearer <ACCESS_TOKEN>'Use your session's access token in place of the placeholder. Check Conventions for CORS and deployment/proxy URLs, then follow Session lifecycle, Billing, or Uploads.