Skip to content

Public Interface

Vexillum includes a public API for exposing reflector information without exposing administrative controls.

It is not a bundled website. The public listener serves an unauthenticated, read-only JSON REST + Server-Sent Events (SSE) API under /api/public/v1 - instance inventory, per-mode runtime stats, connected clients, and recent call history. GET / just redirects to interactive Swagger docs at /api/public/v1/docs. There is no server-rendered status page baked into Vexillum; if you want one, build it against this API or link to a community dashboard that does. See the Public API Reference for the full endpoint list and response shapes.

It is separate from the admin interface. That separation matters.

The public API gives operators a safe, read-only surface to publish user-facing reflector information without handing out admin credentials.

It can expose:

  • Available reflector modes and instances
  • Per-instance runtime state and connection details
  • Connected clients and active calls
  • Recent last-heard history
  • A live activity feed merged across every instance
  • A lean liveness/uptime feed purpose-built for third-party monitors

The goal is to help users (or a dashboard built on their behalf) confirm what’s available and whether the system appears to be online, without giving them access to administrative tools, internal metrics, or sensitive runtime details.

If this reflector is publicly listed on a directory or aggregator site (DVRef and similar), strongly consider exposing at least /api/public/v1/liveness (see Public API Reference) even if you don’t build or link to any dashboard.

Directory sites periodically probe listed reflectors to show an up/down status to prospective users. Without a reachable status endpoint, they’re left to either raw-probe a protocol port - which can look “up” even when the mode instance behind it is disabled or failed, since a TCP/UDP listener accepting a connection proves nothing about the reflector logic behind it - or simply list the reflector’s status as unknown or stale. /liveness gives them (or any other monitoring integration) an accurate, purpose-built answer instead: current state, why it’s in that state, and how long it’s been running.

This is a low-risk thing to expose even for operators who’d rather not publish the fuller status surface: unlike /overview and /activity, /liveness carries no connected-client details and no call history - just mode, instance, state, and timestamps. You can enable [public_api] for this endpoint alone and skip linking to it from anywhere public-facing; directory sites and monitors only need the URL, not a dashboard pointing at it.

The public API and admin interface serve different audiences.

Interface Audience Purpose Typical exposure
Public API Users, communities, dashboard builders Read-only reflector status as JSON/SSE Public or semi-public
Admin interface Operators Management and configuration Private
Metrics listener Monitoring systems Machine-readable runtime data Private

The public API should let users (or a dashboard consuming it) answer questions like:

  • What reflector services are available?
  • Which modes and instances are enabled?
  • What address or port should I use?
  • Is the system currently online?
  • Who’s currently active?

The admin interface should answer operator questions like:

  • Which users can log in?
  • Which mode instances are enabled?
  • What changed recently?
  • Which roles and policies are assigned?
  • Are there audit events to review?

Keep those worlds separate. Giving users read-only status data is helpful. Giving them a control panel is how support requests become evidence.

The public API listener may be safely exposed through HTTPS when configured appropriately.

A common deployment pattern is:

Service Bind address Public exposure
Public API 127.0.0.1 or private address Exposed through reverse proxy
Admin interface 127.0.0.1 or private address Restricted
Metrics listener 127.0.0.1 or private address Private
Protocol listeners 0.0.0.0 or specific interface Public if needed

For most deployments, expose the public API through a reverse proxy such as Caddy, nginx, or Traefik, and set cors_allowed_origins (see Configuration) to the exact origin(s) of any browser-based dashboard that will call it cross-origin.

A simple hostname layout might look like this:

Hostname Purpose
vexillum.example.net Public API (and any dashboard built against it)
admin.vexillum.example.net Restricted admin interface
metrics.vexillum.example.net Restricted metrics endpoint, if exposed at all

The public hostname’s API responses (or a dashboard consuming them) can be linked from club websites, QRZ pages, documentation, or client setup guides.

The admin and metrics hostnames should be restricted by access control, VPN, firewall policy, or private network routing.

