Account
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.

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:
- 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). - 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). - 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). - Citations: one row per source a completed answer cited, with its URL,
rank, and how it was framed (
api/services/exports/citations.py). - 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). - 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.
Related
- 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