# Management HTTP API (/connectors/rabbitmq/reference/management-api)



The connector can serve an **opt-in, read-mostly subset of RabbitMQ's management HTTP API** —
`/api/overview`, `/api/queues`, `/api/exchanges`, `/api/bindings`, `/api/aliveness-test`, plus
`definitions.json` export and import — so tools written against RabbitMQ (`rabbitmqadmin`, CI
health checks, Terraform providers, ops scripts) keep working. It runs on a **second, dedicated
HTTP listener**, never the dashboard's own `/api`, and shapes its replies on RabbitMQ 4.3.4,
measured against a real broker.

## Enabling [#enabling]

| Field                               | Default                                  | Env var                             |
| ----------------------------------- | ---------------------------------------- | ----------------------------------- |
| `Connectors.Amqp.Management.Enable` | `false`                                  | `CONNECTORS_AMQP_MANAGEMENT_ENABLE` |
| `Connectors.Amqp.Management.Port`   | `15672` (RabbitMQ's own management port) | `CONNECTORS_AMQP_MANAGEMENT_PORT`   |

```toml title="config.toml"
[Connectors.Amqp.Management]
  Enable = true
  Port = 15672
```

The listener starts only **beside a running AMQP 0-9-1 connector**: if it is enabled but the
connector did not start (disabled, or it failed open), the server logs a warning and continues
without the management listener. A bind failure on the management port is a soft-fail like every
other AMQP listener — logged, and the server continues with everything else running.

**Port conflicts** are refused at boot and on every settings save: the management port must
differ from the core gRPC/REST/HTTP ports, the shared AMQP 5672/5671 ports, and the MQTT, STOMP,
AWS, GCP and Kafka ports. The check runs whenever `Management.Enable = true`, independent of
`Connectors.Amqp.Enable`.

**TLS follows the server-wide security mode, on the same port.** There is no separate TLS port:
when the global `Security` block configures a mode other than none, the listener serves TLS or
mutual TLS on the one configured port; otherwise it serves plain HTTP.

While the broker is not ready, every request — including one with no `Authorization` header —
gets `503` with `{"error":"service_unavailable","reason":"broker not ready"}`.

## Authentication [#authentication]

HTTP Basic, checked against `Connectors.Amqp.Credentials` — the same connector-local credential
store SASL `PLAIN`/`AMQPLAIN` uses, in the same order:

* **A credential store is configured:** both the username and password are checked against it.
* **No store, but `Authentication.Enable = true`:** the Basic **password** must be a valid KubeMQ
  platform token; the username is not checked.
* **No store and `Authentication.Enable = false`:** any credentials are accepted.

**No certificate logins.** SASL `EXTERNAL` has no equivalent here — HTTP Basic is the only
mechanism this listener offers, whatever `Connectors.Amqp.SslCertLogin` is set to.

A wrong password, an unknown username, and an empty username all produce the byte-identical
reply, so a client can never use the response to enumerate valid usernames:

```json
{"error":"not_authorized","reason":"Not_Authorized"}
```

A request with no `Authorization` header gets an empty body and the challenge header
`WWW-Authenticate: Basic realm="RabbitMQ Management"` with status `401`.

## Tags [#tags]

Every endpoint requires **any one** of RabbitMQ's four management-class tags on the
authenticated user — `management`, `policymaker`, `monitoring`, `administrator` — configured per
credential via `Connectors.Amqp.Credentials[].Tags`. A store user with none of the four is
refused on every route:

```json
{"error":"not_authorised","reason":"Not management user"}
```

`GET`/`POST /api/definitions` additionally require the `administrator` tag; a management-class
user without it gets `{"error":"not_authorised","reason":"Not administrator user"}`.

**A platform-token login, and the no-store/no-authentication login, both count as
`administrator`** — by policy, not by a tag they carry — so they reach every endpoint,
including both definitions routes.

`monitoring` (or `administrator`) widens `GET /api/overview`'s `object_totals`/`queue_totals` to
**every** vhost rather than just the caller's own visible set. It does **not** widen any object
list — measured RabbitMQ behavior, not a KubeMQ choice.

## Visibility [#visibility]

A store user's `Permissions` entries (see
[Users and permissions](/connectors/rabbitmq/how-to/users-and-permissions)) also scope what the
listener shows: a user holding at least one entry sees only the vhosts it has an entry for (a
wildcard `Vhost: ""` entry sees every vhost); a user with no entries, a platform token, and the
no-authentication login all see every vhost. A store user tagged `administrator` also sees every
vhost regardless of its entries, as RabbitMQ 4.3.4 does.

