# Quickstart

URL: https://docs.spacerun.co/docs/platform/guides/quickstart

> Sign in through Supabase and make your first authenticated Platform API request.

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](https://docs.spacerun.co/docs/platform/guides/authentication) for accepted tokens and errors, or [Device authorization](https://docs.spacerun.co/docs/platform/guides/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](https://docs.spacerun.co/docs/platform/reference/profile/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](https://docs.spacerun.co/docs/platform/guides/conventions) for CORS and deployment/proxy URLs, then follow [Session lifecycle](https://docs.spacerun.co/docs/platform/guides/session-lifecycle), [Billing](https://docs.spacerun.co/docs/platform/guides/billing-checkout), or [Uploads](https://docs.spacerun.co/docs/platform/guides/uploads-and-media).
