{
  "generated_at": "2026-10-13T15:51:31Z",
  "summary": "3pm is a fast, robust process manager written in Rust, with the pm2 workflow (same commands, ecosystem.config.js works): one binary that is both CLI and daemon, zero-downtime self-update, process trees, host metrics. Application metrics, events and remote actions come from SDKs for Node.js, Python, C++ and Rust. `3pm link` joins a host to 3pm-backend, a fleet backend (buckets of hosts, live dashboard, JSON HTTP API, SSE feed).",
  "llms_txt": "https://3pm.edgecr4ft.net/llms.txt",
  "llms_full_txt": "https://3pm.edgecr4ft.net/llms-full.txt",
  "release": {
    "version": "0.64.0",
    "published_at": "2026-10-13T15:50:11Z",
    "install": "curl -fsSL https://3pm.edgecr4ft.net/install.sh | sh",
    "checksums": "https://3pm.edgecr4ft.net/v0.64.0/checksums.txt",
    "assets": [
      {
        "target": "macos-arm64",
        "os": "macos",
        "arch": "arm64",
        "libc": "system",
        "size": 18169597,
        "sha256": "76bbcb5e2b0a51ac7485ea1452a63ecae842b054ec089f34d3e1f0d09c831aca",
        "url": "https://3pm.edgecr4ft.net/v0.64.0/3pm-macos-arm64.tar.gz"
      },
      {
        "target": "linux-x86_64",
        "os": "linux",
        "arch": "x86_64",
        "libc": "glibc",
        "size": 19472467,
        "sha256": "901429de8a27a6f54934acf7f3c41424ce09e0dd224d256095d56d6759d812be",
        "url": "https://3pm.edgecr4ft.net/v0.64.0/3pm-linux-x86_64.tar.gz"
      },
      {
        "target": "linux-arm64",
        "os": "linux",
        "arch": "arm64",
        "libc": "glibc",
        "size": 18224842,
        "sha256": "a204f257ace48511f7c6eb95e9058a9031563af1e830a46a0bc124dcd74e67a2",
        "url": "https://3pm.edgecr4ft.net/v0.64.0/3pm-linux-arm64.tar.gz"
      },
      {
        "target": "linux-x86_64-musl",
        "os": "linux",
        "arch": "x86_64",
        "libc": "musl",
        "size": 19516162,
        "sha256": "5964112688ee2ddb0941df999842f3f3a17a9d98e362f1d549901640b6f45b32",
        "url": "https://3pm.edgecr4ft.net/v0.64.0/3pm-linux-x86_64-musl.tar.gz"
      },
      {
        "target": "linux-arm64-musl",
        "os": "linux",
        "arch": "arm64",
        "libc": "musl",
        "size": 18282188,
        "sha256": "af0a629bc6dcf1f5f763c06f73ab5b5899707af363c03b9b10d1897e7d2ef4ab",
        "url": "https://3pm.edgecr4ft.net/v0.64.0/3pm-linux-arm64-musl.tar.gz"
      }
    ]
  },
  "sdks": [
    {
      "id": "node",
      "name": "@3pm/api",
      "version": "0.64.0",
      "install": "npm i https://3pm.edgecr4ft.net/sdk/node/3pm-api-0.64.0.tgz",
      "files": [
        {
          "url": "https://3pm.edgecr4ft.net/sdk/node/3pm-api-0.64.0.tgz",
          "size": 181020,
          "sha256": "b709716ae64c3f59df70ffe32904aa4a43db5ff3c745f8aae59ce9702f42f75e"
        }
      ],
      "versions": [
        "0.64.0",
        "0.63.0",
        "0.62.0",
        "0.61.0",
        "0.60.0",
        "0.58.0",
        "0.57.0",
        "0.56.0",
        "0.55.0",
        "0.54.0",
        "0.53.0",
        "0.52.0",
        "0.51.0",
        "0.50.0",
        "0.49.0",
        "0.48.0",
        "0.47.0",
        "0.46.0",
        "0.45.0",
        "0.44.0",
        "0.43.0",
        "0.42.0",
        "0.41.0",
        "0.40.0",
        "0.37.0",
        "0.36.0",
        "0.35.0",
        "0.34.0",
        "0.32.0",
        "0.31.0",
        "0.30.0",
        "0.29.0",
        "0.27.0",
        "0.25.0",
        "0.24.0",
        "0.23.0",
        "0.22.0",
        "0.21.0",
        "0.20.0",
        "0.19.0",
        "0.18.0",
        "0.17.0",
        "0.16.0",
        "0.15.0",
        "0.1.3",
        "0.1.2",
        "0.1.1"
      ]
    },
    {
      "id": "python",
      "name": "threepm-io",
      "version": "0.64.0",
      "install": "pip install --index-url https://3pm.edgecr4ft.net/simple/ threepm-io",
      "files": [
        {
          "url": "https://3pm.edgecr4ft.net/sdk/python/threepm_io-0.64.0-py3-none-any.whl",
          "size": 39893,
          "sha256": "8889cea5a97cc8de5660ea3a784caf6f2297fed83cb9719d0abe54b733622202"
        },
        {
          "url": "https://3pm.edgecr4ft.net/sdk/python/threepm_io-0.64.0.tar.gz",
          "size": 50993,
          "sha256": "79561cc7ed1b20b4b418eafa445e05547e30a714b112860fc347c3eec0839e19"
        }
      ],
      "versions": [
        "0.64.0",
        "0.63.0",
        "0.62.0",
        "0.61.0",
        "0.60.0",
        "0.58.0",
        "0.57.0",
        "0.56.0",
        "0.55.0",
        "0.54.0",
        "0.53.0",
        "0.52.0",
        "0.51.0",
        "0.50.0",
        "0.49.0",
        "0.48.0",
        "0.47.0",
        "0.46.0",
        "0.45.0",
        "0.44.0",
        "0.43.0",
        "0.42.0",
        "0.41.0",
        "0.40.0",
        "0.37.0",
        "0.36.0",
        "0.35.0",
        "0.34.0",
        "0.32.0",
        "0.31.0",
        "0.30.0",
        "0.29.0",
        "0.27.0",
        "0.25.0",
        "0.24.0",
        "0.23.0",
        "0.22.0",
        "0.21.0",
        "0.20.0",
        "0.19.0",
        "0.18.0",
        "0.17.0",
        "0.16.0",
        "0.15.0",
        "0.1.0"
      ]
    },
    {
      "id": "cpp",
      "name": "threepm_io",
      "version": "0.64.0",
      "install": "FetchContent_Declare(threepm_io URL https://3pm.edgecr4ft.net/sdk/cpp/threepm-io-cpp-0.64.0.tar.gz URL_HASH SHA256=c412e3532644bca19c2c84827c1e4dc90ff5184bbaa7fce912e4df4874d1443c)",
      "files": [
        {
          "url": "https://3pm.edgecr4ft.net/sdk/cpp/threepm-io-cpp-0.64.0.tar.gz",
          "size": 19309,
          "sha256": "c412e3532644bca19c2c84827c1e4dc90ff5184bbaa7fce912e4df4874d1443c"
        }
      ],
      "versions": [
        "0.64.0",
        "0.63.0",
        "0.62.0",
        "0.61.0",
        "0.60.0",
        "0.58.0",
        "0.57.0",
        "0.56.0",
        "0.55.0",
        "0.54.0",
        "0.53.0",
        "0.52.0",
        "0.51.0",
        "0.50.0",
        "0.49.0",
        "0.48.0",
        "0.47.0",
        "0.46.0",
        "0.45.0",
        "0.44.0",
        "0.43.0",
        "0.42.0",
        "0.41.0",
        "0.40.0",
        "0.37.0",
        "0.36.0",
        "0.35.0",
        "0.34.0",
        "0.32.0",
        "0.31.0",
        "0.30.0",
        "0.29.0",
        "0.27.0",
        "0.25.0",
        "0.24.0",
        "0.23.0",
        "0.22.0",
        "0.21.0",
        "0.20.0",
        "0.19.0",
        "0.18.0",
        "0.17.0",
        "0.16.0",
        "0.15.0",
        "0.1.0"
      ]
    },
    {
      "id": "rust",
      "name": "threepm-io",
      "version": "0.64.0",
      "install": "cargo add threepm-io --registry threepm (index sparse+https://3pm.edgecr4ft.net/sdk/cargo/)",
      "files": [
        {
          "url": "https://3pm.edgecr4ft.net/sdk/rust/threepm-io-0.64.0.crate",
          "size": 140910,
          "sha256": "42cea4f6c553aaf1cbfc27e72fc798ff3319992536c81002fc8cc5076475cf83"
        }
      ],
      "versions": [
        "0.64.0",
        "0.63.0",
        "0.62.0",
        "0.61.0",
        "0.60.0",
        "0.58.0",
        "0.57.0",
        "0.56.0",
        "0.55.0",
        "0.54.0",
        "0.53.0",
        "0.52.0",
        "0.51.0",
        "0.50.0",
        "0.49.0",
        "0.48.0",
        "0.47.0",
        "0.46.0",
        "0.45.0",
        "0.44.0",
        "0.43.0",
        "0.42.0",
        "0.41.0",
        "0.40.0",
        "0.37.0",
        "0.36.0",
        "0.35.0",
        "0.34.0",
        "0.32.0",
        "0.31.0",
        "0.30.0",
        "0.29.0",
        "0.27.0"
      ]
    }
  ],
  "documents": [
    {
      "section": "Start here",
      "title": "Concepts",
      "description": "The whole model in one page: daemon, home, apps and instances, statuses, supervision, metrics layers, SDKs, the fleet (accounts, buckets, nodes, tokens, calls). Read this first.",
      "path": "docs/concepts.md",
      "url": "https://3pm.edgecr4ft.net/docs/concepts.md",
      "bytes": 8170,
      "tokens_approx": 2042,
      "sha256": "b7474579bd4974288d4281754c062e5866fad7c288fbc26758ea729f025cc498"
    },
    {
      "section": "Start here",
      "title": "README",
      "description": "What 3pm is, the command table, supervision behavior, control channel, boot persistence, containers, ecosystem file, logs, architecture, divergences from pm2.",
      "path": "docs/readme.md",
      "url": "https://3pm.edgecr4ft.net/docs/readme.md",
      "bytes": 23264,
      "tokens_approx": 5816,
      "sha256": "e9d8d9246a7200eb3c32ec4a7923abecd6abe816299ba911530e930a4dea707f"
    },
    {
      "section": "Start here",
      "title": "Environment variables",
      "description": "Every environment variable: the CLI and daemon, what the daemon injects into apps, modules, SDKs, install.sh, 3pm-backend.",
      "path": "docs/environment.md",
      "url": "https://3pm.edgecr4ft.net/docs/environment.md",
      "bytes": 7991,
      "tokens_approx": 1997,
      "sha256": "85bfa83824b51d5feb8f2f3a437c26d80cd71ca76fcf140d94124b59b298d978"
    },
    {
      "section": "CLI",
      "title": "CLI reference",
      "description": "Every `3pm` command and subcommand with its full `--help`: arguments, flags, defaults, aliases. Generated from the binary.",
      "path": "docs/cli.md",
      "url": "https://3pm.edgecr4ft.net/docs/cli.md",
      "bytes": 87738,
      "tokens_approx": 21934,
      "sha256": "14aada4dfa4e007f9af927311eafa958d7d24439d4e32238a4cfdaa28d8d3994"
    },
    {
      "section": "CLI",
      "title": "Ecosystem file",
      "description": "The config file annotated field by field (TOML, JSON, YAML, ecosystem.config.js), profiles, layering, validate/apply workflow, CLI flag to field map.",
      "path": "docs/ECOSYSTEM.md",
      "url": "https://3pm.edgecr4ft.net/docs/ECOSYSTEM.md",
      "bytes": 26337,
      "tokens_approx": 6584,
      "sha256": "fc949b7eebfdac846eb078f17ddc03a2a986fc1c69fd8d4944d382571f0adcf6"
    },
    {
      "section": "CLI",
      "title": "Features",
      "description": "Every user-facing feature with examples: start targets, restart policies and pacing, crash diagnostics, watch, logs, start order, events, app metrics, actions and profiling, update handoff, hosted users, containers, resource limits, cron, process trees, Node client, modules, serve, fleet agent, fleet RPC.",
      "path": "docs/features.md",
      "url": "https://3pm.edgecr4ft.net/docs/features.md",
      "bytes": 271769,
      "tokens_approx": 67942,
      "sha256": "a53f47d9d5d7dfb8ce506fb9ea2a415ab9b90130efc6567d16fca645aaa226d2"
    },
    {
      "section": "CLI",
      "title": "Logs",
      "description": "Capture, destinations, line format, rotation policy, `3pm logs`, files under the home, `[users]` config.",
      "path": "docs/logs.md",
      "url": "https://3pm.edgecr4ft.net/docs/logs.md",
      "bytes": 18191,
      "tokens_approx": 4547,
      "sha256": "d984d05cf1e312ed0c7eb213a406a9f1098c4cf971fd6d0a87e8c5f9cff7b600"
    },
    {
      "section": "CLI",
      "title": "Resource limits",
      "description": "Soft guards (max_memory_restart, max_cpu_percent), cgroup v2 hard limits, deny_gpu, permissions.",
      "path": "docs/resources.md",
      "url": "https://3pm.edgecr4ft.net/docs/resources.md",
      "bytes": 18796,
      "tokens_approx": 4699,
      "sha256": "56dbe3b73618aac4433a61dee434a2785c2926457965bd035a8e1a28a180e155"
    },
    {
      "section": "CLI",
      "title": "Containers: 3pm runtime",
      "description": "PID 1 entrypoint: invocations, exit codes, Dockerfile shape.",
      "path": "docs/RUNTIME.md",
      "url": "https://3pm.edgecr4ft.net/docs/RUNTIME.md",
      "bytes": 6383,
      "tokens_approx": 1595,
      "sha256": "abe9e7112191ae803f76ca3e373f2b8e18807b19f17eacde4b1a9f9d29f6ffce"
    },
    {
      "section": "CLI",
      "title": "Static server: 3pm serve",
      "description": "Serve a directory or a page as a managed app: flags, pm2 serve compatibility, env variables.",
      "path": "docs/SERVE.md",
      "url": "https://3pm.edgecr4ft.net/docs/SERVE.md",
      "bytes": 5605,
      "tokens_approx": 1401,
      "sha256": "b1ad0a064031e009139d2e33dd77705852681729bf1f90b3dcb651ea7f7bcfa3"
    },
    {
      "section": "CLI",
      "title": "Modules",
      "description": "Using, writing and packaging modules (module.toml, environment, catalogue).",
      "path": "docs/modules.md",
      "url": "https://3pm.edgecr4ft.net/docs/modules.md",
      "bytes": 15924,
      "tokens_approx": 3981,
      "sha256": "6cd0779830ca307293589d1873085c08b7cbd4a353960c3097ec5c766d10d814"
    },
    {
      "section": "Metrics",
      "title": "Metrics reference",
      "description": "Per-process metrics, process trees, application metrics, daemon internals, host metrics: every field and how it is sampled.",
      "path": "docs/metrics.md",
      "url": "https://3pm.edgecr4ft.net/docs/metrics.md",
      "bytes": 50621,
      "tokens_approx": 12655,
      "sha256": "53e4a02b73041120d0f05aaab99556311a4df7d4e791bbeff2ee69600e640226"
    },
    {
      "section": "Metrics",
      "title": "Host metrics agent",
      "description": "`3pm hostmetrics`: the separate sampling process, tiers, 3pm's own metrics, lifecycle, failure semantics, knobs.",
      "path": "docs/hostmetrics.md",
      "url": "https://3pm.edgecr4ft.net/docs/hostmetrics.md",
      "bytes": 25393,
      "tokens_approx": 6348,
      "sha256": "ffbc55ba0a4467f88252555f15c4f9ce127300f4e5faf1f28b8635e312fc4381"
    },
    {
      "section": "SDKs",
      "title": "Application metrics: all SDKs",
      "description": "How app metrics work, the common surface (metrics, labels, histograms, events, guarantees), Node, Python, C++, Rust, writing your own client.",
      "path": "docs/app-metrics.md",
      "url": "https://3pm.edgecr4ft.net/docs/app-metrics.md",
      "bytes": 26856,
      "tokens_approx": 6714,
      "sha256": "d069e31482fd0bb54cca320402962a17cf63447607fbf5b2a095108edb0139e3"
    },
    {
      "section": "SDKs",
      "title": "Node.js SDK: @3pm/api",
      "description": "Programmatic client (connect, start, list, stop, restart, delete, launchBus), `io` metrics/events/actions/profiling, Electron, fleet calls io.call/io.open/io.stream, data.sock.",
      "path": "docs/sdk/node.md",
      "url": "https://3pm.edgecr4ft.net/docs/sdk/node.md",
      "bytes": 41798,
      "tokens_approx": 10449,
      "sha256": "1143fd801ab8040e4eb1557dbbdd833b9bbe2f8496c852e2423efb84f8325327"
    },
    {
      "section": "SDKs",
      "title": "Python SDK: threepm-io",
      "description": "Install from the self-hosted pip index, init, gauge/counter/meter/histogram, events, notify_error, flush/shutdown.",
      "path": "docs/sdk/python.md",
      "url": "https://3pm.edgecr4ft.net/docs/sdk/python.md",
      "bytes": 8872,
      "tokens_approx": 2218,
      "sha256": "8c37d430ee6e767e452e8029d3eeed6bd8b0d4078930f515c183ea903b695966"
    },
    {
      "section": "SDKs",
      "title": "C++ SDK: threepm_io.hpp",
      "description": "Header-only C++17, CMake FetchContent, lock-free metrics, events, errors.",
      "path": "docs/sdk/cpp.md",
      "url": "https://3pm.edgecr4ft.net/docs/sdk/cpp.md",
      "bytes": 4513,
      "tokens_approx": 1128,
      "sha256": "eb67b03f4e1ccd0872e11e8285eae674c76711c4fc8ef90cc851425c69c532fa"
    },
    {
      "section": "SDKs",
      "title": "Rust SDK: threepm-io",
      "description": "Install from the self-hosted sparse Cargo registry, metrics, events, notify_error, custom actions (JSON, files, inputs), lifecycle and serve(), bounds, differences from the Node SDK.",
      "path": "docs/sdk/rust.md",
      "url": "https://3pm.edgecr4ft.net/docs/sdk/rust.md",
      "bytes": 25246,
      "tokens_approx": 6311,
      "sha256": "726b98add6fed1ad929634a2da8685d84463a7e8cd714be9fff2af7370fd6ae9"
    },
    {
      "section": "Fleet backend (3pm-backend)",
      "title": "HTTP API: every route",
      "description": "The route index of 3pm-backend as it serves it on GET /api/v1/routes: name, method, path, body, answer, auth, doc of every route, recipes, and the metrics/events query routes in full.",
      "path": "docs/backend/api.md",
      "url": "https://3pm.edgecr4ft.net/docs/backend/api.md",
      "bytes": 45064,
      "tokens_approx": 11266,
      "sha256": "0992520f1803ebe69a9b339d4b577948c9f9b522ef42cc9abb14649f940790d4"
    },
    {
      "section": "Fleet backend (3pm-backend)",
      "title": "Flags and environment",
      "description": "`3pm-backend --help`: every flag, its environment variable and default.",
      "path": "docs/backend/cli.md",
      "url": "https://3pm.edgecr4ft.net/docs/backend/cli.md",
      "bytes": 8954,
      "tokens_approx": 2238,
      "sha256": "6ca56edb61aba49f0f939ccd059bce39cff2eee427c0352054676eddb78fbddc"
    },
    {
      "section": "Fleet backend (3pm-backend)",
      "title": "Backend features",
      "description": "Buckets and the pasted line, admitting hosts, dashboard, persisted telemetry, metrics query, custom metrics, process trees, remote actions and profiles, node identity and access, node calls, blobs, streams, nginx, JS client, accounts, redaction.",
      "path": "docs/backend/features.md",
      "url": "https://3pm.edgecr4ft.net/docs/backend/features.md",
      "bytes": 123785,
      "tokens_approx": 30946,
      "sha256": "02537282b0fcfd869c356527e16fe2b209578f3b78e6cbf1ec8a2d9cbcbcc81e"
    },
    {
      "section": "Fleet backend (3pm-backend)",
      "title": "Backend README",
      "description": "Run it in dev mode, link a host, build next to 3pm.",
      "path": "docs/backend/readme.md",
      "url": "https://3pm.edgecr4ft.net/docs/backend/readme.md",
      "bytes": 2252,
      "tokens_approx": 563,
      "sha256": "82c038c478383c18e8329ce0e1016083c5c8d693e5947fd41ee32af6ec59f707"
    },
    {
      "section": "Fleet link (3pm link)",
      "title": "Fleet link overview",
      "description": "Shape of the system agent ↔ backend, status by step, security model.",
      "path": "docs/fleet/overview.md",
      "url": "https://3pm.edgecr4ft.net/docs/fleet/overview.md",
      "bytes": 10236,
      "tokens_approx": 2559,
      "sha256": "bdf28b5d811efe342ab0b733dbce364e19771e4a248c14e5f06fa01408ce97ac"
    },
    {
      "section": "Fleet link (3pm link)",
      "title": "The agent module",
      "description": "`3pm link` usage, the supervised agent app, state directory, fleet identity, data socket.",
      "path": "docs/fleet/agent.md",
      "url": "https://3pm.edgecr4ft.net/docs/fleet/agent.md",
      "bytes": 44240,
      "tokens_approx": 11060,
      "sha256": "d35af72f1d2c45b8ab3ed167b7495ac2a8ab32315821b3602aa4d3dadcbac006"
    },
    {
      "section": "Fleet link (3pm link)",
      "title": "Link protocol 3pm.link.v1",
      "description": "Identifiers and token formats, enrollment and link WebSockets, envelope, commands, events, telemetry, app metrics, process trees, error codes.",
      "path": "docs/fleet/link-protocol.md",
      "url": "https://3pm.edgecr4ft.net/docs/fleet/link-protocol.md",
      "bytes": 59218,
      "tokens_approx": 14804,
      "sha256": "3db08466317725f76825a6cad0c9c8a92ae11725b589c673469c62d6afb81596"
    },
    {
      "section": "Optional",
      "title": "Daemon wire protocol",
      "description": "NDJSON over rpc.sock/pub.sock/ctl.sock: transport, versioning, every request, response, event and control message, --json stability.",
      "path": "docs/protocol.md",
      "url": "https://3pm.edgecr4ft.net/docs/protocol.md",
      "bytes": 59644,
      "tokens_approx": 14911,
      "sha256": "be5b8d5584988441505c892e157290de6f338ee056c9e157c02bb27537e47e2d"
    },
    {
      "section": "Optional",
      "title": "This site: what is online and how it is published",
      "description": "The layout of https://3pm.edgecr4ft.net, the three publish commands, how this documentation is generated, the nginx vhost.",
      "path": "docs/internals/site.md",
      "url": "https://3pm.edgecr4ft.net/docs/internals/site.md",
      "bytes": 14969,
      "tokens_approx": 3742,
      "sha256": "7500d63fc9afe633ba8947f6f5de7ea696d38a56e3a3c8ba3971ef37870f65d4"
    },
    {
      "section": "Optional",
      "title": "3pm changelog",
      "description": "Every change to 3pm, newest first.",
      "path": "docs/changelog.md",
      "url": "https://3pm.edgecr4ft.net/docs/changelog.md",
      "bytes": 243285,
      "tokens_approx": 60821,
      "sha256": "5e1dc6fb5d93f489f2292f6360078ca740ca6a68a7087fd36ad8f2883c6a110c"
    },
    {
      "section": "Optional",
      "title": "3pm-backend changelog",
      "description": "Every change to the backend, newest first.",
      "path": "docs/backend/changelog.md",
      "url": "https://3pm.edgecr4ft.net/docs/backend/changelog.md",
      "bytes": 139601,
      "tokens_approx": 34900,
      "sha256": "7ae8dba9235bf3d2908e274828b34a8721ff375e0da0968f1e1960369d57f12d"
    },
    {
      "section": "Optional",
      "title": "Host metrics tiers (design)",
      "description": "Audit and architecture of the tiered host sampler.",
      "path": "docs/internals/hostagent-tiers.md",
      "url": "https://3pm.edgecr4ft.net/docs/internals/hostagent-tiers.md",
      "bytes": 34182,
      "tokens_approx": 8545,
      "sha256": "cbbf10a701050cf3bd4bae4bf59cccc7ae2f51a087c970c6cd4c0902964b9d5b"
    },
    {
      "section": "Optional",
      "title": "Extensibility and pm2 feature map (design)",
      "description": "Where new features plug in, remaining pm2 surface graded.",
      "path": "docs/internals/extensibility.md",
      "url": "https://3pm.edgecr4ft.net/docs/internals/extensibility.md",
      "bytes": 8926,
      "tokens_approx": 2231,
      "sha256": "b90b204aef39f9e7c6dcf00d9caff1bf1048253c2d2c07e1d3beb599818870dc"
    }
  ],
  "backend_routes": [
    {
      "name": "info",
      "method": "GET",
      "path": "/api/v1/info",
      "kind": "json",
      "auth": "public",
      "doc": "Versions, the accepted link range, how the store is doing and what the link got wrong; no credentials needed.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/info.md"
    },
    {
      "name": "routes",
      "method": "GET",
      "path": "/api/v1/routes",
      "kind": "json",
      "auth": "public",
      "doc": "Every route this backend serves: an index for an LLM by default, with `metricsInventory` and `queryMetrics` given whole; one route in full with `?route=<name>` (details, bounds, body and answer types), the JSON table with `?format=json` or `Accept: application/json` (what this client sends); `?version=` defaults to `latest`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/routes.md"
    },
    {
      "name": "client",
      "method": "GET",
      "path": "/api/v1/client.js",
      "kind": "static",
      "auth": "public",
      "doc": "This JavaScript client (`assets/api.js`), under the version of the API it calls.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/client.md"
    },
    {
      "name": "register",
      "method": "POST",
      "path": "/api/auth/register",
      "kind": "json",
      "auth": "public",
      "doc": "Create an account: its token comes back once (there is no other way in, keep it) with a session cookie. Only the first account unless the backend runs with `--registration open`; that first account adopts every bucket without an owner.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/register.md"
    },
    {
      "name": "login",
      "method": "POST",
      "path": "/api/auth/login",
      "kind": "json",
      "auth": "public",
      "doc": "Trade an account's token for a session cookie — the dashboard's way in; a script sends the token as `Authorization: Bearer` on each request instead.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/login.md"
    },
    {
      "name": "logout",
      "method": "POST",
      "path": "/api/auth/logout",
      "kind": "json",
      "auth": "user",
      "doc": "Close this session and drop its cookie; the token stays valid.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/logout.md"
    },
    {
      "name": "me",
      "method": "GET",
      "path": "/api/auth/me",
      "kind": "json",
      "auth": "user",
      "doc": "The account behind this request: its name, its tokens (never their secrets) and how many buckets it owns.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/me.md"
    },
    {
      "name": "createUserToken",
      "method": "POST",
      "path": "/api/auth/tokens",
      "kind": "json",
      "auth": "user",
      "doc": "Another token for the account (a laptop, a CI job); the secret comes back once.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/createUserToken.md"
    },
    {
      "name": "revokeUserToken",
      "method": "DELETE",
      "path": "/api/auth/tokens/{tokenId}",
      "kind": "json",
      "auth": "user",
      "doc": "Revoke one of the account's tokens and close every session it opened; never the last one (409 `ERR_LAST_TOKEN`).",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/revokeUserToken.md"
    },
    {
      "name": "listBuckets",
      "method": "GET",
      "path": "/api/buckets",
      "kind": "json",
      "auth": "user",
      "doc": "The caller's buckets, oldest first.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/listBuckets.md"
    },
    {
      "name": "createBucket",
      "method": "POST",
      "path": "/api/buckets",
      "kind": "json",
      "auth": "user",
      "doc": "Create a bucket: a cluster id to paste, and nodes to enroll into it.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/createBucket.md"
    },
    {
      "name": "getBucket",
      "method": "GET",
      "path": "/api/buckets/{clusterId}",
      "kind": "json",
      "auth": "user",
      "doc": "One bucket with its nodes, last telemetry and history — the shape the feed opens with; `history`, `match` and `view` cut it as they cut the feed's `init`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/getBucket.md"
    },
    {
      "name": "setBucketSettings",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/settings",
      "kind": "json",
      "auth": "user",
      "doc": "Set the bucket's settings — `approval`: whether a host that links with a reusable token waits for `approveNode` (turning it off approves the hosts already waiting); `require_access_rights`: off, every active node may place a call on every other, as if each held `fleet:invoke`, `fleet:list` and `fleet:read`; `record_shells` (on by default): the screen of each shell session is kept as an asciicast artifact of its call. Answers the bucket as it now stands.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/setBucketSettings.md"
    },
    {
      "name": "bucketToken",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/enroll-token",
      "kind": "json",
      "auth": "user",
      "doc": "The bucket's permanent enrollment token and its paste line: the same at every read, never expiring, any number of hosts, not revocable. Created on the first read; its hosts wait for `approveNode` unless the bucket's `approval` is off.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/bucketToken.md"
    },
    {
      "name": "events",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/events",
      "kind": "sse",
      "auth": "user",
      "doc": "The bucket's live feed: `init`, then `node` / `telemetry` / `emit` / `call` / `artifact` — a call's state and an artifact's id and sizes only, never params, results or summaries (`getCall`, `listArtifacts`); `history`, `match` and `view` cut it to what the page draws.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/events.md"
    },
    {
      "name": "listNodes",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/nodes",
      "kind": "json",
      "auth": "user",
      "doc": "Every enrolled node of the bucket, presence layered on.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/listNodes.md"
    },
    {
      "name": "approveNode",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/approve",
      "kind": "json",
      "auth": "user",
      "doc": "Admit a pending node. Idempotent.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/approveNode.md"
    },
    {
      "name": "revokeNode",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/revoke",
      "kind": "json",
      "auth": "user",
      "doc": "Revoke a node, cut its live session and drop the bearer it was using. Idempotent.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/revokeNode.md"
    },
    {
      "name": "deleteNode",
      "method": "DELETE",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}",
      "kind": "json",
      "auth": "user",
      "doc": "Delete a node: its row, its calls and its artifacts go, its live session is cut and the feed sends `reset`. Unlike `revokeNode` nothing is held against its key — the host may enroll again, as a new node.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/deleteNode.md"
    },
    {
      "name": "setNodeAccess",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/access",
      "kind": "json",
      "auth": "user",
      "doc": "What this node may ask of the bucket with its own bearer (`scopes`), who may place a call on it (`callers`), and what it lets go agent to agent with a LAN neighbour (`direct`); no scope by default, and an empty `callers` accepts every node holding `fleet:invoke`. Replaces each list it names, and takes effect on the node's next request.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/setNodeAccess.md"
    },
    {
      "name": "listTokens",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/tokens",
      "kind": "json",
      "auth": "user",
      "doc": "Every enrollment token of the bucket; never the secrets.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/listTokens.md"
    },
    {
      "name": "createToken",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/tokens",
      "kind": "json",
      "auth": "user",
      "doc": "Mint an enrollment token. The secret and its paste line are returned once.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/createToken.md"
    },
    {
      "name": "revokeToken",
      "method": "DELETE",
      "path": "/api/buckets/{clusterId}/tokens/{tokenId}",
      "kind": "json",
      "auth": "user",
      "doc": "Revoke an enrollment token; the bucket's permanent one (`bucketToken`) is refused, 409.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/revokeToken.md"
    },
    {
      "name": "queryMetrics",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/metrics/query",
      "kind": "json",
      "auth": "user",
      "doc": "Time series for charts: metrics, filters, aggregation and step, scoped to the bucket.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/queryMetrics.md"
    },
    {
      "name": "metricsCatalog",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/metrics/catalog",
      "kind": "json",
      "auth": "user",
      "doc": "Every metric a query may name: id, kind, unit, labels, default time function.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/metricsCatalog.md"
    },
    {
      "name": "metricLabelValues",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/metrics/labels/{label}/values",
      "kind": "json",
      "auth": "user",
      "doc": "The values one label takes in this bucket over the last two UTC days, at most 5000.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/metricLabelValues.md"
    },
    {
      "name": "metricsInventory",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/metrics/inventory",
      "kind": "json",
      "auth": "user",
      "doc": "What a dashboard or an LLM starts from, in one call: the metrics the bucket holds series for, with their descriptions, its hosts and their processes — text for an LLM by default, JSON with `?format=json` or `Accept: application/json` (what this client sends).",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/metricsInventory.md"
    },
    {
      "name": "metricsByServer",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/metrics/servers",
      "kind": "json",
      "auth": "user",
      "doc": "Which metric is stored on which server, for which app: each node with its host's own metrics, then each of its apps with theirs — text for an LLM by default, JSON with `?format=json` or `Accept: application/json` (what this client sends).",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/metricsByServer.md"
    },
    {
      "name": "metricsByApp",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/metrics/apps",
      "kind": "json",
      "auth": "user",
      "doc": "Which metric each app has on which server: each app with the servers it has series on and, on each, their metrics — text for an LLM by default, JSON with `?format=json` or `Accept: application/json` (what this client sends).",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/metricsByApp.md"
    },
    {
      "name": "eventsQuery",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/events/query",
      "kind": "json",
      "auth": "user",
      "doc": "The persisted events of the bucket, newest first: a range, exact filters (kind, app, level, event_name…), a limit.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/eventsQuery.md"
    },
    {
      "name": "logsQuery",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/logs/query",
      "kind": "json",
      "auth": "user",
      "doc": "The logs the bucket's nodes shipped over a range: their sources, the templates with their counts (worst first) and the shipped lines (newest first), filtered by node, source, owner, severity, template or text.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/logsQuery.md"
    },
    {
      "name": "insights",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/insights",
      "kind": "json",
      "auth": "user",
      "doc": "What deserves a look on this fleet: incidents ranked by the insights detectors, best first — text for an LLM by default, JSON with `?format=json` or `Accept: application/json` (what this client sends).",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/insights.md"
    },
    {
      "name": "insight",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/insights/{incident}",
      "kind": "json",
      "auth": "user",
      "doc": "One incident of `insights` and the findings it groups, each with its detector, series, score and summary — text for an LLM by default, JSON with `?format=json` or `Accept: application/json`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/insight.md"
    },
    {
      "name": "nodeAppMetrics",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/app-metrics",
      "kind": "json",
      "auth": "user",
      "doc": "The application metrics of every instance of the node, as last received: sources, series, values and histogram buckets.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeAppMetrics.md"
    },
    {
      "name": "nodeActions",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/actions",
      "kind": "json",
      "auth": "user",
      "doc": "The custom actions the apps of the node declare (`io.action`), per instance and source, with descriptions and timeouts — what `nodeAction` may run; none of the metrics, so a page may poll it all the time.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeActions.md"
    },
    {
      "name": "nodeHost",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/host",
      "kind": "json",
      "auth": "user",
      "doc": "The node's host as it is now, whole: the gauges and counters with the identity (hostname, OS, totals, interface and mount names) the feed's ticks blank; 404 before its first frame, and for a node that sends no host.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeHost.md"
    },
    {
      "name": "nodeProcessTrees",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/trees",
      "kind": "json",
      "auth": "user",
      "doc": "The process trees of every instance of the node, as last received — every process and the folded picture of `3pm ls --tree` — with the count of trees refused; what the dashboard's panel polls.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeProcessTrees.md"
    },
    {
      "name": "nodeProcessTree",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/procs/{procId}/tree",
      "kind": "json",
      "auth": "user",
      "doc": "The process tree of one instance of the node, as last received: every process, and the same folded picture as `3pm ls --tree`; 404 while the backend holds none — an instance running alone, or before its first tree.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeProcessTree.md"
    },
    {
      "name": "nodeAction",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/actions",
      "kind": "json",
      "auth": "user",
      "doc": "Run a custom action an app of the node declared (`io.action`), on every instance of the app or on one, optionally carrying bytes in and out; the call is answered as soon as it is placed — follow it with `getCall` or the feed's `call` events.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeAction.md"
    },
    {
      "name": "nodeShell",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/shell",
      "kind": "json",
      "auth": "user",
      "doc": "Open an interactive login shell on the node (`3pm fleet ssh`), or run one command there (`command`, `3pm fleet ssh <node> -- <cmd>`): the answer is the call, whose `input.blob` you `PUT` keystrokes (or stdin) to and `output.blob` you `GET` the screen (or stdout and stderr) from; it replies `exited` when the shell or the command ends. Never from a page on another site.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeShell.md"
    },
    {
      "name": "nodeForward",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/forward",
      "kind": "json",
      "auth": "user",
      "doc": "Open one TCP connection from the node to `host:port` (`3pm fleet ssh -L`, one call per local connection): the answer is the call, whose `input.blob` you `PUT` the bytes to send and `output.blob` you `GET` the bytes that come back; it replies `exited` once both ways are closed. Never from a page on another site.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeForward.md"
    },
    {
      "name": "nodeListen",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/listen",
      "kind": "json",
      "auth": "user",
      "doc": "Make the node listen on a port for you (`3pm fleet ssh -R`): the answer is the call, whose sealed legs carry the port it bound and one `accepted` message per connection, each of which you take over with a `nodeForward` naming it (`accepted`); it replies `exited` when you close it. Never from a page on another site.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeListen.md"
    },
    {
      "name": "nodeCall",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/nodes/{nodeId}/calls",
      "kind": "json",
      "auth": "user",
      "doc": "Send one generic command to the node's agent — `{\"op\": \"restart\", \"target\": {\"name\": \"api\"}}`, `stop` or `ping` (an action goes through `nodeAction`); answered as soon as it is placed — follow it with `getCall`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/nodeCall.md"
    },
    {
      "name": "putCallArtifact",
      "method": "PUT",
      "path": "/api/buckets/{clusterId}/calls/{callId}/artifacts",
      "kind": "agent-http",
      "auth": "user",
      "doc": "The agent's upload of the file an action returned, before the reply that names it: raw bytes, `x-3pm-ext` and `x-3pm-instance`, answered `201 {artifact_id, bytes, sha256}` — agent only; a page reads the call's files off `getCall` or `events`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/putCallArtifact.md"
    },
    {
      "name": "getCall",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/calls/{callId}",
      "kind": "json",
      "auth": "user",
      "doc": "One remote command: its state (`sent`, `acked`, `replied`, `unacked`, `refused`, `lost`), who placed it, the blobs it carries, its result once replied (`result.ok` says the action ran) and the artifacts it left. A node's bearer sees the calls it placed itself and nothing else.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/getCall.md"
    },
    {
      "name": "listArtifacts",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/artifacts",
      "kind": "json",
      "auth": "user",
      "doc": "The files actions left (profiles, dumps), newest first, at most 200; `node` (a node id) and `app` (a name) narrow. Kept 30 days and 1 GB per bucket. A node's bearer sees what its own calls left and nothing else.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/listArtifacts.md"
    },
    {
      "name": "artifactFile",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/artifacts/{artifactId}/file",
      "kind": "static",
      "auth": "user",
      "doc": "One artifact's bytes, as they are stored: gzip (`Content-Encoding: gzip`, so a browser, `fetch` or `curl --compressed` sees the file itself — a `.cpuprofile` opens in Chrome DevTools and speedscope), or raw for the call outputs an older backend kept (`encoding: identity`, no such header). A node's bearer may only fetch what its own calls left.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/artifactFile.md"
    },
    {
      "name": "putBlob",
      "method": "PUT",
      "path": "/api/buckets/{clusterId}/blobs/{blobId}",
      "kind": "static",
      "auth": "user",
      "doc": "Write a call's bytes, raw, once — chunked or with a length; answered `201 {blob, bytes}` when the body has gone through, 409 if the blob was already written. A node writing a blob of its own call needs no scope: the blob is the authorisation, and one that is not its own is a 404.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/putBlob.md"
    },
    {
      "name": "getBlob",
      "method": "GET",
      "path": "/api/buckets/{clusterId}/blobs/{blobId}",
      "kind": "static",
      "auth": "user",
      "doc": "A call's bytes as they arrive: the headers come back before the first byte, and `Range` works once the blob is complete. A node reading a blob of its own call needs no scope: the blob is the authorisation, and one that is not its own is a 404. `?k=` (a `viewBlob` token) lets a browser tag read one, and `?as=mjpeg` renders a stream of JPEG messages for an `<img>`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/getBlob.md"
    },
    {
      "name": "viewBlob",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/blobs/{blobId}/view",
      "kind": "json",
      "auth": "user",
      "doc": "Mint the one credential a browser tag can carry: a `getBlob` URL with `?k=` on it, good for sixty seconds, for this blob, for reading — `{as: \"mjpeg\"}` adds the rendering an `<img>` needs.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/viewBlob.md"
    },
    {
      "name": "enroll",
      "method": "POST",
      "path": "/api/v1/enroll",
      "kind": "agent-http",
      "auth": "public",
      "doc": "The agent's enrollment request (`3pm link`): an enrollment token and a node key for a node id — agent only, a page reads `events`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/enroll.md"
    },
    {
      "name": "link",
      "method": "GET",
      "path": "/api/v1/link",
      "kind": "agent-ws",
      "auth": "public",
      "doc": "The agent's session WebSocket (`Authorization: 3pm-node`, sub-protocol `3pm.link`): telemetry up, commands down — a page reads `events`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/link.md"
    },
    {
      "name": "pushLogs",
      "method": "POST",
      "path": "/api/buckets/{clusterId}/logs",
      "kind": "agent-http",
      "auth": "user",
      "doc": "The agent's log batches (`3pm link`): gzip NDJSON records (`batch`, `src`, `tpl`, `cnt`, `line`, `dropped`), answered `{accepted, skipped, duplicate?, refused?}` (`refused`: new sources/templates past the node's catalog bound) — agent only, a page reads `events`.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/pushLogs.md"
    },
    {
      "name": "indexPage",
      "method": "GET",
      "path": "/",
      "kind": "static",
      "auth": "public",
      "doc": "The bundled dashboard: the bucket list.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/indexPage.md"
    },
    {
      "name": "bucketPage",
      "method": "GET",
      "path": "/b/{clusterId}",
      "kind": "static",
      "auth": "public",
      "doc": "The bundled dashboard: one bucket.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/bucketPage.md"
    },
    {
      "name": "bucketSettingsPage",
      "method": "GET",
      "path": "/b/{clusterId}/settings",
      "kind": "static",
      "auth": "public",
      "doc": "The bundled dashboard: one bucket's settings.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/bucketSettingsPage.md"
    },
    {
      "name": "asset",
      "method": "GET",
      "path": "/assets/{path}",
      "kind": "static",
      "auth": "public",
      "doc": "A file of the bundled dashboard — `api.js` among them.",
      "url": "https://3pm.edgecr4ft.net/docs/backend/routes/asset.md"
    }
  ]
}