When platform (Casbin) authorization is enabled, list endpoints additionally filter through the
same `Read` check the wire runs — a queue absent from `/api/queues` because the caller's policy
denies `Read` on it is the same decision `basic.consume` would make.

Two 404 bodies, for two different reasons:

| When                                                                                         | Body                                                |
| -------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| The caller cannot see the vhost or object at all — whether or not it exists                  | `{"error":"not_found","reason":"Not Found"}`        |
| The caller can see the vhost, but the named object does not exist; or the path names nothing | `{"error":"Object Not Found","reason":"Not Found"}` |

RabbitMQ tells a scoped caller when a vhost does not exist; this connector never makes that
distinction, so the body never discloses existence to a caller not permitted to see it.

## Endpoints [#endpoints]

| Method & path                                                                   | Fields emitted                                                                                                                                                                                                                                           |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/overview`                                                             | `cluster_name`, `exchange_types`, `management_version`, `object_totals{channels, connections, exchanges, queues[, consumers]}`, `product_name`, `product_version`, `queue_totals{messages, messages_ready, messages_unacknowledged}`, `rabbitmq_version` |
| `GET /api/queues`, `/api/queues/{vhost}`, `/api/queues/{vhost}/{name}`          | `arguments`, `auto_delete`, `consumers`, `durable`, `exclusive`, `messages`, `messages_ready`, `messages_unacknowledged`, `name`, `state` (always `"running"`), `type` (the declared `x-queue-type`, else `"classic"`), `vhost`                          |
| `GET /api/exchanges`, `/api/exchanges/{vhost}`, `/api/exchanges/{vhost}/{name}` | `arguments`, `auto_delete`, `durable`, `internal`, `name`, `type`, `vhost` — includes the pre-declared exchanges                                                                                                                                         |
| `GET /api/bindings`, `/api/bindings/{vhost}`                                    | `source`, `vhost`, `destination`, `destination_type`, `routing_key`, `arguments`, `properties_key` — includes one synthesized default-exchange binding per queue                                                                                         |
| `GET /api/aliveness-test/{vhost}`                                               | `{"status":"ok"}` on success; `{"status":"failed","reason":"<text>"}` on failure (`503`)                                                                                                                                                                 |
| `GET /api/definitions`                                                          | administrator only — see [Definitions export](#definitions-export)                                                                                                                                                                                       |
| `POST /api/definitions`                                                         | administrator only — see [Definitions import](#definitions-import)                                                                                                                                                                                       |

**Message counts** come from the same computation the dashboard uses, so they can lag the true
state by up to one snapshot refresh (about 5 seconds) — the same lag RabbitMQ's own default
stats emission has. **Connection and channel counts in `object_totals` are node-local**;
topology and message counts are cluster-wide.

**`properties_key`** is RabbitMQ's own form encoding of the routing key: `[A-Za-z0-9._-]` kept,
a space becomes `+`, every other byte becomes `%` plus two capital hex digits (`orders.#` →
`orders.%23`), and an empty key encodes to `~`. A binding that carries arguments omits it.

`product_name` is `KubeMQ`, never `RabbitMQ`. `rabbitmq_version`, `management_version` and
`rabbit_version` are all `4.3.4` — the API level these shapes mirror, for tools that branch on
the version string, not a claim about what product answered.

## Aliveness [#aliveness]

`GET /api/aliveness-test/{vhost}` runs a **real round-trip** through the connector's own
topology and message path: it declares a server-named queue (`amq.gen-…`, never the literal
`aliveness-test`, so it cannot collide with a queue you own), publishes one message to it
through the default exchange, receives it back and compares the body, then deletes the queue —
under a 3-second deadline for the whole round-trip.

The check needs a real permission on the vhost, not just visibility: a store user with
`Permissions` entries, none covering the vhost, is refused — `401` with
`{"error":"not_authorised","reason":"Access refused."}` if it can still see the vhost (an
administrator), the usual `404` otherwise.

## Definitions export [#definitions-export]

`GET /api/definitions`, **administrator only**. Contains `vhosts`, `queues`, `exchanges`
(excluding the pre-declared ones), `bindings`, `users` (`name` and `tags` **only**), and
`permissions` (exact-vhost entries only — a wildcard `Vhost: ""` entry has no RabbitMQ form and
is omitted). `topic_permissions`, `policies`, `parameters` and `global_parameters` are always
empty arrays.

**The export never contains a password, a password hash, or a hashing algorithm name.**

