# 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/me` here.
- 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" }`.

```json
{
  "status": "success",
  "data": {
    "id": "usr_4f1c9a2b7d3e8f6a5b0c1d2e",
    "username": "alice",
    "slug": "alice",
    "name": "Alice Martin",
    "email": "alice@example.com",
    "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

```ts
// 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;
}
```

```tsx
// 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](https://docs.fullstacked.cloud/auth-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:

```json
{
  "status": "success",
  "data": {
    "id": "usr_0123456789abcdef01234567",
    "username": "alice",
    "slug": "alice",
    "name": "Alice",
    "email": "alice@example.com",
    "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:

```ts
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:

```bash
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:

```bash
curl -sS -H "Authorization: Bearer $AUTH_TOKEN" "https://auth.fullstacked.cloud/api/v1/user/access?resource_type=projects"
```
