Unified API Console
Knowledge Base
Wired · Proxy-Ready
// Knowledge Base · Unified API Command Console
Unified API Console Reference Guide
Complete reference for the Unified API Command Console — an MSP integration governance dashboard for monitoring 41 vendor API integrations, tracking key health, detecting security anomalies, and managing key rotation. The dashboard is wired and proxy-ready. Fetch wrappers, loadDashboard(), and a 30-second auto-refresh cycle are in place. Live data flows once the proxy is stood up and each fetchXxx() function is pointed at real endpoints.
Status: Wired — awaiting proxy
Systems: 32 vendors, 41 integrations
Refresh: Auto every 30 seconds
Actions: Disable Key + Create Ticket wired
01 //What Is This Tool

The Unified API Command Console is a single-file HTML governance dashboard for MSP API operations. It provides a centralized NOC view across all vendor integrations — surfacing system health, live API activity, security anomalies, key age and rotation status, and compliance posture in one screen. It is designed to be the operational layer for managing the credential and integration health of an MSP's full vendor stack.

Single File — No Install
All HTML, CSS, JS, and mock data in one file. No server, no npm. The proxy is the only external dependency — once it's running, swap the fetch wrapper bodies and the dashboard feeds live data.
Wired and Proxy-Ready
Four fetch wrappers (fetchSystemsData, fetchAlertsData, fetchKeysData, fetchActivityData), loadDashboard(), and a 30-second startAutoRefresh() cycle are all in place. Each wrapper contains a comment with the exact fetch() call to uncomment. Mock data stays as the fallback until the proxy is live.
Vendor API Paths Are Accurate
The endpoint paths in the activity pool — /v4_6/apis/3.0/tickets for ConnectWise, /v2/devices for Ninja, /api/v1/organizations for Meraki, etc. — are the real, documented API paths for those vendors. These are the paths the proxy will forward to.
Demo Banner Shows Until Proxy Is Live
A fixed bottom banner reading "◈ DEMO MODE — Live API connections not active" is injected on load. It disappears naturally once real data flows — or remove the injectDemoBanner() call once the proxy is confirmed working.
02 //Integration Status — Wired & Proxy-Ready
Dashboard is wired — proxy is the remaining dependency
The fetch layer, load orchestration, auto-refresh, and live action handlers are all in place. Mock data runs as the fallback. Once the proxy is stood up and each fetchXxx() body is pointed at real endpoints, the dashboard feeds live data with no other changes needed.
What Is Wired and Active Now
fetchSystemsData() — wraps systems[], ready for fetch('/api/systems')
fetchAlertsData() — wraps secAlerts[], ready for fetch('/api/security/alerts')
fetchKeysData() — wraps apiKeys[], ready for fetch('/api/keys')
fetchActivityData() — wraps activityPool[], ready for fetch('/api/activity/recent')
loadDashboard() — orchestrates all four fetches in parallel, re-renders all panels
startAutoRefresh(30000) — 30-second cycle, starts on page load
Status badge — clickable, calls loadDashboard() on demand, shows "REFRESHING…" during load
Panel header badge counts — recomputed from live data on every loadDashboard() call
Compliance metrics — recomputed from live key data (rotation, no-owner, admin counts)
createPSATicket() — Create Ticket button calls this; contains commented-out ConnectWise PSA POST
disableApiKey() — Disable Key button calls this; contains commented-out backend POST pattern
What Activates When the Proxy Is Live
Each fetchXxx() function has the real fetch() call commented out directly above the mock fallback. Uncomment and delete the mock line for each endpoint as the proxy surfaces it. No other changes required — the render functions are already wired to receive the data.

For createPSATicket(): uncomment the fetch() block inside the function. The ConnectWise PSA endpoint, auth header format, and ticket body shape are all documented in the comment.

