# Map the fleet

`GET https://api.mailmasker.org/api/v1/admin/agent/topology`

Every machine, the services on it, and which service talks to which, as a graph.

- Who can use it: Site admins
- Area: Fleet and status
- Answers 200 OK when it works
- Page: https://developer.mailmasker.org/admin-fleet-topology

For drawing a map. `nodes` holds one entry per service per machine, named `host/service`; `edges` say which talks to which, with a `kind` of proxy, http, mail or dns; `hosts` groups the same entries under the machine they run on. The connections come from where the proxies say they pass traffic, plus a fixed list of the calls the services make to each other.

## Good to know

- A service is `healthy` when it is running and its machine checked in within the last `heartbeat_stale_after_sec` seconds; a machine past that is marked `stale`.
- A service found only as somewhere a proxy passes traffic, on a machine with no agent, has `monitored` false and `healthy` null.

## Examples

cURL:

```bash
curl -s 'https://api.mailmasker.org/api/v1/admin/agent/topology' \
  -H "Authorization: Bearer $TOKEN"
```

Perl:

```perl
use Mojo::Base -strict;
use Mojo::UserAgent;

my $ua  = Mojo::UserAgent->new;
my $res = $ua->get('https://api.mailmasker.org/api/v1/admin/agent/topology',
    { Authorization => "Bearer $ENV{TOKEN}" })->result;
die $res->code, ' ', ($res->json('/error') // $res->message), "\n" unless $res->is_success;
print $res->body, "\n";
```

homelab-cli:

```bash
homelab-cli admin fleet topology
```

## Response

200 OK:

```json
{
  "heartbeat_stale_after_sec": 300,
  "hosts": [
    {
      "hostname": "prod-homelab42",
      "address": "192.0.2.42",
      "agent_version": "0.1.9",
      "last_heartbeat": "2026-11-03 21:42:10.123456-06",
      "stale": false,
      "monitored": true,
      "services": [
        { "id": "prod-homelab42/roundcube-nginx", "host": "prod-homelab42", "service": "roundcube-nginx",
          "kind": "http_service", "package": "homelab-roundcube", "address": "192.0.2.42",
          "expected": true, "actual": true, "healthy": true, "monitored": true,
          "description": "Roundcube's internal-only nginx vhost, bridging homelab-webproxy to PHP-FPM" },
        { "id": "prod-homelab42/roundcube-php-fpm", "host": "prod-homelab42", "service": "roundcube-php-fpm",
          "kind": "http_service", "package": "homelab-roundcube", "address": "192.0.2.42",
          "expected": true, "actual": true, "healthy": true, "monitored": true,
          "description": "PHP-FPM pool serving Roundcube itself" }
      ]
    }
  ],
  "nodes": [
    { "id": "prod-homelab42/roundcube-nginx", "host": "prod-homelab42", "service": "roundcube-nginx",
      "kind": "http_service", "package": "homelab-roundcube", "address": "192.0.2.42",
      "expected": true, "actual": true, "healthy": true, "monitored": true,
      "description": "Roundcube's internal-only nginx vhost, bridging homelab-webproxy to PHP-FPM" },
    { "id": "prod-homelab42/roundcube-php-fpm", "host": "prod-homelab42", "service": "roundcube-php-fpm",
      "kind": "http_service", "package": "homelab-roundcube", "address": "192.0.2.42",
      "expected": true, "actual": true, "healthy": true, "monitored": true,
      "description": "PHP-FPM pool serving Roundcube itself" }
  ],
  "edges": [
    { "from": "prod-homelab42/roundcube-nginx", "to": "prod-homelab42/roundcube-php-fpm", "kind": "http" }
  ]
}
```
