Current User (/api/v1/me)
A project finds out who is running it by calling GET ${process.env.AUTH_URL}/api/v1/me with Authorization: Bearer ${process.env.AUTH_TOKEN}. The runtime's fetch sends it from the Go core, so there is no CORS issue and no backend to write: call it directly from index.tsx, a helper module or an action. For permissions, groups and quotas, call GET ${process.env.AUTH_URL}/api/v1/user/access.
Common mistakes
| Mistake | What happens | Do instead |
|---|---|---|
fetch("/api/v1/me") | In an app, looks up the local files out/api/v1/me then <project>/api/v1/me, not found; in an action it throws (invalid URL) | fetch(`${process.env.AUTH_URL}/api/v1/me`, ...) |
A local http.createServer proxy | http has no createServer in the runtime | Call fetch from the code that needs the user |
Hardcoding https://auth.fullstacked.cloud | Breaks in the other environment | process.env.AUTH_URL |
No Authorization header | 401 | Authorization: `Bearer ${process.env.AUTH_TOKEN}` |
GET /api/v1/me
- URL:
${process.env.AUTH_URL}/api/v1/me(alias/v1/me),https://auth.fullstacked.cloud/api/v1/mehere. - Auth:
Authorization: Bearer <fsc_* token>(any native token: session, personal access token, agent, enterprise session). - Returns identity only, never grants. Errors:
401(missing, unknown, revoked or expired token),403(account or authority suspended), body{ "error", "message", "statusCode", "details" }.
{
"status": "success",
"data": {
"id": "usr_4f1c9a2b7d3e8f6a5b0c1d2e",
"username": "alice",
"slug": "alice",
"name": "Alice Martin",
"email": "[email protected]",
"email_verified": true,
"avatar_url": null,
"type": "user",
"status": "active",
"plan": "base",
"plan_name": "Base",
"has_run_client_gui": true,
"restricted": false,
"created_at": "2026-09-20T14:32:00.000Z"
}
}| Field | Meaning |
|---|---|
id | usr_<24 hex> for platform users; <authorityId>:<externalUserId> for enterprise (BYOA) users. Use it as the stable user key. |
username, slug | Same value: the user's handle (Git namespace). Enterprise users: the external user id. |
name, email | May be null. email is always a verified address for platform users. |
email_verified | Platform users only. |
avatar_url | Always null: render initials or your own avatar. |
type | user, operator, agent (token issued to an AI agent) or service_account (Edge token). |
plan, plan_name | Platform users only: base or pro and its display name. |
authority_id | Enterprise users only: the issuing authority (ath_<slug>). |
has_run_client_gui | Whether this principal has run the FullStacked Cloud Client GUI. |
restricted | true while a platform user has not been invited to any asset yet. |
created_at | Account creation (enterprise users: token issue time), ISO 8601. |
Helper
// user.ts
export interface CurrentUser {
id: string;
username: string;
slug: string;
name: string | null;
email: string | null;
email_verified?: boolean;
avatar_url: string | null;
type: "user" | "operator" | "agent" | "service_account";
status: string;
plan?: "base" | "pro";
plan_name?: string;
authority_id?: string;
has_run_client_gui: boolean;
restricted: boolean;
created_at: string;
}
export async function getCurrentUser(): Promise<CurrentUser> {
const { AUTH_URL, AUTH_TOKEN } = process.env;
if (!AUTH_URL || !AUTH_TOKEN) {
throw new Error("AUTH_URL / AUTH_TOKEN missing: run from the FullStacked Cloud Client GUI or with -e AUTH_TOKEN=...");
}
const res = await fetch(`${AUTH_URL}/api/v1/me`, {
headers: { Authorization: `Bearer ${AUTH_TOKEN}`, Accept: "application/json" },
});
if (!res.ok) throw new Error(`GET /api/v1/me failed (${res.status}): ${await res.text()}`);
const body = (await res.json()) as { status: string; data: CurrentUser };
return body.data;
}// index.tsx
import React, { useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
import { getCurrentUser, type CurrentUser } from "./user";
function App() {
const [user, setUser] = useState<CurrentUser | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
getCurrentUser().then(setUser, (e) => setError(String(e?.message ?? e)));
}, []);
if (error) return <p>Not signed in: {error}</p>;
if (!user) return <p>Loading...</p>;
return <p>{user.name ?? user.username} ({user.email ?? "no email"})</p>;
}
const root = document.createElement("div");
document.body.append(root);
createRoot(root).render(<App />);In an action, the same helper returns the user who triggered the run (its AUTH_TOKEN is the caller's token).
GET /api/v1/user/access
GET ${process.env.AUTH_URL}/api/v1/user/access[?resource_type=projects|tunnels|data|mcp], same header. Returns { status: "success", data } where data holds:
| Key | Content |
|---|---|
principal | The resolved principal. |
user | id, username, slug?, name?, email?, email_verified?, type, status, plan?. |
billing | Plan, quotas and usage. |
groups | [{ id, name, inherited_roles }]. |
global_permissions | Platform-wide permission strings. |
resources.projects | [{ repository: <project UUID or "*">, permissions: { read, write, admin }, branches: { allow, deny }, tags: { allow, deny } }]. |
resources.tunnels | [{ host_pattern, can_register, can_connect }]. |
resources.data | { <instance UUID>: { engine, read, write, delete? } }. |
Every entry of resources.projects, resources.tunnels and resources.data also carries the quota flags sponsored, frozen, freeze_reason (count, storage or null) and transfer_exhausted. resource_type empties the other resource lists. Use it to gate UI (for example hide write actions unless resources.data[id].write && !resources.data[id].frozen: a frozen instance is read-only while write stays true); the platform enforces the same rules server side regardless.
Token hygiene
AUTH_TOKEN grants everything the user can do. Send it only to process.env.AUTH_URL, other FullStacked Cloud services, the tunnels hub (inside tunnel.register) or a backend of yours that validates it (see Runtime Sign-In & BYOA). Never log it, render it, write it to a file or commit it.
Endpoints
GET /api/v1/me
Profile of the bearer's principal: identity only, never grants or resources. BYOA principals add authority_id and omit email_verified, plan and plan_name.
- Service:
auth(https://auth.fullstacked.cloud) - Auth: Bearer fsc_* (AUTH_TOKEN)
- Aliases:
/v1/me - In project code:
${process.env.AUTH_URL}/api/v1/me - Pitfall: A relative fetch("/api/v1/me") reads the project's own static files: always prefix process.env.AUTH_URL.
Response:
{
"status": "success",
"data": {
"id": "usr_0123456789abcdef01234567",
"username": "alice",
"slug": "alice",
"name": "Alice",
"email": "[email protected]",
"email_verified": true,
"avatar_url": null,
"type": "user",
"status": "active",
"plan": "base",
"plan_name": "Base",
"has_run_client_gui": true,
"restricted": false,
"created_at": "2026-09-20T14:32:00.000Z"
}
}indexTs:
const res = await fetch(`${process.env.AUTH_URL}/api/v1/me`, {
headers: { Authorization: `Bearer ${process.env.AUTH_TOKEN}` },
});
const { data: user } = await res.json();curl:
curl -sS -H "Authorization: Bearer $AUTH_TOKEN" "https://auth.fullstacked.cloud/api/v1/me"GET /api/v1/user/access
Effective access of the caller: principal, user, billing, groups, global_permissions and resources (projects, tunnels, data, mcp). Narrow with ?resource_type=projects|tunnels|data|mcp.
- Service:
auth(https://auth.fullstacked.cloud) - Auth: Bearer fsc_* (AUTH_TOKEN)
- In project code:
${process.env.AUTH_URL}/api/v1/user/access
curl:
curl -sS -H "Authorization: Bearer $AUTH_TOKEN" "https://auth.fullstacked.cloud/api/v1/user/access?resource_type=projects"