The Trinity Beast Infrastructure — Account Dashboards

Customer self-service portal — passwordless auth, role-based panels, billing, API keys, usage charts, webhooks, support, and admin impersonation.

Account: 211998422884 Region: us-east-2 (Ohio) Dashboard: https://api.cpmp-site.org/dashboard Updated: May 2026

1. Overview

The Trinity Beast Account Dashboard is a customer-facing self-service portal served at https://api.cpmp-site.org/dashboard. It gives every account holder a single place to view their subscription status, API usage, giving history, webhook configuration, and billing — all authenticated via passwordless magic link.

The dashboard is a Single Page Application (SPA) served by the LPO server directly. There are no separate frontend assets, no CDN dependency, and no build step — the entire SPA is embedded in the Go binary as string-concatenated HTML/JS. All data is fetched from /dashboard/api/* JSON endpoints after the initial page load.

Diagram 1.1: Dashboard Architecture Overview
graph LR
  subgraph Browser["Browser (SPA)"]
    direction TB
    A["/dashboard — HTML Shell"] --> B["localStorage\ncpmp_user.token"]
    B --> C["JSON API Calls"]
  end
  subgraph LPO["LPO Server (ECS)"]
    direction TB
    D["SPAHandler\nserves shell"] --> E["RequireSession\nmiddleware"]
    E --> F["Panel API\nhandlers"]
  end
  subgraph Data["Data Layer"]
    G["Aurora\napi_keys · users\nusage_logs"]
    H["Valkey\nsessions · magic links\naudit log"]
    I["Stripe\nsubscriptions\ncharges"]
  end
  C -->|"Authorization: Bearer"| E
  F --> G
  F --> H
  F --> I
    

2. Account Types & Facets

Every dashboard user is resolved into an Account — a unified identity that may carry one or more typed facets. A single email address can have multiple facets simultaneously (e.g., a person who both donates to CPMP and subscribes to the API).

FacetRole GrantedSourceDescription
DonorFacetdonorStripeActive recurring donation subscription via CPMP donate page
APIKeyFacetapi-subscriberAurora api_keysActive API subscription (free, pro, enterprise, unlimited, lifetime)
WebhookFacetwebhook-associateAurora api_keys + webhook_subscriptionsActive webhook push subscription (starter, standard, professional, enterprise)
PartnerFacetpartnerAurora api_keysAWS PrivateLink partner account
AdminadminAurora application_parametersEmail matches admin_email parameter — grants full access + impersonation

When an account has both a DonorFacet and at least one service facet (API, Webhook, or Partner), the sidebar shows a Giving / Services tab split. Single-facet accounts see a flat sidebar with no tabs.

Diagram 2.1: Account Facet Resolution
flowchart TD
  A["resolveAccount(email)"] --> B["Aurora: JOIN api_keys + users\nWHERE email = ? AND revoked = false"]
  B --> C{tier?}
  C -->|"free/pro/enterprise\nunlimited/lifetime"| D["APIKeyFacet"]
  C -->|"partner"| E["PartnerFacet"]
  C -->|"webhook_*"| F["WebhookFacet\n+ webhook_subscriptions query"]
  A --> G["Stripe: customer search by email\n→ active subscriptions"]
  G --> H["DonorFacet"]
  A --> I["application_parameters\nWHERE key = 'admin_email'"]
  I -->|"email matches"| J["IsAdmin = true"]
  D & E & F & H & J --> K["deriveRoles()\n→ roles slice"]
  K --> L["Account returned\nto auth handler"]
    

2.1 Sidebar Visibility Rules

Account HasSidebar Shows
Donor onlyGiving section (Overview, History, Impact)
API subscriber onlyAccount, Billing, Support, API Key, Usage, Rate Limits (if rate-limited tier), Reports (if LRS enabled)
Webhook onlyAccount, Billing, Support, Webhook Configuration, Delivery Log
Partner onlyAccount, Billing, Support, Connection, Usage
Donor + any serviceWorld tabs (❤️ Giving / ⚡ Services) — each tab shows its own sidebar section
AdminAll panels + impersonation capability

3. Authentication — Magic Link Flow

The dashboard uses passwordless magic link authentication. No passwords are stored or transmitted. A time-limited, single-use token is emailed to the user via SES and consumed atomically on first use.

Diagram 3.1: Magic Link Authentication Flow
sequenceDiagram
  participant U as User (Browser)
  participant S as LPO Server
  participant V as Valkey
  participant A as Aurora
  participant E as SES (Email)

  U->>S: POST /dashboard/api/request-link {email}
  S->>V: Rate limit check (3/hr per email, 10/hr per IP)
  S->>A: emailHasAccount(email)?
  alt account exists
    S->>S: generateToken() → 32-byte random
    S->>V: SET magic:{sha256(token)} payload TTL=15min
    S->>E: Send magic link email (async goroutine)
  end
  S-->>U: 200 "If an account exists, a link was sent" (always)

  U->>S: GET /dashboard/authenticate?token=...
  S->>V: GETDEL magic:{sha256(token)} (atomic, single-use)
  alt token valid
    S->>A: resolveAccount(email) → facets + roles
    S->>S: generateToken() → session token
    S->>V: SET session:{sha256(token)} Session TTL=24h
    S-->>U: HTML page → localStorage.setItem('cpmp_user', JSON) → redirect /dashboard
  else token expired or used
    S-->>U: HTML error page "Link expired or already used"
  end
    

3.1 Token Security

PropertyValue
Token entropy32 bytes (256 bits) cryptographically random via crypto/rand
StorageSHA-256 hash stored in Valkey — raw token never persisted server-side
Magic link TTL15 minutes
Single-use enforcementAtomic GETDEL — consumed on first use, cannot be replayed
Session TTL24 hours, sliding (extended on every authenticated request)
Rate limiting3 requests/hour per email, 10 requests/hour per IP
Email enumerationAlways returns 200 regardless of whether account exists
TransportAuthorization: Bearer <token> header — never in cookies, no CSRF needed

3.2 Magic Link Email

Sent from The Trinity Beast <No-Reply@CPMP-Site.org> via SES (us-east-2). Gmail-compatible HTML: bgcolor attributes on every <td>, solid hex colors only, no rgba(). Subject: Your Dashboard Login Link — CPMP.

4. Session Management

Sessions are stored in Valkey under session:{sha256(token)}. The SPA stores the raw token in cpmp_user.token (localStorage JSON object) and sends it as a Bearer token on every API call. The RequireSession middleware validates and slides the TTL on every request.

Valkey KeyTTLContents
session:{hash}24h slidingEmail, roles, created_at, last_seen, IP, user_agent, impersonated_by (if admin session)
magic:{hash}15 minEmail, requested_at, IP, user_agent
ratelimit:magic:email:{email}1 hourRequest counter (max 3)
ratelimit:magic:ip:{ip}1 hourRequest counter (max 10)
audit:dashboard:{email}90 daysSorted set of audit events (last 500), scored by timestamp ms

4.1 Client-Side Storage (localStorage)

The dashboard and website use two localStorage JSON objects to persist state across page loads. Both use underscore-delimited names to distinguish them from the legacy flat keys they replaced.

KeyLifetimeStructureWritten By
cpmp_userSession (cleared on logout)
{
  "token": "Bearer session token (256-bit)",
  "email": "user@example.com",
  "name":  "Display Name",
  "roles": ["api-subscriber", "donor"],
  "lang":  "en"
}
Magic link authenticate endpoint (/dashboard/authenticate)
cpmp_sitePermanent
{
  "lang": "en"
}
i18n.js v5 (language selector flag dropdown)

Key Responsibilities

Legacy Key Migration

Prior to i18n.js v5 (May 2026), the language preference was stored as a flat string in localStorage('cpmp-lang'). On first page load after the upgrade, i18n.js automatically reads the old flat key, migrates the value into cpmp_site.lang, and removes the old key. This migration is transparent to users — no language preference is lost.

Session Validation on Load

On every page load, GET /dashboard/api/session is called to confirm the token in cpmp_user.token is still valid before rendering the dashboard. If the token is expired or revoked (e.g., after email change), the SPA clears cpmp_user and redirects to the login screen.

5. Dashboard Panels

All panels are rendered client-side by the SPA JavaScript. The server serves a single HTML shell; panel content is built from the account data returned by GET /dashboard/api/account plus async data fetched per-panel as needed.

Diagram 5.1: Panel Routing by Account Type
graph TD
  A["Account Loaded"] --> B{HasMultipleWorlds?}
  B -->|"Donor + Service"| C["World Tabs\n❤️ Giving / ⚡ Services"]
  B -->|"Single facet"| D["Flat sidebar"]

  C -->|"Giving tab"| E["giving-overview\ngiving-history\ngiving-impact"]
  C -->|"Services tab"| F["account · billing · support"]
  D --> F

  F --> G{APIKeyFacet?}
  G -->|yes| H["api-key · usage\nrate-limits (if limited tier)\nreports (if LRS enabled)"]

  F --> I{WebhookFacet?}
  I -->|yes| J["webhook · webhook-log"]

  F --> K{PartnerFacet?}
  K -->|yes| L["connection · usage"]

  F --> M{IsAdmin?}
  M -->|yes| N["All panels\n+ impersonation"]
    

5.1 Overview Panel

The default landing panel. Shows a welcome message, key stat tiles, and quick action buttons. Content adapts to the account's facets — a donor sees giving stats, an API subscriber sees usage stats, a partner sees connection status.

If the account has both donor and service facets, a giving bar shows lifetime total with a link to the Giving tab.

Data source: GET /dashboard/api/account (no additional API call needed).

5.2 Giving Panels

Three panels available to accounts with a DonorFacet:

PanelRoute KeyContentsData Source
Giving Overviewgiving-overviewStatus, monthly gift amount, total given, next renewal date, Stripe portal button/account (DonorFacet)
Donation Historygiving-historyTable of last 12 months of Stripe charges — date, amount, receipt linkGET /dashboard/api/giving/history
Impactgiving-impactPhoto gallery of CPMP mission work — medical camps, freedom moments, wheelchairs, Bible distribution, provisions, trainingStatic (embedded in SPA)

The Donation History panel fetches the last 12 months of successful Stripe charges. If Stripe has older records, a note is shown directing the user to contact support for a full history.

5.3 Account & Billing Panels

PanelRoute KeyContentsData Source
ProfileaccountEmail (with change option), display name (editable), preferred language, member since date, roles/account
BillingbillingStripe Customer Portal button, next renewal date. Portal allows payment method updates, invoice history, subscription management.POST /dashboard/api/billing/portal (on button click)

The billing portal resolves the Stripe customer ID in order: Aurora api_keys.stripe_customer_idStripe customer search by email. The portal session URL is returned and opened in a new tab. Return URL is https://cpmp-site.org/dashboard.

5.4 API Key & Usage Panels

Available to accounts with an APIKeyFacet.

PanelRoute KeyContentsData Source
API Keyapi-keyMasked key display, Reveal button (fetches full key on demand), Copy button (appears after reveal), key details (tier, status, LRS, renewal)GET /dashboard/api/api-key/reveal (on Reveal click)
UsageusageCurrent month request count, quota progress bar (color-coded: green/amber/red), 30-day daily bar chartGET /dashboard/api/usage
Rate Limitsrate-limitsQPS limit, burst limit, monthly quota. Only shown for rate-limited tiers (free, pro, enterprise). Hidden for unlimited/lifetime./account (APIKeyFacet)
LRS ReportsreportsInteractive reports panel with Usage and Summary tabs. Date range picker, asset filter, pagination (30/60/90 per page), and export (JSON/CSV/TSV/Text). Proxies to local LRS on port 9090 using the user's API key. Available to all API key holders regardless of tier — same monthly report limits apply.GET /dashboard/api/reports/usage
GET /dashboard/api/reports/summary

The API key is masked by default (tbcc-****-****-**** style). The Reveal action calls /api-key/reveal, which requires api-subscriber, webhook-associate, partner, or admin role. Every reveal is audit-logged.

The 30-day usage chart is rendered as proportional bars from the usage_logs table, grouped by day in EST timezone.

5.5 Webhook Panels

Available to accounts with a WebhookFacet.

PanelRoute KeyContentsStatus
Webhook ConfigurationwebhookCurrent plan, asset count, push interval, HTTPS endpoint, UDP endpoint, last delivery timestamp. Includes categorized asset picker (7 groups: Major Currencies, DeFi, Layer 2, AI & Data, Gaming & NFTs, Meme Coins, Infrastructure) and endpoint configuration wizard.Live — full management
Delivery Logwebhook-logRecent delivery historyStub — coming in next update

The webhook configuration panel includes a categorized asset picker that groups all 150 available assets into 7 categories for easy selection. Plan-tier limits are enforced (e.g., starter = 3 assets, standard = 10, professional = 25, enterprise = 50).

5.6 Partner Connection Panel

Available to accounts with a PartnerFacet.

FieldDescription
Connection Statusconnected / degraded / disconnected (color-coded green/amber/red)
PrivateLink EndpointAWS PrivateLink endpoint identifier
SLA TierPartner SLA level
Hourly VolumeRequests per hour

Connection status is currently static ("unknown"). Live health data via CloudWatch or TCP probe is planned for a future update.

5.7 Support Panel (Customer)

Available to all authenticated accounts. Provides a full inline support experience — customers can view their tickets, read the reply thread, post replies, and mark tickets as resolved without leaving the dashboard.

5.7.1 Ticket List

Displays all support tickets associated with the authenticated user's email, ordered by most recent first. Each row shows ticket number, subject, category, status, and last updated date.

Data source: GET /dashboard/api/support/tickets

5.7.2 Ticket Detail & Thread

Clicking a ticket opens the full conversation thread. Shows the original message, all non-internal replies (admin internal notes are never visible to customers), and the current status. Replies from admin are displayed with translated content when the customer's preferred_lang is not English.

Data source: GET /dashboard/api/support/tickets/{ticket_number}

5.7.3 Customer Reply

Customers can post replies to open tickets directly from the dashboard. Replies are limited to 10,000 characters. If the customer's language is not English, the reply is auto-translated to English for admin readability (stored in message_translated). Posting a reply notifies the admin via email.

Status flow: A customer reply to a ticket in awaiting_customer or resolved status automatically re-opens it to open.

Data source: POST /dashboard/api/support/tickets/{ticket_number}/reply

5.7.4 Mark as Resolved

Customers can mark their own ticket as resolved from the dashboard. This sets the status to resolved and notifies the admin. Tickets already in resolved or closed status return a success message without changes.

Alternatively, customers can resolve tickets via a single-use email link (included in admin reply notifications). The link contains a 32-byte token stored in Valkey with a 30-day TTL, consumed atomically on use.

Data source: POST /dashboard/api/support/tickets/{ticket_number}/resolve

5.7.5 Ticket Statuses

StatusMeaning
newJust submitted, not yet reviewed by admin
openAdmin has replied or customer re-opened
in_progressAdmin is actively working on it
awaiting_customerAdmin is waiting for customer response
resolvedMarked resolved by customer or admin
closedPermanently closed — no further replies allowed from dashboard

5.7.6 Ticket Categories

CategoryDescription
generalGeneral inquiry
api-technicalAPI integration or technical issue
billingBilling, subscription, or payment question
bug-reportBug or unexpected behavior
feature-requestFeature suggestion
mission-donationsCPMP mission or donation inquiry

5.8 Admin Support Panel

Visible only when is_admin: true. Provides a full ticket management interface — view all tickets across all customers, filter by status/category, read threads (including internal notes), post replies, change status, and manage the support queue.

5.8.1 All Tickets View

Lists all support tickets system-wide, ordered by most recently updated. Supports filtering by status and category via query parameters. Limited to 200 results per request.

Data source: GET /dashboard/api/admin/support/tickets?status=open&category=technical

5.8.2 Ticket Detail (Admin View)

Shows the full ticket including customer name, email, IP address, original message, and the complete reply thread — including internal admin notes that are never visible to customers. Useful for context when multiple admins collaborate on a ticket.

Data source: GET /dashboard/api/admin/support/tickets/{id}

5.8.3 Admin Reply

Post a reply to any ticket. Replies can be marked as is_internal: true for admin-only notes that the customer never sees. Customer-visible replies are auto-translated to the customer's preferred_lang and trigger an email notification with a one-click "Mark as Resolved" link.

Status flow: First admin reply to a new ticket automatically advances status to open.

Data source: POST /dashboard/api/admin/support/tickets/{id}/reply

5.8.4 Status Management

Change a ticket's status to any valid value. Audit-logged with the admin's email.

Data source: POST /dashboard/api/admin/support/tickets/{id}/status

5.8.5 Multi-Lingual Support Flow

The support system is fully multi-lingual:

  • Customer submits ticket in their language → auto-translated to English (message_en column) for admin readability
  • Admin replies in English → auto-translated to customer's preferred_lang for email notification and dashboard display
  • Customer replies in their language → auto-translated to English for admin view
  • Translation powered by AWS Translate (real-time, not batch)

5.8.6 AutoOps Integration

When a ticket is submitted, the tbi-raima-support Lambda is invoked asynchronously. It auto-categorizes the ticket, drafts a response, and notifies the admin with category, priority, draft, and internal notes. The analysis is stored in Valkey at support:ticket:{id} and included in the admin ticket detail response.

5.9 Translation Service Panel

Available to accounts with a Translation API key (service_type = 'translation'). Provides a complete self-service interface for submitting translation jobs, choosing AI agents, monitoring progress, and reviewing history.

5.9.1 Submission Form

The translation submission form allows customers to submit documents for translation directly from their dashboard — no API calls required.

FieldTypeDescription
Document URLURL inputPublic URL of the HTML document to translate. Must be accessible via HTTPS. Max 500 KB.
AI AgentDropdownChoose the translation agent: Good (Haiku 3.5), Better (Sonnet 4.6), or Best (Opus 4)
Target LanguagesText inputComma-separated ISO 639-1 codes (e.g., es, fr, de, ja). Supports 300+ languages.

On submission, the form calls POST /translate/quote to get an instant price quote. The customer reviews the quote (document analysis, estimated chunks, difficulty, total price) and clicks Accept & Pay to start the job.

5.9.2 Agent Selection

The agent dropdown includes a dynamic description panel that updates when the selection changes:

TierAgentSpeedBest For~Cost/Pair
GoodClaude Haiku 3.5FastLatin-script languages, batch jobs, cost-sensitive$0.40
BetterClaude Sonnet 4.6ModerateComplex scripts (Arabic, Hindi, Japanese), technical docs. Default.$1.65
BestClaude Opus 4ThoroughCritical documents, legal/medical, maximum fidelity$8.00

All three agents share the same sentinel protection pipeline — code blocks, brand terms, version numbers, and technical identifiers are extracted before the agent sees the document. The difference is the depth of linguistic understanding, not the safety of the content.

5.9.3 Job Status & Progress

After submitting a job, the panel shows real-time status. Customers can check the status of their currently running job at any time by returning to the Translation panel.

StateDescription
queuedJob accepted, waiting for processing capacity
runningTranslation in progress — per-language progress visible
completedAll language pairs finished successfully
partialSome pairs succeeded, some failed — retry available
failedJob failed entirely — error details available
cancelledJob was cancelled by the customer or admin

5.9.4 Translation History

The history tab shows all past translation jobs for the customer's API key. Each entry includes:

  • Job ID, submission date, and completion date
  • Document name and target languages
  • Agent used (Haiku / Sonnet / Opus)
  • Succeeded/failed pair counts
  • Total cost (Bedrock + infrastructure)
  • Click-through to full job detail with per-language status

History is queried from Aurora via the translation_jobs table, filtered by the customer's api_key_id. The dashboard proxies this through GET /dashboard/api/translate/history.

5.9.5 API Endpoints (Translation Service)

Customers can also interact with the Translation Service directly via API. All endpoints require a Translation API key (service_type = 'translation').

Get a Quote

POST /translate/quote
Content-Type: application/json
X-API-Key: your-translation-api-key

{
  "doc_url": "https://example.com/docs/my-document.html",
  "langs": ["es", "fr", "de", "ja", "zh"],
  "model": "claude-sonnet-4.6"
}

Response includes document analysis (size, chunks, difficulty, code blocks, diagrams), pricing breakdown (cost per chunk, per pair, markup, total), and a quote ID valid for 24 hours.

Accept a Quote (Pay & Start)

POST /translate/accept/{quote_id}
X-API-Key: your-translation-api-key

Charges the customer's payment method on file and immediately submits the translation job. Returns the job ID for status tracking.

Check Job Status

GET /translate/quote/{quote_id}
X-API-Key: your-translation-api-key

Returns the quote details including the associated job status if the quote has been accepted.

List All Quotes

GET /translate/quotes
X-API-Key: your-translation-api-key

Returns all quotes for the authenticated API key — pending, accepted, expired, and completed.

List Available Models

GET /translate/models

Public endpoint (no auth required). Returns the curated list of available AI agents with pricing, speed, quality ratings, and descriptions.

5.9.6 API Key Separation

Translation API keys and Prices API keys are distinct. Each key has a service_type field:

Key Typeservice_typeAccess
Prices API Keyprices/price, /reports, LRS endpoints
Translation API Keytranslation/translate/* endpoints

Using the wrong key type returns a clear 403 error explaining which key type is needed — not a generic "invalid key" message. This prevents confusion between the two services.

5.9.7 Brand Terms Protection

Customers can configure up to 150 brand terms that are automatically protected during every translation job. These terms are never translated or transliterated — they appear exactly as written in every target language.

The Brand Terms section appears in the Translation Service panel under Account Settings. Customers can add, edit, and remove terms at any time. Changes take effect on the next translation job — no need to re-submit existing quotes.

How It Works

  • Customer adds brand terms via the dashboard (e.g., "Acme Corp", "DataForge", "CloudSync API")
  • Terms are stored on the customer's API key in Aurora (api_keys.protected_terms JSONB column)
  • When a translation job is submitted, the customer's terms are automatically fetched and passed to the translation worker
  • The sentinel preprocessing system wraps each term with translate="no" annotations before sending text to the AI agent
  • Terms survive translation untouched — no transliteration, no localization, no modification

Limits

ConstraintValue
Maximum terms per account150
Maximum characters per term100
DuplicatesAutomatically removed on save
ScopeAccount-level — applies to all jobs, no per-request overrides

API Endpoints

MethodPathDescription
GET/dashboard/api/protected-termsReturns current terms list with count and limit
PUT/dashboard/api/protected-termsReplaces entire terms list. Body: {"terms": ["Term1", "Term2", ...]}

5.9.8 Notifications, Spend & Refund History

Three additional features enhance the Translation Service panel for customers and admins:

Notification Badge

A 🔔 icon in the dashboard header shows a red dot with the count of unread translation job completions. Clicking the badge displays a summary of recently completed jobs. Notifications are polled every 60 seconds via App.pollNotifications().

  • Badge appears in the global header — visible on all dashboard pages
  • Red dot disappears after clicking (marks notifications as seen)
  • Seen state stored in Valkey at dashboard:notifications:seen:{api_key_id} (30-day TTL)
  • Only translation job completions generate notifications — not quotes or cancellations

Translation Spend Widget

A dedicated spend section shows translation costs aggregated by month and model for the last 6 months. Customers see only their own spend; admins see all accounts.

  • Stat tiles: Total Revenue, Bedrock Cost, Jobs Completed, Language Pairs
  • Monthly breakdown table with per-model columns (Haiku, Sonnet, Opus)
  • Data sourced from Aurora translation_jobs table (the ledger)

Refund History (Admin Only)

When a refund is processed (translation or subscription), the customer receives a localized confirmation email in their preferred_lang. The email includes refund amount, original charge, refund ID, and for subscription refunds, confirms the API key has been revoked. Supported languages: English, Spanish, Portuguese, French, German, Russian, Hindi, Urdu, Arabic, Japanese, Chinese, and Italian.

Admin accounts see a refund history card in the Translation Service panel showing all refunded or partially refunded translation purchases.

ColumnDescription
DateWhen the refund was processed
DocumentOriginal document name from the quote
CustomerCustomer email (admin view only)
OriginalOriginal charge amount (USD)
RefundedRefund amount (USD)
Staterefunded or partially_refunded

API Endpoints

MethodPathDescription
GET/dashboard/api/translate/spendReturns 6-month spend breakdown by month and model. Admin sees all; customers see own.
GET/dashboard/api/translate/refundsReturns refund history. Admin-only.
GET/dashboard/api/translate/notificationsReturns unread notification count and recent completed job summaries.
POST/dashboard/api/translate/notifications/seenMarks all notifications as seen. Resets badge count to 0.

6. API Endpoints

All dashboard endpoints are served under https://api.cpmp-site.org/dashboard. Authenticated endpoints require Authorization: Bearer <token>.

6.1 Public Endpoints (No Auth)

MethodPathDescription
GET/dashboardServes the SPA HTML shell. No auth required — the SPA handles auth state client-side.
GET/dashboard/authenticateValidates magic link token, creates session, returns HTML page that writes cpmp_user JSON to localStorage and redirects to /dashboard.
POST/dashboard/api/request-linkSends magic link email. Body: {"email":"..."}. Always returns 200.

6.2 Authenticated Endpoints (Bearer Token Required)

MethodPathRole RequiredDescription
GET/dashboard/api/sessionAnyValidates token, returns email, roles, created_at, last_seen. Called on SPA load.
POST/dashboard/api/logoutAnyDeletes session from Valkey. SPA clears cpmp_user from localStorage on 200.
GET/dashboard/api/accountAnyReturns full resolved account — all facets, roles, display name. The primary data source for all panels.
POST/dashboard/api/billing/portalAnyCreates Stripe billing portal session. Returns {"url":"..."}. Audit-logged.
GET/dashboard/api/api-key/revealapi-subscriber, webhook-associate, partner, or adminReturns full unmasked API key. Audit-logged on every call.
GET/dashboard/api/usageapi-subscriberReturns current month usage, quota, and 30-day daily breakdown array.
GET/dashboard/api/giving/historydonorReturns last 12 months of Stripe charges — date, amount_usd, status, receipt_url.
GET/dashboard/api/reports/usageapi-subscriberProxies to LRS /reports/usage. Resolves user's API key from session. Accepts same query params as direct LRS (asset, start_date, end_date, page, page_size, format, cached).
GET/dashboard/api/reports/summaryapi-subscriberProxies to LRS /reports/summary. Resolves user's API key from session. Accepts same query params as direct LRS (start_date, end_date, format).
POST/dashboard/api/profileAnyUpdates display name. Body: {"display_name":"..."}.
POST/dashboard/api/email/changeAnyInitiates email change. Sends verification to new address. Body: {"new_email":"..."}.
GET/dashboard/api/support/ticketsAnyReturns all support tickets for the authenticated user's email. Ordered by most recent.
GET/dashboard/api/support/tickets/{ticket_number}AnyReturns full ticket detail with reply thread (excludes internal admin notes). Scoped to user's email.
POST/dashboard/api/support/tickets/{ticket_number}/replyAnyPost a customer reply. Body: {"message":"..."}. Max 10,000 chars. Auto-translates to English for admin. Notifies admin via email.
POST/dashboard/api/support/tickets/{ticket_number}/resolveAnyMark own ticket as resolved. Notifies admin. Closed tickets cannot be resolved (open a new one).
GET/dashboard/api/translate/historytranslation-subscriberReturns translation job history for the authenticated user's API key. Includes job ID, state, docs, langs, model, cost, pair counts.
GET/dashboard/api/translate/status/{job_id}translation-subscriberReturns real-time status of a specific translation job including per-language progress.
GET/dashboard/api/protected-termstranslation-subscriberReturns the customer's brand terms list (terms, count, limit of 150).
PUT/dashboard/api/protected-termstranslation-subscriberReplaces brand terms list. Body: {"terms": [...]}. Max 150 terms, 100 chars each.
GET/dashboard/api/translate/spendtranslation-subscriberReturns 6-month spend breakdown by month and model. Admin sees all accounts; customers see own spend only.
GET/dashboard/api/translate/refundsadminReturns refund history for all translation purchases. Admin-only.
GET/dashboard/api/translate/notificationstranslation-subscriberReturns unread notification count and recent completed job summaries for the authenticated user.
POST/dashboard/api/translate/notifications/seentranslation-subscriberMarks all notifications as seen. Resets badge count to 0.

6.3 Admin-Only Endpoints

MethodPathDescription
POST/dashboard/api/admin/impersonateStart impersonation session for a target email. Body: {"email":"..."}. Returns new session token with target's roles + impersonated_by field.
POST/dashboard/api/impersonate/endEnd impersonation, restore admin session.
GET/dashboard/api/admin/analyticsAdmin-only analytics panel. Queries usage_logs directly from Aurora (no 93-day TTL). Filters: date range, asset, api_key_id, source, node. Returns detail + summary views with breakdowns by asset, source, node, api_key, and day.
GET/dashboard/api/admin/support/ticketsList all support tickets. Filters: ?status=, ?category=. Returns up to 200 tickets ordered by most recently updated.
GET/dashboard/api/admin/support/tickets/{id}Full ticket detail by UUID — includes all replies (internal notes visible), customer IP, email, name.
POST/dashboard/api/admin/support/tickets/{id}/replyPost admin reply. Body: {"message":"...", "is_internal": false}. Internal notes hidden from customer. Customer-visible replies auto-translated and emailed.
POST/dashboard/api/admin/support/tickets/{id}/statusUpdate ticket status. Body: {"status":"open"}. Valid: new, open, in_progress, awaiting_customer, resolved, closed. Audit-logged.
POST/admin/translate/refund/{quote_id}Issue full or partial refund for a translation purchase. Body (optional): {"amount": 5.00, "reason": "..."}. Processes via Stripe, sends confirmation email to customer.

6.4 Response Format — Unified Messaging Envelope (UME)

All /dashboard/api/* JSON endpoints return the standard 12-field Unified Messaging Envelope — the same structure used by the LPO and LRS APIs. There are no exceptions. Every success, every error, every auth failure uses the same shape.

6.4.1 Success Response

On success, data contains the endpoint-specific payload and error is an empty string:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/account] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/account",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T18:30:00Z",
  "data": {
    "email": "user@example.com",
    "display_name": "Cory Dean",
    "preferred_lang": "en",
    "created_at": "2026-01-15T00:00:00Z",
    "roles": ["api-subscriber", "donor"],
    "is_admin": false,
    "api_key": {
      "api_key_id": "uuid",
      "api_key_masked": "tbcc-****-****-****",
      "tier": "pro",
      "subscription_status": "active",
      "usage_this_month": 12847,
      "quota": 50000,
      "rate_limit_qps": 10,
      "burst_limit": 50,
      "lrs_enabled": false,
      "next_renewal": "2026-06-15"
    },
    "donor": {
      "stripe_customer_id": "cus_...",
      "amount_cents": 2500,
      "currency": "usd",
      "interval": "month",
      "status": "active",
      "next_renewal": "2026-06-01",
      "lifetime_total_usd": "125.00"
    },
    "webhook": null,
    "partner": null
  },
  "error": ""
}

6.4.2 Error Response

On error, data is null and error contains the bracket-prefixed message:

{
  "status": "🛑 [LPO] [us-east-2] [BeastMain] [/dashboard/api/account] [401]",
  "status_code": 401,
  "endpoint": "/dashboard/api/account",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T18:30:00Z",
  "data": null,
  "error": "🛑 [LPO] [us-east-2] [BeastMain] [/dashboard/api/account] [401] Session expired or invalid"
}

6.4.3 SPA Envelope Handling

The SPA's apiFetch() function handles envelope unwrapping transparently:

This means all existing panel code that consumes apiFetch results continues to work unchanged — the envelope is invisible to panel rendering logic.

6.4.4 Non-JSON Responses (Exceptions)

Two response types bypass the UME envelope by design:

7. Admin Impersonation

Admin accounts (identified by the admin_email application parameter) can impersonate any account for support and debugging purposes. Impersonation creates a new session with the target account's roles plus an impersonated_by field recording the admin's email.

PropertyBehavior
Visual indicatorRed banner always visible: "You are viewing [email]'s account as admin"
Audit loggingBoth the admin and the target account are audit-logged on impersonation start and end
Session isolationImpersonation creates a new session token — the admin's original session is preserved
End impersonationPOST /dashboard/api/impersonate/end — restores the admin's original session
AccessAdmin sees the target's full dashboard exactly as the target would see it

7.1 How to Impersonate an Account

The impersonation panel appears at the bottom of the Overview panel when logged in as admin. It is only visible when is_admin: true is returned by the account endpoint.

  1. Log in to the dashboard at https://api.cpmp-site.org/dashboard using your admin email (corydeankalani@cpmp-site.org).
  2. On the Overview panel, scroll to the bottom — you will see the Admin — Impersonate Account card.
  3. Enter the target account's email address (e.g. contact@cpmp-site.org for Homer Simpson).
  4. Click Impersonate. The SPA swaps your session token and reloads with the target account's data.
  5. A red banner appears at the top of every panel: "You are viewing [email]'s account as admin."
  6. To switch to a different account, click End Impersonation in the red banner first, then impersonate again with a new email.
  7. To return to your own dashboard, click End Impersonation. You will be prompted to sign in again — request a new magic link for your admin email.

7.2 How to Test Each Account Role

Use impersonation to see exactly what each test account sees without logging out and back in. The four test accounts and their roles:

EmailNameRoleWhat to look for
corydeankalani@cpmp-site.orgCory Dean KalaniAdmin + LifetimeImpersonation card visible, LRS Reports in sidebar, unlimited usage, no rate limits panel
contact@cpmp-site.orgHomer SimpsonPro API SubscriberUsage panel with 30-day chart (3,330 logs), amber quota bar at 62%, Rate Limits panel (10 QPS / 50 burst)
support@cpmp-site.orgBugs BunnyWebhook AssociateWebhook Configuration panel with 9 assets, UDP + HTTPS endpoints, 4,320 pushes this month
admin@cpmp-site.orgTony StarkPartnerConnection panel with PrivateLink status, 7-day usage chart (1,400 logs), no rate limits

7.3 Magic Link Flow (Alternative to Impersonation)

If you want to test an account as that user would experience it (without the admin red banner), use the magic link flow directly:

  1. Open an incognito/private browser window.
  2. Go to https://api.cpmp-site.org/dashboard.
  3. Enter the test account email and click Send Login Link.
  4. Check the inbox for that email address — a magic link will arrive from The Trinity Beast <No-Reply@CPMP-Site.org>.
  5. Click the link — the dashboard opens with that account's full data, no admin banner.

Note: Magic links expire in 15 minutes and are single-use. Rate limit is 3 requests per hour per email address.

8. Data Sources

DataSourceTable / KeyNotes
API key details, usage, tierAuroraapi_keys JOIN usersIdentity anchored on users.email
30-day daily usage breakdownAurorausage_logsGrouped by day in EST timezone
Webhook configurationAurorawebhook_subscriptionsJoined via api_key_id
Donor subscription statusStripeCustomer search + subscriptions APINon-fatal if Stripe is unavailable
Donation historyStripeCharges APILast 12 months, succeeded + captured only
Billing portal URLStripeBilling Portal Sessions APICreated on demand, not cached
Admin emailAuroraapplication_parameters WHERE key = 'admin_email'Single row lookup
Sessions, magic links, rate limitsValkeysession:*, magic:*, ratelimit:*See Section 4
Audit logValkeyaudit:dashboard:{email}Sorted set, last 500 events, 90-day TTL
Dashboard session (client)BrowserlocalStorage cpmp_userJSON: token, email, name, roles, lang — cleared on logout
Language preference (client)BrowserlocalStorage cpmp_siteJSON: lang — persists across sessions, survives logout

9. Security Design

ConcernMitigation
Password exposureNo passwords — magic link only
Token theftTokens are 256-bit random, stored hashed in Valkey, transmitted only in Authorization header (not cookies)
CSRFNot applicable — Bearer tokens in Authorization header are not auto-sent by browsers
Email enumerationRequest-link always returns 200 regardless of account existence
Magic link replayAtomic GETDEL — token consumed on first use, cannot be replayed
Brute forceRate limiting: 3 magic link requests/hour per email, 10/hour per IP
Session fixationNew session token generated on every login — magic link token and session token are separate
API key exposureKey masked by default, full key only returned on explicit Reveal action, audit-logged
Impersonation abuseAdmin-only, both parties audit-logged, red banner always visible, original session preserved
XSSAll user-supplied values escaped via esc() helper before DOM insertion

10. Valkey Keys

Key PatternTypeTTLContents
session:{sha256(token)}STRING24h slidingJSON Session object — email, roles, timestamps, IP, user_agent, impersonated_by
magic:{sha256(token)}STRING15 minJSON MagicLinkPayload — email, requested_at, IP, user_agent
ratelimit:magic:email:{email}STRING1 hourInteger counter — magic link requests from this email
ratelimit:magic:ip:{ip}STRING1 hourInteger counter — magic link requests from this IP
audit:dashboard:{email}ZSET90 daysSorted set of audit events, scored by Unix ms timestamp. Max 500 entries. Events: login, logout, magic-link-requested, api-key-revealed, billing-portal-opened, impersonation-start, impersonation-end.
support:resolve:{token}STRING30 daysMaps a single-use resolve token to a ticket UUID. Consumed atomically via GETDEL when the customer clicks the email resolve link.

11. Implementation Status

ComponentStatusNotes
Magic link auth flow✅ LiveSES email, 15min TTL, single-use atomic GetDel
Session management✅ Live24h sliding TTL, Bearer token, Valkey-backed
Account resolver✅ LiveAurora + Stripe → typed facets + roles
SPA shell + routing✅ LiveAll panels rendered client-side, role-aware sidebar
Overview panel✅ LiveAdapts to all account types
Giving Overview panel✅ LiveDonorFacet data, Stripe portal button
Donation History panel✅ Live12 months of Stripe charges
Impact panel✅ LivePhoto gallery, static content
Profile panel✅ LiveEditable display name, email change with verification, language preference
Billing panel✅ LiveStripe portal session on demand
API Key panel✅ LiveMasked display, reveal + copy
Usage panel✅ LiveMonthly total + 30-day bar chart
Rate Limits panel✅ LiveQPS, burst, quota from APIKeyFacet
Webhook Configuration panel✅ Live (read-only)Shows current config — wizard coming next
Partner Connection panel✅ Live (static)Live health data deferred
Support panel✅ LiveFull inline ticket history, submit new tickets, reply to existing tickets. Customer and Admin views with filtering.
Admin impersonation✅ LiveFull impersonation with audit trail
Admin Analytics panel✅ LiveAurora-based usage analytics (no 93-day TTL). Filters: date range, asset, api_key_id, source, node. Detail + Summary views with bar charts. Export in JSON/CSV/TSV/Text. Admin-only (sidebar "📈 Analytics" under Admin section).
LRS Reports panel✅ LiveInteractive panel with Usage/Summary tabs, date range picker, asset filter, pagination (30/60/90), export (JSON/CSV/TSV/Text). Proxies to local LRS via /dashboard/api/reports/*. Available to all tiers — same monthly report limits apply.
Webhook Delivery Log panel⏳ StubComing in next dashboard session
Webhook Wizard✅ LiveCategorized asset picker (7 groups), endpoint configuration, plan-aware limits
Partner connection live health⏳ PlannedCloudWatch or TCP probe
Dashboard i18n✅ LiveAll panel labels, button text, and status strings delivered via dashboard.* i18n namespace in all 12 language JSON files. SPA reads cpmp_site.lang from localStorage and applies translations dynamically. Fully multi-lingual.
Mobile polish⏳ PlannedResponsive layout improvements
Announcement email⏳ Pre-launchEmail all active subscribers when testing is complete

12. Role Walkthroughs — What Each Account Sees

The following panels show exactly what each test account sees when logged in to the dashboard. Data reflects the current seed state in Aurora. Each account uses a cartoon character as the display name — a convention that keeps test data obviously fictional.

✅ Dashboard is fully multi-lingual.

All panel labels, button text, status strings, and UI copy are delivered in the user's preferred_lang. The dashboard.* i18n namespace is present in all 12 language JSON files and loaded dynamically by the SPA. Language detection follows the same flow as the main site: cpmp_site.lang in localStorage → browser language → English fallback. The dashboard is safe to announce to all subscribers regardless of language.

12.1 Cory Dean Kalani — Admin + Lifetime

The admin account. Sees all panels, has the impersonation capability, LRS Reports enabled, and unlimited usage. The red impersonation banner appears when viewing another account.

corydeankalani@cpmp-site.org  ·  Sign out
Welcome back, Cory Dean Kalani
Lifetime plan · Unlimited access · LRS Reports enabled
Plan
Lifetime
Active
Usage This Month
0
Unlimited
LRS Reports
Enabled
Included
Role
Admin
Full access
Quick Actions
View API Key Usage Stats LRS Reports Open Support Ticket Manage Billing
🔐 Admin — Impersonate Account
Enter any email to view their dashboard as if you were them.
homer@example.com Impersonate

When impersonating, a red banner appears at the top of every panel:

🔴 You are viewing homer@example.com's account as admin  ·  End Impersonation
homer@example.com (impersonated)

12.2 Homer Simpson — Pro API Subscriber

A rate-limited pro subscriber. 31,204 of 50,000 requests used this month (62% — amber progress bar). 30-day usage chart shows realistic ramp-up with heavier recent activity.

contact@cpmp-site.org  ·  Sign out
Usage
Pro plan · 50,000 requests/month
This Month
31,204 requestsof 50,000
62% used · 18,796 remaining
Last 30 Days
Apr 13May 13

Rate Limits panel for Homer's pro tier:

contact@cpmp-site.org
Rate Limits
Current Limits
Requests per Second10 QPS
Burst Limit50 requests
Monthly Quota50,000 requests

12.3 Bugs Bunny — Webhook Associate

A webhook standard subscriber. 9 assets configured, 15-second push interval, both UDP and HTTPS delivery endpoints active. 4,320 pushes delivered this month.

support@cpmp-site.org  ·  Sign out
Webhook Configuration
Standard plan · 9 assets · 15s interval
Current Setup
PlanWebhook Standard
Assets Configured9
Push Interval15 seconds
HTTPS Endpointhooks.bugsbunny.io/tbi/prices
UDP Endpoint198.51.100.42:2679
Pushes This Month4,320
Configured Assets
BTCETHSOLXRPDOGEADALINKDOTAVAX
Manage Configuration
Use the asset picker to add or remove assets. Changes take effect on the next push cycle.
Edit Assets Webhook Docs

12.4 Tony Stark — Partner

An AWS PrivateLink partner account. High-volume, low-latency access via internal network. No rate limits, no monthly quota. Usage panel shows 7 days of PrivateLink traffic across 15 assets.

admin@cpmp-site.org  ·  Sign out
Welcome back, Tony Stark
Partner account · PrivateLink · Enterprise SLA
Connection
Connected
PrivateLink
SLA Tier
Enterprise
99.99% uptime
Hourly Volume
200
req / hour
Rate Limits
None
Unlimited
Connection Details
Status● Connected
Endpointvpce-0f52837e401b8960e
SLA TierEnterprise
Hourly Volume200 requests / hour
Last 7 Days — Usage
May 6May 13
1,400 requests · avg 1–3ms latency · all cached · source: privatelink