For disableApiKey(): uncomment the fetch('/api/keys/disable') block. Keeper record update and vendor-specific revocation notes are in the comment.
Rotation Wizard — UI Complete, Backend Calls Commented Out
The 7-step wizard (Select → Review → Generate → Notify → Grace Period → Revoke → Complete) is fully functional as a UI flow. Step ③ Generate contains a commented-out Keeper API POST /v1/record call. Step ⑥ Revoke contains a commented-out vendor invalidation call. Uncomment both when the proxy is ready. The grace period countdown timer is real and will govern the overlap window.
Activity Table — Simulation Stays Until Log Stream Is Connected
The activity table uses a 1.8-second interval to inject rows from activityPool[]. This continues to run as a placeholder until a real log stream (WebSocket, SIEM poll, or API gateway feed) is connected. The row rendering function generateActivityRow() stays unchanged — replace the data source, not the renderer.
03 //Architecture

The dashboard has a full fetch layer in place. The architecture below shows the wired state — mock data fills each slot until the proxy routes the call to the real vendor endpoint.

// CURRENT STATE — wired, running mock data pending proxy Browser ├─ loadDashboard() ← called on boot + every 30s via startAutoRefresh() │ ├─ fetchSystemsData() → mock systems[] // swap: fetch('/api/systems') │ ├─ fetchAlertsData() → mock secAlerts[] // swap: fetch('/api/security/alerts') │ ├─ fetchKeysData() → mock apiKeys[] // swap: fetch('/api/keys') │ └─ renders all panels + recomputes badge counts + compliance metrics │ ├─ createPSATicket() → mock toast // uncomment: POST /v4_6/apis/3.0/service/tickets └─ disableApiKey() → mock toast // uncomment: POST /api/keys/disable // TARGET — proxy running, fetch() bodies uncommented Browserlocalhost:3000/proxy (Node.js) ├─ ConnectWise PSA /v4_6/apis/3.0/... ├─ Ninja RMM /v2/devices ├─ Meraki /api/v1/organizations ├─ Huntress /v1/agents ├─ SentinelOne /web/api/v2.1/threats ├─ (28 more vendors) └─ Keeper / Azure KV key store reads
ComponentCurrent stateGoes live when
Systems panelMock systems[] via fetchSystemsData()Proxy exposes /api/systems
Activity monitorPool of 16 entries, randomized 1.8sReal log stream connected to activityPool source
Security alertsMock secAlerts[] via fetchAlertsData()Proxy exposes /api/security/alerts
Key inventoryMock apiKeys[] via fetchKeysData()Proxy exposes /api/keys from Keeper/KV
Compliance panelRecomputed from live key data on every loadAlready live — driven by key inventory
Disable KeydisableApiKey() — mock toast, real fetch commented outUncomment the fetch block inside the function
Create TicketcreatePSATicket() — mock toast, real fetch commented outUncomment the fetch block inside the function
Rotation wizardUI complete — Generate and Revoke steps have commented fetch callsUncomment Keeper + vendor calls in steps ③ and ⑥
Auto-refreshstartAutoRefresh(30000) — running on 30s cycleAlready running — no change needed
04 //UI Layout

The dashboard uses a named CSS grid with 7 areas across 3 columns. The grid is responsive — collapses to 2 columns at 1280px and single column at 768px.

