Back to Codexbar

ZoomMate Provider

docs/zoommate.md

0.46.016.9 KB
Original Source

ZoomMate Provider

The ZoomMate provider tracks credit usage against a budget cap from zoommate.zoom.us. ZoomMate is a credits-based quota — there is no session/weekly rate window, just a single primary "Credits" window.

CodexBar's ZoomMate branding uses Zoom's official core palette: Bloom (#0B5CFF) as the primary color, with Dawn (#B4D0F8) and Midnight (#00053D) as supporting colors. Provenance: Zoom Brand Center, Visual identity — Color, retrieved 2026-07-18 (page last modified 2025-08-28).

Setup

  1. Sign in to ZoomMate in Chrome.
  2. Enable ZoomMate in Settings → Providers. Cookie source defaults to Auto.

CodexBar imports your ZoomMate/Zoom session cookies from Chrome's cookie jar (covering zoommate.zoom.us, ai.zoom.us, and the parent zoom.us domain, where Zoom's SSO session cookies actually live) and exchanges them for a short-lived bearer token via the same cookie-to-token bootstrap endpoint ZoomMate's own web app uses internally (GET https://ai.zoom.us/ai-computer/api/v1/login/?continue=...) — no manual paste required. This automatic path intentionally tries Chrome only to avoid surprise prompts from other browser stores; other browsers must use manual capture.

Because the bearer token is minted from your (much longer-lived) session cookies, this keeps working as long as you stay signed in to ZoomMate in Chrome. A minted token is reused from an in-memory cache across refreshes until it nears expiry, then re-minted from the same cookies — no re-paste required. If the Chrome session cookie is missing, CodexBar reports noSession; if Zoom rejects an existing session, CodexBar reports invalidCredentials.

Chrome's cookie decryption key lives in the macOS Keychain, so CodexBar only reads Chrome's cookie store during user-initiated refreshes (a menu or Settings refresh, or codexbar cookie). Once a fresh import validates — the cookie-to-token mint succeeds — the validated host-scoped cookie headers are saved to CodexBar's shared Keychain cookie cache (com.steipete.codexbar.cache, account cookie.zoommate, same as other cookie providers) and reused before re-importing from Chrome. Background refreshes and the bundled codexbar CLI run entirely from those cached headers, so they never touch Chrome's cookie store or trigger a Keychain prompt. If Zoom rejects the cached session, CodexBar drops it and retries one fresh Chrome import (user-initiated contexts only; background refreshes report noSession until the next user refresh).

Manual

