BETAPro is not billed during beta. Lock in the price and we will honor it at launch.Freeze this price

Changelog

Stay up to date with the latest changes in our product.

August 14, 2026

Scan beyond 25, and scheduled scans get a dashboard

  • "Scan all N" on the Queries page — queue every query matching your current filters (search, topics, importance, status) as one background job. No 25-query manual-selection cap, restart-safe, and queries already running are skipped, never double-billed. The manual cap now explains itself instead of silently graying out checkboxes.
  • Bulk form "Run all" honors your text filter — and is available on every status chip, including "All". The set the button launches is exactly the set the list shows.
  • Scheduled scans are configurable in the app — Settings → Scan execution → Scheduled scans. Daily / weekly / monthly cadence, model subset, local-time picker, and per-run cost shape ("N queries × M models") before you enable anything.
  • Per-topic schedules — give one topic its own rhythm. A topic with its own enabled schedule is excluded from the workspace-wide run, so no query is ever scanned twice for the same cadence.
  • An "Auto-scan" chip on the Queries page shows the next scheduled run at a glance.
  • Hand-picked schedules — schedule just the queries you choose (up to 50 per set, any number of sets, each with its own cadence). Pick them in the schedule dialog's searchable selector, or select rows on the Queries page and hit "Schedule these". Hand-picked and topic schedules both exclude their queries from the workspace-wide run — nothing scans twice.
  • The schedule dialog's topic picker is now searchable and shows each topic's query count.
  • Topic pages: searchable, filterable query list — the "Queries in this topic" table now has text search, status (never / stale / run today) and importance filters, plus pagination at 25 rows, matching the main Queries page.
  • Scan schedules moved to their own settings page — Settings → Scan schedules, right after Scan execution. Old "Schedule these" links forward automatically.
  • Upcoming runs on the Scans page — when schedules are enabled, a read-only strip above the scan history shows each schedule's scope, cadence, and next run ("when does money leave"), with a manage link for admins.

August 14, 2026

Dashboard explore board and Insights as a period report

  • Analytics is no longer a third Analyze page. /analytics redirects to the Dashboard explore board. The full prompt library stays on Queries.
  • Dashboard is briefing + board. Visibility hero (mention rate, platforms, ranking) sits above an Explore grid: channel influence, branded vs unbranded topics, composition radar, query shape, top 6 prompts, and content gaps. Each tile can expand or export a CSV of what you are looking at. Admins can still download the full analytics roll-up.
  • Insights is the period report. Summary and findings first, then this-window KPIs, visibility (share of voice), and across-reports history. Page load never generates a narrative — Generate is still a click. Topic reports call the topic procedure, not the workspace one.
  • Honest empty states. One scan day shows a labeled baseline, not a blank chart. Across-reports and thin topic momentum are one-line notes until a second report exists. A 90-day share-of-voice chart buckets by week.
  • Numbers come from stored scans. Channel types, branded queries, presence, and prompt scores are deterministic roll-ups of analytics_daily — no invented Trust/Authority axes, no model on load.

August 14, 2026

See the pages the AIs cite, and take the data anywhere

  • Cited pages — a new view under Analyze showing every page the AI platforms cite for your queries, per platform, with citation counts, average citation rank, the queries that triggered them, and a "My pages only" toggle that spotlights your own site. Click any row for the full breakdown and a link to the page.
  • Citations export — Settings → Data export → Citations: every stored citation as CSV or NDJSON with scan, platform, and query context on each row. Built to join with Google Search Console, Semrush, or BigQuery on the canonical_url column — pair "which pages LLMs cite" with "which pages rank" outside the app today.
  • Google's grounding citations (opaque redirect links) are handled honestly: never fabricated as pages, counted at domain level with a footnote, and flagged is_provider_redirect in the export.
  • Everything here is pure reads from data you already collected — no model calls, no new cost.
  • Answers export — Settings → Data export → Answers: every LLM answer as text (CSV or NDJSON), one row per platform response with query, model, and per-answer cost. Joins the Citations export on the new response_id column, so you can pair every answer with the exact URLs it cited.

August 13, 2026

Export insights, queries, and analytics from Settings

  • Settings → Data export is now a hub, not only the GDPR dump. Admins can download topics and queries (grouped by topic), insights reports, and the daily analytics roll-up from one page.
  • Downloads read stored rows only. Opening the page or clicking download never runs a scan and never generates a new insights narrative.
  • “Query library”, not “prompts”. The tracked queries you scan sit in one file; the per-scan prompt copies stay in the full workspace backup, where they already were.
  • Cost CSV is unchanged — it still lives on Settings → Cost, with a link from the hub.
  • Full workspace backup is still there (JSON + scan-history CSV), same streaming contract as before: the last line tells you the file is complete.

August 13, 2026

Choose current models for provider scans

  • Per-provider model selection — workspace admins can now choose a current OpenRouter or Direct model for each scan provider, while keeping the historical pinned model as the default. Catalog refreshes list models without running them, and changing a model is clearly marked as a new measurement epoch.

August 7, 2026

