All documentation

Account

Customer API and keys

Create scoped, expiring read keys for project prompts, collected answers, citations, daily metrics, brand reasons, objections, attributes, fact checks, saved weekly report digests and Explorer queries.

Read one client's data from your own tools

The customer API provides read access to a project's current prompts, completed collected answers, saved citation records, daily metrics, brand reasons, objections, attributes, fact checks, weekly report digests and Explorer queries and saved views. Create credentials in Settings → API keys. Owners and editors can manage keys; viewers cannot. Standard Starter, Growth and Pro plans include access, using the project owner's persisted API entitlement. Free and Trial do not include it. Custom plans follow their configured entitlement (api/services/customer_api.py#api_available).

Project API key settings with the ten read permissions, from Prompts to Fact checks, and expiry controls

The screenshot shows the create-key form in the local Quillstone demo. No key or secret is visible.

The API provides prompts, answer text, citation records, daily metrics, brand reasons, objections, attributes, fact checks, bounded weekly digests and Explorer results. The read-only MCP connector uses these same keys and permissions with compatible assistants. Refreshable BI connectors and API writes are not included. Exports still provides the thirteen downloadable datasets, and Client reports compares saved weekly reports across accessible projects.

Create and manage a key

Choose the client project, give the key a label, select its permissions and choose a 30, 90 or 365-day expiry. The default is 90 days. A key can hold up to ten scopes, one per permission below (api/schemas/customer_api.py#CreateApiKey, #ReadScope). Permissions are:

Scope Data it permits
prompts:read Current prompt text, IDs, classifications and active state
answers:read Completed collected answers, original prompt text, recorded location, persona and collection provenance; full answer text through the chunk endpoint
citations:read Saved citation URLs, answer and prompt IDs, position, source type, bounded snippet and collection provenance
metrics:read Daily collection and analysis counts, brand visibility and own-domain citation rate by recorded collection provenance
reasons:read Strength and weakness reasons a completed answer gave for a tracked brand, with category, polarity and quoted evidence; also requires the project owner's Brand reasons entitlement
objections:read Objections from succeeded objection studies, with the brand asked about, engine, rank, score, objection group, quote and source URLs; also requires the project owner's Objections entitlement
attributes:read Attributes from succeeded attribute studies, in two measures: what engines associate with each brand, and which brands they name for each asked attribute, with engine, rank, score, attribute, quote and source URLs; also requires the project owner's Attributes entitlement
facts:read Findings of Fact check: where an AI answer supported or contradicted one of your approved facts, with the origin, engine, fact as checked, verdicts, quote, explanation, source URLs and your team's review; also requires the project owner's Fact check entitlement
reports:read Saved weekly digests: four metric readings, summary, activity and recommendation titles; also requires the project owner’s weekly-report entitlement
explorer:read Explorer queries computed on request, and the project's saved Explorer views and their results; see Run Explorer queries

Existing keys keep their original scopes; explorer:read is never added to a key automatically. To add metrics, citation, reasons, objections, attributes, fact check, report or Explorer access, create a new key with the required scopes, update your integration, then revoke its old key. Each permission authorizes only its own dataset; citation access does not grant answer text or report reads.

The secret appears once, immediately after creation. Store it in your server or reporting tool's secret manager, copy it, then dismiss it. DiscoveredBy stores a hash, so the secret cannot be shown again. If it is lost, create a replacement and revoke the old key (api/services/customer_api.py#create_key).

There are at most 10 unexpired, unrevoked keys per project. The list includes all such keys first, followed by recent history, up to 100 entries. It shows creation, expiry and last authenticated request times in UTC. An “active” state means unexpired and unrevoked; it does not prove that the project, creator or plan is currently eligible (api/services/customer_api.py#list_keys).

Every request checks the key's project and scope, expiry and revocation, active project and owner, current owner-plan access, and the creator's active account and editing permission. Removing the creator's membership or changing them to a viewer stops access. Restoring eligibility can make an unexpired, unrevoked key work again. Revocation is permanent. Owners and editors can list and revoke keys after downgrade or archive, even when they cannot create new ones. An admitted request may finish after revocation; downloaded copies cannot be recalled (api/services/customer_api.py#authenticate_key, #revoke_key).

Authentication and base URL

The settings page shows this project's base URL. In production its form is:

https://api.discoveredby.ai/customer-api/v1/projects/PROJECT_ID

Use HTTPS and send the secret in the Authorization header. Keep it out of URLs, browser applications and shared notebooks. Session cookies and keys passed as query parameters do not authorize these routes. An API key cannot sign in to the app or access its internal management endpoints.

curl --fail-with-body \
  -H "Authorization: Bearer $DISCOVEREDBY_API_KEY" \
  'https://api.discoveredby.ai/customer-api/v1/projects/PROJECT_ID/prompts?limit=50'

Set DISCOVEREDBY_API_KEY securely in your execution environment and replace PROJECT_ID with the number shown in settings. A key only works for its own project; use a separate key for each client.

Read and paginate prompts

GET /prompts accepts limit (1–100, default 50) and after_id (default 0). It includes active and paused prompts, using their current text and classifications. An unclassified value is null.

Each item in data has id, public_id, text, active, intent_class, buyer_stage and theme. public_id is a UUID string; id is the numeric prompt ID used by answer records. Rows are ordered by increasing numeric ID.

The response has data and next_after_id. When next_after_id is a number, pass it as the next request's after_id. When it is null, the current listing is complete (api/services/customer_api.py#prompts_page).

Read daily metrics

GET /metrics/daily requires metrics:read under the existing API entitlement; it does not require weekly-report access. Counts use the same definitions as the daily metrics file export (api/services/exports/daily_metrics.py#rows).

Pass inclusive UTC execution dates as start and end: 28 days ending today by default, with a maximum window of 366 days. Only end selects the preceding 28 days; only start uses today as the end. days sets the number of calendar days per page (1–31, default 7). The response contains data, start, end, page_end and next_start. For the next request pass next_start as start, keeping the returned end and your days unchanged. Stop only when next_start is null, even if a page's data is empty. No day's groups are split or truncated (api/services/customer_api.py#daily_metrics_page).

Each row groups by execution_date, platform, surface, collection_method, provider and model_used, in that order. Missing recorded provenance stays an empty string. Countries and prompts are pooled within that group, including paused prompts and targets with history. Search-result surfaces remain distinct from generated answers. Do not merge unlike channels into a consumer-wide score.

Field Definition
responses_collected Completed executions
responses_analysed Completed executions with finished brand-mention extraction
responses_naming_brand Analysed executions with at least one saved self-brand mention; aliases count once per execution
responses_citing_own_domain Completed executions with at least one saved own-domain citation; repeated URLs count once per execution
distinct_domains_cited Distinct saved cited domains across completed executions in this group; not additive across days or channels
responses_failed Failed executions
responses_in_flight Pending or running executions, excluded from both rate denominators
responses_no_answer Google AI Overviews or Google AI Mode runs where Google showed no AI answer; not answers, so excluded from both rate denominators, and always 0 for the other engines
brand_visibility_percent responses_naming_brand / responses_analysed × 100, rounded to one decimal
domain_citation_rate_percent responses_citing_own_domain / responses_collected × 100, rounded to one decimal

These definitions and counts come from api/services/exports/daily_metrics.py#rows. Both rates are null with no denominator. Measured zero is 0.0. brand_visibility_provisional and domain_citation_rate_provisional are true when their respective denominators are below 30, including zero (api/services/customer_api.py#daily_metrics_page). A citation rate of zero means no saved self citation among collected outputs; it cannot prove complete provider citation coverage. Brand extraction that has not finished is excluded from visibility, not treated as a negative mention.

Days with no executions have no rows. To combine compatible groups, sum the numerators and denominators and divide; do not average their percentages or sum distinct-domain counts. These are live reads, not saved weekly metrics or an immutable snapshot. Re-fetch the complete date window and replace rows keyed by all six grouping fields to pick up late collection, extraction or deletions. A sequence of pages can change while it is being read. This endpoint provides no competitor breakdown, retrieval rate, sentiment, country filter or revenue attribution (api/services/customer_api.py#daily_metrics_page).

curl --fail-with-body \
  -H "Authorization: Bearer $DISCOVEREDBY_API_KEY" \
  'https://api.discoveredby.ai/customer-api/v1/projects/PROJECT_ID/metrics/daily?start=2026-09-01&end=2026-09-19&days=7'

Read and paginate collected answers

GET /answers accepts the following parameters:

Parameter Meaning
start, end Inclusive UTC execution dates, YYYY-MM-DD; default is 28 days ending today. Supplying only end uses the 28 days ending on that date; supplying only start uses today as the end. Maximum window: 366 days.
limit 1–25 records, default 25
after_id Last ID from the previous page; default 0

The response contains data, next_after_id, and the resolved start and end. Keep those dates fixed while following next_after_id; rows are ordered by ID, not by execution date. There is no file-export row cap on pagination. A date-window limit is not a promise of retained history (api/services/customer_api.py#answer_window, #answers_page). Only completed answers are returned: a Google AI Overviews or Google AI Mode run where Google showed no AI answer has no text and is not an answer; its count is in daily metrics' responses_no_answer.

Each record contains id, prompt_id, execution_date, executed_at, platform, surface, collection_method, model_used, country, persona, language, city, location_delivery, prompt_text_sent, response_text, response_chars and response_truncated. Dates use ISO 8601; executed_at includes its timezone. city is the target's city, null for a country-wide target; persona is null for the General audience, an {id, name} object otherwise; language is null for As written, an ISO 639-1 code otherwise (api/schemas/customer_api.py#CustomerAnswer).

location_delivery says how the target location was sent to the engine for that answer, as recorded when it ran: an object with country and city, each one of native (a location field of the engine's API), prompt (the "Search context" text block), native_and_prompt, query (added to the search query; no engine is sent a location this way now, and only older Perplexity answers carry it) or not_sent (the city of a country-wide target). A ChatGPT (app) answer (provider chatgpt_app) always reads {"country": "native", "city": "not_sent"}: it is sent the country only, and a city target does not run on it. A Gemini (app) answer (provider gemini_app) to a city target reads {"country": "native", "city": "native"}, because the city is sent as its coordinates, or "city": "not_sent" for a city with no coordinates on record. It is null for an answer collected before this was recorded, never a guess (api/schemas/customer_api.py#CustomerLocationDelivery, api/services/collection.py#LOCATION_DELIVERY). What each engine receives is on Engines and measurement.

Only completed executions are returned. The prompt is the text sent for that execution, so editing today's prompt does not relabel an old answer. Each answer keeps its own recorded collection provenance. Answers from the engines queried through their APIs are not consumer-app observations; ChatGPT (app) and Gemini (app) answers (collection method licensed) are what chatgpt.com and gemini.google.com showed a session that was not signed in, fetched by a third-party data provider. Location describes the saved target and how it was sent, not verified consumer session geolocation. Older provenance fields can be empty.

List responses include up to 8,000 Unicode characters of answer text. response_chars gives the full character count and response_truncated states whether more text exists. No provider payloads, upstream error messages, raw provider objects, account details or credentials are included.

All list endpoints are live reads, not immutable snapshots or change feeds. Existing rows can be edited, deleted or completed between pages. A previously pending execution that completes below your last ID will not appear later in that pagination pass. Re-read the desired date window from after_id=0 and deduplicate by ID when refreshing a report.

Retrieve complete answer text

GET /answers/ANSWER_ID/text requires answers:read. It accepts offset (default 0), limit (1–32,000 characters, default 32,000), and version. It returns id, text, offset, next_offset, total_chars and text_version (api/services/customer_api.py#answer_text).

Start with offset 0 and no version. Append the returned text to your result. When next_offset is not null, send that offset and the first response's text_version as version. Continue until next_offset is null. Use the returned offsets; JavaScript string length counts UTF-16 units and can disagree with these Unicode character offsets.

The content version prevents combining chunks from different revisions. If the stored answer changes, the API returns 409; discard the accumulated text and restart from offset 0. A subsequent nonzero offset without a version is refused. Empty answers return an empty string and no next offset. This reads the full saved response text, not the upstream provider's raw payload.

Read and paginate citations

GET /citations requires citations:read. It returns one record per saved citation from a completed execution, including history for paused prompts and targets. Repeated URLs remain separate citation records. A citation is not a brand mention or proof that the cited page mentions your brand.

Parameter Meaning
start, end Inclusive parent execution dates; same 28-day default, UTC dates and 366-day maximum as /answers
limit 1–25 records, default 25
after_id Last citation ID from the previous page; default 0
answer_id Optional exact answer ID, still restricted to this project and date window

The response has data, next_after_id, start and end. Follow next_after_id until null, keeping the date window and optional answer_id fixed. Records are ordered by citation ID, not date or position. There is no file-export row cap. Missing or other-project answers return an empty list, as does an answer with no saved citations (api/services/customer_api.py#citations_page).

Each record contains:

  • id, answer_id, prompt_id: citation ID and links to the answer and prompt datasets.
  • execution_date, executed_at, platform, surface, collection_method, model_used, country, city: the parent answer's recorded provenance and target location, with the same limitations as /answers.
  • url, domain: the saved source URL and domain.
  • rank, source_type, is_self: saved citation position, provider source type and collection-time own-domain flag. Position is not a brand ranking; source_type is not the editorial category from Source types.
  • snippet, snippet_chars, snippet_truncated: at most 2,000 Unicode characters of saved citation snippet, its full length and whether it was cut. An empty snippet means no snippet was saved. There is no full-snippet endpoint.

Citation records can be read before sentiment classification finishes. This endpoint does not include sentiment, framing, scraped page text, editorial category overrides, provider metadata, raw responses or credentials. It does not fetch a page, collect new answers or run a model. Source URLs and snippets are saved provider observations, not verified page contents.

Use answer_id with /answers/ANSWER_ID/text and a key that also has answers:read when you need the complete saved answer. Except for ChatGPT (app) and Gemini (app), whose answers are consumer-app observations fetched by a third-party data provider, these are API observations, not consumer-app observations. An empty citation result cannot distinguish unsupported or missing provider evidence from an observed absence of citations, and must not be used alone as a zero-citation metric.

As with the other lists, citation reads are live. Refresh the date window from after_id=0 and replace records by citation ID to capture corrections or late completion. Reconcile removals against a full refresh; this is not a change feed or an immutable snapshot. No citation retention period is promised.

Read and paginate brand reasons

GET /brand-reasons requires reasons:read and the project owner's current Brand reasons entitlement in addition to customer API access. It returns one record per strength or weakness reason a completed answer gave for a brand resolved to a tracked family; reasons stored for an untracked mention are never returned (api/services/customer_api.py#brand_reasons_page).

Parameter Meaning
start, end Inclusive UTC answer dates, YYYY-MM-DD; same 28-day default and 366-day maximum as /answers
limit 1–25 records, default 25
after_id Last reason ID from the previous page; default 0

The response has data and next_after_id. Follow next_after_id until null, keeping the date window fixed; records are ordered by reason ID, not date (api/services/customer_api.py#answer_window, #brand_reasons_page).

Each record contains id, execution_id, date, prompt_id, persona (null for General), language (null for As written), country_code, provider, platform, surface, collection_method, model, brand ({id, name, kind}), parent_brand (null for a top-level brand), reason, category, polarity and evidence (api/schemas/customer_api.py#CustomerBrandReason). category is one of six fixed groups over the thirteen reason labels; see Brand reasons for what each group contains.

These are live reads over the same underlying data as the brand_reasons file export and the Brand reasons screen, not a separate computation. Reasons are not returned for an answer whose brand extraction predates this feature; there is no field distinguishing "not yet analysed" from "analysed, no reasons given" in this endpoint, unlike the Brand reasons screen's own analysed-answer counts.

Read and paginate objections

GET /objections requires objections:read and the project owner's current Objections entitlement in addition to customer API access. It returns one record per objection found in a succeeded objection study created in the date window; only objections that were grouped are returned, so a failed study contributes none (api/services/customer_api.py#objections_page).

Parameter Meaning
start, end Inclusive UTC dates on which the study was created, YYYY-MM-DD; same 28-day default and 366-day maximum as /answers
limit 1–25 records, default 25
after_id Last objection ID from the previous page; default 0

The response has data and next_after_id. Follow next_after_id until null, keeping the date window fixed; records are ordered by ID, not by date or brand, unlike the file export, which lists your own brand first within each study (api/services/customer_api.py#answer_window, #objections_page).

Each record contains id, study_date, study_id, trigger, language (null for As written), brand, brand_kind (own or competitor), provider, platform, surface, model, rank, score, objection and objection_description (the group's label and description), claim, quote and sources, a list of source URLs (api/schemas/customer_api.py#ObjectionApiRow). These are the columns of the objections file export plus id, with sources as a list rather than one space-separated string (see Exports).

These are live reads over the same studies as the Objections screen. The rank, score and group are what that screen explains; the grouping and its labels are an AI model's wording, and each quote is text located in the engine's saved answer, returned exactly as it appears there, markdown and citation markers included; the screen hides those markers. sources holds the URLs our code linked to the objection from where the engine placed its citations; it is empty when none is linked, which is always the case for an engine that records no citation positions; see how sources are linked.

Read and paginate attributes

GET /attributes requires attributes:read and the project owner's current Attributes entitlement in addition to customer API access. It returns rows from succeeded attribute studies created in the date window, one measure at a time (api/services/customer_api.py#attributes_page):

Parameter Meaning
measure association (the default): one record per attribute an engine gave when asked what a brand is known for, grouped attributes only. market: one record per brand an engine named when asked which brands are known for an asked attribute
start, end Inclusive UTC dates on which the study was created, YYYY-MM-DD; same 28-day default and 366-day maximum as /answers
limit 1–25 records, default 25
after_id Last ID from the previous page; default 0

The response has data and next_after_id. Follow next_after_id until null, keeping measure and the date window fixed; each measure has its own IDs, and records are ordered by ID (api/services/customer_api.py#answer_window, #attributes_page).

Each record contains id, study_date, study_id, trigger, language (null for As written), measure, attribute, attribute_description, custom_attribute (yes when it is one of your attributes now, otherwise no), brand, brand_kind (own or competitor, and for market also untracked), provider, platform, surface, model, rank, score, phrase, quote and sources, a list of source URLs (api/schemas/customer_api.py#AttributeApiRow). For association, brand is the brand asked about and phrase the engine's wording of the attribute; for market, brand and phrase are the brand's name as written in the answer, and attribute is the label the study asked. These are the columns of the attributes file export plus id, with sources as a list (see Exports).

These are live reads over the same studies as the Attributes screen, not its computed scores: association and market prominence are averages over these rows that the screen calculates. The grouping and its labels are an AI model's wording, each quote is located in the engine's saved answer and returned exactly as it appears there, and sources is empty when no source is linked; see how sources are linked.

Read and paginate fact checks

GET /fact-checks requires facts:read and the project owner's current Fact check entitlement in addition to customer API access. It returns one record per finding: one approved fact that one checked answer supported or contradicted, from a fact study or from one of your checked prompts, checked on a date in the window. Only checks that succeeded have findings, so failed checks and answers skipped by the daily limit contribute none (api/services/customer_api.py#fact_checks_page).

Parameter Meaning
start, end Inclusive UTC dates on which the answer was checked, YYYY-MM-DD; same 28-day default and 366-day maximum as /answers
limit 1–25 records, default 25
after_id Last finding ID from the previous page; default 0

The response has data and next_after_id. Follow next_after_id until null, keeping the date window fixed; records are ordered by ID, while the file export orders by when the answer was checked (api/routers/customer_api.py#list_fact_checks, api/services/customer_api.py#fact_checks_page).

Each record contains id, checked_at, answered_at (for a checked prompt, its run time, when the engine answered; for a study answer, when it was stored after its check, not when the engine answered), origin (study or tracked), study_id (study findings only), prompt_id and prompt (checked-prompt findings only), provider, platform, surface, model, fact_id, fact_category, fact_statement and fact_version (the wording and version the answer was checked against), earlier_wording (true when the fact has been edited since), fact_status (the fact's status now), verdict (the model's), effective_verdict (after your team's review; null when marked not relevant or the fact out of date), quote, explanation, sources, a list of source URLs, and review_status and review_note (api/schemas/customer_api.py#FactCheckRow). These are the columns of the fact_checks file export plus id, with sources as a list rather than one space-separated string (see Exports).

The quote is text located in the engine's saved answer, returned exactly as it appears there; the explanation is an AI model's wording. sources holds the URLs our code linked to the quote from where the engine placed its citations, and is empty when none is linked, which is always the case for Claude; see how sources are linked.

Read saved weekly report digests

GET /reports requires reports:read and the project owner's current weekly-report entitlement in addition to customer API access. Standard Starter, Growth and Pro include both; custom overrides still apply. Reads never generate a report or run a model. Only saved, generated reports for completed Monday–Sunday UTC weeks are returned. Skipped, failed, malformed, current and future weeks are excluded (api/services/customer_api.py#report_scope).

The generator always writes that shape, snapping to the last complete Monday–Sunday week even when a run is delayed past its Monday (api/services/weekly_report.py#week_bounds). A small number of older rows were written by a delayed run before that rule existed and start on another weekday; those are not returned here. They remain readable on the Weekly report screen (Reports → Weekly insights) and through report sharing.

Parameter Meaning
start, end Inclusive saved week-start dates, not overlapping date ranges. Default: 28 days ending today; maximum: 366 days. Dates follow the same defaulting rules as /answers.
limit 1–25 digests, default 25
after_id Last report ID from the previous page; default 0

The response has data, next_after_id and resolved start and end. Follow next_after_id until null, keeping dates fixed. Ordering is ascending report ID, which can differ from week order. An empty list means there are no eligible saved reports in that window; it does not mean zero visibility. To select one exact week, set both dates to that Monday (api/services/customer_api.py#reports_page).

curl --fail-with-body \
  -H "Authorization: Bearer $DISCOVEREDBY_API_KEY" \
  'https://api.discoveredby.ai/customer-api/v1/projects/PROJECT_ID/reports?start=2026-09-07&end=2026-09-07'

Each item has id and digest. The digest follows version 1 of the client sharing format, with these fields:

Field Meaning
version Digest format version, currently 1; separate from the internal metrics version
client_name, accent Current project name and validated brand colour
week_start, week_end Saved report dates
summary At most 2,000 characters of the saved narrative
metrics Four {label, value, unit} readings: Brand visibility, Citation rate, Citation share and Average citation position
recommendations At most three saved recommendation titles, each capped at 300 characters
articles_published, pages_optimized, prompts_added Saved nonnegative activity counts, or null if unavailable

Unavailable metric readings stay null; measured zero stays 0. Legacy reports retain the conservative unknown-value handling used in the app and client snapshots. Percent readings use %; average citation position has an empty unit. These are stored weekly readings, not freshly computed metrics or averages across clients. Raw metrics JSON, revenue, findings, engine notes, recommendation details, generation errors, token usage, account data and sharing secrets are excluded (api/services/report_shares.py#build_snapshot).

GET /reports/REPORT_ID returns the same {id, digest} for one eligible report. It returns 404 for missing, other-project or ineligible reports; it never substitutes another week (api/services/customer_api.py#report_detail).

These are live saved reports, not previously issued link snapshots. Regeneration and project renaming can change later reads. Refresh from after_id=0 and replace records by ID to pick up changes to earlier rows. Use report sharing when you need an issued snapshot with expiry and revocation. A date-window limit is not a retention promise. No cross-client aggregation or automatic BI refresh is provided.

Run Explorer queries

Three routes require explorer:read. They use the customer API entitlement, with no extra plan gate, and compute the same numbers as the app's Explorer (api/services/customer_api.py#explorer_query, #explorer_views_page, #explorer_view_result).

Route Returns
POST /explorer/query The result of the Explorer query in the JSON body
GET /explorer/views Every saved view as data: id, name, chart, query, needs_repair, problems, updated_at
GET /explorer/views/VIEW_ID/result view and result: the saved view run now

The query body is the Explorer's own query object: metrics (1 to 5), breakdowns (0 to 2), filters (up to 8 of dimension and values), window ({"days": 7}, 28 or 90, or {"start", "end"}), grain (none, day or week), compare (none, previous or year) and smoothing (none or avg7). Unknown fields are refused. Metric and dimension keys, and every rule a query must follow, are on the Explorer page (api/schemas/explorer.py#ExplorerQuery). Every Explorer metric is accepted, including the three that count runs, answer_shown_rate, organic_first_page_rate and organic_position, and the seven answer-feature rates, web_search_rate, shopping_rate, local_businesses_rate, ads_rate, video_rate, images_rate and tables_rate (api/schemas/explorer.py#MetricKey; see Answer features).

curl --fail-with-body \
  -H "Authorization: Bearer $DISCOVEREDBY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"metrics": ["brand_visibility"], "breakdowns": ["engine"], "window": {"days": 28}}' \
  'https://api.discoveredby.ai/customer-api/v1/projects/PROJECT_ID/explorer/query'

A result has the normalised query, start and end, compare_start and compare_end (null without a comparison), rows, truncated and notes. Each row has keys and labels per breakdown, period (a date with a grain, otherwise null), partial, and values per metric: value, observations, unit, provisional, prior_value, prior_observations and delta (api/schemas/explorer.py#ExplorerResult). Labels are display text and can change (the Gemini API engine's label changed from "Gemini" to "Gemini (API)" when Gemini (app) was added); key on keys. A value of null means no data, never zero. truncated names each breakdown that was cut to 25 values and how many values it had.

The views list is not paginated. A view's query is the stored query exactly as saved; a view with needs_repair cannot run until it is fixed in the app, and problems says why. These are live reads: each query and each view result is computed at the moment of the request, not a stored snapshot. Nothing here saves views, edits dashboards or creates share links.

Each query and each view result also spends one query from the project's Explorer budget of 120 a minute, which the app, dashboards, exports and every key on the project share. Listing views spends only the key's own request (api/services/explorer/budget.py#charge_budget).

Limits, errors and audit history

Each key admits 60 authenticated requests in a one-minute window shared across all its read endpoints and MCP requests, including MCP discovery and initialization. A new window begins with the next request after the previous window expires. Valid-key requests that are refused for a missing scope, fail parameter validation or return a missing answer or report all consume a request. On 429, wait at least the Retry-After number of seconds before retrying. The limit is shared across server processes (api/services/customer_api.py#authenticate_key).

HTTP status Action
401 Check the header, project, key expiry/revocation and current project/creator/plan eligibility. The response deliberately does not reveal which eligibility check failed.
403 Use a key with the endpoint's required read scope; report reads also require weekly-report access, brand reasons reads also require the Brand reasons entitlement, objections reads also require the Objections entitlement, attributes reads also require the Attributes entitlement, and fact check reads also require the Fact check entitlement.
404 The answer, completed weekly report or saved Explorer view is unavailable in that project.
409 Answer text changed during retrieval; restart the text read.
422 Correct the IDs, date range, page size, offset, version or measure. On Explorer routes, a query outside the query shape gets the usual validation array; one that cannot run for this project lists every reason in detail.problems; one that ran too long says so in detail.
429 Wait for the Retry-After interval. On Explorer routes this is either the key's own limit or the project's Explorer budget; the detail says which.

Error responses carry a detail field; validation details may be an array, and an Explorer query that cannot run carries an object with a problems list. API responses and key-management responses use Cache-Control: no-store. Successful reads record the key ID, dataset and returned row count under the API reads tab of project activity, which is kept separate from the changes view so read traffic does not crowd out human actions. Creation and revocation are audited as changes. Neither the secret nor the answer contents are written to those receipts (api/services/customer_api.py#record_read, api/routers/pages.py#get_activity_page). Last use means an authenticated request was admitted; it is not a guarantee that data was returned.

  • Exports for CSV and JSON files across thirteen datasets.
  • Explorer for the metrics, dimensions and rules behind explorer:read queries.
  • Brand reasons for what a reason, category and polarity mean on the analysis screen this endpoint mirrors.
  • Objections for what rank, score, prominence and an objection group mean on the screen GET /objections reads from.
  • Attributes for what association, market prominence and an attribute mean on the screen GET /attributes reads from.
  • Fact check for what a finding, a verdict and the effective verdict mean on the screen GET /fact-checks reads from.
  • Personas for what the persona field on an answer or a brand reason refers to.
  • Languages and templates for what the language field on an answer or a brand reason refers to.
  • Team for project roles.
  • Client reports for the same-week agency overview.
  • Plans and limits for shared account allowances.

Last verified 2026-09-28

Start monitoring your AI visibility.

See how AI search engines talk about your brand.

Free to start. No credit card required.