grid-template-areas: "systems systems systems" ← full-width system cards "statsbar statsbar statsbar" ← 6-column traffic chip row "activity security security" ← 3fr left / 1.35fr+1.35fr right "keys actions compliance" ← bottom row, 3 equal panels
ZonePanel classFixed heightDescription
Top BannerSticky headerautoBrand, stat chips, clickable status badge, system clock. No home bar — removed.
Ticker.ticker-wrapautoScrolling vendor status feed, 40s animation loop
Systems.panel-systemsautoCSS auto-fill grid of sys-cards, min 148px each
Stats bar.statsbarauto6 traffic-chip KPI cards
Activity.panel-activity600pxLive API log table with internal scroll
Security.panel-security600pxAlert list with internal scroll
Keys.panel-keysflex fillKey inventory table (shares panel with Activity via tab switcher)
Actions.panel-actionsflex fill6 action buttons + action log + pending items + on-call engineer
Compliance.panel-complianceflex fill2×2 metric grid + 8 policy rows
05 //Top Banner
Brand
"UNIFIED API COMMAND CONSOLE" in Orbitron. Sub-label: "MSP Integration Intelligence Platform · v4.2.1". No home bar — removed.
Stat chips
4 chips: SYSTEM STATUS (green, blink), ACTIVE INTEGRATIONS (#stat-active — updated by loadDashboard() from live system count), WARNINGS (7, yellow blink), CRITICAL ALERTS (3, red pulse), API CALLS/HR (#stat-calls, cosmetic counter).
Clock
#clock — real toTimeString(), updates every 1 second. #datelinetoDateString().toUpperCase().
Status badge
Clickable — calls loadDashboard() on click for on-demand refresh. Shows "● REFRESHING…" during a load cycle, reverts to "● ALL SYSTEMS NOMINAL" on completion, "● LOAD ERROR" on failure.
Call counter
Cosmetic drift — callCount starts at 148,302, drifts ±10–40 every 3 seconds. For live use replace this interval with a real poll from your API gateway metrics.
06 //Live Ticker

A horizontally scrolling ticker below the banner. Runs on pure CSS animation (animation: ticker 40s linear infinite). Content is duplicated in HTML for a seamless loop — 10 items appear twice, giving 20 total spans so the second half fills in as the first scrolls out.

Ticker is static HTML — not dynamically populated
Unlike the activity table (which uses JS to inject rows), the ticker items are hardcoded in the HTML source. To update ticker content for live use, replace the .ticker-inner innerHTML with a JavaScript function that reads from the same data source as the systems panel. The IP address 198.51.100.42 is from RFC 5737 documentation range — intentionally not a real IP.
07 //Integrated Systems Panel

Rendered by renderSystems() from the systems[] array into #systems-grid. Each card shows vendor name, status dot, calls/hr, and last sync time. Clicking a card opens the system detail modal.

StatusCSS classLeft borderDot behavior
HEALTHY.sys-card.healthyGreen with glowSolid green dot
WARNING.sys-card.warningYellow with glowYellow dot, blink 1.4s
CRITICAL.sys-card.criticalRed with glowRed dot, blink 0.8s

Current demo distribution: 29 healthy, 3 warning (KnowBe4, Auvik, LastPass), 2 critical (Meraki Console, Mimecast). The panel header badge counts (healthy/warning/critical/total) are now recomputed from live data on every loadDashboard() call — they will reflect real vendor status once the proxy is live.

System detail modal endpoint field
The "ENDPOINT" field in the system detail modal currently renders api.{name}.com from a simple string replace — it is cosmetic. For live use, add a real endpoint field to each entry in systems[] so the modal shows the actual vendor base URL.
08 //Traffic Stats Bar

Six traffic-chip KPI cards showing call volume by traffic source. All values are static HTML — not computed from the activity pool. Each chip has a 2px colored top accent, a large value, a percentage, and a sub-label.

ChipColorValuePctSub-label
Corporate Networkgreen76,91752%internal · corp LAN
VPN Gatewaycyan41,52528%tunneled · remote users
Cloud Infrastructureblue20,76214%AWS · Azure · GCP
Remote Agentsorange5,9324%endpoint · scheduled
Automation / Scriptspurple2,2001.5%cron · pipelines
Unknown Sourcesred2,9662%unclassified · review
09 //Live API Activity Monitor

The left panel, 600px fixed height with internal scroll. Initialized by initActivityTable() with 20 random rows from activityPool. Every 1.8 seconds a new row is prepended and the oldest row (beyond 24) is removed, simulating a live log feed.

Columns
TIME (HH:MM:SS.mmm) · SYSTEM · API KEY · ENDPOINT · SOURCE IP · METHOD · STATUS · LATENCY
Row highlighting
.row-crit (red left border) for HTTP 4xx/5xx. .row-warn (yellow left border) for HTTP 429 (rate limit). Normal rows have no highlight.
IP color logic
IPs starting with 10. or 172. render in normal text. 198.51.100.42 renders in red — it is the RFC 5737 documentation IP used as the "unknown/suspicious" source in demo data.
Latency color
Green if ≤200ms. Yellow if >200ms. Zero latency (timeout/error) shows "—".
Tab switcher
The panel header has Activity / Keys tabs. switchTab(tab) shows/hides #view-activity and #view-keys divs and updates badge text. Both views share the same panel-keys/panel-activity layout area.
10 //Security & Abuse Alerts

Right-column panel, 600px fixed height. Rendered by renderAlerts() from secAlerts[]. Each alert row shows a colored severity bar, type, detail text, severity tag, API key name, and timestamp. Clicking an alert opens the alert detail modal.

Alert typeSeverityKeyWhat it demonstrates
Unknown IP DetectedHIGHMERAKI-API-01External IP not in allowlist (Amsterdam, NL)
Multiple Auth FailuresHIGHMIME-SEC-0114 consecutive 401s from same external IP
Burst API TrafficHIGHAUVIK-NET-KEYRate limit breach — 2,400 calls/60s (normal: 180/min)
Key Used Outside RegionMEDKB4-PHISH-KEYEU-WEST-1 origin for normally US-EAST key
Rotation OverdueMEDCW-PSA-LEGACY104-day-old key past 90-day policy
Privilege EscalationMEDITG-READ-ONLYRead-only key attempted POST — denied
Orphaned Key ActivityMEDLEGACY-RMM-APINo-owner key active after 47-day dormancy
11 //API Key Inventory

Rendered by renderKeys() from apiKeys[] (22 entries). Row highlighting: red background for critical status, yellow background for rotation status. Clicking any row opens the key detail modal.

StatusTag displayedRow colorMeaning
healthyHEALTHYNormalIn policy, used recently, owner assigned
rotationROTATION REQYellow rowAge >90d or policy-flagged
criticalCRITICALRed rowActive security incident (auth failures, unknown IP)
highprivHIGH PRIVILEGENormalAdmin-level permissions — requires dual approval to rotate
unusedUNUSEDNormalNever used or dormant (LASTPASS-INT: 142d old, never used)

Age coloring on the age column: red if >90 days, yellow if >60 days, normal otherwise — computed inline in renderKeys(). Owner names starting with ( (e.g. (unassigned), (orphaned)) render in red.

12 //Quick Response Actions
Disable Key — Wired
Calls disableApiKey(keyName, system). Contains commented-out fetch('/api/keys/disable') POST with notes on Keeper record update and vendor-specific revocation. Demo fallback fires a toast. Uncomment the fetch block when the proxy is ready.
Create Ticket — Wired
Calls createPSATicket(). Contains the full commented-out ConnectWise PSA POST /v4_6/apis/3.0/service/tickets call with correct auth header format (Basic companyId+publicKey:privateKey) and ticket body shape. Demo fallback generates a random ticket ID. Uncomment when proxy is ready.
ButtonCurrent behaviorGoes live when
Disable API KeydisableApiKey() — mock toast, real fetch commented outUncomment the fetch block in disableApiKey()
Rotate Key7-step wizard with mock key generation — Generate and Revoke steps have commented fetch callsUncomment Keeper + vendor calls in wizard steps ③ and ⑥
View LogsModal with 5 hardcoded log lines → export toastReplace modal body with fetch from SIEM or vendor audit endpoint
Export ActivityFormat selector modal → toastBackend export endpoint querying real log database
Create TicketcreatePSATicket() — mock toast, real fetch commented outUncomment the fetch block in createPSATicket()
Security ScanConfirmation modal → toastBackend job to validate keys, check allowlists, detect violations

The panel also contains three sub-sections below the buttons: Last Actions Log (#action-log, rendered by renderActionLog(), updates when actions fire), Pending Action Items (5 hardcoded urgent/review items), and On-Call Engineer (L. Chen, static — Page/Slack/Escalate buttons fire toasts only).

13 //Compliance & Policy

Bottom-right panel. Four metric cards (2×2 grid) followed by 8 policy rows. All values are static HTML — not computed from the key inventory array.

Metric cards
58 Keys In Policy
Green — within rotation and ownership rules
4 Past Rotation
Red — past 90-day policy window
2 No Owner
Yellow — unassigned credentials
3 Admin Privilege
Orange — high-privilege keys flagged for review
Policy rows
Rotation Policy
90-DAY ENFORCED
MFA on API Access
ENABLED
IP Allowlisting
PARTIAL 31/41
Unused Key Disable
60-DAY POLICY
Audit Logging
ALL SYSTEMS
Privilege Separation
3 VIOLATIONS
Orphaned Keys
2 FOUND
Next Audit Due
2026-03-15
14 //Modal System

showModal(title, bodyHTML, actions) populates and activates the single modal overlay. closeModal(e) closes on backdrop click. closeModalDirect() closes programmatically. The modal has a fade+scale-in animation (modalIn keyframe). Width is 480px default, expanded to 600px for the rotation wizard via #modal-box-inner and a dynamic style injection.

Modal triggerFunctionWidth
System card clickshowSystemDetail(name, status, calls, sync)480px
Alert item clickshowAlertDetail(type, key, sev)480px
Key row clickshowKeyDetail(name)480px
Action buttonsshowAction(action)configs[action]480px
Rotate Key buttonPatched via window.showAction override → startRotationWizard()600px
15 //Key Rotation Wizard

A 7-step guided workflow for key rotation. State is held in rotState object. Each step re-renders the modal body. A visual progress bar shows completed (green ✓), active (cyan glow), and pending (dim) steps.

#StepWhat happensReal action needed
Select KeyShows rotation candidates (status: rotation/critical/highpriv). User selects one.Filter live key inventory
Review & ConfirmShows key details, notification method selector (Email/Slack/PSA Ticket), grace period selector (30s demo / 5m / 15m / 1hr). Admin keys show dual-approval block.Send notification via chosen channel
Generate New KeyAnimated 5-step sequence. generateFakeKey() returns vault://keeper/IT-API-Keys/XXXXXXXX-XXXX. Key preview revealed with copy button.Call vendor API to issue real new key; store in Keeper/KV
Notify OwnerShows notification preview with owner name and chosen channel. Simulate Send button fires toast.Real webhook/email/Slack API call
Grace PeriodSVG countdown ring. Old and new keys shown as "both active." When timer hits zero, revoke button activates.Both keys must remain valid during real grace period
Revoke Old Key5-step animated revocation sequence. Fires toast on completion.Call vendor API to invalidate old key; update credential store
CompleteGreen success display. Updates ACTION_LOG_ENTRIES. Done button closes modal.Mark rotation complete in audit log
Dual approval is simulated
Admin-level keys trigger a "Simulate Approval" button that sets rotState._approved = true and shows a fake approver name. For live use, this needs a real approval flow — a PSA ticket, a Slack approval bot, or a second-factor confirmation from a named engineer.
16 //Toast Notifications

showToast(msg, type) appends a toast to #toast-container (fixed bottom-right). Auto-dismisses after 3.5 seconds with a fade-out animation. Types: green (●), red (⚠), yellow (⚡), cyan (◉). Two toasts fire automatically on load: green "Console initialized" at 800ms, red "Unknown IP detected on MERAKI-API-01" at 3000ms.

17 //Systems Inventory Reference
VendorStatusDemo calls/hrCategory
ConnectWise PSAhealthy12,441PSA
CW PSA Consolehealthy5,302PSA
CW Automatehealthy9,102RMM
Ninja RMMhealthy9,208RMM
PSA Managehealthy4,880PSA
Meraki Consolecritical8,201Network
ScreenConnecthealthy7,440Remote
Mimecastcritical5,640Email Security
Huntresshealthy3,940EDR/MDR
IT Gluehealthy3,201Documentation
Dattohealthy4,440Backup
Acronishealthy2,880Backup
Cisco Umbrellahealthy2,880DNS Security
Auvikwarning6,340Network Mon
SentinelOnehealthy4,102EDR
Sophoshealthy3,240AV/EDR
Dropsuitehealthy2,104Email Backup
Proofpointhealthy2,104Email Security
Liongardhealthy2,080Automation
DUO Securityhealthy1,883MFA
Malwarebyteshealthy1,440AV
KnowBe4warning1,540Security Training
Axcienthealthy1,204Backup
RocketCyberhealthy1,302SOC/MDR
N-Able Covehealthy3,110Backup
Axcient/eFolderhealthy880Backup
Ruckuswarning880Wireless
Keeperhealthy702Password Mgmt
LastPasswarning612Password Mgmt
BrightGaugehealthy520Reporting
ScalePadhealthy640Asset Mgmt
8x8 / MS Dialpadhealthy441 / 340VoIP
18 //Activity Pool — Real Vendor API Endpoints

The activityPool[] entries use real, documented vendor API endpoint paths. These are accurate reference material for the live integration phase.

SystemKey nameEndpointVendor docs section
ConnectWise PSACW-PSA-PROD-01/v4_6/apis/3.0/ticketsManage REST API v4.6
Ninja RMMNINJA-MAIN-KEY/v2/devicesNinjaOne API v2
Meraki ConsoleMERAKI-API-01/api/v1/organizationsMeraki Dashboard API v1
HuntressHUNT-PROD-KEY/v1/agentsHuntress Partner API v1
SentinelOneS1-PROD-MAIN/web/api/v2.1/threatsSentinelOne Management API v2.1
MimecastMIME-SEC-01/api/email/logsMimecast Email Security API
IT GlueITG-READ-ONLY/v1/organizationsIT Glue API v1
DUO SecurityDUO-AUTH-KEY/admin/v1/usersDuo Admin API v1
DattoDATTO-BKP-01/bcdr/deviceDatto BCDR API
AuvikAUVIK-NET-KEY/v1/inventory/networkAuvik API v1
AcronisACR-BACKUP-KEY/api/2/tenantsAcronis Cyber Cloud API v2
SophosSOPH-CENTRAL/whoami/v1Sophos Central API v1
Cisco UmbrellaUMB-NET-SEC/v2/domainsUmbrella Management API v2
ScreenConnectSC-REMOTE-KEY/Services/PageServiceConnectWise Control REST API
KnowBe4KB4-PHISH-KEY/v1/phishing/campaignsKnowBe4 API v1
RocketCyberRC-SOC-KEY/api/v3/incidentsRocketCyber API v3
19 //Key Inventory Schema

Each entry in apiKeys[] follows this shape. The live version should pull from Keeper, Azure Key Vault, or your credential store and normalise to this schema before rendering.

{ name: "CW-PSA-PROD-01", // display key name / identifier sys: "ConnectWise PSA", // vendor name (must match systems[]) perm: "Read/Write", // "Read Only" | "Read/Write" | "Admin" owner: "J. Harrington", // "(unassigned)" or "(orphaned)" → renders red last: "< 1min ago", // relative string — compute from last_used timestamp age: "34d", // string — age coloring: >90d red, >60d yellow status: "healthy" // "healthy" | "rotation" | "critical" | "highpriv" | "unused" }
20 //Going Live — Proxy Activation Checklist

The wiring is done. These are the steps to activate live data once the proxy is running. Each fetch wrapper already contains the exact commented-out call — nothing needs to be architected, just uncommented and pointed at the right endpoint.

1
Start the proxy
Run node main.js from the proxy directory. Confirm it is listening on localhost:3000. All fetch wrappers route through this proxy.
2
Activate fetchSystemsData()
In fetchSystemsData(), uncomment the return fetch('/api/systems').then(r => r.json()) line and delete the mock fallback lines. The proxy needs to expose /api/systems returning an array matching the {name, status, calls, sync} shape. Badge counts recompute automatically on the first load.
3
Activate fetchAlertsData()
Uncomment return fetch('/api/security/alerts').then(r => r.json()) in fetchAlertsData(). The proxy endpoint should return an array of alert objects matching the secAlerts[] shape — see Section 10 for the field list.
4
Activate fetchKeysData()
Uncomment return fetch('/api/keys').then(r => r.json()) in fetchKeysData(). The proxy should pull from Keeper, Azure Key Vault, or your credential store and normalize to the schema in Section 19. Compliance metrics recompute automatically from this data.
5
Activate createPSATicket()
In createPSATicket(), uncomment the full fetch() block. The ConnectWise PSA endpoint URL, auth header format (Basic companyId+publicKey:privateKey), and ticket body shape are all documented in the comment. Delete the demo toast fallback below it.
6
Activate disableApiKey()
In disableApiKey(), uncomment the fetch('/api/keys/disable') POST block. Notes on Keeper record update and vendor-specific revocation endpoints are in the comment.
7
Wire rotation wizard steps ③ and ⑥
In renderRotationStep(), step index 2 (Generate) contains a commented-out Keeper POST /v1/record call. Step index 5 (Revoke) contains a commented-out vendor invalidation call. Uncomment both when the Keeper integration and vendor key revocation endpoints are confirmed through the proxy.
8
Remove the demo banner
Delete or comment out the injectDemoBanner() call near the top of the script block once live data is confirmed flowing. The auto-refresh is already running — no other boot changes needed.
21 //Troubleshooting
Panel header badge counts now auto-update
The healthy/warning/critical/total counts in the systems panel header are now recomputed by loadDashboard() on every cycle. They reflect the actual distribution of whatever is in systems[] — no manual update needed when entries change.
Rotation wizard doesn't retain selection if key detail is opened first
When Rotate Key is triggered from a key detail modal, startRotationWizard() is called with that key's data pre-loaded. When triggered from the quick action button directly, it defaults to CW-PSA-LEGACY. Expected behavior — the wizard step 0 selector allows changing the selection.
Grace period timer doesn't stop if modal is closed mid-wizard
rotState.graceTimer keeps running if the modal is closed during step ⑤. Add clearInterval(rotState.graceTimer) inside closeModalDirect() to clean this up when the wizard is abandoned.
Activity table max row count is 24
The 1.8s interval prepends rows and removes any beyond the 24th. Change the threshold in the interval: if (rows.length > 24) rows[rows.length - 1].remove().
Call counter is cosmetic until proxy is live
The counter drifts randomly from 148,302. Replace the 3-second drift interval with a real poll from your API gateway metrics once the proxy is running.
Auto-refresh runs immediately on page load
loadDashboard() fires once on boot, then startAutoRefresh(30000) starts the 30-second cycle. If a load is already in progress when the interval fires, _dashboardLoading blocks the second call so cycles don't stack.
Ticker CSS animation doesn't restart on content update
The ticker uses pure CSS animation: ticker 40s linear infinite. If you update the ticker innerHTML dynamically, force a restart: el.style.animation = 'none'; el.offsetHeight; el.style.animation = ''.

KB · Unified API Command Console · dashboard-unified-api-console.html · v1.1 — Wired & Proxy-Ready