Developer API
Analytics Export REST API
Programmatically pull brand analytics from GEO Scout — for BI tools, custom dashboards, and automation.
What you get
- —Up to 90 days of brand analytics in a single request
- —12 datasets: prompts, sources, AI responses, Share of Voice, competitors, timelines, and gaps
- —CSV archive (zip with multiple files) or JSON bundle
- —Identical data to the in-dashboard export button
Authentication
Endpoint accepts a Bearer token. Same tokens as MCP integration — Personal Access Token (PAT) or OAuth token.
Generate a PAT on the /mcp page. Requires scope mcp:read (granted by default).
Dataset catalog
12 datasets cover the full spectrum of GEO analytics — from raw sources and AI responses to aggregated GEO metrics and action-oriented breakdowns. Pick any combination.
| Key | What's inside |
|---|---|
| prompts | Brand prompts with per-period metrics: reach, SoV, sentiment for each prompt. |
| sources | Cited sources long-form: response_id, prompt_id, URL, domain, page title, brand/competitor flags, and per-source brand/competitor attribution. Use prompt_id to see which prompt a source belongs to. |
| responses | Raw AI response rows: prompt_id, response_id, provider, sentiment, brand position, citation type. Full answer text is included only with includeResponseText: true. |
| share_of_voice | SoV snapshot: brand + competitors, mention counts and shares for the period. |
| sentiment | Snapshot of sentiment distribution (positive/neutral/negative/not_mentioned), 4 rows. |
| competitors | Comparative table brand+competitors with the same GEO metrics (reach, SoV, avg position, recommendation rate, citation share). |
| providers | AI provider catalog (id, slug, name) — for joins with other datasets. |
| daily_timeline | Brand-level metrics per day: responses, mentions, reach %, SoV %, sentiment counts. |
| competitor_timeline | Per-day × per-entity: brand + top-20 competitors per day (long-form). For multi-line charts. |
| provider_timeline | Per-day × per-provider: total_responses, brand_mentions, mention_rate%, sov% per AI provider per day. |
| position | Brand position: avg_position, top_1_rate%, top_3_rate%, position_4_plus_rate%. |
| competitive_gaps | Prompts where competitors are mentioned but the brand is NOT — priority list of «here you're losing share». |
Request
Method and URL
POST /api/v1/analytics/export
Body fields
| Field | Type | Req. | Description |
|---|---|---|---|
| brandId | string (uuid) | ✓ | Brand UUID. Must belong to the token owner. |
| datasets | string[] | ✓ | Array. Allowed keys: prompts, sources, responses, share_of_voice, sentiment, competitors, providers, daily_timeline, competitor_timeline, provider_timeline, position, competitive_gaps. At least one. See the dataset catalog below. |
| format | "csv" | "json" | ✓ | csv or json. CSV with >1 dataset returns a zip. |
| includeResponseText | boolean | Optional. When true and the responses dataset is selected, adds the full AI answer text as response_text. Defaults to false. | |
| filters.startDate | string (ISO) | ✓ | ISO start timestamp (UTC). Range ≤ 90 days. |
| filters.endDate | string (ISO) | ✓ | ISO end timestamp (UTC). |
| filters.providerIds | string[] (uuid) | Optional. Provider UUIDs (filter). | |
| filters.promptIds | string[] (uuid) | Optional. Prompt UUIDs (filter). |
Example: curl
Replace brandId and token with your own.
curl -X POST https://geoscout.pro/api/v1/analytics/export \
-H "Authorization: Bearer gs_..." \
-H "Content-Type: application/json" \
-d '{
"brandId": "00000000-0000-0000-0000-000000000000",
"datasets": ["prompts", "responses", "sources", "daily_timeline"],
"format": "csv",
"includeResponseText": true,
"filters": {
"startDate": "2026-04-01T00:00:00Z",
"endDate": "2026-04-30T23:59:59Z"
}
}' -o export.zipResponse
200 OK — file body. Filename in Content-Disposition header (RFC 5987).
Useful headers
Content-Type— MIME type: text/csv, application/zip, or application/jsonContent-Disposition— attachment + UTF-8 filenameX-Export-Truncated— Datasets where rows were capped at their row limit (comma-separated keys). Empty = full export.Retry-After— Seconds until rate-limit resets (429 only)
Example: JSON format
{
"manifest": {
"exported_at": "2026-05-24T10:15:00.000Z",
"brand_id": "...",
"brand_name": "Acme",
"format": "json",
"filters": { ... },
"datasets": ["prompts", "responses"],
"include_response_text": true,
"row_counts": { "prompts": 59, "responses": 640 },
"truncated": {},
"notes": {
"responses": "one row per response; join on prompt_id to the prompts dataset for prompt-level metrics"
}
},
"datasets": {
"prompts": { "rows": [ ... ], "truncated": false },
"responses": { "rows": [ ... ], "truncated": false }
}
}Error codes
| Code | HTTP | When returned |
|---|---|---|
| invalid_request | 400 | Malformed JSON or Zod validation failed (see detail field) |
| unauthorized | 401 | Token missing, malformed, or unknown |
| token_expired | 401 | Token has expired |
| token_revoked | 401 | Token revoked by owner |
| insufficient_scope | 403 | Token lacks scope mcp:read |
| forbidden | 403 | Brand does not belong to the token owner |
| rate_limited | 429 | Exceeded 10 requests per hour |
| internal | 500 | Internal error. Details in server logs, not in response |
Example error response
{
"error": "rate_limited"
}Limits
- —Date range: up to 90 days
- —Rows per dataset: depends on the dataset (for example, sources up to 100,000 and responses up to 10,000). Excess data is truncated, see X-Export-Truncated
- —Rate limit: 10 requests per hour per user (PAT and OAuth tokens of one user share the bucket)
- —Heavy exports take 10-30s — set client timeout to 60s+