Boom Labs · Session Handoff

BMBB Dashboard — API cards

Everything the bmbb-portal session needs to render live tiles from the six SaaS apps. The provider side (each app's /api/v1) is built & deployed — this is the consumer side. Paste-ready; restyle to match the portal.

1. Mint one key per app → env vars

While signed into each app, POST /api/keys (Localey: use its existing BOO/Dashee key). Each returns the plaintext once. Put them in the bmbb-portal env:

# bmbb-portal .env
REPLYEE_API_KEY=rpl_live_...
RANKEE_API_KEY=rk_live_...
BLOGGY_API_KEY=blg_live_...
LOCALEY_API_KEY=lc_...        # Localey's existing key
POSTTEE_API_KEY=pt_live_...
ASSISTEE_API_KEY=ast_live_...

Mint via curl (example — Bloggy), copy the key from the response:

curl -X POST https://bloggy.online/api/keys \
  -H "Content-Type: application/json" \
  -H "Cookie: <your logged-in session cookie>" \
  -d '{"label":"BMBB dashboard"}'

2. Endpoint cheat-sheet

AppEndpointAuth headerKey returns (fields)
RankeeGET /api/v1/summary?domain=Authorization: Bearerwebsites, keywords_tracked, avg_rank, latest_audit
BloggyGET /api/v1/summaryAuthorization: Bearerposts, published, drafts, scheduled, latest_posts[]
LocaleyGET /api/v1/summaryX-API-Keyavg_rating, review_count, new_reviews_30d, citations_live/total
PostteeGET /api/v1/summaryAuthorization: Bearerscheduled, published_this_month, connected_accounts
AssisteeGET /api/v1/devicesAuthorization: Bearertotal, online, offline
ReplyeeGET /api/v1/meAuthorization: Bearerbots[] — leads already flow to /admin/leads via the webhook
Replyee is a special case. Its leads already land in the portal's Leads page via the lead-webhook you built, so it may not need a fetch card at all. /api/v1/me is available if you want a small "N bots active" tile.

3. The fetch helper

One config map, one function. Server-side only (keys never reach the browser). 5-min cache.

// bmbb-portal/src/lib/boomApi.ts
type AppKey = 'replyee' | 'rankee' | 'bloggy' | 'localey' | 'posttee' | 'assistee'

const APPS: Record<AppKey, { base: string; path: string; auth: 'bearer' | 'x-api-key'; env: string }> = {
  replyee:  { base: 'https://replyee.online',  path: '/api/v1/me',      auth: 'bearer',    env: 'REPLYEE_API_KEY' },
  rankee:   { base: 'https://rankee.online',   path: '/api/v1/summary', auth: 'bearer',    env: 'RANKEE_API_KEY' },
  bloggy:   { base: 'https://bloggy.online',   path: '/api/v1/summary', auth: 'bearer',    env: 'BLOGGY_API_KEY' },
  localey:  { base: 'https://localey.online',  path: '/api/v1/summary', auth: 'x-api-key', env: 'LOCALEY_API_KEY' },
  posttee:  { base: 'https://posttee.online',  path: '/api/v1/summary', auth: 'bearer',    env: 'POSTTEE_API_KEY' },
  assistee: { base: 'https://assistee.online', path: '/api/v1/devices', auth: 'bearer',    env: 'ASSISTEE_API_KEY' },
}

export async function fetchAppSummary(app: AppKey, query = ''): Promise<Record<string, unknown>> {
  const cfg = APPS[app]
  const key = process.env[cfg.env]
  if (!key) return { ok: false, error: 'missing key' }
  const headers: Record<string, string> =
    cfg.auth === 'bearer' ? { Authorization: `Bearer ${key}` } : { 'X-API-Key': key }
  try {
    const res = await fetch(cfg.base + cfg.path + query, { headers, next: { revalidate: 300 } })
    if (!res.ok) return { ok: false, error: `HTTP ${res.status}` }
    return await res.json()
  } catch {
    return { ok: false, error: 'unreachable' }
  }
}

4. A card component

Generic tile: title, a big stat, a sub-line. Server component — it fetches, so no client key exposure. Restyle to the portal's look.

// bmbb-portal/src/components/SummaryCard.tsx  (server component)
export function StatTile({ label, value, sub }: { label: string; value: string | number; sub?: string }) {
  return (
    <div style={{ background: '#fff', border: '1px solid #e5e7eb', borderRadius: 12, padding: 18 }}>
      <div style={{ fontSize: 12, color: '#6b7280', fontWeight: 600 }}>{label}</div>
      <div style={{ fontSize: 28, fontWeight: 800, margin: '4px 0' }}>{value}</div>
      {sub && <div style={{ fontSize: 12, color: '#9ca3af' }}>{sub}</div>}
    </div>
  )
}

Wiring the tiles (dashboard page — server component)

import { fetchAppSummary } from '@/lib/boomApi'
import { StatTile } from '@/components/SummaryCard'

export default async function DashboardCards() {
  const [rankee, localey, posttee, assistee] = await Promise.all([
    fetchAppSummary('rankee', '?domain=bigmikesbailbonds.com'),
    fetchAppSummary('localey'),
    fetchAppSummary('posttee'),
    fetchAppSummary('assistee'),
  ])

  const n = (v: unknown) => (typeof v === 'number' ? v : v == null ? '—' : String(v))

  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 14 }}>
      <StatTile label="Avg Google Rank" value={n(rankee.avg_rank)} sub={`${n(rankee.keywords_tracked)} keywords`} />
      <StatTile label="Review Rating"   value={n(localey.avg_rating)} sub={`${n(localey.review_count)} reviews · ${n(localey.new_reviews_30d)} new`} />
      <StatTile label="Scheduled Posts" value={n(posttee.scheduled)} sub={`${n(posttee.published_this_month)} published this mo`} />
      <StatTile label="Devices Online"  value={`${n(assistee.online)}/${n(assistee.total)}`} sub="remote support" />
    </div>
  )
}
Guarded fields = a blank/0 tile means a column mismatch, not a crash. Each app's summary field is wrapped in try/catch and degrades to 0/null. If a tile reads blank on first load, tell Claude which app and it'll fix that one column (known suspects: Bloggy post status values, Posttee pt_posts.user_id, Localey lc_reviews.rating).

5. Prompt to paste into the BMBB session

Wire six live SaaS cards into this bmbb-portal dashboard. Use the fetch
helper + StatTile from the Boom Labs handoff (boomApi.ts config map: Rankee/
Bloggy/Posttee/Assistee via Authorization: Bearer, Localey via X-API-Key,
endpoints /api/v1/summary except Assistee /api/v1/devices). Keys are in env
(REPLYEE/RANKEE/BLOGGY/LOCALEY/POSTTEE/ASSISTEE_API_KEY). Render tiles matching
the portal's existing card style; server-side fetch only. Replyee's leads
already arrive via webhook on /admin/leads, so no Replyee card needed.
Boom Labs · BMBB dashboard cards handoff · Provider side (all six /api/v1) is live — see API Access Hub.