{"service":"fullstacked-cloud-docs","version":"1.22.3","environment":"live","generatedAt":"2026-10-24T09:38:39.686Z","formats":{"html":"https://docs.fullstacked.cloud/","markdown":"https://docs.fullstacked.cloud/?format=md","json":"https://docs.fullstacked.cloud/?format=json"},"services":{"console":"https://console.fullstacked.cloud","auth":"https://auth.fullstacked.cloud","git":"https://git.fullstacked.cloud","tunnels":"https://tunnels.fullstacked.cloud","tunnelsHost":"tunnels.fullstacked.cloud","mcp":"https://mcp.fullstacked.cloud","install":"https://install.fullstacked.cloud","docs":"https://docs.fullstacked.cloud"},"runtime":{"version":"1.0.0-alpha.1844","guide":"https://docs.fullstacked.cloud/runtime?format=md"},"dataEngineLimits":{"postgres":{"image":"postgres:18.6-alpine","port":5432,"memoryMB":256,"cpus":2,"pids":256},"redis":{"image":"redis:8.10.2-alpine","port":6379,"memoryMB":256,"cpus":1,"pids":128},"mongodb":{"image":"mongo:8.3.11-noble","port":27017,"memoryMB":512,"cpus":2,"pids":512},"s3":{"image":"rustfs/rustfs:1.0.0","port":9000,"memoryMB":768,"cpus":2,"pids":1024}},"projectRuntimeEnv":[{"name":"TUNNELS_HOST","injectedBy":"GET /api/v1/projects/:id/env","description":"Tunnels hub host for tunnel.register from fullstacked/tunnel."},{"name":"AUTH_HOST","injectedBy":"GET /api/v1/projects/:id/env","description":"Bare host of the Authentication service."},{"name":"AUTH_URL","injectedBy":"GET /api/v1/projects/:id/env","description":"Base URL of the Authentication service (GET ${AUTH_URL}/api/v1/me)."},{"name":"DOCS_URL","injectedBy":"GET /api/v1/projects/:id/env","description":"Base URL of this documentation (?format=md | ?format=json)."},{"name":"AUTH_TOKEN","injectedBy":"Client GUI / action runner at launch (in memory, never stored)","description":"The signed-in user's fsc_* bearer token: Authorization: Bearer ${AUTH_TOKEN}."}],"topics":[{"slug":"runtime","title":"FullStacked Runtime","url":"https://docs.fullstacked.cloud/runtime","markdownUrl":"https://docs.fullstacked.cloud/runtime?format=md","jsonUrl":"https://docs.fullstacked.cloud/runtime?format=json","markdown":"# FullStacked Runtime\n\nFullStacked Cloud projects run on the FullStacked runtime (`fullstacked` npm package, version 1.0.0-alpha.1844 on this platform). A project is one bundled ES module: UI code (DOM, React) and \"backend\" code (Node built-ins, database drivers, `process.env`) run in the same JavaScript context, bridged to a native Go core. There is no separate server process: never start an HTTP server, call your own functions directly. This page lists what the runtime actually provides so code written for it works the first time.\n\n## Execution model\n\n| Fact | Consequence for your code |\n| :--- | :--- |\n| One entrypoint per project: the first of `index.tsx`, `index.ts`, `index.jsx`, `index.js` found at the project root | Import everything else from it. When both `index.tsx` and `index.ts` exist, `index.tsx` is the entrypoint. |\n| The entrypoint is always compiled with the classic JSX transform (`React.createElement`), whatever `tsconfig.json` says; other files follow `tsconfig.json` `jsx` (classic when unset) | Start every file that contains JSX with `import React from \"react\";`. Without it the app fails with `ReferenceError: React is not defined`. |\n| esbuild bundles the entrypoint (ES module, TypeScript and JSX built in) into `out/`, with a generated `out/index.html` that only loads the bundle's script and stylesheet: it has no root element | There is no HTML file to write. Create your root element in code (see below). CSS you import is bundled and linked. |\n| Only `.png`, `.jpg`, `.jpeg`, `.gif` and `.svg` imports have a loader (they resolve to file URLs) | Other asset types (`.webp`, fonts, media), including those referenced by CSS `url()`, fail the build with \"No loader is configured\". `out/` is deleted and rebuilt on every bundle: keep static files at the project root, where `fetch(\"/<file>\")` finds them. |\n| The bundle targets the browser platform | npm packages resolve to their `browser` builds (for example `ws` resolves to its browser stub, which throws: use the global `WebSocket`). |\n| UI and logic share one context | Put data access in plain `async` functions (for example `db.ts` or `actions/<name>.ts`) and call them from components. No REST layer, no `express`, no `http.createServer`. |\n| `process.env` comes from the runtime (Client GUI launch, `.env` + `-e` flags, or the action runner) | Read `process.env.*` anywhere. Never hardcode platform hosts. See [Injected process.env](https://docs.fullstacked.cloud/project-runtime). |\n| The filesystem is rooted at the project directory | `fs` paths starting with `/` resolve inside the project, relative paths against `process.cwd()` (which starts at `/`, the project root). A path that would leave the project resolves to the project root instead of failing: `fs.rm(\"..\")` deletes the whole project. |\n\n```tsx\n// index.tsx: the single entrypoint\nimport React from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { App } from \"./app\";\n\nconst root = document.createElement(\"div\");\ndocument.body.append(root);\ncreateRoot(root).render(<App />);\n```\n\n### Running a project\n\n```bash\nnpx fullstacked              # bundle and run the project in the current directory (port 9000 by default)\nnpx fullstacked --port 3000  # another starting port (-p is --plugin, not the port)\nnpx fullstacked -b           # bundle only\nnpx fullstacked -f script.ts # bundle and run one script file, then exit\nnpx fullstacked -e AUTH_TOKEN=$AUTH_TOKEN -e KEY=value\n```\n\n`<project>/.env` is loaded first, then every `-e KEY=VALUE` (which wins); with `-f`, `.env` is read from the current directory. In the FullStacked Cloud Client GUI the environment is injected for you.\n\n## Module resolution\n\nThe bundler maps runtime modules before npm resolution: it strips a `node:` prefix and a `fullstacked/` prefix, then looks the name up in the runtime's module table. Consequences:\n\n- `node:fs`, `fs` and `fullstacked/fs` are the same module; `fullstacked/tunnel` and `tunnel` too.\n- npm packages whose name collides with a runtime module (`auth`, `bundle`, `config`, `fetch`, `git`, `packages`, `parentWindow`, `plugin`, `run`, `test`, `tunnel`, `websocket`, or a Node built-in listed on this page) cannot be imported: the runtime module is used instead.\n- A named import of something a runtime module does not export (`import { createServer } from \"http\"`, `import { exec } from \"child_process\"`, `import { copyFile } from \"fs\"`) fails the bundle with \"No matching export\".\n- `require()` of these modules works in CommonJS dependencies.\n\n## Global `fetch` and `WebSocket`\n\nAt startup the runtime replaces `globalThis.fetch` and `globalThis.WebSocket` with implementations executed by the Go core.\n\n| Call | Behavior |\n| :--- | :--- |\n| `fetch(\"https://...\")` / `fetch(\"http://...\")` | Sent by the Go core: no browser CORS, no preflight, no mixed-content blocking. Custom headers (including `Authorization`) and methods work. |\n| `fetch(\"http://<tunnel name>/...\")` | Routed through the registered tunnel (see [fullstacked/tunnel](#fullstackedtunnel)). |\n| `fetch(\"/path\")` or `fetch(\"fs://...\")` | In an app, served by the runtime's local file handler: the file at `out/<path>`, else `<project>/<path>`, else not found. It never reaches FullStacked Cloud (in `-f` scripts and actions it fails). |\n| `fetch(\"./path\")` | Throws (invalid URL). Use an absolute URL. |\n| `new WebSocket(\"wss://...\" \\| \"ws://...\", protocols?)` | Dialed by the Go core, tunnel names included. Standard browser signature only: no custom headers option. |\n\nPitfalls of the `fetch` implementation:\n\n- `Content-Type` is never inferred from the body: set it yourself (`\"Content-Type\": \"application/json\"` with `JSON.stringify(...)`). Avoid `FormData` bodies: no multipart boundary header is generated.\n- `response.clone()` and `response.formData()` return `null`. `json()`, `text()`, `arrayBuffer()`, `blob()`, `bytes()` and streaming `response.body` work.\n- A response header sent several times keeps only its last value (several `Set-Cookie` headers collapse to one).\n- `redirect: \"manual\"` or `\"error\"` disables following redirects; the default follows them. `signal` (AbortController) is honored.\n\n```ts\nconst res = await fetch(`${process.env.AUTH_URL}/api/v1/me`, {\n    headers: { Authorization: `Bearer ${process.env.AUTH_TOKEN}` },\n});\n```\n\n## Node built-in modules\n\n### Backed by the Go core\n\n| Module | What exists |\n| :--- | :--- |\n| `fs`, `fs/promises` | Sync, callback and promise forms of `readFile`, `writeFile`, `appendFile`, `readdir` (`withFileTypes`), `mkdir` (always recursive), `rm` / `unlink` (`rm` is recursive), `rename`, `stat`, `cp`; plus `existsSync`, `statSync`, `realpathSync`, `createReadStream`, `createWriteStream`, `constants`. Not available: `copyFile`, `access`, `lstat`, `chmod`, `chown`, `utimes`, `symlink`, `watch`. Copy a file or a directory with `cp` / `cpSync` (parent directories are created). |\n| `path` | `join`, `resolve`, `normalize`, `parse`, `extname`, `dirname`, `basename`, `isAbsolute`, `relative`, `sep`; `path.posix` and `path.win32` are the same object. No `format` or `delimiter`. |\n| `os` | `platform()`, `arch()`, `endianness()`, `release()`, `type()`, `hostname()`, `tmpdir()` (`/.tmp`, inside the project). Nothing else (no `cpus`, `homedir`, `networkInterfaces`, `EOL`). |\n| `net` | TCP client only: `Socket`, `connect`, `createConnection`, `isIP`, `isIPv4`, `isIPv6`. No `createServer`. Database drivers (`pg`, `ioredis`, `mongodb`) use it, including through tunnel names. |\n| `dns`, `dns/promises` | `lookup` (callback and promise); promises `resolve4`, `resolve6`, `resolveCname`, `resolveMx`, `resolveNs`, `resolveSrv`, `resolveTxt`. The generic `resolve()` is a stub: use the typed functions. |\n| `dgram` | `createSocket(\"udp4\" \\| \"udp6\")` and `Socket`. |\n| `crypto` | Node's API implemented by the Go core: `createHash`, `createHmac`, `hash`, `randomBytes`, `randomFill(Sync)`, `randomInt`, `randomUUID`, `getRandomValues`, `pbkdf2(Sync)`, `scrypt(Sync)`, `hkdf(Sync)`, `createCipheriv` / `createDecipheriv`, `createSign` / `createVerify`, `sign` / `verify`, `generateKeyPair(Sync)`, `generateKey(Sync)`, key objects, public/private encrypt and decrypt, Diffie-Hellman and ECDH, `timingSafeEqual`. `X509Certificate` and `Certificate` throw. Do not rely on `crypto.subtle` / `webcrypto`: the webview may not be a secure context. |\n| `zlib` | Deflate, inflate, gzip, gunzip, unzip, raw variants and Brotli: streams (`createGzip`, ...), callback and `*Sync` forms, `crc32`, `constants`. |\n| `worker_threads` | `new Worker(path)` (a module worker: point it at a separately bundled `.js` file, for example from `fullstacked/bundle` `bundleFile`) and `parentPort` inside the worker. `message` listeners receive a `MessageEvent`: read `event.data`. No `workerData`, `isMainThread` or `MessageChannel`. |\n| `process` (also the global), in apps | `env` (read from the runtime on each access: treat it as read-only, assignments are not kept), `argv`, `versions.fullstacked` (object with `major`, `minor`, `patch`, `build`, `branch`, `hash`), `cwd()`, `chdir()`, `nextTick`, `hrtime` (no `hrtime.bigint`), `exit()`, `stdout` / `stderr` (`write` goes to the console). `process.on(...)` and the other event methods are no-ops; `version` is `\"\"`; `browser` is `true`; `platform` / `arch` are not set (use `os`). In `-f` scripts and remote action runs the code runs under Node and `process` is Node's own. |\n\n`fs` details:\n\n- Errors carry no `code` (no `ENOENT`): the message reads like `error from bridge: [open /abs/path: no such file or directory]`. Test with `existsSync` instead of catching `ENOENT`.\n- `rm` and `unlink` of a missing path succeed silently.\n- The callback forms of `mkdir`, `rm`, `unlink`, `writeFile`, `appendFile`, `rename` and `cp` take no options argument: `fs.mkdir(p, { recursive: true }, cb)` never calls `cb`. Prefer `fs.promises` or the sync forms.\n\n### Pure JavaScript\n\n`buffer` (global `Buffer`), `events`, `stream` (`promises`, `web`, `consumers`), `util` (`util/types`), `url`, `querystring`, `string_decoder`, `timers` (`timers/promises`, global `setImmediate`), `assert`, `readline` (`readline/promises`), `perf_hooks`, `diagnostics_channel`, `constants`, `tty`, `console`.\n\n### Unavailable\n\n| Module | Behavior | Use instead |\n| :--- | :--- | :--- |\n| `http`, `https`, `http2` | Export only `Agent` (no `createServer`, `request`, `get`, `STATUS_CODES`): `http.createServer(...)` fails with `TypeError: ... is not a function`, and a named import of them fails the bundle | `fetch`; for UI to logic calls, import and call your functions. Libraries that need `http.request` or `http.Agent` do not work: use `fetch`-based clients. |\n| `tls` | `connect`, `createServer`, `createSecureContext` throw `Error(\"unavailable\")` | Keep `ssl: false` on drivers connected through a tunnel (the tunnel is already `wss://`). |\n| `child_process`, `async_hooks`, `module`, `vm`, `v8`, `sqlite` | Any property access on the default import throws the string `\"unavailable\"`; a named import fails the bundle | `fullstacked/git`, `fullstacked/packages`, `fullstacked/bundle`, `fullstacked/run`. |\n| Any other built-in (`cluster`, `inspector`, `repl`, ...) | Not provided | |\n\n## Runtime modules (`fullstacked/*`)\n\n### fullstacked/tunnel\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\n\ninterface Tunnel {\n    host: string;           // hub hostname only: no scheme, no port (use process.env.TUNNELS_HOST)\n    authorization?: string; // sent verbatim as the Authorization header of the WebSocket upgrade\n    port?: number;          // hub port, default 443 (80 when unsecure)\n    unsecure?: boolean;     // ws:// instead of wss://\n    path?: string;          // hub WebSocket path (FullStacked Cloud: leave unset)\n    name?: string;          // the name to register, default 8 random letters\n}\ntunnel.register(t: Tunnel): Promise<string>; // resolves with the tunnel name\n```\n\nThe returned name is a virtual host recognized by `net` (and every TCP driver), `fetch` and `WebSocket`; for `fullstacked/git`, pass it as the `tunnel` argument (`clone(url, dir, { tunnel })`, `pull(dir, tunnel)`, ...). The port you give a driver or put in a URL is ignored: the hub picks the target from the token.\n\nHow it works:\n\n- `register` stores the settings under the name and opens the tunnel's first WebSocket session to `wss://<host>:<port><path>` in the background. It never fails on bad credentials: authorization errors surface on the first connection.\n- Each registered tunnel keeps its own WebSocket sessions to the hub, authenticated once at their upgrade. Every connection to the name (socket, `fetch`, `WebSocket`) is a stream multiplexed on one of them: no handshake per connection. The runtime adds sessions under load (up to 8, 32 streams each) and closes idle ones.\n- Registering the same `name` again with identical settings reuses its sessions; different settings replace them (the old sessions close once their streams end). The registry is shared by every project context of the runtime. Register once and reuse the name.\n\nWith FullStacked Cloud, `authorization` is `` `${process.env.AUTH_TOKEN}:${process.env.<X>_TOKEN}` ``: see [Managed Data](https://docs.fullstacked.cloud/data) and [Edge & LAN Tunnels](https://docs.fullstacked.cloud/tunnels).\n\n### Other modules\n\n| Import | API |\n| :--- | :--- |\n| `fullstacked/auth` | `authenticate(url: string, opts?: { windowFeatures?: string }): Promise<any>`: opens `url` with `auth=1` added in the platform's auth surface and resolves with what the page posts back. Needs a window (apps only). See [Runtime Sign-In](https://docs.fullstacked.cloud/auth-byoa). |\n| `fullstacked/config` | `get(key)`, `set(key, value: string)`, `list()`, `delete(key)` (alias `del`): string key/value store of the runtime context, persisted in the project's `.git/config.json`. |\n| `fullstacked/git` | The directory comes first (`clone(url, dir, { tunnel?, proxy? })` excepted): `hasGit(dir)`, `init(dir, url)`, `head(dir)`, `status(dir)`, `add(dir, path)`, `log(dir)` (the last 10 commits), `commit(dir, message, { name, email })`, `pull(dir, tunnel?)`, `fetch(dir, tunnel?)`, `push(dir, tunnel?)`, `reset(dir, hard, ...files)`, `branch(dir)`, `tags(dir)`, `checkout(dir, ref, create?, tunnel?)`, `merge(dir, branch)`, `restore(dir, ...files)`, `setConfig(dir, key, value)`. Network calls return progress streams. Credentials come from a `git-auth` plugin. |\n| `fullstacked/packages` | `install(dir, saveDev: boolean, ...names)` (no names: the dependencies of `package.json`), `uninstall(dir, ...names)`, `audit(dir)`, `resolve(moduleName, startDir?)`, `addNodePath(path)`. Install and uninstall return a progress event emitter. |\n| `fullstacked/bundle` | `bundle(entryPoint?)` (a directory or file, default `.`), `bundleFile(entryPoint)`, `esbuildVersion()`. Resolve with `{ OutputFiles, Errors, Warnings }`. |\n| `fullstacked/run` | `run(directory \\| { directory?, env?, safe? }): Promise<number>`: creates a runtime context for the project in `directory` and resolves with its id. It does not open it: pass the id to `globalThis.fullstacked.open(id)` to display the project. |\n| `fullstacked/plugin` | `register(\"git-auth\", { callback: (host) => ({ username, password }) })`, `register(\"build\", { data: { filter, ext? }, callback })`, `register(\"deeplink\", { callback: (url) => void })`; resolves with `{ unregister() }`. `deeplink(url, ctx?)` triggers deeplink plugins. |\n| `fullstacked/parentWindow` | `disableAutoWindowSize()`: stop saving the window size when it is resized (the saved size is restored at startup, before your code runs). |\n| `fullstacked` | `execute(command, opts?)` runs a `fullstacked ...` CLI command line in-process; `validateCommand(command)` returns its tokens from `fullstacked` on, or `null`. |\n\n## Checklist for agents\n\n1. One entrypoint (`index.tsx` for apps with UI); create the root element in code.\n2. `import React from \"react\";` in every file that contains JSX, the entrypoint included.\n3. Call your own functions, never a local HTTP server; call FullStacked Cloud with absolute URLs built from `process.env`.\n4. Set `Content-Type` on every `fetch` with a body.\n5. Use `fs.promises` or the sync forms listed above; copy with `cp`, not `copyFile`; check paths with `existsSync`.\n6. Reach managed data and LAN services only through `fullstacked/tunnel` with `ssl: false` on the driver.\n7. No `child_process`, `http.request`, `tls`, or npm packages that depend on them.\n"},{"slug":"me","title":"Current User (/api/v1/me)","url":"https://docs.fullstacked.cloud/me","markdownUrl":"https://docs.fullstacked.cloud/me?format=md","jsonUrl":"https://docs.fullstacked.cloud/me?format=json","markdown":"# Current User (/api/v1/me)\n\nA 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`.\n\n## Common mistakes\n\n| Mistake | What happens | Do instead |\n| :--- | :--- | :--- |\n| `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`, ...) `` |\n| A local `http.createServer` proxy | `http` has no `createServer` in the runtime | Call `fetch` from the code that needs the user |\n| Hardcoding `https://auth.fullstacked.cloud` | Breaks in the other environment | `process.env.AUTH_URL` |\n| No `Authorization` header | `401` | `` Authorization: `Bearer ${process.env.AUTH_TOKEN}` `` |\n\n## GET /api/v1/me\n\n- URL: `${process.env.AUTH_URL}/api/v1/me` (alias `/v1/me`), `https://auth.fullstacked.cloud/api/v1/me` here.\n- Auth: `Authorization: Bearer <fsc_* token>` (any native token: session, personal access token, agent, enterprise session).\n- Returns identity only, never grants. Errors: `401` (missing, unknown, revoked or expired token), `403` (account or authority suspended), body `{ \"error\", \"message\", \"statusCode\", \"details\" }`.\n\n```json\n{\n  \"status\": \"success\",\n  \"data\": {\n    \"id\": \"usr_4f1c9a2b7d3e8f6a5b0c1d2e\",\n    \"username\": \"alice\",\n    \"slug\": \"alice\",\n    \"name\": \"Alice Martin\",\n    \"email\": \"alice@example.com\",\n    \"email_verified\": true,\n    \"avatar_url\": null,\n    \"type\": \"user\",\n    \"status\": \"active\",\n    \"plan\": \"base\",\n    \"plan_name\": \"Base\",\n    \"has_run_client_gui\": true,\n    \"restricted\": false,\n    \"created_at\": \"2026-09-20T14:32:00.000Z\"\n  }\n}\n```\n\n| Field | Meaning |\n| :--- | :--- |\n| `id` | `usr_<24 hex>` for platform users; `<authorityId>:<externalUserId>` for enterprise (BYOA) users. Use it as the stable user key. |\n| `username`, `slug` | Same value: the user's handle (Git namespace). Enterprise users: the external user id. |\n| `name`, `email` | May be `null`. `email` is always a verified address for platform users. |\n| `email_verified` | Platform users only. |\n| `avatar_url` | Always `null`: render initials or your own avatar. |\n| `type` | `user`, `operator`, `agent` (token issued to an AI agent) or `service_account` (Edge token). |\n| `plan`, `plan_name` | Platform users only: `base` or `pro` and its display name. |\n| `authority_id` | Enterprise users only: the issuing authority (`ath_<slug>`). |\n| `has_run_client_gui` | Whether this principal has run the FullStacked Cloud Client GUI. |\n| `restricted` | `true` while a platform user has not been invited to any asset yet. |\n| `created_at` | Account creation (enterprise users: token issue time), ISO 8601. |\n\n## Helper\n\n```ts\n// user.ts\nexport interface CurrentUser {\n    id: string;\n    username: string;\n    slug: string;\n    name: string | null;\n    email: string | null;\n    email_verified?: boolean;\n    avatar_url: string | null;\n    type: \"user\" | \"operator\" | \"agent\" | \"service_account\";\n    status: string;\n    plan?: \"base\" | \"pro\";\n    plan_name?: string;\n    authority_id?: string;\n    has_run_client_gui: boolean;\n    restricted: boolean;\n    created_at: string;\n}\n\nexport async function getCurrentUser(): Promise<CurrentUser> {\n    const { AUTH_URL, AUTH_TOKEN } = process.env;\n    if (!AUTH_URL || !AUTH_TOKEN) {\n        throw new Error(\"AUTH_URL / AUTH_TOKEN missing: run from the FullStacked Cloud Client GUI or with -e AUTH_TOKEN=...\");\n    }\n    const res = await fetch(`${AUTH_URL}/api/v1/me`, {\n        headers: { Authorization: `Bearer ${AUTH_TOKEN}`, Accept: \"application/json\" },\n    });\n    if (!res.ok) throw new Error(`GET /api/v1/me failed (${res.status}): ${await res.text()}`);\n    const body = (await res.json()) as { status: string; data: CurrentUser };\n    return body.data;\n}\n```\n\n```tsx\n// index.tsx\nimport React, { useEffect, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { getCurrentUser, type CurrentUser } from \"./user\";\n\nfunction App() {\n    const [user, setUser] = useState<CurrentUser | null>(null);\n    const [error, setError] = useState<string | null>(null);\n    useEffect(() => {\n        getCurrentUser().then(setUser, (e) => setError(String(e?.message ?? e)));\n    }, []);\n    if (error) return <p>Not signed in: {error}</p>;\n    if (!user) return <p>Loading...</p>;\n    return <p>{user.name ?? user.username} ({user.email ?? \"no email\"})</p>;\n}\n\nconst root = document.createElement(\"div\");\ndocument.body.append(root);\ncreateRoot(root).render(<App />);\n```\n\nIn an action, the same helper returns the user who triggered the run (its `AUTH_TOKEN` is the caller's token).\n\n## GET /api/v1/user/access\n\n`GET ${process.env.AUTH_URL}/api/v1/user/access[?resource_type=projects|tunnels|data|mcp]`, same header. Returns `{ status: \"success\", data }` where `data` holds:\n\n| Key | Content |\n| :--- | :--- |\n| `principal` | The resolved principal. |\n| `user` | `id`, `username`, `slug?`, `name?`, `email?`, `email_verified?`, `type`, `status`, `plan?`. |\n| `billing` | Plan, quotas and usage. |\n| `groups` | `[{ id, name, inherited_roles }]`. |\n| `global_permissions` | Platform-wide permission strings. |\n| `resources.projects` | `[{ repository: <project UUID or \"*\">, permissions: { read, write, admin }, branches: { allow, deny }, tags: { allow, deny } }]`. |\n| `resources.tunnels` | `[{ host_pattern, can_register, can_connect }]`. |\n| `resources.data` | `{ <instance UUID>: { engine, read, write, delete? } }`. |\n\nEvery 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.\n\n## Token hygiene\n\n`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.\n\n## Endpoints\n\n### GET /api/v1/me\n\nProfile of the bearer's principal: identity only, never grants or resources. BYOA principals add authority_id and omit email_verified, plan and plan_name.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (AUTH_TOKEN)\n- **Aliases**: `/v1/me`\n- **In project code**: `${process.env.AUTH_URL}/api/v1/me`\n- **Pitfall**: A relative fetch(\"/api/v1/me\") reads the project's own static files: always prefix process.env.AUTH_URL.\n\nResponse:\n\n```json\n{\n  \"status\": \"success\",\n  \"data\": {\n    \"id\": \"usr_0123456789abcdef01234567\",\n    \"username\": \"alice\",\n    \"slug\": \"alice\",\n    \"name\": \"Alice\",\n    \"email\": \"alice@example.com\",\n    \"email_verified\": true,\n    \"avatar_url\": null,\n    \"type\": \"user\",\n    \"status\": \"active\",\n    \"plan\": \"base\",\n    \"plan_name\": \"Base\",\n    \"has_run_client_gui\": true,\n    \"restricted\": false,\n    \"created_at\": \"2026-09-20T14:32:00.000Z\"\n  }\n}\n```\n\nindexTs:\n\n```ts\nconst res = await fetch(`${process.env.AUTH_URL}/api/v1/me`, {\n    headers: { Authorization: `Bearer ${process.env.AUTH_TOKEN}` },\n});\nconst { data: user } = await res.json();\n```\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://auth.fullstacked.cloud/api/v1/me\"\n```\n\n### GET /api/v1/user/access\n\nEffective access of the caller: principal, user, billing, groups, global_permissions and resources (projects, tunnels, data, mcp). Narrow with ?resource_type=projects|tunnels|data|mcp.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (AUTH_TOKEN)\n- **In project code**: `${process.env.AUTH_URL}/api/v1/user/access`\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://auth.fullstacked.cloud/api/v1/user/access?resource_type=projects\"\n```\n"},{"slug":"project-runtime","title":"Injected process.env","url":"https://docs.fullstacked.cloud/project-runtime","markdownUrl":"https://docs.fullstacked.cloud/project-runtime?format=md","jsonUrl":"https://docs.fullstacked.cloud/project-runtime?format=json","markdown":"# Injected process.env\n\nEvery FullStacked Cloud project reads its configuration from `process.env`: the platform hosts, the credentials of linked managed data and Edge tunnels, the project's custom variables, and `AUTH_TOKEN`, the current user's own token. The environment comes from `GET https://git.fullstacked.cloud/api/v1/projects/:id/env`, resolved for the caller, plus `AUTH_TOKEN` added at launch. Code never hardcodes a host, a database user or a token.\n\n## Where the environment comes from\n\n| Where the code runs | `process.env` contains |\n| :--- | :--- |\n| FullStacked Cloud Client GUI | The resolved environment of the signed-in user plus `AUTH_TOKEN` = their session token. If the environment cannot be fetched, the project still runs with `AUTH_TOKEN` alone. |\n| Local CLI (`npx fullstacked`) | `<project>/.env` merged with `-e KEY=VALUE` flags (flags win). Write `.env` from `GET .../env?format=dotenv` and pass the token: `npx fullstacked -e AUTH_TOKEN=$AUTH_TOKEN` with a personal access token (`fsc_pat_...`, https://console.fullstacked.cloud Settings, Access tokens). Keep `.env` out of Git. |\n| Project action run (`actions/<name>.ts`) | The environment resolved for the caller of the run, `AUTH_TOKEN` = the caller's token, plus run variables (see [Project Actions](https://docs.fullstacked.cloud/actions)). |\n\n## Variables\n\n| Variable | Present | Value |\n| :--- | :--- | :--- |\n| `AUTH_URL` | Resolved environment | Authentication service base URL, `https://auth.fullstacked.cloud` here. Use for `/api/v1/me`, `/api/v1/user/access`, token exchange. |\n| `AUTH_HOST` | Resolved environment | Its host, `auth.fullstacked.cloud`. |\n| `TUNNELS_HOST` | Resolved environment | Tunnels hub host for `tunnel.register({ host })`, `tunnels.fullstacked.cloud`. |\n| `DOCS_URL` | Resolved environment | This documentation, `https://docs.fullstacked.cloud` (`?format=md` for Markdown). |\n| `AUTH_TOKEN` | At launch only | The current user's FullStacked Cloud token: `fsc_ses_...` (Client GUI), `fsc_ath_<slug>_...` (enterprise sign-in), `fsc_pat_...` (personal access token) or the action caller's token. Never part of `GET .../env`, never stored in the project. |\n| `PG_DATA_TOKEN`, `REDIS_DATA_TOKEN`, `MONGO_DATA_TOKEN`, `S3_DATA_TOKEN` | Linked data instance | `tun_...` routing token of the instance's tunnel at the caller's access level. Use as `` `${AUTH_TOKEN}:${PG_DATA_TOKEN}` ``. |\n| `DATA_USER`, `DATA_PASSWORD` | Linked PostgreSQL, Redis, MongoDB | `app_rw` or `app_ro` and its password, matching the caller's access (read-write or read-only). |\n| `DATA_DB` | Linked PostgreSQL, MongoDB | Database name, e.g. `data_pg_1`, `data_mongo_1`. Not set for Redis. |\n| `DATA_KEY_ID`, `DATA_KEY_SECRET`, `DATA_BUCKET` | Linked S3 | Access key, secret key and bucket name for the caller's access level. |\n| `<PREFIX>_TUNNEL_TOKEN` | Linked Edge tunnel | `tun_...` token of the tunnel. Use as `` `${AUTH_TOKEN}:${OFFICE_API_TUNNEL_TOKEN}` ``. |\n| Custom variables | Set on the project | Any `NAME=value` stored with `PUT .../env` (Console: Project, Environment). |\n\nThe four platform hosts are part of every resolved environment, for the environment the project belongs to (live here): the same code runs against test and live. They are missing when no environment was resolved: a Client GUI launch whose environment request failed (only `AUTH_TOKEN` is set), or a local CLI run without a `.env` written from `GET .../env`. Check them like any other variable.\n\n## Naming rules\n\n- The **oldest linked data instance** gets unprefixed names (`DATA_USER`, `PG_DATA_TOKEN`, ...).\n- Every **other linked data instance** gets the same names prefixed with its display name: upper-cased, non-alphanumeric runs replaced by `_`, leading and trailing `_` trimmed, `_` prepended before a leading digit (instance `analytics-db` gives `ANALYTICS_DB_DATA_USER`, `ANALYTICS_DB_PG_DATA_TOKEN`, ...).\n- **Edge tunnels** are always prefixed with the tunnel name, normalized the same way (`office-api` gives `OFFICE_API_TUNNEL_TOKEN`). Tunnel names are unique per Edge only: two linked tunnels with the same name produce the same variable, and the second is dropped into `conflicts`. Give one of them a prefix mapping.\n- A **prefix mapping** (`PUT .../env` with `mappings`) overrides the prefix of one resource; an empty prefix is not allowed (prefix: `A-Z` then up to 63 of `A-Z`, `0-9`, `_`).\n- Names never overwrite each other. Platform hosts come first, then linked data (oldest link first), then tunnels, then custom variables; a later entry with an existing name is dropped and reported in `conflicts`.\n- Resources the caller cannot use are omitted silently, so a variable can be absent for one user and present for another: check before use.\n\n```ts\nfunction requireEnv(name: string): string {\n    const value = process.env[name];\n    if (!value) throw new Error(`${name} is not set: link the resource to the project or run with AUTH_TOKEN`);\n    return value;\n}\n```\n\n## GET /api/v1/projects/:id/env\n\n`GET https://git.fullstacked.cloud/api/v1/projects/<projectId>/env?format=json|dotenv` with `Authorization: Bearer <fsc_* token>`. Any project tier may call it; the values are resolved with the caller's own access. MCP: `projects_get_env`.\n\n```json\n{\n  \"projectId\": \"7f3b89a2-4c1e-48a0-9d21-f8e910243abc\",\n  \"project\": \"alice/task-flow\",\n  \"accessTier\": \"FULL_ACCESS\",\n  \"env\": {\n    \"TUNNELS_HOST\": \"tunnels.fullstacked.cloud\",\n    \"AUTH_HOST\": \"auth.fullstacked.cloud\",\n    \"AUTH_URL\": \"https://auth.fullstacked.cloud\",\n    \"DOCS_URL\": \"https://docs.fullstacked.cloud\",\n    \"PG_DATA_TOKEN\": \"tun_4b8c9d...\",\n    \"DATA_USER\": \"app_rw\",\n    \"DATA_PASSWORD\": \"...\",\n    \"DATA_DB\": \"data_pg_1\",\n    \"OFFICE_API_TUNNEL_TOKEN\": \"tun_3f9a2b...\",\n    \"APP_TITLE\": \"Task Flow\"\n  },\n  \"meta\": { \"PG_DATA_TOKEN\": { \"source\": \"linked_data\", \"resourceId\": \"9b2f6c1e-...\", \"name\": \"tasks-db\", \"engine\": \"postgres\", \"access\": \"READ_WRITE\", \"secret\": true } },\n  \"resources\": [{ \"source\": \"linked_data\", \"resourceId\": \"9b2f6c1e-...\", \"name\": \"tasks-db\", \"prefix\": \"\", \"mapped\": false }],\n  \"variables\": { \"APP_TITLE\": \"Task Flow\" },\n  \"mappings\": {},\n  \"conflicts\": []\n}\n```\n\n`meta` has one entry per variable (`source`: `platform`, `linked_data`, `linked_tunnel` or `custom`; `secret` is false only for hosts, `DATA_USER`, `DATA_DB`, `DATA_BUCKET` and custom variables). `format=dotenv` returns the same `env` as `KEY=value` lines.\n\n## PUT /api/v1/projects/:id/env\n\nRequires `write` on the project. MCP: `projects_set_env`.\n\n```json\n{ \"variables\": { \"APP_TITLE\": \"Task Flow\", \"OLD_FLAG\": null }, \"mappings\": { \"<resourceId>\": { \"prefix\": \"CACHE\" } } }\n```\n\n- `null` removes a variable or mapping.\n- Names: `^[A-Z_][A-Z0-9_]{0,127}$`; values: single-line strings up to 4096 characters; at most 200 variables and 100 mappings.\n- Setting `AUTH_TOKEN`, `AUTH_URL`, `AUTH_HOST`, `TUNNELS_HOST` or `DOCS_URL` is refused (`400 RESERVED_VARIABLE`).\n- Custom variables are visible to every project tier, including release-only users: store configuration there, never secrets.\n\n## Endpoints\n\n### GET /api/v1/projects/:id/env\n\nThe project's resolved runtime environment: DATA_* wiring and tokens of linked instances, platform URLs (AUTH_URL, AUTH_HOST, TUNNELS_HOST, DOCS_URL) and custom variables. ?format=json|dotenv; never contains AUTH_TOKEN.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://git.fullstacked.cloud/api/v1/projects/<projectId>/env?format=dotenv\"\n```\n\n### PUT /api/v1/projects/:id/env\n\nMerges { variables?, mappings? } into the project's environment config (null removes a key). Needs write.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"variables\": {\n    \"API_BASE\": \"https://example.com\"\n  }\n}\n```\n"},{"slug":"data","title":"Managed Data","url":"https://docs.fullstacked.cloud/data","markdownUrl":"https://docs.fullstacked.cloud/data?format=md","jsonUrl":"https://docs.fullstacked.cloud/data?format=json","markdown":"# Managed Data\n\nFullStacked Cloud runs PostgreSQL, Redis, MongoDB and S3-compatible object storage (RustFS) instances, each in its own container. A project reaches an instance linked to it through `fullstacked/tunnel`: register the tunnels hub with the user's `AUTH_TOKEN` and the instance's data token, then point the standard npm driver at the returned name. The runtime keeps a WebSocket session to the hub for that tunnel and carries every driver connection as a stream on it. Credentials come from `process.env` and match the current user's access (read-write or read-only), so the same code serves every user.\n\n## Setup\n\n1. Provision an instance and link it to the project: Console (https://console.fullstacked.cloud, Data, then Project, Linked data) or MCP (`data_provision`, then `data_link_project`).\n2. The project environment then carries the data token and credentials (see [Injected process.env](https://docs.fullstacked.cloud/project-runtime)):\n\n| Engine | Variables |\n| :--- | :--- |\n| PostgreSQL | `PG_DATA_TOKEN`, `DATA_USER`, `DATA_PASSWORD`, `DATA_DB` |\n| Redis | `REDIS_DATA_TOKEN`, `DATA_USER`, `DATA_PASSWORD` |\n| MongoDB | `MONGO_DATA_TOKEN`, `DATA_USER`, `DATA_PASSWORD`, `DATA_DB` |\n| S3 | `S3_DATA_TOKEN`, `DATA_KEY_ID`, `DATA_KEY_SECRET`, `DATA_BUCKET` |\n\nThe oldest linked instance uses these names; other instances add a prefix from their name (`ANALYTICS_DB_DATA_USER`, `ANALYTICS_DB_PG_DATA_TOKEN`, ...).\n\n3. Connect with the code below. Always `ssl: false` (the tunnel is already `wss://`); the port given to the driver is ignored (the token selects the instance), keep the engine's default for readability.\n\n## PostgreSQL (`pg`)\n\n```ts\n// db.ts\nimport tunnel from \"fullstacked/tunnel\";\nimport pg from \"pg\";\n\nlet pool: pg.Pool | null = null;\n\nexport async function getPool(): Promise<pg.Pool> {\n    if (pool) return pool;\n    const host = await tunnel.register({\n        host: process.env.TUNNELS_HOST!,\n        authorization: `${process.env.AUTH_TOKEN}:${process.env.PG_DATA_TOKEN}`,\n    });\n    pool = new pg.Pool({\n        host,\n        port: 5432,\n        user: process.env.DATA_USER,\n        password: process.env.DATA_PASSWORD,\n        database: process.env.DATA_DB,\n        ssl: false,\n        max: 10,\n    });\n    pool.on(\"error\", (err) => console.error(\"pg pool error\", err));\n    return pool;\n}\n\nconst { rows } = await (await getPool()).query(\"SELECT now()\");\n```\n\n## Redis (`ioredis`)\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\nimport Redis from \"ioredis\";\n\nconst host = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.REDIS_DATA_TOKEN}`,\n});\nconst redis = new Redis({\n    host,\n    port: 6379,\n    username: process.env.DATA_USER,\n    password: process.env.DATA_PASSWORD,\n});\nredis.on(\"error\", (err) => console.error(\"redis error\", err));\nawait redis.set(\"greeting\", \"hello\");\n```\n\n## MongoDB (`mongodb`)\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\nimport { MongoClient } from \"mongodb\";\n\nconst host = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.MONGO_DATA_TOKEN}`,\n});\nconst { DATA_USER, DATA_PASSWORD, DATA_DB } = process.env;\nconst client = new MongoClient(\n    `mongodb://${DATA_USER}:${DATA_PASSWORD}@${host}:27017/${DATA_DB}?authSource=${DATA_DB}&directConnection=true`\n);\nawait client.connect();\nconst db = client.db(DATA_DB);\n```\n\nKeep `directConnection=true` (a single server reached through the tunnel name) and `authSource` set to the database: the app users live there.\n\n## S3 (`@aws-sdk/client-s3`)\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\nimport { S3Client, PutObjectCommand, ListObjectsV2Command } from \"@aws-sdk/client-s3\";\n\nconst host = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.S3_DATA_TOKEN}`,\n});\nconst s3 = new S3Client({\n    endpoint: `http://${host}`,\n    region: \"us-east-1\",\n    forcePathStyle: true,\n    credentials: {\n        accessKeyId: process.env.DATA_KEY_ID!,\n        secretAccessKey: process.env.DATA_KEY_SECRET!,\n    },\n});\nconst Bucket = process.env.DATA_BUCKET!;\nawait s3.send(new PutObjectCommand({ Bucket, Key: \"hello.txt\", Body: \"hi\", ContentType: \"text/plain\" }));\nconst list = await s3.send(new ListObjectsV2Command({ Bucket }));\n```\n\nUse `http://` (not `https://`) and `forcePathStyle: true`. Each instance has one bucket, `DATA_BUCKET`.\n\n## Read-only users\n\nA user with read access only receives the `app_ro` credentials and the read-only token: PostgreSQL rejects writes (`SQLSTATE 25006` read-only transaction, or `42501` insufficient privilege), Redis allows only read and subscribe commands, MongoDB and S3 refuse writes. A frozen instance (over quota) also resolves to read-only credentials for everyone. Design the UI to handle a failed write, and gate write controls with `GET /api/v1/user/access`: `resources.data[<instanceId>].write && !resources.data[<instanceId>].frozen` (`write` stays true on a frozen instance).\n\n## Container limits\n\n| Engine | Image | Port | Memory | CPUs | PIDs |\n| :--- | :--- | :--- | :--- | :--- | :--- |\n| PostgreSQL | `postgres:18.6-alpine` | `5432` | 256 MB | 2 | 256 |\n| Redis | `redis:8.10.2-alpine` | `6379` | 256 MB | 1 | 128 |\n| MongoDB | `mongo:8.3.11-noble` | `27017` | 512 MB | 2 | 512 |\n| S3 (RustFS) | `rustfs/rustfs:1.0.0` | `9000` | 768 MB | 2 | 1024 |\n\nEngine settings:\n\n- **PostgreSQL**: `shared_buffers=32MB`, `maintenance_work_mem=16MB`, `max_connections=110`, and each backend's private memory capped at 64 MB. A query that needs more fails alone with `out of memory` (SQLSTATE `53200`); if backends together exceed the container memory, one is killed and PostgreSQL resets every connection of the instance. A query's temporary files stop at 1 GB (`temp_file_limit`, SQLSTATE `53400` for that query alone).\n- **Redis**: AOF persistence (`appendonly yes`) and `maxmemory 192mb` with `noeviction`: past 192 MB of data, writes fail with an `OOM` error while reads keep working; nothing is evicted. Keep keys bounded (TTLs).\n- **MongoDB**: default settings inside the container memory limit (diagnostic data capped at 20 MB).\n- **S3 (RustFS)**: at most 8 concurrent disk reads and 4 concurrent uploads per instance; further uploads queue for up to 60 s.\n\nContainers have no swap and restart automatically after being killed.\n\n## Fair use and driver practices\n\n1. **Page large results**: `LIMIT` with keyset pagination or cursors in PostgreSQL so no single query needs more than 64 MB of backend memory; `SCAN` instead of `KEYS *` in Redis; batch MongoDB reads with `limit` and cursors.\n2. **Bound concurrency**: keep `pg.Pool` small (`max` 10 or less per app) and avoid many heavy queries at once (`Promise.all` over dozens of large result sets) that together exceed the instance memory.\n3. **Bound uploads**: run at most 4 S3 uploads at a time per instance and keep the AWS SDK's default retries.\n4. **Handle errors**: attach `error` listeners to pools and clients so a dropped connection (revoked access, instance restart) does not surface as an unhandled error; reconnect on demand.\n5. **Register once**: call `tunnel.register` once per instance and reuse the name and the client.\n6. **Plan quotas**: on the Base plan an owner keeps 5 data instances, 1 GiB of storage across their projects and data instances together, and 5 GiB per UTC calendar month of data tunnel transfer (both directions). Instances beyond the 5 oldest are frozen (read-only) until others are deleted; over 1 GiB of storage every non-sponsored instance is frozen until storage falls below 95% of it; once the monthly transfer is spent, new connections are refused (`402 quota_exceeded`) until the next month. A Pro owner, or a Pro member accepted on a project the instance is linked to, lifts the limits.\n\n## Troubleshooting\n\nA refused tunnel session fails every connection with an error naming the tunnel and the WebSocket handshake status (for example `expected handshake response status code 101 but got 401`); the reason word below is in the hub's response body, not in the error.\n\n| Symptom | Cause |\n| :--- | :--- |\n| Variable undefined | Instance not linked, or the user has no access to it (omitted from the environment). |\n| Connection fails, status `401` | `AUTH_TOKEN` missing, expired or revoked, or the data token was rolled (re-read the environment). |\n| Status `403` (`read_access_denied` / `write_access_denied`) | The user lost access to the instance. |\n| Status `402` (`quota_exceeded`) | Frozen instance (read-write token) or monthly data transfer spent. |\n| Status `503` (`auth_unavailable`) | The hub could not check the token: retry. |\n| SSL negotiation error | Driver TLS enabled: set `ssl: false` / plain `http://`. |\n\n## Endpoints\n\n### GET /api/v1/data\n\nManaged data instances visible to the caller (?engine=).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://tunnels.fullstacked.cloud/api/v1/data\"\n```\n\n### POST /api/v1/data/provision\n\nProvisions an isolated instance { engine: postgres | redis | mongodb | s3, label? } (201).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"engine\": \"postgres\"\n}\n```\n\n### GET /api/v1/data/:id\n\nOne instance.\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/data/:id\n\nDeprovisions an instance (owner only, 204).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/data/:id/connection\n\nConnection parameters, DATA_* variables and fullstacked/tunnel snippets for ?access_level=rw|ro within the caller's permission.\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/data/:id/schema\n\nIntrospects tables, keys, collections or buckets (?schema=).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/data/:id/query\n\nRuns one operation: { sql, params? } (postgres), { command } (redis), { operation, collection, ... } (mongodb) or { operation, key?, prefix?, ... } (s3).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"sql\": \"select now()\"\n}\n```\n"},{"slug":"tunnels","title":"Edge & LAN Tunnels","url":"https://docs.fullstacked.cloud/tunnels","markdownUrl":"https://docs.fullstacked.cloud/tunnels?format=md","jsonUrl":"https://docs.fullstacked.cloud/tunnels?format=json","markdown":"# Edge & LAN Tunnels\n\nAn Edge tunnel lets project code reach a TCP service inside a private network (an office database, an internal HTTP API) without opening an inbound port. An Edge daemon running in that network keeps an outbound connection to the tunnels hub (tunnels.fullstacked.cloud); each tunnel under the Edge names one internal target (`host:port`). Project code registers the hub with `fullstacked/tunnel`, using the user's `AUTH_TOKEN` and the tunnel's token, and then connects to the returned name with `fetch`, `WebSocket` or any TCP driver.\n\n## Setup\n\n1. In the Console (https://console.fullstacked.cloud, Tunnels), enroll an Edge and run its daemon in the private network with the credential the Console shows (the daemon only connects outbound).\n2. Add a tunnel under the Edge with a name and an internal target, for example `office-api` to `192.168.1.20:8080`. The target can be any TCP service the daemon's machine can reach.\n3. Link the tunnel to the project (Console: Project, Linked data, Linked tunnels). Its token is then injected as `<PREFIX>_TUNNEL_TOKEN`, the prefix being the tunnel name upper-cased with non-alphanumeric runs replaced by `_` (`office-api` gives `OFFICE_API_TUNNEL_TOKEN`). Tunnel names are unique per Edge only: two linked tunnels with the same name collide (the second variable is dropped), so give one a prefix mapping on the project.\n\nEveryone with a tier on the project may dial a linked tunnel; otherwise a user needs to own the tunnel or its Edge, or hold a connect grant on it.\n\n## HTTP service\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\n\nconst office = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.OFFICE_API_TUNNEL_TOKEN}`,\n});\n\nconst res = await fetch(`http://${office}/internal/inventory`, {\n    headers: { Accept: \"application/json\" },\n});\nconst inventory = await res.json();\n```\n\nUse `http://` and `ws://` URLs: the hop to the hub is already `wss://`, and the tunnel name is not a real hostname a TLS certificate could match. The URL port is ignored: the tunnel's target decides.\n\n## TCP service (database)\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\nimport pg from \"pg\";\n\nconst host = await tunnel.register({\n    host: process.env.TUNNELS_HOST!,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.OFFICE_DB_TUNNEL_TOKEN}`,\n});\n\nconst pool = new pg.Pool({\n    host,\n    port: 5432,\n    user: process.env.OFFICE_DB_USER,         // custom project variables: an Edge tunnel carries no credentials\n    password: process.env.OFFICE_DB_PASSWORD,\n    database: \"inventory\",\n    ssl: false,\n});\npool.on(\"error\", (err) => console.error(\"office db error\", err));\n```\n\nCredentials of the internal service are yours to provide (custom project variables are visible to every project tier: prefer per-user credentials or a service that authenticates the FullStacked user, see [Runtime Sign-In & BYOA](https://docs.fullstacked.cloud/auth-byoa)).\n\n## WebSocket service\n\n```ts\nconst ws = new WebSocket(`ws://${office}/events`);\nws.onmessage = (event) => console.log(event.data);\n```\n\n## How a connection flows\n\n1. `tunnel.register` stores the hub host and the compound credential `<AUTH_TOKEN>:<tun_ token>` under a name and opens the tunnel's WebSocket session to `wss://tunnels.fullstacked.cloud` in the background. It never fails on bad credentials: errors surface on the first connection.\n2. The hub authenticates the session once, at its upgrade: it checks the `fsc_*` token with the auth service, then the user's right to the tunnel.\n3. Every connection to the name (socket, `fetch`, `WebSocket`) is a stream multiplexed on that session; the runtime adds sessions under load and closes idle ones. For each stream the hub asks the Edge daemon to dial the target inside its network, and the daemon splices the bytes.\n4. Revoking a user's token or access, rolling or deleting the tunnel token, or the owner's transfer quota running out closes the live sessions of the tunnel, other users' included; their next connection opens a new session (refused if they no longer have access). Handle dropped connections and reconnect on demand.\n\n## Errors\n\nErrors reach project code as connection errors whose message names the tunnel.\n\nA refused session (the upgrade) fails with the WebSocket handshake status, for example `expected handshake response status code 101 but got 403`; the reason word is in the hub's response body only.\n\n| Status | Meaning |\n| :--- | :--- |\n| `401` | `AUTH_TOKEN` missing, expired or revoked, the credential is not `<fsc_token>:<tun_token>`, or the `tun_` token was rolled or the tunnel deleted (re-read the environment). |\n| `403` | The user may not dial this tunnel (`access_denied`) or their account is not active (`principal_inactive`). |\n| `402` | `quota_exceeded`: frozen tunnel or the owner's monthly Edge transfer is spent. |\n| `503` | `auth_unavailable`: the hub could not check the token; retry. |\n\nA refused stream (one connection) fails with the hub's reason, for example `tunnel <name>: target_unreachable`:\n\n| Reason | Meaning |\n| :--- | :--- |\n| `edge_disconnected` | The Edge daemon is offline. |\n| `target_unreachable`, `connect_timeout` | The target could not be reached in time (wrong address, service down, LAN firewall). |\n| `edge_saturated`, `stream_limit` | Too many concurrent streams: retry later. |\n| `relay_dial_failed` | The Edge daemon did not open a relay session to the hub in time: retry. |\n\nFrom an agent, `tunnels_list` lists tunnels and Edges, and `tunnels_get_edge_status` (Edge managers only) and `tunnels_test_reachability` report whether the Edge daemon is connected. Neither probes the target behind an Edge: check that from the Edge's network.\n\n## Quotas\n\nOn the Base plan an owner keeps 1 Edge with up to 5 tunnels and 500 MiB per UTC calendar month of Edge tunnel transfer (both directions). Edges and tunnels beyond those counts, and every non-sponsored Edge and tunnel while the owner's storage (projects and data instances) is over 1 GiB, are frozen: their sessions are refused with `402 quota_exceeded`. When the transfer is spent, new sessions get `402 quota_exceeded` and live ones are closed until the next month. A Pro owner has no limit, and neither has an Edge or tunnel shared with an accepted Pro member or a tunnel linked to a project with an accepted Pro member.\n\n## Rules for agents\n\n- Never hardcode the hub host or a `tun_` token: read `process.env.TUNNELS_HOST` and `process.env.<PREFIX>_TUNNEL_TOKEN`.\n- Register each tunnel once and reuse the name; check the token variable is set before registering (it is absent when the tunnel is not linked or the user has no access).\n- Every user tunnel belongs to an Edge (creating a direct tunnel that the hub would dial itself is refused); the target is whatever the Edge daemon can reach.\n\n## Endpoints\n\n### WS /\n\nMultiplexed tunnel session dialed by fullstacked/tunnel: each registered tunnel keeps a WebSocket session (more under load), authenticated once at the upgrade, and every TCP/HTTP/WS connection to the tunnel name is a stream on it, to a data instance or Edge tunnel.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Authorization: <fsc_* token>:<tun_* token> (Bearer prefix optional)\n- **In project code**: `${process.env.TUNNELS_HOST}`\n- **Pitfall**: Pass both halves joined by ':': the user's AUTH_TOKEN and the env's <ENGINE>_DATA_TOKEN or <PREFIX>_TUNNEL_TOKEN (a tun_ token).\n\nindexTs:\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\n\nconst pgHost = await tunnel.register({\n    host: process.env.TUNNELS_HOST,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.PG_DATA_TOKEN}`,\n});\n```\n\n### GET /api/v1/edges\n\nEdge machines visible to the caller.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/edges\n\nRegisters an Edge { name } (201); its daemon then authenticates with an fsc_edge_ token from auth POST /v1/tokens.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/edges/:id\n\nOne Edge.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/edges/:id/roll\n\nRotates an Edge's edg_ key (owner or can_register grantee): the lifeline is severed, tunnels and grants stay; the daemon needs a fresh fsc_edge_ token.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/edges/:id\n\nDeletes an Edge (owner or can_register grantee).\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/tunnels\n\nLAN tunnels visible to the caller (?edgeId=); managed data tunnels are not listed.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/tunnels\n\nCreates a tunnel { name, edgeId, internalHost, internalPort } under an Edge the caller manages (201).\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/tunnels/:id\n\nOne tunnel.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/tunnels/test\n\nReachability of { tunnelId }, { token }, or a candidate { edgeId, internalHost, internalPort }: for a tunnel under an Edge it reports whether the Edge daemon is connected, without probing the target.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/tunnels/:id/roll\n\nRolls the tunnel's tun_ token.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/tunnels/:id\n\nDeletes a tunnel.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n"},{"slug":"actions","title":"Project Actions","url":"https://docs.fullstacked.cloud/actions","markdownUrl":"https://docs.fullstacked.cloud/actions?format=md","jsonUrl":"https://docs.fullstacked.cloud/actions?format=json","markdown":"# Project Actions\n\nAn action is a TypeScript file `actions/<name>.ts` in a project. Its default export is an ordinary async function: the app imports and calls it directly, and once a `v*` release tag is built, any authorized caller can run the same function remotely in a sandbox with `POST https://git.fullstacked.cloud/api/v1/projects/:id/actions/:name/run` (MCP: `projects_run_action`). Remote runs get the caller's environment and token, so data access follows the caller's permissions.\n\n## File contract\n\n```ts\n// actions/add-todo.ts\nimport type pg from \"pg\";\nimport { getPool } from \"../db\";\n\nexport const action = {\n    title: \"Add Todo\",\n    description: \"Creates a todo item\",\n    parameters: {\n        type: \"object\",\n        properties: {\n            title: { type: \"string\", minLength: 1, description: \"Todo title\" },\n            priority: { type: \"string\", enum: [\"low\", \"medium\", \"high\"], default: \"medium\" },\n        },\n        required: [\"title\"],\n        additionalProperties: false,\n    },\n};\n\ntype Params = { title: string; priority?: \"low\" | \"medium\" | \"high\" };\n\n/** Remote runs only: runs first, its return value is the handler's ctx. */\nexport async function setup(params: Params) {\n    return { pool: await getPool() };\n}\n\n/** The handler: called by the app directly (no ctx) and by remote runs. */\nexport default async function addTodo(params: Params, ctx?: { pool: pg.Pool }) {\n    const pool = ctx?.pool ?? (await getPool());\n    const { rows } = await pool.query(\n        \"INSERT INTO todos (title, priority) VALUES ($1, $2) RETURNING *\",\n        [params.title, params.priority ?? \"medium\"]\n    );\n    return rows[0];\n}\n\n/** Remote runs only: always runs after the handler, even when it threw. */\nexport async function teardown(ctx?: { pool: pg.Pool }) {\n    await ctx?.pool.end();\n}\n```\n\n| Export | Required | Rules |\n| :--- | :--- | :--- |\n| `export const action` | Yes (the release build fails without it) | A **static literal**: objects, arrays, strings (template strings without `${}`), numbers, booleans, `null`, comments, trailing commas; a type annotation, `as const` and `satisfies` are allowed. No identifiers, calls, spreads or shorthand properties. Fields: `title?` (defaults to the name), `description?`, `parameters?` (a JSON Schema whose root is `{ \"type\": \"object\" }`, default no properties), `name?` (must equal the file name). |\n| `export default` | Yes | `(params, ctx?) => result`. Receives validated params with schema defaults applied. The result must be JSON-serializable (`undefined` becomes `null`). |\n| `export function setup(params)` | No | Remote runs only. Opens resources; returns `ctx`. |\n| `export function teardown(ctx)` | No | Remote runs only. Always runs, bounded by 5 s (`ACTION_TEARDOWN_TIMEOUT_MS`); its errors are logged and never change the outcome. |\n\nFile rules:\n\n- Only top-level `actions/*.ts` files are actions. Files starting with `_` or `.`, `*.d.ts`, subdirectories and other extensions are helpers (`actions/_shared.ts`).\n- The file name is the action name: `^[a-z0-9][a-z0-9_-]{0,63}$`. Any other `actions/*.ts` name fails the build.\n- Never run the handler at module top level: remote runs import the module, so top-level side effects run too. Detect a remote run with `process.env.FULLSTACKED_ACTION_RUN === \"1\"` if needed.\n- Hooks are plain exports and can be shared: `export { setup, teardown } from \"./_lifecycle\";`.\n\n## Parameter schema\n\nSupported keywords: `type` (`string`, `number`, `integer`, `boolean`, `object`, `array`, `null`, or a list), `properties`, `required`, `additionalProperties`, `items`, `enum`, `const`, `default`, `minLength`, `maxLength`, `pattern`, `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `minItems`, `maxItems`. Other keywords are ignored. Invalid params answer `400 INVALID_PARAMS` naming the parameter, before anything runs. `required` is an array on the object schema, not a flag on each property.\n\n## Calling from the app\n\n```tsx\nimport addTodo from \"./actions/add-todo\";\n\nasync function onSubmit(title: string) {\n    const todo = await addTodo({ title });\n}\n```\n\nIn the app no schema validation or hooks run: the handler gets exactly what you pass, and resources live as long as the app.\n\n## Remote runs\n\nActions run only from a successfully built release: push a tag starting with `v` (MCP `projects_create_tag`), the build bundles each action with its dependencies, and an invalid action fails the build.\n\n```http\nPOST https://git.fullstacked.cloud/api/v1/projects/<projectId>/actions/add-todo/run\nAuthorization: Bearer <fsc_* token>\nContent-Type: application/json\n\n{ \"tag\": \"v1.2.0\", \"params\": { \"title\": \"Ship docs\" } }\n```\n\n`tag` is optional: the newest succeeded release the caller can access is used. The response is `200` even when the run failed or timed out:\n\n```json\n{\n  \"run_id\": \"3c1f0e2a-...\",\n  \"action\": \"add-todo\",\n  \"tag\": \"v1.2.0\",\n  \"status\": \"succeeded\",\n  \"exit_code\": 0,\n  \"error\": null,\n  \"duration_ms\": 1840,\n  \"created_at\": \"2026-10-23T14:02:11.000Z\",\n  \"output\": { \"id\": 42, \"title\": \"Ship docs\", \"priority\": \"medium\" }\n}\n```\n\n| Status | When |\n| :--- | :--- |\n| `succeeded` | The handler returned; `output` is its JSON result. |\n| `failed` | `setup` or the handler threw (`error` is its message; an error with a 5-character `code` such as a PostgreSQL SQLSTATE becomes `SQLSTATE <code>: <message>`), a non-zero exit, or no valid result. |\n| `timeout` | The run exceeded 30 s (`ACTION_TIMEOUT_MS`) and was killed. |\n\nOther endpoints (same host, `Authorization: Bearer`):\n\n| Endpoint | Returns |\n| :--- | :--- |\n| `GET /api/v1/projects/:id/actions?tag=` | `{ tag, actions: [{ name, title, description, file, bundle, parameters }] }` of a release (MCP `projects_list_actions`). |\n| `GET /api/v1/projects/:id/actions/runs?tag=&action=&status=&limit=` | `{ runs: [{ run_id, action, tag, caller, caller_name, status, exit_code, error, duration_ms, created_at }] }`, newest first (`limit` 1 to 200, default 50). Users without read access see only their own runs. |\n| `GET /api/v1/projects/:id/actions/runs/:runId` | `{ run }`: the same fields plus `output`, `logs` (last 256 KiB of stdout and stderr) and `finished_at`. |\n\nErrors: `400 INVALID_PARAMS` params rejected by the schema, `403` tag not accessible, `404` unknown action, tag or release without a successful build, `502` environment resolution failed, `503` sandbox unavailable.\n\n## Sandbox environment\n\nEach run is a fresh container running the bundled action with `fullstacked --file` (runtime 1.0.0-alpha.1844). The code runs under Node: `process` is Node's own, there is no `document`, `fullstacked/auth` (which opens a window) cannot be used, and a relative `fetch(\"/...\")` fails. Keep UI code out of the modules an action imports.\n\n| Limit | Value |\n| :--- | :--- |\n| Timeout | 30 s by default |\n| Memory | 512 MiB, no swap |\n| CPU / processes | 1 vCPU, 256 processes |\n| Network | Internet allowed (including tunnels.fullstacked.cloud, so managed data and Edge tunnels work through `fullstacked/tunnel`); private networks and the host are blocked |\n| Packages | Bundled at release time; nothing is installed at run time |\n\n`process.env` holds the project environment resolved **for the caller** ([Injected process.env](https://docs.fullstacked.cloud/project-runtime)), so a read-only caller gets read-only database credentials and writes fail. On top of it:\n\n| Variable | Value |\n| :--- | :--- |\n| `AUTH_TOKEN` | The caller's own `fsc_*` token (calls to `/api/v1/me` return the caller). |\n| `ACTION_NAME`, `ACTION_RUN_ID`, `ACTION_PARAMS` | Name, run id, validated params as JSON. |\n| `RELEASE_TAG`, `PROJECT_ID` | The release and project being run. |\n| `FULLSTACKED_ACTION_RUN` | `\"1\"`. |\n| `ACTION_TEARDOWN_TIMEOUT_MS` | Teardown bound in ms. |\n\nThese run variables override custom project variables of the same name. Output written to `console.log` / `console.error` is kept as logs; only the handler's return value is the result. Open pools or sockets cannot keep a run alive: the runner exits after `teardown`.\n\n## Who can run\n\nAnyone with access to the release tag (owner, any project tier, or a release-only grant) can list and run actions; the run executes with that caller's permissions on linked data and tunnels.\n\n## Endpoints\n\n### GET /api/v1/projects/:id/actions\n\nActions compiled in a release (?tag=, default the newest release the caller can access).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/actions/:name/run\n\nRuns a release's action with the caller's own token and data wiring; answers 200 with the run result even when the run failed or timed out.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"tag\": \"v1.0.0\",\n  \"params\": {\n    \"customerId\": \"cus_123\"\n  }\n}\n```\n\ncurl:\n\n```bash\ncurl -sS -X POST -H \"Authorization: Bearer $AUTH_TOKEN\" -H \"Content-Type: application/json\" -d '{\"tag\":\"v1.0.0\",\"params\":{\"customerId\":\"cus_123\"}}' \"https://git.fullstacked.cloud/api/v1/projects/<projectId>/actions/sync-orders/run\"\n```\n\n### GET /api/v1/projects/:id/actions/runs\n\nPast action runs, filtered by ?tag=, ?action=, ?status= and ?limit=.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/actions/runs/:runId\n\nOne action run.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n"},{"slug":"auth-byoa","title":"Runtime Sign-In & BYOA","url":"https://docs.fullstacked.cloud/auth-byoa","markdownUrl":"https://docs.fullstacked.cloud/auth-byoa?format=md","jsonUrl":"https://docs.fullstacked.cloud/auth-byoa?format=json","markdown":"# Runtime Sign-In & BYOA\n\nProjects launched from the FullStacked Cloud Client GUI already receive the user's token as `process.env.AUTH_TOKEN`: most projects never need to sign anyone in. This page covers the two other cases: a standalone runtime app that signs a user in itself (PKCE through `fullstacked/auth` and the auth page at https://auth.fullstacked.cloud), and an enterprise backend that validates the FullStacked tokens of users signed in through its own identity provider (Bring Your Own Auth).\n\n## Interactive sign-in from a runtime app\n\n`fullstacked/auth` exports one function:\n\n```ts\nauthenticate(url: string, opts?: { windowFeatures?: string }): Promise<any>\n```\n\nIt adds `auth=1` to `url`, opens it in the platform's auth surface (system auth session on Apple and Android, an auth window or browser popup elsewhere) and resolves with the object the page posts back: `{ exchange_token }` on success. A denial resolves `{ error }` (Android rejects with an `Error` instead). Dismissing the auth surface rejects on Apple, Android and Linux; on Windows and in the browser window of the npm CLI the promise may never settle, so keep a way to cancel in your UI. There is no `authorize` export in the runtime.\n\nFlow:\n\n1. Create a PKCE pair: `code_verifier` (32 random bytes, base64url) and `code_challenge` = base64url(SHA-256(verifier)).\n2. ``authenticate(`${AUTH_URL}/?code_challenge=<challenge>&code_challenge_method=S256`)``. The user signs in and approves on the consent screen.\n3. Redeem: `POST ${AUTH_URL}/v1/auth/exchange/token` with `{ exchangeToken, codeVerifier }`. The response carries the session token.\n\n```ts\nimport { authenticate } from \"fullstacked/auth\";\nimport { createHash, randomBytes } from \"node:crypto\";\n\nexport async function signIn(authUrl: string): Promise<string> {\n    const verifier = randomBytes(32).toString(\"base64url\");\n    const challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\n    const result = await authenticate(\n        `${authUrl}/?code_challenge=${challenge}&code_challenge_method=S256`\n    );\n    if (result?.error) throw new Error(result.error);\n    if (!result?.exchange_token?.startsWith(\"fsc_exc_\")) throw new Error(\"Invalid authentication response\");\n\n    const res = await fetch(`${authUrl}/v1/auth/exchange/token`, {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: JSON.stringify({ exchangeToken: result.exchange_token, codeVerifier: verifier }),\n    });\n    if (!res.ok) throw new Error(`Exchange failed (${res.status}): ${await res.text()}`);\n    const session = await res.json();\n    return session.token; // fsc_ses_... (or fsc_ath_<slug>_... from an enterprise authority)\n}\n```\n\nCall `signIn` from a click handler so the window can open. Use node `crypto` (core-backed), not `crypto.subtle`.\n\nExchange response (`200`):\n\n```json\n{\n  \"token\": \"fsc_ses_...\",\n  \"tokenType\": \"Bearer\",\n  \"userId\": \"usr_4f1c9a2b7d3e8f6a5b0c1d2e\",\n  \"sessionId\": \"tok_...\",\n  \"user\": { \"userId\": \"usr_4f1c9a2b7d3e8f6a5b0c1d2e\", \"email\": \"alice@example.com\", \"name\": \"Alice Martin\", \"slug\": \"alice\", \"createdAt\": \"2026-09-20T14:32:00.000Z\" },\n  \"session\": { \"sessionId\": \"tok_...\", \"userId\": \"usr_4f1c9a2b7d3e8f6a5b0c1d2e\", \"expiresAt\": 1792000000000 },\n  \"issuedAt\": 1790000000,\n  \"expiresAt\": 1792000000,\n  \"restricted\": false\n}\n```\n\nRules:\n\n- The challenge is mandatory and must be S256 (43 base64url characters); without it the auth page refuses to start.\n- The exchange token (`fsc_exc_...`) is single use and expires after 300 s. A wrong or missing verifier burns it (`400` / `401`). The endpoint is limited to 30 requests per minute per IP (`429 RATE_LIMITED`).\n- `body` keys also accept `exchange_token` / `code_verifier`.\n- Sign out with `POST ${AUTH_URL}/v1/auth/logout` and `Authorization: Bearer <token>` (returns `{ \"status\": \"revoked\" }`).\n- Store the token like a password (for example `fullstacked/config` is plain text in the project directory: acceptable on the user's own device only).\n\n## Enterprise Bring Your Own Auth (BYOA)\n\nAn organization can run its own sign-in for FullStacked users through a hosted **authority** (`ath_<slug>`, created by FullStacked Cloud operators, with a secret key `sec_auth_<env>_...` shown once). Its users get session tokens `fsc_ath_<slug>_<64 hex>` that work like any FullStacked token (`/api/v1/me`, projects, data, tunnels). On `/api/v1/me` their `id` is `<authorityId>:<externalUserId>` and `authority_id` is set.\n\n### Minting a sign-in for your user (your IdP backend)\n\nAfter your identity provider authenticates the user, your backend mints an exchange token bound to the runtime's PKCE challenge and hands it back the same way the FullStacked auth page does. The runtime app opens your sign-in page with `authenticate(\"<your page>?code_challenge=<challenge>&code_challenge_method=S256\")`: the page receives `code_challenge` (the S256 challenge to mint with) and `auth=1`, and native shells also add `native=1` and `callback_scheme=fullstacked-auth`.\n\n- Native (`native=1`): redirect to `<callback_scheme>://auth?exchange_token=fsc_exc_...` (on denial `?error=<message>`), that is `fullstacked-auth://auth?...`.\n- Popup (no `native`): `window.opener.postMessage({ exchange_token }, <opener origin>)` (on denial `{ error }`), then close the window.\n\nThe runtime app then redeems it at `https://auth.fullstacked.cloud/v1/auth/exchange/token` exactly as above and receives `fsc_ath_<slug>_...`.\n\n```http\nPOST /v1/authorities/ath_acme/token HTTP/1.1\nHost: auth.fullstacked.cloud\nX-FullStacked-Authority-Key: sec_auth_...\nContent-Type: application/json\n\n{ \"userId\": \"alice\", \"codeChallenge\": \"<S256 challenge from the runtime>\", \"email\": \"alice@example.com\", \"name\": \"Alice Martin\", \"groups\": [\"devops\"] }\n```\n\nResponse: `{ \"exchangeToken\": \"fsc_exc_...\", \"tokenType\": \"ExchangeToken\", \"authorityId\": \"ath_acme\", \"userId\": \"alice\", \"expiresAt\": ... }` (single use, 300 s). Optional body fields: `ttl`, `metadata`. Limit: 50 mints per second per authority.\n\n### Validating a user's token in your backend\n\nWhen a project calls one of your services with `Authorization: Bearer ${process.env.AUTH_TOKEN}`, the service validates it:\n\n```http\nPOST /v1/authorities/ath_acme/validate HTTP/1.1\nHost: auth.fullstacked.cloud\nX-FullStacked-Authority-Key: sec_auth_...\nContent-Type: application/json\n\n{ \"token\": \"fsc_ath_acme_...\" }\n```\n\n`200`:\n\n```json\n{\n  \"valid\": true,\n  \"authorityId\": \"ath_acme\",\n  \"userId\": \"alice\",\n  \"email\": \"alice@example.com\",\n  \"name\": \"Alice Martin\",\n  \"groups\": [\"devops\"],\n  \"tokenClass\": \"ath\",\n  \"tokenId\": \"tok_...\",\n  \"issuedAt\": 1790000000,\n  \"expiresAt\": 1790086400,\n  \"metadata\": {}\n}\n```\n\n| Response | Meaning |\n| :--- | :--- |\n| `401 { \"valid\": false, \"error\": \"token_invalid_or_expired\" }` | Not an `fsc_ath_` token, or unknown, expired or revoked. |\n| `400` | Token issued by another authority, or `token` missing. |\n| `401` | Wrong authority key. |\n| `403` | Authority suspended. |\n| `429 RATE_LIMITED` | Over 100 validations per second per authority. |\n\nOnly `fsc_ath_` tokens of your own authority validate here. To check a platform user's token (`fsc_ses_`, `fsc_pat_`) instead, call `GET https://auth.fullstacked.cloud/api/v1/me` with it: `200` means valid, and the body says who it is.\n\n```ts\n// Express-style middleware in your own backend (Node.js, not the FullStacked runtime)\nexport async function fullstackedUser(req, res, next) {\n    const token = req.headers.authorization?.replace(/^Bearer\\s+/i, \"\");\n    const r = await fetch(`${process.env.FSC_AUTH_URL}/v1/authorities/${process.env.FSC_AUTHORITY_ID}/validate`, {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/json\", \"X-FullStacked-Authority-Key\": process.env.FSC_AUTHORITY_KEY! },\n        body: JSON.stringify({ token }),\n    });\n    const body = await r.json();\n    if (!r.ok || !body.valid) return res.status(401).json({ error: \"unauthorized\" });\n    req.user = body;\n    next();\n}\n```\n\nKeep the authority key on your server only: never ship it in a project.\n\n## Endpoints\n\n### POST /v1/auth/exchange/token\n\nRedeems a single-use fsc_exc_ exchange token with its PKCE code_verifier: a BYOA sign-in returns an fsc_ath_<slug>_ session token, a session handoff a fresh fsc_ses_ token.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Public (PKCE code_verifier)\n- **In project code**: `${process.env.AUTH_URL}/v1/auth/exchange/token`\n\nRequest:\n\n```json\n{\n  \"exchangeToken\": \"fsc_exc_...\",\n  \"codeVerifier\": \"...\"\n}\n```\n\n### POST /v1/authorities/:id/token\n\nServer-to-server: an enterprise IdP mints a PKCE-bound fsc_exc_ exchange token for { userId, email?, name?, groups?, ttl?, codeChallenge, metadata? }.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator\n\n### POST /v1/authorities/:id/validate\n\nValidates an fsc_ath_<slug>_ token issued for this authority and returns its claims (userId, email, name, groups, tokenId, issuedAt, expiresAt, metadata).\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator\n\nRequest:\n\n```json\n{\n  \"token\": \"fsc_ath_acme_...\"\n}\n```\n\ncurl:\n\n```bash\ncurl -sS -X POST -H \"X-FullStacked-Authority-Key: $AUTHORITY_KEY\" -H \"Content-Type: application/json\" -d '{\"token\":\"fsc_ath_...\"}' \"https://auth.fullstacked.cloud/v1/authorities/<authorityId>/validate\"\n```\n\n### POST /v1/authorities/:id/revoke\n\nRevokes the authority's tokens by { userId }, by { token }, or { all: true }.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator\n"},{"slug":"api-reference","title":"Platform API Reference","url":"https://docs.fullstacked.cloud/api-reference","markdownUrl":"https://docs.fullstacked.cloud/api-reference?format=md","jsonUrl":"https://docs.fullstacked.cloud/api-reference?format=json","markdown":"# Platform API Reference\n\nEvery public endpoint of the FullStacked Cloud fleet in this environment. Project code calls them with absolute URLs (`process.env.AUTH_URL`, ...) and `Authorization: Bearer ${process.env.AUTH_TOKEN}`. Machine-readable: `https://docs.fullstacked.cloud/api/v1/endpoints`.\n\n## auth (`https://auth.fullstacked.cloud`)\n\n| Method | Path | Auth | Summary |\n| :--- | :--- | :--- | :--- |\n| `GET` | `/api/v1/me` | Bearer fsc_* (AUTH_TOKEN) | 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. |\n| `GET` | `/api/v1/user/access` | Bearer fsc_* (AUTH_TOKEN) | 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. |\n| `POST` | `/v1/auth/exchange/token` | Public (PKCE code_verifier) | Redeems a single-use fsc_exc_ exchange token with its PKCE code_verifier: a BYOA sign-in returns an fsc_ath_<slug>_ session token, a session handoff a fresh fsc_ses_ token. |\n| `POST` | `/v1/authorities/:id/token` | X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator | Server-to-server: an enterprise IdP mints a PKCE-bound fsc_exc_ exchange token for { userId, email?, name?, groups?, ttl?, codeChallenge, metadata? }. |\n| `POST` | `/v1/authorities/:id/validate` | X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator | Validates an fsc_ath_<slug>_ token issued for this authority and returns its claims (userId, email, name, groups, tokenId, issuedAt, expiresAt, metadata). |\n| `POST` | `/v1/authorities/:id/revoke` | X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator | Revokes the authority's tokens by { userId }, by { token }, or { all: true }. |\n| `POST` | `/v1/auth/logout` | Bearer fsc_* | Revokes the bearer token and, unless { keepClerkSession: true }, its Clerk sign-in. |\n| `GET` | `/v1/tokens` | Bearer fsc_* (usr_ user) | Lists the caller's active tokens (sessions, PATs, agent and edge tokens); OAuth app connections are not included. |\n| `POST` | `/v1/tokens` | Bearer fsc_* (usr_ user) | Mints a token { type: \"pat\" \\| \"agent\" \\| \"edge\", name?, ttl?, scopes? (agent), edgeId? (edge) }; the raw token is returned once (201). |\n| `DELETE` | `/v1/tokens/:id` | Bearer fsc_* (usr_ user) | Revokes one of the caller's tokens. |\n| `GET` | `/v1/billing` | Bearer fsc_* | The caller's effective plan (base \\| pro), quota limits and counted usage. |\n| `GET` | `/v1/grants` | Bearer fsc_* (usr_ user) | Lists grants on assets the caller manages, filtered by ?principal_id=, ?resource_type= and ?resource_id=. |\n| `PUT` | `/v1/grants` | Bearer fsc_* (usr_ user) | Shares an asset: { principal (user id or email), resourceType, resourceId, permissions, branches?, tags? }. An unknown email becomes a pending invitation. |\n| `DELETE` | `/v1/grants` | Bearer fsc_* (usr_ user) | Removes a grant, keyed by principal, resource type and id (query or JSON body). |\n| `GET` | `/v1/devices` | Bearer fsc_* | The caller's registered Client GUI devices. |\n| `PATCH` | `/v1/devices/:id` | Bearer fsc_* | Renames one of the caller's devices ({ name }). |\n| `DELETE` | `/v1/devices/:id` | Bearer fsc_* | Forgets a device; it is registered again on its next report. |\n| `GET` | `/.well-known/oauth-authorization-server` | Public | OAuth 2.1 authorization server metadata (authorize, token, register and revoke endpoints) used by MCP clients. |\n\n## git (`https://git.fullstacked.cloud`)\n\n| Method | Path | Auth | Summary |\n| :--- | :--- | :--- | :--- |\n| `GET` | `/api/v1/projects/:id/env` | Bearer fsc_* | The project's resolved runtime environment: DATA_* wiring and tokens of linked instances, platform URLs (AUTH_URL, AUTH_HOST, TUNNELS_HOST, DOCS_URL) and custom variables. ?format=json\\|dotenv; never contains AUTH_TOKEN. |\n| `PUT` | `/api/v1/projects/:id/env` | Bearer fsc_* | Merges { variables?, mappings? } into the project's environment config (null removes a key). Needs write. |\n| `GET` | `/api/v1/projects/:id/actions` | Bearer fsc_* | Actions compiled in a release (?tag=, default the newest release the caller can access). |\n| `POST` | `/api/v1/projects/:id/actions/:name/run` | Bearer fsc_* | Runs a release's action with the caller's own token and data wiring; answers 200 with the run result even when the run failed or timed out. |\n| `GET` | `/api/v1/projects/:id/actions/runs` | Bearer fsc_* | Past action runs, filtered by ?tag=, ?action=, ?status= and ?limit=. |\n| `GET` | `/api/v1/projects/:id/actions/runs/:runId` | Bearer fsc_* | One action run. |\n| `GET` | `/api/v1/projects` | Bearer fsc_* | Projects the caller owns or holds any tier on (?filter= on title, name, description). |\n| `POST` | `/api/v1/projects` | Bearer fsc_* | Creates a project { name, title?, description?, default_branch?, template?, owner? } (201). |\n| `GET` | `/api/v1/projects/:id` | Bearer fsc_* | Project details (release-only consumers get the summary). |\n| `PATCH` | `/api/v1/projects/:id` | Bearer fsc_* | Updates { title?, description? } (write) or renames { name } (admin). |\n| `DELETE` | `/api/v1/projects/:id` | Bearer fsc_* | Deletes the project; needs admin and { confirm: \"<owner>/<name>\" }. |\n| `PUT` | `/api/v1/projects/:id/data-links` | Bearer fsc_* | Links a managed data instance to the project ({ data_instance: <UUID> }). |\n| `DELETE` | `/api/v1/projects/:id/data-links` | Bearer fsc_* | Unlinks a data instance (?data_instance=<UUID> or JSON body). |\n| `GET` | `/api/v1/projects/:id/branches` | Bearer fsc_* | Branches. |\n| `POST` | `/api/v1/projects/:id/branches` | Bearer fsc_* | Creates a branch { name, from? } (201). |\n| `GET` | `/api/v1/projects/:id/tags` | Bearer fsc_* | Tags. |\n| `POST` | `/api/v1/projects/:id/tags` | Bearer fsc_* | Creates a tag { name, message?, ref? } (201); a v* tag enqueues a release build. |\n| `POST` | `/api/v1/projects/:id/validate-diagrams` | Bearer fsc_* | Validates ```mermaid blocks in the branch's Markdown files ({ branch?, file_path? }). |\n| `GET` | `/api/v1/projects/:id/files` | Bearer fsc_* | Dev-state file tree (?branch=&path=&recursive=&ref=), submodules included. |\n| `GET` | `/api/v1/projects/:id/files/content` | Bearer fsc_* | Reads a dev-state file (?branch=&path=&ref=). |\n| `PUT` | `/api/v1/projects/:id/files/content` | Bearer fsc_* | Writes a dev-state file { path, content, encoding?: utf-8 \\| base64, base_sha256?, branch? }, broadcast to live sync clients (needs dev_sync). |\n| `DELETE` | `/api/v1/projects/:id/files/content` | Bearer fsc_* | Deletes a dev-state file (?branch=&path=). |\n| `GET` | `/api/v1/projects/:id/diff` | Bearer fsc_* | Uncommitted dev-state changes against HEAD (?branch=). |\n| `POST` | `/api/v1/projects/:id/commit` | Bearer fsc_* | Commits the dev state { message, branch? } (201; needs write). |\n| `POST` | `/api/v1/projects/:id/stash` | Bearer fsc_* | Stashes the dev state { branch?, message? }. |\n| `POST` | `/api/v1/projects/:id/revert` | Bearer fsc_* | Discards dev-state changes back to HEAD { branch? }. |\n| `GET` | `/api/v1/projects/:id/releases` | Bearer fsc_* (optional on public release projects) | Release builds (v* tags) visible to the caller. |\n| `GET` | `/api/v1/projects/:id/releases/latest` | Bearer fsc_* (optional on public release projects) | Newest succeeded release, with its artifact for ?runtime=<version>. |\n| `GET` | `/api/v1/projects/:id/releases/tags/:tag` | Bearer fsc_* (optional on public release projects) | One release's metadata (?runtime=<version>). |\n| `GET` | `/api/v1/projects/:id/releases/tags/:tag/artifact` | Bearer fsc_* (optional on public release projects) | Downloads the release .tar.gz compiled for ?runtime=<version> (compiled on demand). |\n| `GET` | `/api/v1/projects/:id/releases/tags/:tag/log` | Bearer fsc_* (optional on public release projects) | The release's build log (text/plain). |\n| `WS` | `/api/v1/projects/:id/sync` | Bearer fsc_* (or the fsc-sync subprotocol token / ?access_token=) | Live dev-state file sync for ?branch= (default branch otherwise); needs dev_sync. |\n| `GET` | `/:owner/:repo/info/refs` | Basic (fsc_* token as password) or Bearer fsc_* | Git Smart HTTP for the git CLI (clone, fetch, push); POST git-upload-pack and git-receive-pack follow on the same path. |\n\n## tunnels (`https://tunnels.fullstacked.cloud`)\n\n| Method | Path | Auth | Summary |\n| :--- | :--- | :--- | :--- |\n| `WS` | `/` | Authorization: <fsc_* token>:<tun_* token> (Bearer prefix optional) | Multiplexed tunnel session dialed by fullstacked/tunnel: each registered tunnel keeps a WebSocket session (more under load), authenticated once at the upgrade, and every TCP/HTTP/WS connection to the tunnel name is a stream on it, to a data instance or Edge tunnel. |\n| `GET` | `/api/v1/edges` | Bearer fsc_* | Edge machines visible to the caller. |\n| `POST` | `/api/v1/edges` | Bearer fsc_* | Registers an Edge { name } (201); its daemon then authenticates with an fsc_edge_ token from auth POST /v1/tokens. |\n| `GET` | `/api/v1/edges/:id` | Bearer fsc_* | One Edge. |\n| `POST` | `/api/v1/edges/:id/roll` | Bearer fsc_* | Rotates an Edge's edg_ key (owner or can_register grantee): the lifeline is severed, tunnels and grants stay; the daemon needs a fresh fsc_edge_ token. |\n| `DELETE` | `/api/v1/edges/:id` | Bearer fsc_* | Deletes an Edge (owner or can_register grantee). |\n| `GET` | `/api/v1/tunnels` | Bearer fsc_* | LAN tunnels visible to the caller (?edgeId=); managed data tunnels are not listed. |\n| `POST` | `/api/v1/tunnels` | Bearer fsc_* | Creates a tunnel { name, edgeId, internalHost, internalPort } under an Edge the caller manages (201). |\n| `GET` | `/api/v1/tunnels/:id` | Bearer fsc_* | One tunnel. |\n| `POST` | `/api/v1/tunnels/test` | Bearer fsc_* | Reachability of { tunnelId }, { token }, or a candidate { edgeId, internalHost, internalPort }: for a tunnel under an Edge it reports whether the Edge daemon is connected, without probing the target. |\n| `POST` | `/api/v1/tunnels/:id/roll` | Bearer fsc_* | Rolls the tunnel's tun_ token. |\n| `DELETE` | `/api/v1/tunnels/:id` | Bearer fsc_* | Deletes a tunnel. |\n\n## data (`https://tunnels.fullstacked.cloud`)\n\n| Method | Path | Auth | Summary |\n| :--- | :--- | :--- | :--- |\n| `GET` | `/api/v1/data` | Bearer fsc_* | Managed data instances visible to the caller (?engine=). |\n| `POST` | `/api/v1/data/provision` | Bearer fsc_* | Provisions an isolated instance { engine: postgres \\| redis \\| mongodb \\| s3, label? } (201). |\n| `GET` | `/api/v1/data/:id` | Bearer fsc_* | One instance. |\n| `DELETE` | `/api/v1/data/:id` | Bearer fsc_* | Deprovisions an instance (owner only, 204). |\n| `GET` | `/api/v1/data/:id/connection` | Bearer fsc_* | Connection parameters, DATA_* variables and fullstacked/tunnel snippets for ?access_level=rw\\|ro within the caller's permission. |\n| `GET` | `/api/v1/data/:id/schema` | Bearer fsc_* | Introspects tables, keys, collections or buckets (?schema=). |\n| `POST` | `/api/v1/data/:id/query` | Bearer fsc_* | Runs one operation: { sql, params? } (postgres), { command } (redis), { operation, collection, ... } (mongodb) or { operation, key?, prefix?, ... } (s3). |\n\n## mcp (`https://mcp.fullstacked.cloud`)\n\n| Method | Path | Auth | Summary |\n| :--- | :--- | :--- | :--- |\n| `POST` | `/mcp` | Bearer fsc_* (OAuth-connected AI apps hold agent tokens) | Streamable HTTP JSON-RPC 2.0 MCP endpoint: docs_list / docs_get (this documentation, open to every token), projects_*, data_*, tunnels_*, users_* and auth_get_my_access tools, plus fullstacked:// resources (fullstacked://docs/{topic} included). |\n| `DELETE` | `/mcp` | Bearer fsc_* | Terminates the MCP session named by the Mcp-Session-Id header (204). |\n| `GET` | `/sse` | Bearer fsc_* | HTTP+SSE transport (2024-11-05): opens the event stream and announces the /messages?sessionId= endpoint. |\n| `POST` | `/messages` | Bearer fsc_* | Posts a JSON-RPC message for an SSE session (?sessionId=); answered on the stream. |\n| `GET` | `/.well-known/oauth-protected-resource` | Public | RFC 9728 protected resource metadata pointing AI apps to the Auth OAuth server. |\n| `GET` | `/api/v1/audit` | Bearer fsc_* | The caller's own agent audit trail of MCP tool calls (?limit=, default 100). |\n\n## install (`https://install.fullstacked.cloud`)\n\n| Method | Path | Auth | Summary |\n| :--- | :--- | :--- | :--- |\n| `GET` | `/` | Public | The Client GUI installer: an ES module evaluated by the FullStacked runtime through exec; ?force=1 reinstalls. |\n| `GET` | `/deeplink` | Public | HTML launcher that opens the installer in the FullStacked runtime (?authority=&auth= for a BYOA sign-in). |\n| `GET` | `/v1/install-deeplink` | Public (30 requests/minute per IP) | A signed exec deeplink for the installer, valid one hour (?authority=&auth= must name a registered authority URL). |\n| `GET` | `/manifest.json` | Public | Runtime-aware Client GUI release manifest (?runtime=<version>). |\n| `GET` | `/releases/:file` | Public | Downloads a Client GUI archive cloud-client-v<version>-rt.<runtime>.tar.gz. |\n\n## docs (`https://docs.fullstacked.cloud`)\n\n| Method | Path | Auth | Summary |\n| :--- | :--- | :--- | :--- |\n| `GET` | `/` | Public | The documentation portal: HTML, ?format=md (Markdown for agents) or ?format=json. |\n| `GET` | `/:topic` | Public | One topic guide (runtime, project-runtime, me, data, ...) as HTML, md or json. |\n| `GET` | `/api/v1/endpoints` | Public | This endpoint catalog as JSON. |\n| `GET` | `/healthz` | Public | Health check. |\n| `GET` | `/version` | Public | Service version. |\n\n## Details\n\n### GET /api/v1/me\n\nProfile of the bearer's principal: identity only, never grants or resources. BYOA principals add authority_id and omit email_verified, plan and plan_name.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (AUTH_TOKEN)\n- **Aliases**: `/v1/me`\n- **In project code**: `${process.env.AUTH_URL}/api/v1/me`\n- **Pitfall**: A relative fetch(\"/api/v1/me\") reads the project's own static files: always prefix process.env.AUTH_URL.\n\nResponse:\n\n```json\n{\n  \"status\": \"success\",\n  \"data\": {\n    \"id\": \"usr_0123456789abcdef01234567\",\n    \"username\": \"alice\",\n    \"slug\": \"alice\",\n    \"name\": \"Alice\",\n    \"email\": \"alice@example.com\",\n    \"email_verified\": true,\n    \"avatar_url\": null,\n    \"type\": \"user\",\n    \"status\": \"active\",\n    \"plan\": \"base\",\n    \"plan_name\": \"Base\",\n    \"has_run_client_gui\": true,\n    \"restricted\": false,\n    \"created_at\": \"2026-09-20T14:32:00.000Z\"\n  }\n}\n```\n\nindexTs:\n\n```ts\nconst res = await fetch(`${process.env.AUTH_URL}/api/v1/me`, {\n    headers: { Authorization: `Bearer ${process.env.AUTH_TOKEN}` },\n});\nconst { data: user } = await res.json();\n```\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://auth.fullstacked.cloud/api/v1/me\"\n```\n\n### GET /api/v1/user/access\n\nEffective access of the caller: principal, user, billing, groups, global_permissions and resources (projects, tunnels, data, mcp). Narrow with ?resource_type=projects|tunnels|data|mcp.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (AUTH_TOKEN)\n- **In project code**: `${process.env.AUTH_URL}/api/v1/user/access`\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://auth.fullstacked.cloud/api/v1/user/access?resource_type=projects\"\n```\n\n### POST /v1/auth/exchange/token\n\nRedeems a single-use fsc_exc_ exchange token with its PKCE code_verifier: a BYOA sign-in returns an fsc_ath_<slug>_ session token, a session handoff a fresh fsc_ses_ token.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Public (PKCE code_verifier)\n- **In project code**: `${process.env.AUTH_URL}/v1/auth/exchange/token`\n\nRequest:\n\n```json\n{\n  \"exchangeToken\": \"fsc_exc_...\",\n  \"codeVerifier\": \"...\"\n}\n```\n\n### POST /v1/authorities/:id/token\n\nServer-to-server: an enterprise IdP mints a PKCE-bound fsc_exc_ exchange token for { userId, email?, name?, groups?, ttl?, codeChallenge, metadata? }.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator\n\n### POST /v1/authorities/:id/validate\n\nValidates an fsc_ath_<slug>_ token issued for this authority and returns its claims (userId, email, name, groups, tokenId, issuedAt, expiresAt, metadata).\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator\n\nRequest:\n\n```json\n{\n  \"token\": \"fsc_ath_acme_...\"\n}\n```\n\ncurl:\n\n```bash\ncurl -sS -X POST -H \"X-FullStacked-Authority-Key: $AUTHORITY_KEY\" -H \"Content-Type: application/json\" -d '{\"token\":\"fsc_ath_...\"}' \"https://auth.fullstacked.cloud/v1/authorities/<authorityId>/validate\"\n```\n\n### POST /v1/authorities/:id/revoke\n\nRevokes the authority's tokens by { userId }, by { token }, or { all: true }.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator\n\n### POST /v1/auth/logout\n\nRevokes the bearer token and, unless { keepClerkSession: true }, its Clerk sign-in.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nResponse:\n\n```json\n{\n  \"status\": \"revoked\"\n}\n```\n\n### GET /v1/tokens\n\nLists the caller's active tokens (sessions, PATs, agent and edge tokens); OAuth app connections are not included.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (usr_ user)\n\n### POST /v1/tokens\n\nMints a token { type: \"pat\" | \"agent\" | \"edge\", name?, ttl?, scopes? (agent), edgeId? (edge) }; the raw token is returned once (201).\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (usr_ user)\n\n### DELETE /v1/tokens/:id\n\nRevokes one of the caller's tokens.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (usr_ user)\n\n### GET /v1/billing\n\nThe caller's effective plan (base | pro), quota limits and counted usage.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /v1/grants\n\nLists grants on assets the caller manages, filtered by ?principal_id=, ?resource_type= and ?resource_id=.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (usr_ user)\n\n### PUT /v1/grants\n\nShares an asset: { principal (user id or email), resourceType, resourceId, permissions, branches?, tags? }. An unknown email becomes a pending invitation.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (usr_ user)\n\n### DELETE /v1/grants\n\nRemoves a grant, keyed by principal, resource type and id (query or JSON body).\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (usr_ user)\n\n### GET /v1/devices\n\nThe caller's registered Client GUI devices.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### PATCH /v1/devices/:id\n\nRenames one of the caller's devices ({ name }).\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /v1/devices/:id\n\nForgets a device; it is registered again on its next report.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /.well-known/oauth-authorization-server\n\nOAuth 2.1 authorization server metadata (authorize, token, register and revoke endpoints) used by MCP clients.\n\n- **Service**: `auth` (`https://auth.fullstacked.cloud`)\n- **Auth**: Public\n- **Aliases**: `/.well-known/openid-configuration`\n\n### GET /api/v1/projects/:id/env\n\nThe project's resolved runtime environment: DATA_* wiring and tokens of linked instances, platform URLs (AUTH_URL, AUTH_HOST, TUNNELS_HOST, DOCS_URL) and custom variables. ?format=json|dotenv; never contains AUTH_TOKEN.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://git.fullstacked.cloud/api/v1/projects/<projectId>/env?format=dotenv\"\n```\n\n### PUT /api/v1/projects/:id/env\n\nMerges { variables?, mappings? } into the project's environment config (null removes a key). Needs write.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"variables\": {\n    \"API_BASE\": \"https://example.com\"\n  }\n}\n```\n\n### GET /api/v1/projects/:id/actions\n\nActions compiled in a release (?tag=, default the newest release the caller can access).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/actions/:name/run\n\nRuns a release's action with the caller's own token and data wiring; answers 200 with the run result even when the run failed or timed out.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"tag\": \"v1.0.0\",\n  \"params\": {\n    \"customerId\": \"cus_123\"\n  }\n}\n```\n\ncurl:\n\n```bash\ncurl -sS -X POST -H \"Authorization: Bearer $AUTH_TOKEN\" -H \"Content-Type: application/json\" -d '{\"tag\":\"v1.0.0\",\"params\":{\"customerId\":\"cus_123\"}}' \"https://git.fullstacked.cloud/api/v1/projects/<projectId>/actions/sync-orders/run\"\n```\n\n### GET /api/v1/projects/:id/actions/runs\n\nPast action runs, filtered by ?tag=, ?action=, ?status= and ?limit=.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/actions/runs/:runId\n\nOne action run.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects\n\nProjects the caller owns or holds any tier on (?filter= on title, name, description).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects\n\nCreates a project { name, title?, description?, default_branch?, template?, owner? } (201).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id\n\nProject details (release-only consumers get the summary).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### PATCH /api/v1/projects/:id\n\nUpdates { title?, description? } (write) or renames { name } (admin).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/projects/:id\n\nDeletes the project; needs admin and { confirm: \"<owner>/<name>\" }.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### PUT /api/v1/projects/:id/data-links\n\nLinks a managed data instance to the project ({ data_instance: <UUID> }).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/projects/:id/data-links\n\nUnlinks a data instance (?data_instance=<UUID> or JSON body).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/branches\n\nBranches.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/branches\n\nCreates a branch { name, from? } (201).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/tags\n\nTags.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/tags\n\nCreates a tag { name, message?, ref? } (201); a v* tag enqueues a release build.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/validate-diagrams\n\nValidates ```mermaid blocks in the branch's Markdown files ({ branch?, file_path? }).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/files\n\nDev-state file tree (?branch=&path=&recursive=&ref=), submodules included.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/files/content\n\nReads a dev-state file (?branch=&path=&ref=).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### PUT /api/v1/projects/:id/files/content\n\nWrites a dev-state file { path, content, encoding?: utf-8 | base64, base_sha256?, branch? }, broadcast to live sync clients (needs dev_sync).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/projects/:id/files/content\n\nDeletes a dev-state file (?branch=&path=).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/diff\n\nUncommitted dev-state changes against HEAD (?branch=).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/commit\n\nCommits the dev state { message, branch? } (201; needs write).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/stash\n\nStashes the dev state { branch?, message? }.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/projects/:id/revert\n\nDiscards dev-state changes back to HEAD { branch? }.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/projects/:id/releases\n\nRelease builds (v* tags) visible to the caller.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (optional on public release projects)\n\n### GET /api/v1/projects/:id/releases/latest\n\nNewest succeeded release, with its artifact for ?runtime=<version>.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (optional on public release projects)\n\n### GET /api/v1/projects/:id/releases/tags/:tag\n\nOne release's metadata (?runtime=<version>).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (optional on public release projects)\n\n### GET /api/v1/projects/:id/releases/tags/:tag/artifact\n\nDownloads the release .tar.gz compiled for ?runtime=<version> (compiled on demand).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (optional on public release projects)\n\n### GET /api/v1/projects/:id/releases/tags/:tag/log\n\nThe release's build log (text/plain).\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (optional on public release projects)\n\n### WS /api/v1/projects/:id/sync\n\nLive dev-state file sync for ?branch= (default branch otherwise); needs dev_sync.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (or the fsc-sync subprotocol token / ?access_token=)\n\n### GET /:owner/:repo/info/refs\n\nGit Smart HTTP for the git CLI (clone, fetch, push); POST git-upload-pack and git-receive-pack follow on the same path.\n\n- **Service**: `git` (`https://git.fullstacked.cloud`)\n- **Auth**: Basic (fsc_* token as password) or Bearer fsc_*\n\nshell:\n\n```bash\ngit clone https://git.fullstacked.cloud/<owner>/<name>.git\n```\n\n### WS /\n\nMultiplexed tunnel session dialed by fullstacked/tunnel: each registered tunnel keeps a WebSocket session (more under load), authenticated once at the upgrade, and every TCP/HTTP/WS connection to the tunnel name is a stream on it, to a data instance or Edge tunnel.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Authorization: <fsc_* token>:<tun_* token> (Bearer prefix optional)\n- **In project code**: `${process.env.TUNNELS_HOST}`\n- **Pitfall**: Pass both halves joined by ':': the user's AUTH_TOKEN and the env's <ENGINE>_DATA_TOKEN or <PREFIX>_TUNNEL_TOKEN (a tun_ token).\n\nindexTs:\n\n```ts\nimport tunnel from \"fullstacked/tunnel\";\n\nconst pgHost = await tunnel.register({\n    host: process.env.TUNNELS_HOST,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.PG_DATA_TOKEN}`,\n});\n```\n\n### GET /api/v1/edges\n\nEdge machines visible to the caller.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/edges\n\nRegisters an Edge { name } (201); its daemon then authenticates with an fsc_edge_ token from auth POST /v1/tokens.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/edges/:id\n\nOne Edge.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/edges/:id/roll\n\nRotates an Edge's edg_ key (owner or can_register grantee): the lifeline is severed, tunnels and grants stay; the daemon needs a fresh fsc_edge_ token.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/edges/:id\n\nDeletes an Edge (owner or can_register grantee).\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/tunnels\n\nLAN tunnels visible to the caller (?edgeId=); managed data tunnels are not listed.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/tunnels\n\nCreates a tunnel { name, edgeId, internalHost, internalPort } under an Edge the caller manages (201).\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/tunnels/:id\n\nOne tunnel.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/tunnels/test\n\nReachability of { tunnelId }, { token }, or a candidate { edgeId, internalHost, internalPort }: for a tunnel under an Edge it reports whether the Edge daemon is connected, without probing the target.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/tunnels/:id/roll\n\nRolls the tunnel's tun_ token.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/tunnels/:id\n\nDeletes a tunnel.\n\n- **Service**: `tunnels` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/data\n\nManaged data instances visible to the caller (?engine=).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\ncurl:\n\n```bash\ncurl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://tunnels.fullstacked.cloud/api/v1/data\"\n```\n\n### POST /api/v1/data/provision\n\nProvisions an isolated instance { engine: postgres | redis | mongodb | s3, label? } (201).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"engine\": \"postgres\"\n}\n```\n\n### GET /api/v1/data/:id\n\nOne instance.\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### DELETE /api/v1/data/:id\n\nDeprovisions an instance (owner only, 204).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/data/:id/connection\n\nConnection parameters, DATA_* variables and fullstacked/tunnel snippets for ?access_level=rw|ro within the caller's permission.\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /api/v1/data/:id/schema\n\nIntrospects tables, keys, collections or buckets (?schema=).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /api/v1/data/:id/query\n\nRuns one operation: { sql, params? } (postgres), { command } (redis), { operation, collection, ... } (mongodb) or { operation, key?, prefix?, ... } (s3).\n\n- **Service**: `data` (`https://tunnels.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\nRequest:\n\n```json\n{\n  \"sql\": \"select now()\"\n}\n```\n\n### POST /mcp\n\nStreamable HTTP JSON-RPC 2.0 MCP endpoint: docs_list / docs_get (this documentation, open to every token), projects_*, data_*, tunnels_*, users_* and auth_get_my_access tools, plus fullstacked:// resources (fullstacked://docs/{topic} included).\n\n- **Service**: `mcp` (`https://mcp.fullstacked.cloud`)\n- **Auth**: Bearer fsc_* (OAuth-connected AI apps hold agent tokens)\n\nmcpUrl:\n\n```ts\nhttps://mcp.fullstacked.cloud/mcp\n```\n\n### DELETE /mcp\n\nTerminates the MCP session named by the Mcp-Session-Id header (204).\n\n- **Service**: `mcp` (`https://mcp.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /sse\n\nHTTP+SSE transport (2024-11-05): opens the event stream and announces the /messages?sessionId= endpoint.\n\n- **Service**: `mcp` (`https://mcp.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### POST /messages\n\nPosts a JSON-RPC message for an SSE session (?sessionId=); answered on the stream.\n\n- **Service**: `mcp` (`https://mcp.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /.well-known/oauth-protected-resource\n\nRFC 9728 protected resource metadata pointing AI apps to the Auth OAuth server.\n\n- **Service**: `mcp` (`https://mcp.fullstacked.cloud`)\n- **Auth**: Public\n- **Aliases**: `/.well-known/oauth-protected-resource/mcp`, `/.well-known/oauth-protected-resource/sse`\n\n### GET /api/v1/audit\n\nThe caller's own agent audit trail of MCP tool calls (?limit=, default 100).\n\n- **Service**: `mcp` (`https://mcp.fullstacked.cloud`)\n- **Auth**: Bearer fsc_*\n\n### GET /\n\nThe Client GUI installer: an ES module evaluated by the FullStacked runtime through exec; ?force=1 reinstalls.\n\n- **Service**: `install` (`https://install.fullstacked.cloud`)\n- **Auth**: Public\n\nshell:\n\n```bash\nexec https://install.fullstacked.cloud\n```\n\n### GET /deeplink\n\nHTML launcher that opens the installer in the FullStacked runtime (?authority=&auth= for a BYOA sign-in).\n\n- **Service**: `install` (`https://install.fullstacked.cloud`)\n- **Auth**: Public\n\n### GET /v1/install-deeplink\n\nA signed exec deeplink for the installer, valid one hour (?authority=&auth= must name a registered authority URL).\n\n- **Service**: `install` (`https://install.fullstacked.cloud`)\n- **Auth**: Public (30 requests/minute per IP)\n\n### GET /manifest.json\n\nRuntime-aware Client GUI release manifest (?runtime=<version>).\n\n- **Service**: `install` (`https://install.fullstacked.cloud`)\n- **Auth**: Public\n\n### GET /releases/:file\n\nDownloads a Client GUI archive cloud-client-v<version>-rt.<runtime>.tar.gz.\n\n- **Service**: `install` (`https://install.fullstacked.cloud`)\n- **Auth**: Public\n- **Aliases**: `/dist/:file`\n\n### GET /\n\nThe documentation portal: HTML, ?format=md (Markdown for agents) or ?format=json.\n\n- **Service**: `docs` (`https://docs.fullstacked.cloud`)\n- **Auth**: Public\n\ncurl:\n\n```bash\ncurl -sS \"https://docs.fullstacked.cloud/?format=md\"\n```\n\n### GET /:topic\n\nOne topic guide (runtime, project-runtime, me, data, ...) as HTML, md or json.\n\n- **Service**: `docs` (`https://docs.fullstacked.cloud`)\n- **Auth**: Public\n\n### GET /api/v1/endpoints\n\nThis endpoint catalog as JSON.\n\n- **Service**: `docs` (`https://docs.fullstacked.cloud`)\n- **Auth**: Public\n\n### GET /healthz\n\nHealth check.\n\n- **Service**: `docs` (`https://docs.fullstacked.cloud`)\n- **Auth**: Public\n\n### GET /version\n\nService version.\n\n- **Service**: `docs` (`https://docs.fullstacked.cloud`)\n- **Auth**: Public"}],"endpoints":[{"service":"auth","topic":"me","id":"auth.me","method":"GET","path":"/api/v1/me","aliases":["/v1/me"],"auth":"Bearer fsc_* (AUTH_TOKEN)","summary":"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.","inProjectUrlExpression":"${process.env.AUTH_URL}/api/v1/me","commonPitfall":"A relative fetch(\"/api/v1/me\") reads the project's own static files: always prefix process.env.AUTH_URL.","responseExample":{"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"}},"codeExamples":{"indexTs":"const res = await fetch(`${process.env.AUTH_URL}/api/v1/me`, {\n    headers: { Authorization: `Bearer ${process.env.AUTH_TOKEN}` },\n});\nconst { data: user } = await res.json();","curl":"curl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://auth.fullstacked.cloud/api/v1/me\""},"fullUrl":"https://auth.fullstacked.cloud/api/v1/me"},{"service":"auth","topic":"me","id":"auth.userAccess","method":"GET","path":"/api/v1/user/access","auth":"Bearer fsc_* (AUTH_TOKEN)","summary":"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.","inProjectUrlExpression":"${process.env.AUTH_URL}/api/v1/user/access","codeExamples":{"curl":"curl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://auth.fullstacked.cloud/api/v1/user/access?resource_type=projects\""},"fullUrl":"https://auth.fullstacked.cloud/api/v1/user/access"},{"service":"auth","topic":"auth-byoa","id":"auth.exchangeToken","method":"POST","path":"/v1/auth/exchange/token","auth":"Public (PKCE code_verifier)","summary":"Redeems a single-use fsc_exc_ exchange token with its PKCE code_verifier: a BYOA sign-in returns an fsc_ath_<slug>_ session token, a session handoff a fresh fsc_ses_ token.","inProjectUrlExpression":"${process.env.AUTH_URL}/v1/auth/exchange/token","requestExample":{"exchangeToken":"fsc_exc_...","codeVerifier":"..."},"fullUrl":"https://auth.fullstacked.cloud/v1/auth/exchange/token"},{"service":"auth","topic":"auth-byoa","id":"auth.authorityToken","method":"POST","path":"/v1/authorities/:id/token","auth":"X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator","summary":"Server-to-server: an enterprise IdP mints a PKCE-bound fsc_exc_ exchange token for { userId, email?, name?, groups?, ttl?, codeChallenge, metadata? }.","fullUrl":"https://auth.fullstacked.cloud/v1/authorities/:id/token"},{"service":"auth","topic":"auth-byoa","id":"auth.authorityValidate","method":"POST","path":"/v1/authorities/:id/validate","auth":"X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator","summary":"Validates an fsc_ath_<slug>_ token issued for this authority and returns its claims (userId, email, name, groups, tokenId, issuedAt, expiresAt, metadata).","requestExample":{"token":"fsc_ath_acme_..."},"codeExamples":{"curl":"curl -sS -X POST -H \"X-FullStacked-Authority-Key: $AUTHORITY_KEY\" -H \"Content-Type: application/json\" -d '{\"token\":\"fsc_ath_...\"}' \"https://auth.fullstacked.cloud/v1/authorities/<authorityId>/validate\""},"fullUrl":"https://auth.fullstacked.cloud/v1/authorities/:id/validate"},{"service":"auth","topic":"auth-byoa","id":"auth.authorityRevoke","method":"POST","path":"/v1/authorities/:id/revoke","auth":"X-FullStacked-Authority-Key, or Bearer fsc_* of an authority administrator","summary":"Revokes the authority's tokens by { userId }, by { token }, or { all: true }.","fullUrl":"https://auth.fullstacked.cloud/v1/authorities/:id/revoke"},{"service":"auth","topic":"api-reference","id":"auth.logout","method":"POST","path":"/v1/auth/logout","auth":"Bearer fsc_*","summary":"Revokes the bearer token and, unless { keepClerkSession: true }, its Clerk sign-in.","responseExample":{"status":"revoked"},"fullUrl":"https://auth.fullstacked.cloud/v1/auth/logout"},{"service":"auth","topic":"api-reference","id":"auth.listTokens","method":"GET","path":"/v1/tokens","auth":"Bearer fsc_* (usr_ user)","summary":"Lists the caller's active tokens (sessions, PATs, agent and edge tokens); OAuth app connections are not included.","fullUrl":"https://auth.fullstacked.cloud/v1/tokens"},{"service":"auth","topic":"api-reference","id":"auth.createToken","method":"POST","path":"/v1/tokens","auth":"Bearer fsc_* (usr_ user)","summary":"Mints a token { type: \"pat\" | \"agent\" | \"edge\", name?, ttl?, scopes? (agent), edgeId? (edge) }; the raw token is returned once (201).","fullUrl":"https://auth.fullstacked.cloud/v1/tokens"},{"service":"auth","topic":"api-reference","id":"auth.revokeToken","method":"DELETE","path":"/v1/tokens/:id","auth":"Bearer fsc_* (usr_ user)","summary":"Revokes one of the caller's tokens.","fullUrl":"https://auth.fullstacked.cloud/v1/tokens/:id"},{"service":"auth","topic":"api-reference","id":"auth.billing","method":"GET","path":"/v1/billing","auth":"Bearer fsc_*","summary":"The caller's effective plan (base | pro), quota limits and counted usage.","fullUrl":"https://auth.fullstacked.cloud/v1/billing"},{"service":"auth","topic":"api-reference","id":"auth.listGrants","method":"GET","path":"/v1/grants","auth":"Bearer fsc_* (usr_ user)","summary":"Lists grants on assets the caller manages, filtered by ?principal_id=, ?resource_type= and ?resource_id=.","fullUrl":"https://auth.fullstacked.cloud/v1/grants"},{"service":"auth","topic":"api-reference","id":"auth.putGrant","method":"PUT","path":"/v1/grants","auth":"Bearer fsc_* (usr_ user)","summary":"Shares an asset: { principal (user id or email), resourceType, resourceId, permissions, branches?, tags? }. An unknown email becomes a pending invitation.","fullUrl":"https://auth.fullstacked.cloud/v1/grants"},{"service":"auth","topic":"api-reference","id":"auth.deleteGrant","method":"DELETE","path":"/v1/grants","auth":"Bearer fsc_* (usr_ user)","summary":"Removes a grant, keyed by principal, resource type and id (query or JSON body).","fullUrl":"https://auth.fullstacked.cloud/v1/grants"},{"service":"auth","topic":"api-reference","id":"auth.listDevices","method":"GET","path":"/v1/devices","auth":"Bearer fsc_*","summary":"The caller's registered Client GUI devices.","fullUrl":"https://auth.fullstacked.cloud/v1/devices"},{"service":"auth","topic":"api-reference","id":"auth.renameDevice","method":"PATCH","path":"/v1/devices/:id","auth":"Bearer fsc_*","summary":"Renames one of the caller's devices ({ name }).","fullUrl":"https://auth.fullstacked.cloud/v1/devices/:id"},{"service":"auth","topic":"api-reference","id":"auth.removeDevice","method":"DELETE","path":"/v1/devices/:id","auth":"Bearer fsc_*","summary":"Forgets a device; it is registered again on its next report.","fullUrl":"https://auth.fullstacked.cloud/v1/devices/:id"},{"service":"auth","topic":"api-reference","id":"auth.oauthMetadata","method":"GET","path":"/.well-known/oauth-authorization-server","aliases":["/.well-known/openid-configuration"],"auth":"Public","summary":"OAuth 2.1 authorization server metadata (authorize, token, register and revoke endpoints) used by MCP clients.","fullUrl":"https://auth.fullstacked.cloud/.well-known/oauth-authorization-server"},{"service":"git","topic":"project-runtime","auth":"Bearer fsc_*","id":"git.getEnv","method":"GET","path":"/api/v1/projects/:id/env","summary":"The project's resolved runtime environment: DATA_* wiring and tokens of linked instances, platform URLs (AUTH_URL, AUTH_HOST, TUNNELS_HOST, DOCS_URL) and custom variables. ?format=json|dotenv; never contains AUTH_TOKEN.","codeExamples":{"curl":"curl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://git.fullstacked.cloud/api/v1/projects/<projectId>/env?format=dotenv\""},"fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/env"},{"service":"git","topic":"project-runtime","auth":"Bearer fsc_*","id":"git.setEnv","method":"PUT","path":"/api/v1/projects/:id/env","summary":"Merges { variables?, mappings? } into the project's environment config (null removes a key). Needs write.","requestExample":{"variables":{"API_BASE":"https://example.com"}},"fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/env"},{"service":"git","topic":"actions","auth":"Bearer fsc_*","id":"git.listActions","method":"GET","path":"/api/v1/projects/:id/actions","summary":"Actions compiled in a release (?tag=, default the newest release the caller can access).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/actions"},{"service":"git","topic":"actions","auth":"Bearer fsc_*","id":"git.runAction","method":"POST","path":"/api/v1/projects/:id/actions/:name/run","summary":"Runs a release's action with the caller's own token and data wiring; answers 200 with the run result even when the run failed or timed out.","requestExample":{"tag":"v1.0.0","params":{"customerId":"cus_123"}},"codeExamples":{"curl":"curl -sS -X POST -H \"Authorization: Bearer $AUTH_TOKEN\" -H \"Content-Type: application/json\" -d '{\"tag\":\"v1.0.0\",\"params\":{\"customerId\":\"cus_123\"}}' \"https://git.fullstacked.cloud/api/v1/projects/<projectId>/actions/sync-orders/run\""},"fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/actions/:name/run"},{"service":"git","topic":"actions","auth":"Bearer fsc_*","id":"git.listActionRuns","method":"GET","path":"/api/v1/projects/:id/actions/runs","summary":"Past action runs, filtered by ?tag=, ?action=, ?status= and ?limit=.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/actions/runs"},{"service":"git","topic":"actions","auth":"Bearer fsc_*","id":"git.getActionRun","method":"GET","path":"/api/v1/projects/:id/actions/runs/:runId","summary":"One action run.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/actions/runs/:runId"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.listProjects","method":"GET","path":"/api/v1/projects","summary":"Projects the caller owns or holds any tier on (?filter= on title, name, description).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.createProject","method":"POST","path":"/api/v1/projects","summary":"Creates a project { name, title?, description?, default_branch?, template?, owner? } (201).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.getProject","method":"GET","path":"/api/v1/projects/:id","summary":"Project details (release-only consumers get the summary).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.updateProject","method":"PATCH","path":"/api/v1/projects/:id","summary":"Updates { title?, description? } (write) or renames { name } (admin).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.deleteProject","method":"DELETE","path":"/api/v1/projects/:id","summary":"Deletes the project; needs admin and { confirm: \"<owner>/<name>\" }.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.linkData","method":"PUT","path":"/api/v1/projects/:id/data-links","summary":"Links a managed data instance to the project ({ data_instance: <UUID> }).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/data-links"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.unlinkData","method":"DELETE","path":"/api/v1/projects/:id/data-links","summary":"Unlinks a data instance (?data_instance=<UUID> or JSON body).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/data-links"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.listBranches","method":"GET","path":"/api/v1/projects/:id/branches","summary":"Branches.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/branches"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.createBranch","method":"POST","path":"/api/v1/projects/:id/branches","summary":"Creates a branch { name, from? } (201).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/branches"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.listTags","method":"GET","path":"/api/v1/projects/:id/tags","summary":"Tags.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/tags"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.createTag","method":"POST","path":"/api/v1/projects/:id/tags","summary":"Creates a tag { name, message?, ref? } (201); a v* tag enqueues a release build.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/tags"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.validateDiagrams","method":"POST","path":"/api/v1/projects/:id/validate-diagrams","summary":"Validates ```mermaid blocks in the branch's Markdown files ({ branch?, file_path? }).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/validate-diagrams"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.listFiles","method":"GET","path":"/api/v1/projects/:id/files","summary":"Dev-state file tree (?branch=&path=&recursive=&ref=), submodules included.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/files"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.readFile","method":"GET","path":"/api/v1/projects/:id/files/content","summary":"Reads a dev-state file (?branch=&path=&ref=).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/files/content"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.writeFile","method":"PUT","path":"/api/v1/projects/:id/files/content","summary":"Writes a dev-state file { path, content, encoding?: utf-8 | base64, base_sha256?, branch? }, broadcast to live sync clients (needs dev_sync).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/files/content"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.deleteFile","method":"DELETE","path":"/api/v1/projects/:id/files/content","summary":"Deletes a dev-state file (?branch=&path=).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/files/content"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.diff","method":"GET","path":"/api/v1/projects/:id/diff","summary":"Uncommitted dev-state changes against HEAD (?branch=).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/diff"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.commit","method":"POST","path":"/api/v1/projects/:id/commit","summary":"Commits the dev state { message, branch? } (201; needs write).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/commit"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.stash","method":"POST","path":"/api/v1/projects/:id/stash","summary":"Stashes the dev state { branch?, message? }.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/stash"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_*","id":"git.revert","method":"POST","path":"/api/v1/projects/:id/revert","summary":"Discards dev-state changes back to HEAD { branch? }.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/revert"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_* (optional on public release projects)","id":"git.listReleases","method":"GET","path":"/api/v1/projects/:id/releases","summary":"Release builds (v* tags) visible to the caller.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/releases"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_* (optional on public release projects)","id":"git.latestRelease","method":"GET","path":"/api/v1/projects/:id/releases/latest","summary":"Newest succeeded release, with its artifact for ?runtime=<version>.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/releases/latest"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_* (optional on public release projects)","id":"git.getRelease","method":"GET","path":"/api/v1/projects/:id/releases/tags/:tag","summary":"One release's metadata (?runtime=<version>).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/releases/tags/:tag"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_* (optional on public release projects)","id":"git.releaseArtifact","method":"GET","path":"/api/v1/projects/:id/releases/tags/:tag/artifact","summary":"Downloads the release .tar.gz compiled for ?runtime=<version> (compiled on demand).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/releases/tags/:tag/artifact"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_* (optional on public release projects)","id":"git.releaseLog","method":"GET","path":"/api/v1/projects/:id/releases/tags/:tag/log","summary":"The release's build log (text/plain).","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/releases/tags/:tag/log"},{"service":"git","topic":"api-reference","auth":"Bearer fsc_* (or the fsc-sync subprotocol token / ?access_token=)","id":"git.sync","method":"WS","path":"/api/v1/projects/:id/sync","summary":"Live dev-state file sync for ?branch= (default branch otherwise); needs dev_sync.","fullUrl":"https://git.fullstacked.cloud/api/v1/projects/:id/sync"},{"service":"git","topic":"api-reference","auth":"Basic (fsc_* token as password) or Bearer fsc_*","id":"git.smartHttp","method":"GET","path":"/:owner/:repo/info/refs","summary":"Git Smart HTTP for the git CLI (clone, fetch, push); POST git-upload-pack and git-receive-pack follow on the same path.","codeExamples":{"shell":"git clone https://git.fullstacked.cloud/<owner>/<name>.git"},"fullUrl":"https://git.fullstacked.cloud/:owner/:repo/info/refs"},{"service":"tunnels","topic":"tunnels","auth":"Authorization: <fsc_* token>:<tun_* token> (Bearer prefix optional)","id":"tunnels.session","method":"WS","path":"/","summary":"Multiplexed tunnel session dialed by fullstacked/tunnel: each registered tunnel keeps a WebSocket session (more under load), authenticated once at the upgrade, and every TCP/HTTP/WS connection to the tunnel name is a stream on it, to a data instance or Edge tunnel.","inProjectUrlExpression":"${process.env.TUNNELS_HOST}","commonPitfall":"Pass both halves joined by ':': the user's AUTH_TOKEN and the env's <ENGINE>_DATA_TOKEN or <PREFIX>_TUNNEL_TOKEN (a tun_ token).","codeExamples":{"indexTs":"import tunnel from \"fullstacked/tunnel\";\n\nconst pgHost = await tunnel.register({\n    host: process.env.TUNNELS_HOST,\n    authorization: `${process.env.AUTH_TOKEN}:${process.env.PG_DATA_TOKEN}`,\n});"},"fullUrl":"https://tunnels.fullstacked.cloud/"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.listEdges","method":"GET","path":"/api/v1/edges","summary":"Edge machines visible to the caller.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/edges"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.createEdge","method":"POST","path":"/api/v1/edges","summary":"Registers an Edge { name } (201); its daemon then authenticates with an fsc_edge_ token from auth POST /v1/tokens.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/edges"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.getEdge","method":"GET","path":"/api/v1/edges/:id","summary":"One Edge.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/edges/:id"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.rollEdge","method":"POST","path":"/api/v1/edges/:id/roll","summary":"Rotates an Edge's edg_ key (owner or can_register grantee): the lifeline is severed, tunnels and grants stay; the daemon needs a fresh fsc_edge_ token.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/edges/:id/roll"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.deleteEdge","method":"DELETE","path":"/api/v1/edges/:id","summary":"Deletes an Edge (owner or can_register grantee).","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/edges/:id"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.listTunnels","method":"GET","path":"/api/v1/tunnels","summary":"LAN tunnels visible to the caller (?edgeId=); managed data tunnels are not listed.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/tunnels"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.createTunnel","method":"POST","path":"/api/v1/tunnels","summary":"Creates a tunnel { name, edgeId, internalHost, internalPort } under an Edge the caller manages (201).","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/tunnels"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.getTunnel","method":"GET","path":"/api/v1/tunnels/:id","summary":"One tunnel.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/tunnels/:id"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.testTunnel","method":"POST","path":"/api/v1/tunnels/test","summary":"Reachability of { tunnelId }, { token }, or a candidate { edgeId, internalHost, internalPort }: for a tunnel under an Edge it reports whether the Edge daemon is connected, without probing the target.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/tunnels/test"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.rollTunnel","method":"POST","path":"/api/v1/tunnels/:id/roll","summary":"Rolls the tunnel's tun_ token.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/tunnels/:id/roll"},{"service":"tunnels","topic":"tunnels","auth":"Bearer fsc_*","id":"tunnels.deleteTunnel","method":"DELETE","path":"/api/v1/tunnels/:id","summary":"Deletes a tunnel.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/tunnels/:id"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.list","method":"GET","path":"/api/v1/data","summary":"Managed data instances visible to the caller (?engine=).","codeExamples":{"curl":"curl -sS -H \"Authorization: Bearer $AUTH_TOKEN\" \"https://tunnels.fullstacked.cloud/api/v1/data\""},"fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.provision","method":"POST","path":"/api/v1/data/provision","summary":"Provisions an isolated instance { engine: postgres | redis | mongodb | s3, label? } (201).","requestExample":{"engine":"postgres"},"fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/provision"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.get","method":"GET","path":"/api/v1/data/:id","summary":"One instance.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.deprovision","method":"DELETE","path":"/api/v1/data/:id","summary":"Deprovisions an instance (owner only, 204).","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.connection","method":"GET","path":"/api/v1/data/:id/connection","summary":"Connection parameters, DATA_* variables and fullstacked/tunnel snippets for ?access_level=rw|ro within the caller's permission.","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id/connection"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.schema","method":"GET","path":"/api/v1/data/:id/schema","summary":"Introspects tables, keys, collections or buckets (?schema=).","fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id/schema"},{"service":"data","topic":"data","auth":"Bearer fsc_*","id":"data.query","method":"POST","path":"/api/v1/data/:id/query","summary":"Runs one operation: { sql, params? } (postgres), { command } (redis), { operation, collection, ... } (mongodb) or { operation, key?, prefix?, ... } (s3).","requestExample":{"sql":"select now()"},"fullUrl":"https://tunnels.fullstacked.cloud/api/v1/data/:id/query"},{"service":"mcp","topic":"api-reference","id":"mcp.post","method":"POST","path":"/mcp","auth":"Bearer fsc_* (OAuth-connected AI apps hold agent tokens)","summary":"Streamable HTTP JSON-RPC 2.0 MCP endpoint: docs_list / docs_get (this documentation, open to every token), projects_*, data_*, tunnels_*, users_* and auth_get_my_access tools, plus fullstacked:// resources (fullstacked://docs/{topic} included).","codeExamples":{"mcpUrl":"https://mcp.fullstacked.cloud/mcp"},"fullUrl":"https://mcp.fullstacked.cloud/mcp"},{"service":"mcp","topic":"api-reference","id":"mcp.delete","method":"DELETE","path":"/mcp","auth":"Bearer fsc_*","summary":"Terminates the MCP session named by the Mcp-Session-Id header (204).","fullUrl":"https://mcp.fullstacked.cloud/mcp"},{"service":"mcp","topic":"api-reference","id":"mcp.sse","method":"GET","path":"/sse","auth":"Bearer fsc_*","summary":"HTTP+SSE transport (2024-11-05): opens the event stream and announces the /messages?sessionId= endpoint.","fullUrl":"https://mcp.fullstacked.cloud/sse"},{"service":"mcp","topic":"api-reference","id":"mcp.messages","method":"POST","path":"/messages","auth":"Bearer fsc_*","summary":"Posts a JSON-RPC message for an SSE session (?sessionId=); answered on the stream.","fullUrl":"https://mcp.fullstacked.cloud/messages"},{"service":"mcp","topic":"api-reference","id":"mcp.resourceMetadata","method":"GET","path":"/.well-known/oauth-protected-resource","aliases":["/.well-known/oauth-protected-resource/mcp","/.well-known/oauth-protected-resource/sse"],"auth":"Public","summary":"RFC 9728 protected resource metadata pointing AI apps to the Auth OAuth server.","fullUrl":"https://mcp.fullstacked.cloud/.well-known/oauth-protected-resource"},{"service":"mcp","topic":"api-reference","id":"mcp.audit","method":"GET","path":"/api/v1/audit","auth":"Bearer fsc_*","summary":"The caller's own agent audit trail of MCP tool calls (?limit=, default 100).","fullUrl":"https://mcp.fullstacked.cloud/api/v1/audit"},{"service":"install","topic":"api-reference","id":"install.script","method":"GET","path":"/","auth":"Public","summary":"The Client GUI installer: an ES module evaluated by the FullStacked runtime through exec; ?force=1 reinstalls.","codeExamples":{"shell":"exec https://install.fullstacked.cloud"},"fullUrl":"https://install.fullstacked.cloud/"},{"service":"install","topic":"api-reference","id":"install.deeplinkPage","method":"GET","path":"/deeplink","auth":"Public","summary":"HTML launcher that opens the installer in the FullStacked runtime (?authority=&auth= for a BYOA sign-in).","fullUrl":"https://install.fullstacked.cloud/deeplink"},{"service":"install","topic":"api-reference","id":"install.signedDeeplink","method":"GET","path":"/v1/install-deeplink","auth":"Public (30 requests/minute per IP)","summary":"A signed exec deeplink for the installer, valid one hour (?authority=&auth= must name a registered authority URL).","fullUrl":"https://install.fullstacked.cloud/v1/install-deeplink"},{"service":"install","topic":"api-reference","id":"install.manifest","method":"GET","path":"/manifest.json","auth":"Public","summary":"Runtime-aware Client GUI release manifest (?runtime=<version>).","fullUrl":"https://install.fullstacked.cloud/manifest.json"},{"service":"install","topic":"api-reference","id":"install.release","method":"GET","path":"/releases/:file","aliases":["/dist/:file"],"auth":"Public","summary":"Downloads a Client GUI archive cloud-client-v<version>-rt.<runtime>.tar.gz.","fullUrl":"https://install.fullstacked.cloud/releases/:file"},{"service":"docs","topic":"api-reference","id":"docs.index","method":"GET","path":"/","auth":"Public","summary":"The documentation portal: HTML, ?format=md (Markdown for agents) or ?format=json.","codeExamples":{"curl":"curl -sS \"https://docs.fullstacked.cloud/?format=md\""},"fullUrl":"https://docs.fullstacked.cloud/"},{"service":"docs","topic":"api-reference","id":"docs.topic","method":"GET","path":"/:topic","auth":"Public","summary":"One topic guide (runtime, project-runtime, me, data, ...) as HTML, md or json.","fullUrl":"https://docs.fullstacked.cloud/:topic"},{"service":"docs","topic":"api-reference","id":"docs.endpoints","method":"GET","path":"/api/v1/endpoints","auth":"Public","summary":"This endpoint catalog as JSON.","fullUrl":"https://docs.fullstacked.cloud/api/v1/endpoints"},{"service":"docs","topic":"api-reference","id":"docs.healthz","method":"GET","path":"/healthz","auth":"Public","summary":"Health check.","fullUrl":"https://docs.fullstacked.cloud/healthz"},{"service":"docs","topic":"api-reference","id":"docs.version","method":"GET","path":"/version","auth":"Public","summary":"Service version.","fullUrl":"https://docs.fullstacked.cloud/version"}]}