## Definitions import [#definitions-import]

`POST /api/definitions`, **administrator only**, applies each entry through the **same
declare/bind code path an AMQP client's own `queue.declare`/`exchange.declare`/`queue.bind`
takes** — the same argument validation, the same durable persistence, the same cluster
replication. Administrator import bypasses the connector's own permission layer for every
create, mirroring RabbitMQ; platform (Casbin) authorization still runs per entry.

**Every entry in the request body is attempted, in order — vhosts, users, permissions,
exchanges, queues, bindings — and one entry's refusal never stops the ones after it.** RabbitMQ
stops at the first error; this connector deliberately does not, so a migration run reports
everything that needs fixing in one pass.

* **`204 No Content`**: every entry applied, with no caveat.
* **`400 Bad Request`**: every entry was still attempted — read `applied`, `unapplied` and
  `report` to find out which:

```json
{
  "error": "bad_request",
  "reason": "<the first entry that was not applied>",
  "applied": 3,
  "unapplied": 2,
  "report": [
    {"kind": "policy", "vhost": "m3v", "name": "m3policy", "applied": false, "reason": "policies are not supported"}
  ]
}
```

**Users and permissions are configuration, not runtime state — import never writes
`Connectors.Amqp.Credentials`.** A `users[]` entry counts as applied only when the configured
store already holds that username with the same tag set, and — when the entry carries a
`password_hash` — that hash verifies against the configured password
(`rabbit_password_hashing_sha256` and `sha512` are verified; `md5` and unknown algorithms are
reported by name, unverified). Anything not already matching is reported, naming the config path
to edit. See the migration recipe in
[Moving topology with definitions export/import](/connectors/rabbitmq/reference/migration-from-rabbitmq#moving-topology-with-definitions-exportimport).

**Reported and not applied:** policies, runtime parameters, topic permissions, operator
policies, vhost limits, and an unsupported exchange type. Exchange-to-exchange bindings
(`destination_type: "exchange"`) **are** applied.

**Reported but still applied** (a warning): a queue declared with an inert argument is still
created; the argument is named in `report` with `applied: true`. `x-queue-type: "classic"` and
`x-queue-mode` are not reported at all.

**Silently accepted as RabbitMQ export noise:** an empty `global_parameters` `cluster_tags`
value, `internal_cluster_id`, the `amq.rabbitmq.log`/`amq.rabbitmq.trace` exchanges, and a
queue's `x-queue-type: "classic"` / `x-queue-mode` arguments — every fresh RabbitMQ export
carries them, and reporting them would mean no real migration ever reaches `204`.

## Not implemented [#not-implemented]

A tool that assumes RabbitMQ parity needs to know the boundary before it hits it:

* **The management UI is not served** — this listener answers JSON only.
* **Every write endpoint except `POST /api/definitions`**: no `PUT`/`DELETE` for queues,
  exchanges, bindings, users, permissions, vhosts or policies.
* **`/api/connections`, `/api/channels`, `/api/nodes`, `/api/vhosts`, `/api/whoami`,
  `/api/health/checks/*`** — not served.
* **Pagination (`page`, `page_size`) and `columns=`** — every list always answers in full.
* **Rates, `message_stats`, memory and node fields** are not emitted.
* **The firehose tracer and log forwarder** behind `amq.rabbitmq.trace` and `amq.rabbitmq.log`:
  both exchanges exist and are listed, but nothing is ever published to them.
* **A known path with an unsupported HTTP method answers `405`** with an empty body and the
  measured `Allow` header, never the `404` an unregistered path gets.

**Every field or endpoint in this list is simply absent from a reply, never emitted as a zero
or a placeholder** — a tool checking for a field's presence sees it is missing rather than being
misled by a confident-looking `0`.

## Related [#related]

<Cards>
  <Card title="Users and permissions" href="/connectors/rabbitmq/how-to/users-and-permissions" description="The credential store this listener authenticates against, the per-vhost permission triple, and tags." />

  <Card title="Migrating from RabbitMQ" href="/connectors/rabbitmq/reference/migration-from-rabbitmq" description="The definitions export/import recipe and the full deviation list." />

  <Card title="Configuration reference" href="/connectors/rabbitmq/reference/configuration" description="Management.Enable, Management.Port, and every other Connectors.Amqp.* field." />

  <Card title="Connections & Observability" href="/connectors/rabbitmq/reference/connections-endpoint" description="The dashboard's own /api/amqp endpoints, metrics, and audit events — a different listener from this one." />
</Cards>