Brand matching stopped dropping case-variant aliases

  • An alias that differed from your brand name only by capitalisation was silently discarded. If your brand was stored as "NiCE" and you added "NICE" as an alias, the alias never reached the matcher: terms were de-duplicated case-insensitively while matching ran case-sensitively, so "NICE" looked like a duplicate of "NiCE" and was thrown away. You did the right thing and nothing happened, which is the worst way for a feature to fail.
  • Measured impact before the fix, on one real workspace: of 141 scan responses, 95 contained a standalone "NICE" and 2 contained "NiCE". Nineteen brand-bearing responses recorded no mention of the tracked brand at all — roughly a 20% undercount, partly masked because the AI extractor rescues mentions the pattern misses.
  • Nothing changes for case-insensitive brands. They still fold every casing into one term. The reason case-sensitivity exists is unaffected too: "that's a nice feature" still doesn't count as a mention of NICE.
  • Historical mentions are not re-scanned. Existing rows keep the results they were extracted with, stamped matcherVersion: 2; new scans use version 3. If a brand of yours has a name that is also a common word, adding the exact casing you see in the wild as an alias now actually does something.

August 7, 2026

Analytics now rebuild themselves every night

  • Daily analytics are re-derived from your raw scan data every night instead of being accumulated once. Previously each finalized scan added its contribution to a running total, which meant any miss was permanent: re-extracting a response, or recovering mentions after the fact, could never correct a number that had already been counted. Now the figures are recomputed from source, so a correction lands on its own within a day.
  • Some numbers may shift once as history is recomputed. They are moving toward what your raw scan data actually says. Domain and citation tallies in particular become exact rather than approximate, because they are no longer built by merging capped snapshots in arrival order.
  • Topic momentum now compares against a bounded baseline. It used to take the oldest surviving snapshot as its "before" value with no age limit, so the longer a workspace ran, the further back "momentum" reached. It is now capped at twice the selected window, matching every other period-over-period comparison in the product. A topic whose only baseline is older than that reports no momentum rather than a number measured against a different era.

August 6, 2026

Your workspace export now streams, and the format changed

  • The export is NDJSON, not one JSON object — this is a breaking change if you parse it. Each line is a self-contained {"kind":"response","row":{…}} record instead of a single document with a responses array. Anything that did JSON.parse(wholeFile) needs to switch to line-by-line. The trade is that the export now works at all: it was returning a single buffered response, and a Vercel function caps that at 4.5 MB. Any workspace past roughly 450 responses got a 413 instead of a download.
  • The last line tells you the file is complete. A download cut short by a timeout is still valid NDJSON and silently missing the tail, so the final record is {"kind":"complete","counts":{…}} with a per-table row count. If you don't see it, treat the file as partial. The CSV gets the same guarantee as a trailing # complete,<rows> row — more important there, because a short CSV opens cleanly in Excel with nothing to indicate anything is missing.
  • The filename carries your workspace slug again — brandbanta-<workspace>-<timestamp>.ndjson. Resolved server-side, so two workspaces never produce two identically-named files.
  • `rawResponse` is included. It's your data, and now that size isn't fatal there's no reason to withhold it. Embedding vectors stay out: ~3 KB per row and useless without the same model and index.

August 4, 2026

Chat searches your scan history by meaning

  • Chat now retrieves by semantic similarity instead of "the 200 newest rows" — asking "what do people say about our pricing?" now finds the mentions that are actually about pricing, wherever they sit in your history, rather than whatever happened to be recent. Workspaces with no embeddings yet keep the old chronological behaviour, so nothing regresses while the backfill catches up.
  • Retrieval degrades instead of failing — a missing key, a rate-limited embedding call, or a database fault falls back to chronological history and still answers the question. A failed read is never reported as "you have no scan results yet", so an established workspace is never told to go run its first scan.
  • Embedding spend is metered like every other model call — text-embedding-3-small is priced in the ledger under both its bare and namespaced ids, so the per-turn query embedding records a real, non-null cost against the budget cap rather than disappearing as unknown pricing.
  • Embeddings are written only when a scan finalizes, or by an explicit operator backfill — opening a chat never triggers one. The backfill skips demo workspaces, which hold fictional data.

August 2, 2026

