All documentation

Exports and activity

Every downloadable dataset, what one row means in each, the row cap that refuses rather than truncates, and what the project activity log can actually show.

Exports screen showing the date range picker and the six downloadable CSV datasets

The six datasets

Every export on this screen comes from one registry, an insertion-ordered dict that is also what the page lists them in (api/services/exports/__init__.py#REGISTRY). It holds exactly six datasets, no more and no fewer:

  1. Prompts: one row per tracked prompt, for each country it runs in (and each city, when a prompt targets one). Not windowed: this is the project's current configuration, not a time series, so a date range does nothing to it (api/services/exports/prompts.py).
  2. Daily metrics: one row per combination of six things, all of them emitted as columns: the day, the platform, the surface, the collection method, the provider and the model used. Expect more than one row for a given day, provider and surface whenever any of the other three changes inside the window, which a model identifier does on every model upgrade. Each row counts every attempted response in its slice whether it completed, failed, or is still running: this is the one dataset that is not restricted to completed answers, and the next four are (api/services/exports/daily_metrics.py#rows).
  3. Brand mentions: one row per time a completed answer named your brand or a tracked competitor, with its position and sentiment (api/services/exports/entity_mentions.py).
  4. Citations: one row per source a completed answer cited, with its URL, rank, and how it was framed (api/services/exports/citations.py).
  5. Cited URLs: one row per page cited by a completed answer in the window, aggregated across every prompt and provider that cited it (api/services/exports/cited_urls.py).
  6. Domains: one row per domain cited by a completed answer in the window, aggregated the same way as Cited URLs (api/services/exports/domains.py).

Datasets 2 through 6 are windowed: the date range you pick shapes what they contain. Dataset 1, Prompts, is the only one that is not.

The row cap

Every dataset shares one ceiling, fifty thousand rows (api/services/exports/base.py#MAX_EXPORT_ROWS). Hitting it does not truncate the file: the download route asks for one row past the ceiling, and if that many come back, it refuses the whole request outright with an error naming the dataset and the limit, before any file is produced (api/routers/exports.py#download_export). Narrowing the date window is what gets you under it; nothing here silently drops rows to fit.

The filename

Every download's filename follows one pattern: discoveredby-{project}-{dataset}.csv for Prompts, or discoveredby-{project}-{dataset}-{start}-{end}.csv, with both dates in ISO form, for the five windowed datasets (api/routers/exports.py#_filename). The project segment is your project's name, lowercased, stripped to letters, digits and hyphens, and cut to forty characters, specifically so a project name cannot inject anything into the response header that names the file (api/routers/exports.py#_project_slug).

Who can download

Downloading a project's exports needs only the same membership check every project-scoped screen starts with: the caller must be the project's owner or an active team member of any role. Neither endpoint behind this screen adds the write-access check that gates most other changes in the product, so at the API level a viewer can download every export a project has, exactly like an owner or an editor can (api/routers/exports.py#exports_index, api/routers/exports.py#download_export). See Team for where that write-access check does apply, and for why a member of a project someone else created cannot reach that project's screens in the app today, which is what stands between the two endpoints above and anyone actually doing this.

Whether exports are available at all is a separate question, and it is answered by the project owner's plan, never by the plan of whoever is looking at the screen. A viewer or an editor on a project someone else owns is served by the owner's plan, again at the API level, even if their own plan, on projects they own themselves, would not allow an export (api/routers/exports.py#exports_index, api/routers/exports.py#download_export). See Plans and limits for what governs a project's plan generally, and for the mechanism this specific check uses.

Activity

/settings/activity reads from the same audit table every write in the backend can log to, filtered to the rows carrying one project's id (api/routers/pages.py#get_activity_page). Seeing it needs only the same membership check as downloading an export: owner, editor and viewer all see the identical log, because the endpoint reads no role beyond confirming you belong to the project.

The query behind this screen has a hard ceiling of two hundred rows and no other way to move through the log: no query parameter of any kind, and so no page two. If a project's log ever holds more than two hundred rows, the oldest of them are simply not reachable from this screen.

The screen's three filter menus are built from the rows it just loaded, not from a fixed list of what an audit row could say: the action menu is the distinct actions present in those rows, sorted, and the person and resource-type menus are assembled the same way by the endpoint that fetched them (frontend/src/routes/(app)/settings/activity/+page.svelte#actionOptions, api/routers/pages.py#get_activity_page). So the menus can never offer something the log does not already hold. Given what actually reaches this log, described next, the action menu and the resource-type menu each have one value to offer no matter how large the log grows, both of them written by the export route, and only the person menu varies, by who has downloaded (api/routers/exports.py#download_export).

The backend writes an audit row from eleven places in total, and only one of them fills in this screen's project column: a CSV export download does (api/routers/exports.py#download_export). The other ten, an admin-triggered login link from the CLI and nine actions inside the admin panel (enqueuing pages, blocking or unblocking a domain, resyncing billing plans, granting a plan, revoking a plan, generating a login link, triggering a research run, and triggering the visibility watchdog for one project or for all of them), all leave the project column this screen filters on empty, whatever else they record (api/cli.py#cmd_mint_login_link, api/routers/admin.py#enqueue_web_pages, #_set_user_domain_blocked, #resync_admin_billing_plans, #grant_user_plan, #revoke_user_plan, #generate_user_login_link, #trigger_admin_research_run, #admin_trigger_watchdog, #admin_trigger_watchdog_all). None of the everyday actions on a project, saving its record, inviting a team member, adding a keyword or a tag, write an audit row at all. So the only entry a project's Activity screen can ever show today is one per successful CSV export download: who downloaded it, when, and which dataset.

  • Plans and limits: the mechanism behind the paid-plan gate this page's exports use, and why a project's plan is always its owner's
  • Team: the write-access check that every other project screen enforces and this one's two endpoints do not
  • Projects: how a project's own identity, and the keywords, tags and activity that belong to it, fit together as one scope

Last verified 2026-08-11

Start monitoring your AI visibility.

See how AI search engines talk about your brand.

Free to start. No credit card required.