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.
Purpose
Section titled “Purpose”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.
Liveness for public reflector listings
Section titled “Liveness for public reflector listings”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.
Public API versus admin interface
Section titled “Public API versus admin interface”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.
Recommended exposure
Section titled “Recommended exposure”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.
Reverse proxy layout
Section titled “Reverse proxy layout”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.
Suggested deployment pattern
Section titled “Suggested deployment pattern”A recommended deployment pattern:
- Bind the public API locally or to a private address.
- Put it behind a reverse proxy.
- Serve it over HTTPS.
- Keep admin and metrics listeners private.
- Configure CORS and rate limiting to match how the API will actually be consumed.
- Publish only user-safe connection information (via the API, a dashboard, or a documentation page).
- Test the public API from outside your network.
- Confirm no admin routes are reachable from the public hostname.
Example layout:
Admin and metrics stay separate:
Testing the public interface
Section titled “Testing the public interface”From the Vexillum host:
You should get back JSON, not HTML. To browse it interactively instead:
From another machine:
Check that the public hostname does not expose admin routes:
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.
Common problems
Section titled “Common problems”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).
Public API exposes admin routes
Section titled “Public API exposes admin routes”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
/adminis 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.
CORS errors in a browser-based dashboard
Section titled “CORS errors in a browser-based dashboard”Check:
- Whether
cors_allowed_originsincludes 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.
Operator checklist
Section titled “Operator checklist”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_originsand 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_runtimerows. - 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/livenessis reachable from outside the local network, even if no dashboard is published.
Current status
Section titled “Current status”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.