Closed beta: invite-only signup, per-member spend cap, dependency sweep

  • Signup is closed (enableSignup: false) for the closed-beta window. Two flags move together, and this is the important part: invitationOnlyPlugin only matches /sign-up/email, while Better Auth's magic link auto-creates an unknown user with `emailVerified: true` unless disableSignUp is set. magicLink({ disableSignUp: !config.enableSignup }) now tracks the config flag — without it the login form's magic-link field would keep minting accounts for any address while public signup _looked_ closed. Reopening signup means flipping both back together. Not a regression: with signup closed, autoSignIn turns on and requireEmailVerification off by design — the invitation proves the address.
  • Per-member scan cap (PER_MEMBER_SCAN_CAP, apiKeys/lib/member-scan-cap.ts). Members cannot add or edit org API keys (every write requires org admin), so they spend the admin's key — and the free-tier gates only run when usedOurKey is true, so under BYOK there was no per-member limit at all and budgetCapUsd defaults to NULL. The cap therefore keys off the member, not the key source, and applies on both. Gate order: free-tier (managed only) → member cap → budget cap, so a capped member never burns a monthly quota slot. No-ops for cron runs (userId null) and for org owners/admins. No migration — scan_session already carries userId.
  • Fixed: bulk-execute re-ran its per-unit preflight only when usedOurKey, so a single BYOK bulk run could blow straight past the member cap in one batch. It now always re-runs.
  • All 10 open Dependabot advisories resolved (1 high, 9 moderate). Every one was the same package on the same path — start-server-and-test > wait-on > axios@1.16.0 — with the same fix. Pinned axios: "^1.18.0" in the pnpm-workspace.yaml overrides block (resolves to 1.19.0). Dev-only e2e orchestration; nothing shipped was exposed, which is why pnpm audit --prod stayed green the whole time. No code imports axios directly.
  • Docs: VERCEL.md now covers the launch-day traps it was missing — Hobby _fails the deployment_ on sub-daily crons (it does not silently skip them), production must start with an empty Turso DB (a restored local.db leaves BYOK rows undecryptable under the new KEYRING_MASTER_KEY and forces a rollup backfill), the expected check:go-live:prod result, and a new §7b for first login, since with signup closed there is no way into a fresh deployment without create:user + grant:tier.

August 2, 2026

Alert volume: repeats must earn themselves, discoveries can be resolved, email batches

  • The discovery alert could never stop. ALERT_NEW_COMPETITOR_DISCOVERED re-fired every 7 days over a 14-day rolling window for the same untracked name, forever. Its two exclusion filters (observation.promotedBrandId, observation.dismissedAt) were never written by any code path — and the "Track" button called brands.create, which creates a brand row but stamps nothing, so tracking a competitor didn't silence the alert about it either. The email told users to do the one thing that wouldn't work.
  • Materiality gate on every repeat (claimAlertFire). A cooldown is a timer, and a timer re-sends alerts whose situation hasn't changed. A repeat now also has to clear a per-rule bar against the last-reported value: counts must grow ≥1.5×, mention rate must move ≥5pp in either direction (a drop → recovery → fresh drop is real news even when the new number isn't below the old one). Outage-class alerts (API key, spend threshold) are exempt by design. A suppressed repeat deliberately does not reset lastFiredAt, so a genuine move tomorrow isn't held for another cooldown.
  • Discovered competitors now have an end state — plan 014's Phase 2, never built. New tracking.discoveries procedures (list / promote / dismiss / restore) and a card on /brands; the alert deep-links the row (/brands?candidate=<name>). Promote creates the brand and stamps every matching observation in one transaction, and is idempotent against a name that's already tracked. Dismiss writes a discovery_dismissal row keyed (organizationId, canonicalName) — a per-row marker can't work, because tomorrow's scan inserts fresh observation rows with a null marker that re-cross the threshold within days.
  • Daily email digest. Market-movement alerts (competitor surge, new competitor, mention rate drop) default to one batched email at 12:00 UTC; act-now alerts (API key, spend threshold) and the weekly digest still send immediately. Implemented as an outbox collapse, not sender-side grouping: parked rows carry digestGroup and are excluded from the drain query _and_ the row claim, so a new composer cron owns them completely and folds N rows into one ordinary outbox row. Every row-scoped invariant (atomic claim, lease, idempotencyKey, backoff) is untouched. A lone parked row is released as a normal single email rather than a one-item digest.
  • Nothing batched or suppressed is lost: the in-app notification list still receives every alert the moment it fires, whatever the email settings say. Per-type email cadence (Immediate / Daily / Off) is in Settings → Notifications; in-app stays a simple on/off because a feed has no cadence.
  • Migration 0046: discovery_dismissal, user_notification_preference.frequency (defaults OFF, which preserves the old "a row means disabled" semantics for every existing row), notification_delivery_outbox.digestGroup. New cron /api/cron/compose-notification-digest.
  • Copy: the discovery alert no longer prints a raw URL path in prose ("Promote X from /brands") and now names dismissal as the other way out. 22 new tests (439 total).

July 28, 2026

Scan-dispatch stability batch

  • Credit-exhaustion circuit breaker. After 3+ "insufficient credits"-class provider errors in 15 minutes with no success since, bulk and scheduled dispatch pause that platform for the org instead of firing hundreds of scans into a dead key (the exact incident behind 1,200+ recorded errors locally). Manual single scans deliberately bypass it — a successful manual run is the probe that resumes automatic dispatch instantly. Derived from existing error/response rows: no migration, no pause state to corrupt.
  • In-flight guard on bulk. Re-running "failed" queries skips any whose retry is already pending/running (double-click / two admins). Toast reports skips.
  • Job-based bulk ("Run all N"). With a status filter active, one click launches every matching query as a background Inngest job — server resolves the selector, fans out in durable 25-query chunks (survives restarts/redeploys mid-job, resumes from the last chunk), re-checks in-flight + credit breaker per chunk, ceiling 1000/job. The manual ≤25 selection flow is unchanged. Inngest function count: 8 → 9 (bulk-scan-fanout).

Page 1 of 8