Turn 30 minutes of API disable/enable work into 5 seconds. One click. Instant effect. Full audit trail.
A Phoenix LiveView library that gives you real-time control over your backend routes. Discover every endpoint in your Phoenix app, then enable or disable them from a protected dashboard — no redeployment needed.
What It Does
- Route Discovery — Automatically pulls every route from your Phoenix router
- Grouped Management — Routes organized by controller name so you can toggle in bulk
- One-Click Toggles — Enable or disable individual routes or entire groups instantly via LiveView (page reload fallback for apps without LiveView JS)
- Route Guard Plug — Disabled routes return
403at the Plug level, before they hit your controller - Session-Based Login — Secure login page with admin/viewer role-based access control
- Account Management — Add/remove users, set roles (admin/viewer) directly from the dashboard
- Persistent State — Route policies, accounts, and audit logs stored via CubDB (crash-safe, ACID atomic toggles)
- Audit Log — Every toggle, hide, and account change logged with who/what/when — expandable with pagination and CSV download
- Dead Render Fallback — Full static HTML fallback for API-only apps without LiveView JS
Why It Matters
| Scenario | Without Console | With Console |
|---|---|---|
| Emergency API shutdown | 30–60 min (find team, redeploy) | 5 seconds, one click |
| Peak traffic load shedding | SSH, edit configs, restart services | Dashboard on second monitor, instant |
| Security vulnerability exposure | 45 min (CI/CD + deploy) | Disable endpoint immediately |
| Maintenance window | Wake up at 2am or write custom scripts | Toggle it and sleep |
| Compliance (GDPR data freeze) | Days of discovery and code changes | One click to disable all tagged routes |
Quick Start
Prerequisites
Your Phoenix app must have LiveView's JavaScript client loaded. Every app created with mix phx.new ships with this in assets/js/app.js:
import { Socket } from "phoenix"
import { LiveSocket } from "phoenix_live_view"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken } })
liveSocket.connect()Without LiveView JS (--no-assets or API-only apps): the console operates in dead-render mode using standard HTML forms and links. Every feature works: toggles, search, audit log expand, account management, compare plans modal, reset all. Features that require JavaScript (bulk checkboxes/select) are hidden in dead mode.
Install
# Add the dependency (Git for now — Hex package coming soon)
def deps do
[
{:api_management_console, github: "rizwankhalid/api_management_console"}
]
end
# Install
$ mix deps.getMount the console in your router
# In your router — `use` injects route guard + auth pipelines automatically
use ApiManagementConsoleV2.Router
scope "/" do
pipe_through [:browser, :route_guard]
api_console "/admin/api-console"
endThat's it. The use macro automatically:
- Defines the
:route_guardpipeline — blocks disabled routes with403 - Defines the
:api_console_authpipeline — session-based login protection - Registers the
api_console/1macro to mount dashboard routes under your chosen path
To enforce disabled routes, add :route_guard to every scope you want protected:
scope "/", SampleBlogWeb do
pipe_through [:browser, :route_guard]
get "/", PageController, :index
end
scope "/api", SampleBlogWeb do
pipe_through [:api, :route_guard]
get "/blogs", BlogController, :index
endOr add the plug directly inside any pipeline:
pipeline :api do
plug ApiManagementConsoleV2.Plugs.RouteGuard
endDefault login
The console uses session-based authentication. A default admin account is auto-created on first access:
- Username:
admin - Password:
admin123
Override via env vars:
export API_CONSOLE_ADMIN_USERNAME=admin
export API_CONSOLE_ADMIN_PASSWORD=your_password
Configuration
All settings under a single config key:
config :api_management_console,
# Protected routes — cannot be toggled (supports strings and regex)
protected_routes: [
"/dev/dashboard",
~r{HealthController},
~r{^/api/internal/}
],
# Storage path (default: "api-console-data/")
storage_dir: "/var/data/api_console",
# Company branding (requires paid license — see Licensing below)
app_name: "Acme Corp API Console",
hide_powered_by: true,
# Debug logging (default: false)
debug: true,
# License key — unlock paid features (offline JWT validation)
license_key: "eyJhbGciOiJSUzI1NiIs..."Or set the license key via environment variable:
export API_CONSOLE_LICENSE_KEY=eyJhbGciOiJSUzI1NiIs...
Manual route setup (no macro)
# Wire it up yourself instead of using `api_console`
scope "/admin/api-console" do
pipe_through [:browser]
# Login routes (unauthenticated)
get "/login", ApiManagementConsoleV2Web.LoginController, :index
post "/login", ApiManagementConsoleV2Web.LoginController, :create
# Protected console routes
pipe_through [:api_console_auth]
get "/logout", ApiManagementConsoleV2Web.Plugs.Logout, []
get "/audit.csv", ApiManagementConsoleV2Web.Plugs.AuditDownload, []
live "/", ApiManagementConsoleV2Web.RouteConsoleLive, :index
endStart your server:
$ mix phx.server
# Visit http://localhost:4000/admin/api-console
Features
Free Tier
| Feature | Description |
|---|---|
| Route Discovery | Auto-discovers all routes from your Phoenix router |
| One-Click Toggles | Enable/disable individual routes or entire groups — instant via LiveView, page reload in dead mode |
| Dead Render Fallback | Full static HTML fallback for apps without LiveView JS |
| Route Guard | Blocks disabled routes with 403 at the Plug level |
| Session Login | Secure login page with session-based authentication |
| RBAC | Admin and Viewer roles — admins toggle, viewers read-only |
| Account Management | Add/remove users, change roles, up to 5 users |
| CubDB Storage | Embedded key-value store — crash-safe, ACID atomic toggles |
| Grouped Routes | Routes organized by controller name, toggleable to flat view |
| Search & Filter | Search by path, method, or controller name |
| Health Bar | Visual progress bar showing enabled vs disabled ratio |
| Protected Routes | Immutable routes (greyed out, untoggleable) via config |
| Dark Mode | Auto-detects prefers-color-scheme |
| Bulk Operations | Checkboxes, select all/clear per group, bulk enable/disable/hide (LiveView only) |
| Hide Routes | Hide routes from console view, restore from modal |
| Audit Log | Every action logged with username, expandable with pagination, CSV download |
| 30-Day Audit History | Free tier retains 30 days of audit entries |
| Reset All | One-click re-enable all routes with confirmation dialog |
| 50-Route Limit | Free tier manages up to 50 routes — extras shown as faded teaser with upgrade CTA |
| Compare Plans Modal | Side-by-side Free vs PRO feature matrix with active plan highlighted |
Paid (PRO) Tier
| Feature | Description |
|---|---|
| Company Branding | Custom app name, hide "Powered by" footer ✅ |
| Unlimited Routes | No 50-route cap ✅ |
| Unlimited Users | No 5-user cap on RBAC ✅ |
| Full Audit History | No 30-day retention limit ✅ |
| Scheduled Toggles | Schedule enable/disable at specific times (coming soon) |
| PostgreSQL Storage | Multi-node consistency via Ecto (coming soon) |
| Slack Notifications | Webhook alerts on policy changes (coming soon) |
Route Limit (Free Tier)
When your app has more than 50 managed routes on the free tier:
- Routes 1–47 render normally — fully interactive
- Routes 48–50 appear in a faded teaser container below the groups:
- Dimmed, non-interactive, with a gradient blur overlay
- Lock icon + "X routes hidden" + "Upgrade to PRO" button
- Routes 51+ are hidden entirely
The "Compare Plans" modal (button next to the tier badge) shows exactly what each tier includes, with the active plan highlighted in green.
Storage
| Storage | Use Case | Status |
|---|---|---|
| CubDB | Default — embedded, zero config, crash-safe, ACID transactions | ✅ Free |
| PostgreSQL | Multi-node consistency, team environments | Planned (paid) |
How Licensing Works
Offline JWT validation — no phone-home, no external server dependency.
- Receive your license key (signed JWT with embedded tier, expiry, trial claims)
- Set
API_CONSOLE_LICENSE_KEYenv var orlicense_keyin config - Paid features unlock automatically based on the license tier
No license key = Free tier. All paid features degrade gracefully — limits are enforced, upgrade prompts appear where applicable.
License keys are issued by the library maintainer. Contact the author to obtain a key for your organization.
Requirements
- Elixir ~> 1.13
- Phoenix ~> 1.6, ~> 1.7, or ~> 1.8
- Erlang/OTP
Dependencies
| Dependency | Purpose |
|---|---|
cubdb (~> 2.0) | Embedded key-value store for policies, accounts, audit logs |
bcrypt_elixir (~> 3.0) | Password hashing for account management |
joken (~> 2.6) | JWT verification for offline license validation |
phoenix (optional) | Web framework integration |
phoenix_live_view (optional) | Real-time dashboard UI |
Screenshots
Main Dashboard (LiveView)

Main Dashboard (Static / Dead Render)

Routes with Bulk Selection

Audit Log & Account Management

Compare Plans Modal

Login Screen

Roadmap
See ROADMAP.md for completed features, planned work, and known issues.
License
MIT License — use, modify, distribute freely. Paid features are unlocked via a signed JWT license key, not a separate software license.
Community
- GitHub Issues — Bug reports & feature requests
Built with ❤️ in Elixir for the Phoenix ecosystem.