A recommended deployment pattern:

  1. Bind the public API locally or to a private address.
  2. Put it behind a reverse proxy.
  3. Serve it over HTTPS.
  4. Keep admin and metrics listeners private.
  5. Configure CORS and rate limiting to match how the API will actually be consumed.
  6. Publish only user-safe connection information (via the API, a dashboard, or a documentation page).
  7. Test the public API from outside your network.
  8. Confirm no admin routes are reachable from the public hostname.

Example layout:

Internet
  |
  | HTTPS
  v
Reverse proxy
  |
  | http://127.0.0.1:<public-api-port>
  v
Vexillum public API

Admin and metrics stay separate:

Operator VPN / SSH tunnel
  |
  v
Vexillum admin interface and metrics listener

From the Vexillum host:

curl -v http://127.0.0.1:<public-api-port>/api/public/v1/overview

You should get back JSON, not HTML. To browse it interactively instead:

curl -v http://127.0.0.1:<public-api-port>/api/public/v1/docs

From another machine:

curl -v https://vexillum.example.net/api/public/v1/overview

Check that the public hostname does not expose admin routes:

curl -I https://vexillum.example.net/admin

Depending on your routing model, this should return a redirect to the public API/dashboard, a 404, a 403, or another intentionally restricted response.

What it should not do is hand over an admin login page unless that is explicitly part of your access-control design.

Public API works locally but not externally

Section titled “Public API works locally but not externally”

Check:

  • DNS points to the correct server.
  • The reverse proxy is running.
  • TLS certificates are valid.
  • The firewall allows inbound HTTPS.
  • The reverse proxy upstream points to the correct local port.
  • The public API is listening on the expected address ([public_api] enabled/listen).

Check:

  • Reverse proxy route matching.
  • Hostname separation.
  • Path routing rules.
  • Whether admin and public listeners share a listener (they shouldn’t - they’re separate [admin] / [public_api] sections).
  • Whether /admin is intentionally restricted.

Do not rely on “nobody will guess the path.” They will. Then bots will. Then some search index will. Then you’ll get to invent a new swear word.

Public API shows stale or unexpected instance state

Section titled “Public API shows stale or unexpected instance state”

Check:

  • Whether the mode instance is actually enabled.
  • Whether you’re reading a cached response from an intermediate proxy or CDN.
  • Whether the SSE stream (/api/public/v1/events/overview) disconnected and the client stopped reconnecting.
  • Whether the underlying mode instance needs a restart.

Check:

  • Whether cors_allowed_origins includes the dashboard’s exact origin (scheme://host[:port], no path).
  • Whether the dashboard is calling the API cross-origin at all - same-origin requests aren’t affected by CORS.
  • Whether you meant to use ["*"] for a public, no-restrictions dashboard.

Users cannot connect even though the public API is online

Section titled “Users cannot connect even though the public API is online”

The public API being online only proves the JSON/SSE surface works.

Check:

  • Protocol listener ports
  • UDP versus TCP firewall rules
  • NAT forwarding
  • Mode instance state
  • Client configuration
  • Logs for incoming packets or sessions
  • Whether the published hostname resolves to the correct address

A working API response does not prove the reflector protocol ports are reachable. Naturally, users will still treat it as proof of everything.

Before publishing the public API (or a dashboard built on it):

  • Confirm published data is accurate.
  • Confirm admin routes are not exposed unintentionally.
  • Confirm metrics are not exposed unintentionally.
  • Confirm cors_allowed_origins and rate-limit settings match how the API will actually be consumed.
  • Confirm published ports match firewall and NAT rules.
  • Confirm each enabled mode instance has useful connection details in its public_runtime rows.
  • Confirm the API (and any dashboard) works over HTTPS.
  • Confirm the API works from outside the local network.
  • Confirm users have a clear contact or support path.
  • If listed on a public directory (e.g. DVRef), confirm /api/public/v1/liveness is reachable from outside the local network, even if no dashboard is published.

The public API may change as Vexillum continues development - see the Public API Reference for the current Beta notes. Operators should review that page and the release notes when upgrading.