Set Cookie source to Manual in the ZoomMate provider settings, then paste a full curl command captured from DevTools:

  1. Open zoommate.zoom.us and sign in.
  2. Navigate to the AI credit usage page (Settings → AI credit usage, or equivalent).
  3. Open Developer Tools → Network tab.
  4. Reload the page and find the credits/status request (GET https://ai.zoom.us/ai-computer/api/v1/credits/status).
  5. Right-click → Copy → Copy as cURL.
  6. Paste the full curl command into the ZoomMate capture field in CodexBar settings.

Unlike some other manual-paste providers, the forwarded header allowlist for ZoomMate includes Authorization — that's the required credential (see "Common errors" below). Cookie and selected browser headers may also be forwarded, but Origin and Referer are always replaced with the fixed https://zoommate.zoom.us value. Captures are accepted only for the HTTPS /ai-computer/api/v1/credits/status endpoint on ai.zoom.us or zoommate.zoom.us — DevTools may show the request on either host, since both currently serve the same first-party API. A captured Cookie header is bound to that request's host: CodexBar tries the captured host first and never forwards its cookies if a non-auth failure falls back to the sibling host.

Token expiry (~hourly)

The ZoomMate bearer token itself is short-lived — typically about an hour. Session cookies live far longer, so:

  • Automatic mode: no action needed as long as you stay signed in to ZoomMate in Chrome; CodexBar mints a bearer token from your session cookies and reuses it (in memory) until it nears expiry, then re-mints automatically.
  • Manual mode: re-capture and re-paste a fresh curl command when the pasted token expires (surfaced as invalidCredentials — see below). This is expected behavior, not a bug — switching to Automatic mode avoids it entirely.

Auth & privacy

Automatic mode uses the same first-party ZoomMate web-client flow a signed-in browser uses: CodexBar imports Zoom session cookies from the local Chrome cookie jar, partitions them into the headers a browser would send to each fixed API host, exchanges one of those host-scoped headers for a short-lived bearer token, and then uses that bearer token for the credits/status and credits/history requests.

The minted bearer token is never persisted — it is held only in a process-lifetime in-memory cache. The validated host-scoped cookie headers (and only those headers — never the bearer) are persisted to CodexBar's existing shared Keychain cookie cache after a successful mint, the same cache and lifecycle other cookie providers (Claude web, Perplexity, OpenCode, …) already use, so background refreshes and the bundled CLI can reuse the session without rereading Chrome. ZoomMate logs intentionally omit cookies, bearer tokens, nak values, and raw response bodies. The cookie read spans the parent zoom.us domain because Zoom's SSO session cookies are parent-scoped, but the imported set is then narrowed to only cookies a browser would actually attach to ai.zoom.us / zoommate.zoom.us (RFC 6265 domain-matching), dropping cookies host-scoped to unrelated *.zoom.us siblings that these endpoints never receive.

All authentication and credit requests use fixed HTTPS URLs on the two first-party API hosts: automatic mode tries ai.zoom.us first, while manual mode starts with the host in the accepted cURL capture; either mode falls back to the other host on non-auth failures. Each request receives only the cookies scoped to its destination host. The hosts currently serve the same /ai-computer/ API interchangeably and either may retire in the future, so the provider works with both; auth rejections (401/403) never trigger the fallback since the host answered and the session is the problem. zoommate.zoom.us additionally provides the product UI and the fixed web-client continue, Origin, and Referer values. The parent zoom.us scope is used only to discover parent-scoped SSO cookies; it does not authorize requests to arbitrary Zoom subdomains. ZoomMate does not currently expose a documented public API, so this provider depends on the product's first-party web-client endpoints.

This cookie-to-bearer shape follows an existing CodexBar precedent in the Factory provider's WorkOS cookie exchange. The minted bearer is cached in memory (keyed by a SHA-256 of the cookie session) and reused until it nears its JWT exp; a token whose expiry can't be read is never cached, and a 401/403 from a downstream request evicts the cached token so the next refresh mints fresh.

Data Source

CodexBar sends up to a few GET requests per refresh:

text
GET https://ai.zoom.us/ai-computer/api/v1/credits/status
GET https://ai.zoom.us/ai-computer/api/v1/credits/history?app_id=demo_app&limit=50&page=<n>&sort_by=time&sort_order=desc&start_time=<ISO8601>&end_time=<ISO8601>

(Automatic requests retry once on zoommate.zoom.us with the same path if ai.zoom.us fails with a non-auth error. Manual requests start on the captured host and retry the other host without the captured host's cookies — see "Auth & privacy" above.)

The credits/status response's data.credit_status object is decoded into a ZoomMateCreditStatus struct. The credits/history request is paginated (looping on page until page * limit + records.length reaches the response's flat data.total, or a page's records are entirely older than the requested start_time) to cover the last 30 days; real accounts have modest history (tens of records total), so this is normally 1–2 requests. app_id is sent as a fixed placeholder matching ZoomMate's own web UI — it does not scope the result set to a particular integration. A failed credits/history fetch is non-fatal: it never blocks the primary credits/status snapshot, it just means the history dashboard (below) is omitted for that refresh.

Fields mapped to the Credits window

Source fieldCodexBar mapping
used_credit / budget_capPrimary window usedPercent (clamped 0–100)
cycle_end_datePrimary window reset time (epoch ms)
is_unlimited / budget_cap <= 0usedPercent forced to 0, reset countdown omitted

There is no secondary window; resetDescription is always "Credits".

Account identity (Automatic mode only)

The same login bootstrap response used to mint the bearer token (GET .../login/?continue=...) also carries a data.user_profile object with the signed-in user's account details. CodexBar reads user_profile.email from it and surfaces it as the provider's accountEmail — the same identity field Codex/Claude populate — so the menu card's account row shows who is signed in, matching those providers' presentation. loginMethod is set to "Cookie" whenever an email was resolved this way. This is purely additive enrichment: a missing/absent user_profile or email never fails the token mint, it just leaves identity unset. Manual (.web cURL-capture) mode has no equivalent bootstrap call, so accountEmail stays nil there.

Credits history and pacing

When ZoomMate is enabled and history data is available, the menu's Credits card shows a Today/30d credits dashboard and pacing verdict inline, directly under the credits progress bar — on both ZoomMate's own tab and the Overview aggregate view, matching Claude's/Codex's inline dashboards. It includes:

  • Two KPI tiles, reusing the same InlineUsageDashboardContent component Claude/Codex render their inline dashboards with: Today (emphasized — the current calendar day's summed cost from credits/history, or 0 if nothing posted yet today) and 30d credits (the sum across the full 30-day window). This mirrors Codex's "Today" / "30d cost" tile pair and Claude's "Today" / "30d spend" tile, swapping the "$"/cost unit for "credits" since ZoomMate has no dollar-cost concept.
  • A row of mini usage bars below the tiles, one per calendar day that has ZoomMate usage (credits/history's per-event ledger summed by day), capped at the most recent 30 days, rendered in ZoomMate's brand color (#0B5CFF) — matching Codex/Claude's own bar density and per-provider coloring exactly.
  • A pacing line ("Pace: on track" / "Pace: N% ahead of budget" / "Pace: N% behind budget") rendered as a plain inline-dashboard detail line below the mini-bars. It's computed from credits/status's budget_cap, cumulative used_credit, and the current billing cycle's cycle_start_date/cycle_end_date — comparing actual usage against the expected linear-elapsed-fraction of the cycle. This reuses UsagePace's existing stage thresholds (on track / slightly ahead / ahead / far ahead / slightly behind / behind / far behind) rather than a ZoomMate-specific scale, so the wording matches other CodexBar pacing indicators.

is_deleted history records are excluded from both the KPI tiles and the mini-bars; still-running sessions (is_running: true) are included since their cost reflects consumption so far. The 30-day window is enforced independently at both fetch time (the request's start_time) and display time (dailyBreakdown() filters to the trailing 30 calendar days regardless of what the fetch returned), so the chart's calendar span is guaranteed either way. The pacing line only needs the always-fetched credits/status snapshot, so it can appear even in refreshes where credits/history fails or returns nothing. The inline section is gated on having either a non-empty daily breakdown or a computable pacing verdict — an empty/failed history fetch silently omits the section instead of showing an empty dashboard.

Status page

ZoomMate's descriptor points at Zoom's public status page, zoomstatus.com, which is an Atlassian Statuspage.io site — the same platform Claude's status page already uses. This means the overall-status row and its "Updated …" subtitle work through CodexBar's existing shared Statuspage.io fetch/parse path with no ZoomMate-specific fetcher code.

Zoom's status page lists ~300+ components, dominated by heavy per-region duplication for services unrelated to ZoomMate (Zoom Phone, Contact Center, and CX each repeated across many regions). Showing all of them in ZoomMate's status drill-down would be noise, so ZoomMate's component submenu is filtered to a named allowlist:

  • Zoom Meetings
  • ZoomMate
  • My Notes
  • Zoom Workflows
  • Zoom Developer Platform
  • Zoom Support
  • Zoom Website

The allowlist matches component/group names exactly (case-sensitive) against whatever the live API returns, and is tolerant of any subset being renamed or removed on Zoom's side: a missing name is silently omitted, never an error, and if none of the allowlisted names are present the submenu falls back to the same "components not loaded" empty state every other provider already has (no ZoomMate-specific empty-state UI). "Zoom Meetings" and "Zoom Workflows" are themselves groups in Zoom's data (each with their own child components); an allowlisted group is shown with its full existing child list when expanded, same as it would be for any other provider.

This allowlist lives in ZoomMate's provider descriptor metadata. Every other provider has no descriptor allowlist and keeps showing every component their feed returns, unchanged.

CLI

bash
codexbar usage --provider zoommate

The CLI reuses the host-scoped cookie headers cached by a previous validated refresh; it does not read Chrome's cookie store itself. If no cached session exists yet (noSession), refresh once from the app or seed the cache from the terminal with codexbar cookie --provider zoommate (add --allow-keychain-prompt to acknowledge that Chrome cookie decryption may prompt).

ZoomMate provides no token-cost data and is not yet supported in the CodexBar widget (this includes the credits history dashboard and status page above — both are app-menu-only).

Common errors

ErrorCauseFix
noCaptureManual mode is selected but the capture is empty, off-domain, or lacks a parseable Authorization headerPaste a fresh cURL capture of the HTTPS credits/status request from ai.zoom.us or zoommate.zoom.us
noSessionAutomatic mode found no cached session and no ZoomMate/Zoom session cookies it may read (background refreshes and the CLI never read Chrome directly)Sign in to ZoomMate in Chrome and refresh once from the app (or codexbar cookie --provider zoommate), or switch to Manual and paste a capture
invalidCredentialsHTTP 401/403 — the token expired (~hourly) or was revokedRe-sign-in (auto) or re-paste a fresh capture (manual)
apiErrorAny other non-200 HTTP statusCheck ZoomMate's status; retry later
parseFailedHTTP 200 body did not contain the expected credit_status shapeOpen a CodexBar issue with a redacted response sample

Key files

  • Sources/CodexBarCore/Providers/ZoomMate/ZoomMateProviderDescriptor.swift — provider metadata (including statusPageURL and the status-component allowlist) and the unified fetch strategy (calls both credits/status and credits/history)
  • Sources/CodexBarCore/Providers/ZoomMate/ZoomMateUsageFetcher.swift — credits/status request, cURL parsing, and cookie-to-token minting
  • Sources/CodexBarCore/Providers/ZoomMate/ZoomMateCreditsHistoryFetcher.swift — credits/history request, paginated with a date-boundary stop, and the ZoomMateCreditsHistorySnapshot model
  • Sources/CodexBarCore/Providers/ZoomMate/ZoomMateModels.swift — response decoding, error taxonomy, window mapping, daily-bucket aggregation (dailyBreakdown()), today's-total lookup (todayCreditsUsed(now:calendar:)), and pacing verdict computation
  • Sources/CodexBarCore/Providers/ZoomMate/ZoomMateCookieImporter.swift — Chrome cookie-jar import (macOS only)
  • Sources/CodexBar/InlineUsageDashboardContent.swift — shared Today/30d KPI-tile + mini-bar view also used by Claude/Codex/OpenRouter/etc.; ZoomMate renders through this same component
  • Sources/CodexBar/MenuCardView.swift — renders the generic inline-dashboard slot for credits-only stacked cards
  • Sources/CodexBar/StatusItemController+Menu.swiftstatusComponentsSubmenuProviders and descriptor-backed filterStatusComponents
  • Sources/CodexBar/Providers/ZoomMate/ZoomMateProviderImplementation.swift — settings pickers and bindings
  • Sources/CodexBar/Providers/ZoomMate/ZoomMateSettingsStore.swift — cookie source and capture persistence