openapi: 3.0.1 info: title: Martekio Public API description: | The Martekio Public API provides programmatic access to AI Share of Voice analytics data. Track how brands and competitors appear in responses from AI models (ChatGPT, Perplexity, Gemini, Anthropic, Grok), including citations, sentiment, prominence, and visibility trends. ## Authentication All endpoints require a Bearer token in the `Authorization` header. Tokens use the format `mkto_selector.validator` and can be generated from the Martekio dashboard under My Profile > API Token. Tokens issued before the `mkto_` prefix was introduced remain valid with or without it. ## Rate Limiting API requests are rate-limited. If you exceed the limit, you will receive a `429 Too Many Requests` response. ## Maintenance Mode Martekio operates a kill switch on the public API. When the switch is active, every endpoint returns `503 Service Unavailable` with `{"error": "API is temporarily disabled for maintenance"}`. Treat `503` as transient and back off; the switch is only used for short maintenance windows or incident response. ## Changes in 1.3.0 `best_provider` on a Radar analysis result is **removed** and replaced by two fields, `best_provider_branded` and `best_provider_nonbranded`, because one field could not distinguish where a brand wins when a prompt names it from where it wins when the prompt does not. Either may be an empty string, meaning no assistant stood out, which is a normal answer rather than a missing value and is common on the non-branded field. The `providers` array is now documented as being returned in canonical assistant order. There is no alias for the old field: a client reading `best_provider` will find it absent. ## Client Ownership Client-scoped endpoints (`/clients/{clientID}/...`) enforce ownership validation. You can only access data for clients associated with your API token. version: "1.3.0" contact: name: Martekio Support email: support@martekio.com servers: - url: https://api.martekio.com/api/v1 description: Production - url: http://localhost:8089/api/v1 description: Local development security: - BearerAuth: [] tags: - name: Industry description: Industry-level insights and domain data - name: Share of Voice description: Share of Voice metrics and historical trends - name: Performance description: Client performance data and averages - name: Competitors description: Competitor citations, trends, and groupings - name: Analysis description: Analysis summaries, details, responses, and dates - name: Prompt Check description: Run a single prompt through selected AI assistants in real time and get back the visibility insights (ephemeral, metered, AI Overview excluded). - name: Tekka description: Ask Tekka, the Martekio AI assistant, a question in plain language and get a written answer grounded in your own space's data. Metered per day. - name: Responses description: Bulk and single-record access to AI replies with their citations. Designed for external tools that need the full response payload alongside citation URLs in one call. - name: Citations description: Citation trends and URL-level data - name: Citation Consistency description: How consistently the brand appears in AI-generated responses across a trailing window of analyses, per provider and branding axis. Per-(session, prompt) BOOL_OR dedupe means a prompt counts at most once per session. - name: Sentiment description: Sentiment score averages and trends - name: Agentic Traffic description: AI bot visit summaries and traffic distribution - name: AI Referral Traffic description: Human visits referred from AI chatbots (e.g. ChatGPT) detected via UTM parameters and referrer domains - name: Sources description: Influential sources tracking - name: Recommendations description: AI-generated recommendations for improving brand visibility - name: Feeds description: Product feed management. Refresh feed URLs, regenerate feed JSON, and download the merchant-facing feed file for ACP / Impact Engine workflows. - name: Radar description: > Martekio Radar: the metered, partner-issued AI visibility API. Submit a brand plus a set of prompts and get back, asynchronously, the same Share of Voice analysis Martekio runs in-app across the AI assistants enabled for the partner: six as standard (ChatGPT, Perplexity, Gemini, Anthropic, Google AI Mode, Google AI Overview), with Grok as a paid add-on. Authenticated with a per-partner key issued by Martekio (not a self-serve dashboard token); one prompt is deducted per prompt submitted from the key's annual allowance, whatever the number of assistants, so the assistant set changes what you get back rather than what you are charged. Read the set from the `providers` array in a result rather than assuming a fixed number. paths: # ─── General (no client scope) ─────────────────────────────────────── /industry/insights: get: operationId: getIndustryInsights summary: Get industry insights description: Returns industry data grouped by industry and AI provider. tags: [Industry] parameters: - name: industryCode in: query description: Restrict results to a single industry sector code. schema: type: integer responses: "200": description: Industry insights grouped by provider content: application/json: schema: type: array items: $ref: "#/components/schemas/IndustryInsight" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /industry/top-domains: get: operationId: getTopIndustryDomains summary: Get top cited domains per industry description: Returns the most-cited domains for each industry, grouped by AI provider. tags: [Industry] parameters: - name: country in: query description: Filter results by country code. schema: type: string - name: industryCode in: query description: Restrict results to a single industry sector code. schema: type: integer responses: "200": description: Top domains grouped by industry and provider content: application/json: schema: type: array items: $ref: "#/components/schemas/IndustryTopDomains" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Share of Voice ────────────────────────────────────────────────── /clients/{clientID}/sov/metrics: get: operationId: getSoVMetrics summary: Get Share of Voice metrics by provider description: Returns SoV metrics for a specific AI provider. If no date is specified, returns the latest available data. tags: [Share of Voice] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/aiProviderQuery" - name: analysis_date in: query description: Specific analysis date (YYYY-MM-DD). If omitted, returns the latest. schema: type: string format: date - name: getLatestSoV in: query description: If `true`, forces retrieval of the latest data regardless of date. schema: type: boolean responses: "200": description: SoV metrics for the requested provider content: application/json: schema: $ref: "#/components/schemas/SoVMetrics" "400": $ref: "#/components/responses/BadRequest" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/sov/metrics/historic: get: operationId: getSoVMetricsHistoric summary: Get historical SoV trends description: Returns SoV metrics over a date range, grouped by AI provider. tags: [Share of Voice] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query required: true description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query required: true description: End of date range (YYYY-MM-DD) schema: type: string format: date responses: "200": description: Historical SoV data grouped by provider content: application/json: schema: $ref: "#/components/schemas/SoVHistoricResponse" "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalError" # ─── Performance ───────────────────────────────────────────────────── /clients/{clientID}/performance: get: operationId: getClientPerformance summary: Get client performance data description: | Returns performance data for a client, grouped by AI provider. Optionally filter by analysis date and/or include a comparison date. tags: [Performance] parameters: - $ref: "#/components/parameters/clientID" - name: analysis_date in: query description: Filter to a specific analysis date (YYYY-MM-DD) schema: type: string format: date - name: comparison_date in: query description: Compare against a second date (YYYY-MM-DD). Requires `analysis_date`. schema: type: string format: date responses: "200": description: Performance data grouped by provider content: application/json: schema: $ref: "#/components/schemas/PerformanceResponse" "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/performance/all-time: get: operationId: getClientPerformanceAllTime summary: Get all-time performance averages description: Returns all-time average performance metrics grouped by AI provider. tags: [Performance] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: All-time performance averages by provider content: application/json: schema: $ref: "#/components/schemas/PerformanceAllTimeResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Competitors ───────────────────────────────────────────────────── /clients/{clientID}/competitors: get: operationId: getCompetitors summary: Get competitors by provider description: >- Returns the client's TRACKED competitors and their citation counts for a specific AI provider within a date range. Competitors are opt-in: only brands the client has chosen to track (via Curate Competitors) are returned; an untracked space yields an empty list. tags: [Competitors] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/aiProviderQuery" - $ref: "#/components/parameters/startDate" - $ref: "#/components/parameters/endDate" - name: includeClientBrand in: query description: Include the client's own brand in results schema: type: boolean responses: "200": description: List of competitors with citation counts content: application/json: schema: type: array items: $ref: "#/components/schemas/Competitor" "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/competitors/curated: get: operationId: getCuratedCompetitors summary: Get curated competitors description: >- Returns both of the space's curated competitor sets in one call. Tracked is the explicit set chosen for ongoing monitoring in Competition Explorer; Threats are the curated real rivals. The two are mutually exclusive, so they are returned together rather than as separate calls. Both lists are curated independently of citation volume and are ordered alphabetically. An uncurated space returns empty lists. tags: [Competitors] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: The client's curated competitor sets content: application/json: schema: $ref: "#/components/schemas/CuratedCompetitorsResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/competitors/trends: get: operationId: getCompetitorTrends summary: Get competitor citation trends description: >- Returns citation trends (branded vs non-branded) for the client's TRACKED competitors on a specific AI provider. Only brands the client has chosen to track are included. tags: [Competitors] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/aiProviderQuery" - $ref: "#/components/parameters/startDate" - $ref: "#/components/parameters/endDate" - name: includeClientBrand in: query description: Include the client's own brand in results schema: type: boolean responses: "200": description: Competitor trends with branded/non-branded split content: application/json: schema: $ref: "#/components/schemas/CompetitorTrendsResponse" "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/competitors/trends/grouped: get: operationId: getCompetitorTrendsGrouped summary: Get competitors grouped as winners/losers description: >- Returns the client's CURATED competitors grouped into winners and losers across all AI providers. The curated set is the union of the client's Tracked brands and its Competitive Threats (the two mutually exclusive lists). Each row carries onTrackedList and onWatchList flags so callers can tell which set a competitor belongs to. Brands the client has neither tracked nor flagged as a threat are excluded. tags: [Competitors] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date - name: includeClientBrand in: query description: Include the client's own brand in results schema: type: boolean - name: sort in: query description: | Ranking mode. `cumulative` (default, empty also means cumulative) ranks by all-time citation totals from the CompetitorCitationTrends running totals. `momentum` switches to a "current vs prior window" comparison computed live from all_citation_urls so recent gainers / losers surface ahead of long-tail totals. schema: type: string enum: [cumulative, momentum] default: cumulative responses: "200": description: Winners and losers grouped by provider content: application/json: schema: $ref: "#/components/schemas/CompetitorTrendsGroupedResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Analysis ──────────────────────────────────────────────────────── /clients/{clientID}/analysis/summaries: get: operationId: getAnalysisSummaries summary: Get analysis summaries description: Returns analysis run summaries for a client, including provider-level totals. tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: Client profile and analysis summaries content: application/json: schema: $ref: "#/components/schemas/AnalysisSummariesResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/analysis/details: get: operationId: getAnalysisDetails summary: Get analysis details (paginated) description: | Returns detailed analysis results for a client. Supports pagination via `page` and `limit`, plus optional date-range filtering via `startDate`/`endDate`. Default windowing rule: if neither date range nor pagination is supplied, the response is restricted to the trailing 30 days. Pass `page=1&limit=N` for paginated access to the full history, or `startDate`/`endDate` to scope to a specific range. tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" - name: page in: query description: Page number (1-based). Omit for all results. schema: type: integer minimum: 1 - name: limit in: query description: Records per page (max 1000) schema: type: integer minimum: 1 maximum: 1000 - name: startDate in: query description: Start of date range (YYYY-MM-DD). Filters by analysis CreatedAt. schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD). Filters by analysis CreatedAt (inclusive of the end day). schema: type: string format: date responses: "200": description: Analysis details with optional pagination metadata content: application/json: schema: $ref: "#/components/schemas/AnalysisDetailsResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/prompt-movers: get: operationId: getPromptMovers summary: Get Prompt Momentum (rising and falling citation rate) description: | Ranks the client's prompts (tracked requests) by the change in citation rate, the share of AI answers that cite the brand, between a trailing window and the previous equal window, cross-provider, with a per-assistant breakdown. The change is reported in percentage points (`delta_points`): positive is improving, negative is declining. Prompts with fewer than `min_answers` answers in either window are excluded, so a single answer never surfaces as a trend. The windows anchor to the space's latest analysis run, so `recent_period`/`prior_period` return the exact dates compared. Designed for BI dashboards and alert rules ("notify when a prompt's citation rate drops 20 points"). tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" - name: days in: query description: Trailing window in days for each side of the comparison (default 30). schema: type: integer minimum: 7 maximum: 90 - name: min_answers in: query description: Minimum answers a prompt needs in BOTH windows to qualify (default 3). schema: type: integer minimum: 1 maximum: 50 - name: direction in: query description: Filter to improving, declining, or all movers. schema: type: string enum: [improving, declining, all] default: all - name: branded in: query description: Filter to branded prompts, non-branded prompts, or all. schema: type: string enum: ["true", "false", all] default: all - name: limit in: query description: Cap the number of movers returned (omit for no cap). schema: type: integer minimum: 1 responses: "200": description: Prompts ranked by Prompt Momentum, with the comparison periods content: application/json: schema: type: object properties: window_days: type: integer min_answers: type: integer recent_period: type: object properties: start: type: string format: date end: type: string format: date prior_period: type: object properties: start: type: string format: date end: type: string format: date count: type: integer movers: type: array items: type: object properties: prompt: type: string origin_keyword: type: string branded: type: boolean rate_current_pct: type: number description: Share of answers citing the brand in the recent window (0..100). rate_prior_pct: type: number delta_points: type: number description: Change in citation rate in percentage points; positive = improving. trend: type: string enum: [surging, rising, stable, declining, disappearing] answers_current: type: integer answers_prior: type: integer cited_current: type: integer cited_prior: type: integer providers: type: array items: type: object properties: ai_provider: type: string rate_current_pct: type: number rate_prior_pct: type: number delta_points: type: number trend: type: string "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/visibility-report-prompt: get: operationId: getVisibilityReportPrompt summary: Get the Visibility Report prompt description: | Returns the effective AI Visibility Report prompt for the client. Martekio publishes several named reports; `prompt` selects one by name and omitting it returns the default, so a call written before named reports existed keeps working unchanged. The response is the space's own saved version of that report when it has one (`custom: true`), otherwise the report as Martekio wrote it (`custom: false`). The latter still contains `martekio_*` placeholders; a saved version is already personalised for the space. `available` lists every report name you can ask for. tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" - name: prompt in: query required: false schema: type: string description: Which named report to return, e.g. `Default Long Form`. Matched case-insensitively. Omit for the default report. responses: "200": description: The Visibility Report prompt content: application/json: schema: type: object properties: prompt: type: string description: The report prompt text. promptName: type: string description: The name of the report returned. custom: type: boolean description: True when this is the space's own saved version of the report; false when it is the report as Martekio wrote it. available: type: array items: type: string description: Every report name available for this client. "401": $ref: "#/components/responses/Unauthorized" "404": description: No report carries the requested name. The body lists the `available` names. "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/analysis/responses/{queryID}: get: operationId: getAIResponse summary: Get full AI response description: Returns the full AI-generated response text for a specific query. tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" - name: queryID in: path required: true description: Unique identifier of the query schema: type: string responses: "200": description: The full AI response text content: application/json: schema: $ref: "#/components/schemas/AIResponseResponse" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/analysis/dates: get: operationId: getAnalysisDates summary: Get available analysis dates description: Returns a list of dates on which analysis was performed for this client. tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: Array of analysis dates content: application/json: schema: type: array items: type: string format: date example: ["2026-03-15", "2026-03-14", "2026-03-13"] "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Responses ─────────────────────────────────────────────────────── /clients/{clientID}/responses: get: operationId: getAllResponses summary: Get all AI responses with citations description: | Returns every non-archived AI response for the client, with each row carrying its full reply text, sentiment, brand presence flags, and the full citation URL list. Optional query parameters narrow the result to a single session or single provider. The payload is dense: prefer narrowing via `sessionId` or `aiProvider` for routine calls. Paged since 2026-09-29: `limit` (default 100, maximum 1000) and `offset` (default 0) select a page ordered newest first; `total` is the number of rows that match the filters, `returned` the number in this page. Page with `offset` until `offset + returned` reaches `total`. tags: [Responses] parameters: - $ref: "#/components/parameters/clientID" - name: sessionId in: query description: Restrict results to one analysis session. schema: type: string - name: aiProvider in: query description: Restrict results to one AI provider. schema: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] - name: limit in: query description: Rows per page (default 100, maximum 1000; larger values are clamped). schema: type: integer minimum: 1 maximum: 1000 default: 100 - name: offset in: query description: Rows to skip before the page (default 0). schema: type: integer minimum: 0 default: 0 responses: "200": description: One page of responses with citations content: application/json: schema: $ref: "#/components/schemas/ResponsesListResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/responses/{queryID}: get: operationId: getResponseWithCitations summary: Get a single AI response with citations description: | Returns one AI response identified by `queryID`, including the full reply text, brand presence flags, sentiment, and the ordered citation URL list. Use this when drilling down from a list-level response into a single row. tags: [Responses] parameters: - $ref: "#/components/parameters/clientID" - name: queryID in: path required: true description: Unique identifier of the query. schema: type: string responses: "200": description: Single response with citations content: application/json: schema: $ref: "#/components/schemas/ResponseDetail" "400": $ref: "#/components/responses/BadRequest" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" # ─── Search Queries (fan-out) ──────────────────────────────────────── /clients/{clientID}/search-queries: get: operationId: getSearchQueries summary: Get fan-out web-search queries description: | Returns the fan-out web-search queries an AI provider fired while generating per-keyword responses for a client. Surfaces the long-tail of related searches the model considered, even when those queries did not appear in the final reply. Populated for providers whose integrations expose their tool-use telemetry: ChatGPT-via-DFS, Anthropic, Gemini. Three response shapes: - default (flat): one row per fan-out query. - `group=prompt`: rows collapsed by parent prompt, queries[] preserves their original order. - `dedupe=true`: identical (search_query, provider) pairs collapsed, sorted by occurrence_count desc — useful for discovering canonical research questions about an industry. tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" - name: provider in: query description: Filter to a single provider. Omit to return all. schema: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] - name: session_id in: query description: Exact session id to scope the lookup. schema: type: string - name: prompt in: query description: Exact match on the parent prompt (the original keyword that triggered the fan-out). schema: type: string - name: from in: query description: Lower bound on parent analysis_date (YYYY-MM-DD, inclusive). schema: type: string format: date - name: to in: query description: Upper bound on parent analysis_date (YYYY-MM-DD, inclusive). schema: type: string format: date - name: group in: query description: | `prompt` collapses rows by parent prompt. Omit (or `none`) for a flat list. Mutually exclusive with `dedupe=true`; when both are set, `dedupe` wins. schema: type: string enum: [none, prompt] default: none - name: dedupe in: query description: | When `true`, collapses identical (search_query, provider) pairs into a single row with an occurrence_count and a sample of up to 5 parent prompts that triggered it. Sorted by occurrence_count desc. schema: type: boolean default: false - name: limit in: query description: Max rows to return. Default 100, max 1000. schema: type: integer minimum: 1 maximum: 1000 default: 100 - name: offset in: query description: Pagination offset. schema: type: integer minimum: 0 default: 0 responses: "200": description: Fan-out search queries (one of three shapes) content: application/json: schema: type: object properties: client_id: type: string shape: type: string enum: [flat, grouped, deduped] total: type: integer limit: type: integer offset: type: integer queries: description: Present when shape is `flat` or `deduped`. type: array items: type: object prompts: description: Present when shape is `grouped`. type: array items: type: object "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Analysis Compare ──────────────────────────────────────────────── /clients/{clientID}/analysis/compare: post: operationId: compareAnalyses summary: Compare two analysis sessions description: | Returns the delta between two analysis sessions for one client: per-prompt changes in citation and brand-citation counts, an aggregated summary, and optionally the diff of DataForSEO-extracted competitor brand entities (new vs lost across the two runs). Designed for BI dashboards and alert rules ("notify when brand citations drop >20% week-over-week"). The same prompt evaluated by multiple providers is compared per-provider; rows are keyed on (prompt, ai_provider). tags: [Analysis] parameters: - $ref: "#/components/parameters/clientID" requestBody: required: true content: application/json: schema: type: object required: [session_id_a, session_id_b] properties: session_id_a: type: string description: Baseline session id. session_id_b: type: string description: Comparison session id. Must differ from session_id_a. providers: type: array description: Restrict comparison to a subset of providers. Omit for all. items: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, GoogleAIMode, GoogleAIOverview, Grok] changed_only: type: boolean description: | When true, omits rows where citations_delta, brand_citations_delta, and brand_cited are all unchanged. Default false. default: false include_competitors: type: boolean description: | When true, includes new_competitors and lost_competitors in the summary, diffed from analysis_detail_brand_entities. Currently populated only by the ChatGPT-via-DFS route; the diff is empty when neither session was routed via DFS. Default false. default: false responses: "200": description: Per-prompt comparison plus summary header content: application/json: schema: type: object properties: client_id: type: string session_a: type: object properties: session_id: type: string analysis_date: type: string format: date is_partial: type: boolean session_b: type: object properties: session_id: type: string analysis_date: type: string format: date is_partial: type: boolean providers: type: array items: type: string changed_only: type: boolean summary: type: object properties: prompts_a: type: integer prompts_b: type: integer prompts_in_both: type: integer total_citations_a: type: integer total_citations_b: type: integer total_citations_delta: type: integer brand_citations_a: type: integer brand_citations_b: type: integer brand_citations_delta: type: integer new_competitors: type: array items: type: string lost_competitors: type: array items: type: string by_prompt: type: array items: type: object properties: prompt: type: string ai_provider: type: string a_present: type: boolean b_present: type: boolean citations_a: type: integer citations_b: type: integer citations_delta: type: integer brand_citations_a: type: integer brand_citations_b: type: integer brand_citations_delta: type: integer brand_cited_a: type: boolean brand_cited_b: type: boolean "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" # ─── AI Overview Attendance ────────────────────────────────────────── /clients/{clientID}/ai-overview/attendance: get: operationId: getAIOverviewAttendance summary: Get AI Overview attendance trend description: | Returns the time series of how often Google rendered an AI Overview for this client's queries. A metric unique to Martekio: Google chooses to render AI Overview for roughly half of queries, and which half varies by prompt intent, locale, and ranking landscape. Tracking attendance over time surfaces shifts in coverage well before they show up in traditional ranking reports. The `is_partial` flag on each bucket marks dates where the DataForSEO async queue timed out before all tasks were retrieved — a 0% rendered_pct on a partial day reflects DFS infrastructure, not Google's render decision. tags: [AI Overview] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query description: Lower bound on analysis_date (YYYY-MM-DD, inclusive). Defaults to trailing 30 days. schema: type: string format: date - name: to in: query description: Upper bound on analysis_date (YYYY-MM-DD, inclusive). Defaults to today (UTC). schema: type: string format: date - name: granularity in: query description: Bucket width. schema: type: string enum: [day, week, month] default: day - name: branded in: query description: Filter to one branding axis. `all` (default) returns both. schema: type: string enum: ["true", "false", "all"] default: all responses: "200": description: AI Overview attendance trend content: application/json: schema: type: object properties: client_id: type: string from: type: string format: date to: type: string format: date granularity: type: string branded: type: string series: type: array items: type: object properties: period_start: type: string format: date total_queries: type: integer rendered_count: type: integer rendered_pct: type: number format: float is_partial: type: boolean "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/ai-overview/attendance/prompts: get: operationId: getAIOverviewAttendancePrompts summary: Get per-prompt AI Overview attendance description: | For each prompt the client ran in the window, returns how often Google rendered an AI Overview. Useful for surfacing prompts that reliably trigger AIO (a vulnerability for citation steering) and prompts that never do (where AIO is not a SoV signal). tags: [AI Overview] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query schema: type: string format: date - name: to in: query schema: type: string format: date - name: branded in: query schema: type: string enum: ["true", "false", "all"] default: all - name: sort in: query description: Sort order for the returned prompts. schema: type: string enum: [rendered_pct_desc, rendered_pct_asc, total_queries_desc, total_queries_asc] default: rendered_pct_desc - name: limit in: query schema: type: integer minimum: 1 maximum: 1000 default: 100 responses: "200": description: Per-prompt attendance list content: application/json: schema: type: object properties: client_id: type: string from: type: string format: date to: type: string format: date branded: type: string limit: type: integer prompts: type: array items: type: object properties: prompt: type: string is_prompt_branded: type: boolean total_queries: type: integer rendered_count: type: integer rendered_pct: type: number format: float "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/ai-overview/citations: get: operationId: getAIOverviewCitations summary: Get AI Overview citation URLs description: | Returns the citation URLs Google's AI Overview box linked to for the client's tracked prompts, with the full ownership / competitor / influential-source classification from the citation pipeline. Unlike chatbot citations these come from real Google SERPs, so they are the closest thing to consumer-truth visibility. tags: [AI Overview] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query description: Lower bound (YYYY-MM-DD, inclusive). Defaults to trailing 30 days. schema: { type: string, format: date } - name: to in: query description: Upper bound (YYYY-MM-DD, inclusive). Defaults to today (UTC). schema: { type: string, format: date } - name: branded in: query schema: { type: string, enum: ["true", "false", "all"], default: all } - name: domain in: query description: Substring filter on the citation URL. schema: { type: string } - name: ownedOnly in: query description: When true, limits results to client-owned citations. schema: { type: boolean, default: false } - name: limit in: query schema: { type: integer, default: 100, maximum: 1000 } responses: "200": description: AI Overview citations content: application/json: schema: type: object properties: client_id: { type: string } from: { type: string, format: date } to: { type: string, format: date } branded: { type: string } count: { type: integer } citations: type: array items: type: object properties: prompt: { type: string } url: { type: string } position_rank: { type: integer } is_prompt_branded: { type: boolean } is_client_owned: { type: boolean } is_competition: { type: boolean } is_influential_source: { type: boolean } competitor_name: { type: string } path_contains_brand: { type: boolean } analysis_date: { type: string, format: date-time } session_id: { type: string } "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── SERP capture ──────────────────────────────────────────────────── /clients/{clientID}/serp/questions: get: operationId: getSerpQuestions summary: Get People Also Ask questions description: | Returns the distinct People Also Ask questions Google surfaced alongside the client's tracked prompts, deduplicated to their most recent occurrence. Each question carries the answer source where Google attributed one, classified against the client's brand websites (`answered_by`: own | other | none). These are real user questions and make strong candidates for new custom prompts. tags: [SERP Capture] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query schema: { type: string, format: date } - name: to in: query schema: { type: string, format: date } - name: keyword in: query description: Exact seed keyword (case-insensitive). schema: { type: string } - name: prompt in: query description: Exact analysed prompt (case-insensitive). schema: { type: string } - name: branded in: query schema: { type: string, enum: ["true", "false", "all"], default: all } - name: answeredBy in: query description: Filter by who owns the answer source domain. schema: { type: string, enum: [own, other, none, all], default: all } - name: limit in: query schema: { type: integer, default: 100, maximum: 1000 } responses: "200": description: People Also Ask questions content: application/json: schema: type: object properties: client_id: { type: string } from: { type: string, format: date } to: { type: string, format: date } branded: { type: string } answered_by: { type: string } count: { type: integer } questions: type: array items: type: object properties: question: { type: string } answer_domain: { type: string } answer_url: { type: string } answer_text: { type: string } prompt: { type: string } keyword: { type: string } is_branded: { type: boolean } answered_by: { type: string, enum: [own, other, none] } occurrences: { type: integer } first_seen: { type: string, format: date-time } last_seen: { type: string, format: date-time } "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/serp/organic: get: operationId: getSerpOrganic summary: Get organic blue-link results description: | Returns the organic "blue link" results captured alongside the AI Overview for the client's tracked prompts: a rank tracker scoped to exactly the prompts tracked for AI visibility. Each link carries the client-ownership classification and whether the same domain was cited inside the AI Overview for that query, so "you rank #3 but the AI Overview cites someone else" is a single API call. tags: [SERP Capture] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query schema: { type: string, format: date } - name: to in: query schema: { type: string, format: date } - name: keyword in: query schema: { type: string } - name: prompt in: query schema: { type: string } - name: branded in: query schema: { type: string, enum: ["true", "false", "all"], default: all } - name: domain in: query description: Substring filter on the result domain. schema: { type: string } - name: ownedOnly in: query schema: { type: boolean, default: false } - name: limit in: query schema: { type: integer, default: 100, maximum: 1000 } responses: "200": description: Organic results content: application/json: schema: type: object properties: client_id: { type: string } from: { type: string, format: date } to: { type: string, format: date } branded: { type: string } owned_only: { type: boolean } count: { type: integer } results: type: array items: type: object properties: prompt: { type: string } keyword: { type: string } rank: { type: integer } url: { type: string } domain: { type: string } title: { type: string } snippet: { type: string } is_branded: { type: boolean } is_client_owned: { type: boolean } ai_overview_rendered: { type: boolean } cited_in_ai_overview: { type: boolean } seen_at: { type: string, format: date-time } session_id: { type: string } "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/serp/products: get: operationId: getSerpProducts summary: Get popular products carousel entries description: | Returns the Google shopping carousel ("popular products") entries captured for the client's tracked prompts, with seller, price, and rating where Google provides them: competitor shelf placement on the SERPs that matter to the brand. tags: [SERP Capture] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query schema: { type: string, format: date } - name: to in: query schema: { type: string, format: date } - name: keyword in: query schema: { type: string } - name: prompt in: query schema: { type: string } - name: branded in: query schema: { type: string, enum: ["true", "false", "all"], default: all } - name: seller in: query description: Substring filter on the seller name. schema: { type: string } - name: limit in: query schema: { type: integer, default: 100, maximum: 1000 } responses: "200": description: Popular products content: application/json: schema: type: object properties: client_id: { type: string } from: { type: string, format: date } to: { type: string, format: date } branded: { type: string } count: { type: integer } products: type: array items: type: object properties: prompt: { type: string } keyword: { type: string } rank: { type: integer } title: { type: string } description: { type: string } seller: { type: string } price_current: { type: number, nullable: true } price_displayed: { type: string } currency: { type: string } rating_value: { type: number, nullable: true } rating_votes: { type: integer, nullable: true } is_branded: { type: boolean } seen_at: { type: string, format: date-time } session_id: { type: string } "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/serp/visibility-matrix: get: operationId: getSerpVisibilityMatrix summary: Get the per-prompt SERP visibility matrix description: | Classifies each tracked prompt by how the client shows up on the Google SERP, using the latest AI Overview query per prompt in the window. Classifications: `fully_visible` (AI Overview rendered, client cited in it AND ranked organically), `rank_not_converted` (ranked organically but the rendered Overview does not cite the client), `ai_only` (cited in the Overview without an organic ranking), `invisible` (Overview rendered, client neither cited nor ranked), `no_ai_overview` (Google rendered no AI Overview for the prompt). The `rank_not_converted` bucket is the actionable one: pages that rank but are not being chosen as Overview sources. tags: [SERP Capture] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query schema: { type: string, format: date } - name: to in: query schema: { type: string, format: date } - name: branded in: query schema: { type: string, enum: ["true", "false", "all"], default: all } responses: "200": description: Visibility matrix content: application/json: schema: type: object properties: client_id: { type: string } from: { type: string, format: date } to: { type: string, format: date } branded: { type: string } count: { type: integer } summary: type: object additionalProperties: { type: integer } description: Prompt counts per classification bucket. prompts: type: array items: type: object properties: prompt: { type: string } is_branded: { type: boolean } ai_overview_rendered: { type: boolean } owned_organic_rank: { type: integer, nullable: true } cited_in_ai_overview: { type: boolean } classification: type: string enum: [fully_visible, rank_not_converted, ai_only, invisible, no_ai_overview] seen_at: { type: string, format: date-time } "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Citations ─────────────────────────────────────────────────────── /clients/{clientID}/citations/trends: get: operationId: getCitationTrends summary: Get citation trends description: | Returns citation trend data with source breakdowns and provider-level insights. Supports filtering by competitor, industry, date range, and citation type. tags: [Citations] parameters: - $ref: "#/components/parameters/clientID" - name: client in: query description: Client ID filter schema: type: string - name: competitor in: query description: Filter to a specific competitor name schema: type: string - name: industry in: query description: Filter by industry code schema: type: string - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date - name: filter in: query description: Citation type filter schema: type: string enum: [my_brand, competitors, brand_in_url, branded, non_branded] - name: limit in: query description: Maximum number of raw rows to process schema: type: integer responses: "200": description: Citation trends with provider insights and top URLs content: application/json: schema: $ref: "#/components/schemas/CitationTrendsResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/citations/by-query/{queryID}: get: operationId: getCitationsByQuery summary: Get citations for a query description: Returns all citation URLs associated with a specific analysis query. tags: [Citations] parameters: - $ref: "#/components/parameters/clientID" - name: queryID in: path required: true description: Unique identifier of the query schema: type: string responses: "200": description: List of citation URLs with metadata content: application/json: schema: $ref: "#/components/schemas/CitationsByQueryResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/citations/by-session/{sessionID}: get: operationId: getCitationsBySession summary: Get top competitors in a session description: Returns the top competitors by citation count within an analysis session. tags: [Citations] parameters: - $ref: "#/components/parameters/clientID" - name: sessionID in: path required: true description: Unique identifier of the analysis session schema: type: string responses: "200": description: Competitors by citation count, grouped by provider content: application/json: schema: type: array items: $ref: "#/components/schemas/SessionCompetitorGroup" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/citations/domains: get: operationId: getCitationDomains summary: Get cited domains with counts description: | Returns every distinct cited domain for the client with its total citation count, ordered by count descending. Useful for building "most-cited sources" rollups outside of `/citations/trends`. Optional query parameters narrow the result to a single session or single provider. tags: [Citations] parameters: - $ref: "#/components/parameters/clientID" - name: sessionId in: query description: Restrict counts to one analysis session. schema: type: string - name: aiProvider in: query description: Restrict counts to one AI provider. schema: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] responses: "200": description: Cited domains with counts content: application/json: schema: $ref: "#/components/schemas/CitationDomainsResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Citation Consistency ──────────────────────────────────────────── /clients/{clientID}/consistency/trends: get: operationId: getCitationConsistencyTrend summary: Get citation consistency trend description: | Returns the per-(session, provider, branding-axis) citation consistency time series for one client. Each anchor session carries a score computed against the trailing `window` analyses; scores are floats from 0.0 (never cited / mentioned) to 1.0 (cited / mentioned in every analysis in the window). Use this endpoint to pipe consistency into BI tools, scheduled reports, or custom dashboards. tags: [Citation Consistency] parameters: - $ref: "#/components/parameters/clientID" - name: window in: query description: | Trailing N analyses each anchor's score is computed over. Default 8 (~4 weeks at the platform's standard 2x/week cadence). Schedule-invariant — the same `window` value produces comparable scores across clients regardless of their analysis frequency. schema: type: integer minimum: 1 default: 8 - name: provider in: query description: Filter to a single provider. Omit to return all four. schema: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] - name: branded in: query description: Filter to one branding axis. `all` (default) returns both. schema: type: string enum: ["true", "false", "all"] default: all - name: startDate in: query description: Anchor date lower bound (YYYY-MM-DD, inclusive). schema: type: string format: date - name: endDate in: query description: Anchor date upper bound (YYYY-MM-DD, inclusive). schema: type: string format: date responses: "200": description: Citation consistency trend points content: application/json: schema: $ref: "#/components/schemas/CitationConsistencyTrendResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/consistency/prompts: get: operationId: getCitationConsistencyPrompts summary: Get per-prompt citation consistency detail description: | Returns every (prompt, provider, branding-axis) tuple in the trailing window for one client, with per-prompt consistency scores plus full-window binary patterns aligned 1:1 with analysis date and session_id arrays. Pattern cells carry one of three values: `1` = brand was cited (or mentioned), `0` = prompt ran but brand was not cited / mentioned, `-1` = prompt did not run in that analysis (window slot present, no analysis_details row). Use this endpoint to build custom drill-downs, score buckets client-side, or feed analysis into a data warehouse. tags: [Citation Consistency] parameters: - $ref: "#/components/parameters/clientID" - name: window in: query description: Trailing N analyses. Default 8. schema: type: integer minimum: 1 default: 8 - name: branded in: query description: Filter to one branding axis. `all` (default) returns both. schema: type: string enum: ["true", "false", "all"] default: all - name: providers in: query description: | Comma-separated subset of providers. Omit to include all four. Example: `Anthropic,ChatGPT`. schema: type: string responses: "200": description: Per-prompt consistency rows content: application/json: schema: $ref: "#/components/schemas/CitationConsistencyPromptsResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Sentiment ─────────────────────────────────────────────────────── /clients/{clientID}/sentiment/averages: get: operationId: getSentimentAverages summary: Get sentiment score averages description: | Returns overall and per-provider average sentiment scores for a client. By default the averages are computed across the client's full history; pass `getLatest=true` to limit the average to the most recent analysis session. tags: [Sentiment] parameters: - $ref: "#/components/parameters/clientID" - name: getLatest in: query description: When `true`, restricts the average to the most recent analysis session. schema: type: boolean default: false responses: "200": description: Sentiment averages overall and by provider content: application/json: schema: $ref: "#/components/schemas/SentimentAveragesResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/sentiment/scores: get: operationId: getSentimentScores summary: Get sentiment score trends description: Returns daily sentiment scores over a date range, grouped by AI provider. tags: [Sentiment] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/startDate" - $ref: "#/components/parameters/endDate" responses: "200": description: Sentiment scores by date, grouped by provider content: application/json: schema: $ref: "#/components/schemas/SentimentScoresResponse" "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalError" # ─── Agentic Traffic ───────────────────────────────────────────────── /clients/{clientID}/agentic/summaries: get: operationId: getAgenticSummaries summary: Get AI bot visit summaries description: Returns daily visit counts by AI bot name within an optional date range. tags: [Agentic Traffic] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date responses: "200": description: Bot visit summaries content: application/json: schema: type: array items: $ref: "#/components/schemas/AIBotSummary" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/agentic/distribution: get: operationId: getAgenticDistribution summary: Get bot visit distribution description: Returns aggregated visit counts per bot over an optional date range. tags: [Agentic Traffic] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date responses: "200": description: Visit distribution by bot content: application/json: schema: type: array items: $ref: "#/components/schemas/BotDistribution" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/agentic/proportion: get: operationId: getTrafficProportion summary: Get AI vs non-AI traffic proportion description: Returns a breakdown of traffic between AI bots and non-AI traffic. tags: [Agentic Traffic] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date responses: "200": description: Traffic proportion breakdown content: application/json: schema: type: array items: $ref: "#/components/schemas/TrafficProportion" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── AI Referral Traffic ───────────────────────────────────────────── /clients/{clientID}/referrals/summaries: get: operationId: getAIReferralSummaries summary: Get AI referral visit summaries description: Returns daily human visit counts from AI chatbot referrals, grouped by source. tags: [AI Referral Traffic] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date responses: "200": description: Daily referral summaries content: application/json: schema: type: array items: $ref: "#/components/schemas/AIReferralSummary" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/referrals/distribution: get: operationId: getAIReferralDistribution summary: Get AI referral source distribution description: Returns aggregated visit counts per AI referral source over an optional date range. tags: [AI Referral Traffic] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date responses: "200": description: Referral distribution by source content: application/json: schema: type: array items: $ref: "#/components/schemas/ReferralDistribution" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/referrals/top-pages: get: operationId: getAIReferralTopPages summary: Get top pages receiving AI referral traffic description: Returns the top pages receiving human visits from AI chatbot referrals, grouped by URL and source. Limited to 100 results. tags: [AI Referral Traffic] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD) schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD) schema: type: string format: date responses: "200": description: Top pages by referral visit count content: application/json: schema: type: array items: $ref: "#/components/schemas/ReferralTopPage" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Sources ───────────────────────────────────────────────────────── # ─── Performance Summary ────────────────────────────────────────────── /clients/{clientID}/performance/summary: get: operationId: getPerformanceSummary summary: Get performance summary description: | Returns aggregated performance metrics for the requested window, broken down by AI provider, plus an `overall` row covering every provider together. Includes citation rate, mention rate, sentiment score, and Share of Voice. If no date parameters are provided, returns all-time figures. How the figures are aggregated: * **Share of Voice** (`totalSOV`, `brandedSOV`, `nonBrandedSOV`) is pooled from the underlying citation counts: total brand citations divided by total citations across the window. It is not an average of per-analysis percentages, so a session with 500 citations counts for more than one with 3. The same pooling produces the `overall` row, which is therefore not the mean of the per-provider values. Analyses that returned no citations at all contribute nothing rather than counting as a zero. * **Citation rate, mention rate and sentiment** are means of per-analysis values. In the `overall` row each provider is weighted by `analysisCount`, so providers with more analyses carry more of the result. * `analysisCount` on the `overall` row is the total number of analyses behind it, not the number of providers. tags: [Performance] parameters: - $ref: "#/components/parameters/clientID" - name: startDate in: query description: Start of date range (YYYY-MM-DD). Optional, omit for all-time data. schema: type: string format: date - name: endDate in: query description: End of date range (YYYY-MM-DD). Optional, omit for all-time data. schema: type: string format: date responses: "200": description: Performance summary per provider, plus a pooled overall row content: application/json: schema: $ref: "#/components/schemas/PerformanceSummaryResponse" example: overall: aiProvider: "All" citationRate: 50.71 mentionRate: 56.06 sentimentScore: 0.66 totalSOV: 21.41 brandedSOV: 43.01 nonBrandedSOV: 1.61 analysisCount: 187 providers: - aiProvider: "ChatGPT" citationRate: 44.05 mentionRate: 51.89 sentimentScore: 0.63 totalSOV: 17.90 brandedSOV: 42.50 nonBrandedSOV: 0.36 analysisCount: 43 "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Recommendations ───────────────────────────────────────────────── /clients/{clientID}/recommendations: get: operationId: getRecommendations summary: Get AI recommendations description: | Returns active (non-archived, non-dismissed) AI-generated recommendations for improving the client's brand visibility in AI responses. Recommendations are sorted by priority (1 = most urgent). Each recommendation includes an action, supporting context, type, and whether it has been added to the backlog. Use the optional `type` query parameter to return only certain recommendation types (single value or comma-separated list). Valid types: `content_gap`, `citation_improvement`, `competitor_insight`, `sentiment_improvement`, `ranking_opportunity`. tags: [Recommendations] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/recommendationTypeFilter" responses: "200": description: List of active recommendations content: application/json: schema: type: array items: $ref: "#/components/schemas/Recommendation" example: - id: 1 recommendationAction: "Create dedicated product education content targeting non-branded queries" recommendationText: "ChatGPT shows only 2 non-branded citations vs 620 competitor citations." recommendationType: "citation_improvement" priority: 1 aiProviderContext: "All" isDismissed: false isArchived: false isOnBacklog: true "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/recommendations/all: get: operationId: getAllRecommendations summary: Get all recommendations (full history) description: | Returns the client's FULL recommendation history, newest first, including archived and dismissed entries (unlike /recommendations, which returns only the active, latest set). Each item carries the analysis sessionId and createdAt so you can see which run produced it. Internal audit fields are not exposed. Use the optional `type` query parameter to return only certain recommendation types (single value or comma-separated list). Valid types: `content_gap`, `citation_improvement`, `competitor_insight`, `sentiment_improvement`, `ranking_opportunity`. tags: [Recommendations] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/recommendationTypeFilter" responses: "200": description: Full recommendation history content: application/json: schema: $ref: "#/components/schemas/RecommendationHistory" example: total: 2 recommendations: - id: 168 sessionId: "a1b2c3" createdAt: "2026-06-16" recommendationAction: "Create dedicated product education content targeting non-branded queries" recommendationText: "ChatGPT shows only 2 non-branded citations vs 620 competitor citations." recommendationType: "citation_improvement" priority: 1 aiProviderContext: "All" isDismissed: false isArchived: false isOnBacklog: true - id: 120 sessionId: "z9y8x7" createdAt: "2026-06-02" recommendationAction: "Improve structured data on key product pages" recommendationText: "Competitor pages expose richer schema markup." recommendationType: "content_gap" priority: 2 aiProviderContext: "ChatGPT,Perplexity" isDismissed: false isArchived: true isOnBacklog: false "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/recommendations/overview: get: operationId: getRecommendationsOverview summary: Get the recommendations overview (all datasets in one call) description: | Convenience endpoint that returns all four recommendation datasets in a single response: the latest (active) recommendations, the full recommendation history, and the Visibility Backlog. Equivalent to calling /recommendations, /recommendations/all, and /backlog together. Use this when you want everything in one round-trip; the four standalone endpoints remain available when you only need one dataset. tags: [Recommendations] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: All recommendation datasets for the client content: application/json: schema: $ref: "#/components/schemas/RecommendationsOverview" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/impact-actions: get: operationId: getImpactActions summary: Get Impact Actions description: | Returns the client's recommendations GROUPED by the work they describe and ranked by how many SEPARATE analyses reached the same conclusion. Use this instead of /recommendations when the question is what to prioritise or what keeps coming back: /recommendations returns one analysis's list, this returns the durable picture across every analysis. Each action carries `sessionCount`, the ranking key, with `firstSeen` and `lastSeen` dates and a `lane` of recurring, new or unconfirmed. Read `sessionCount` against `analysisCount`: raised by 3 analyses is strong out of 4 and weak out of 30. Treat it as evidence only when `rankingEnabled` is true; below `minToRank` analyses there is not enough history for recurrence to mean anything. Whether an action has been added to the Visibility Backlog is NOT reported here; /backlog is where a caller asks what has been picked up. tags: [Recommendations] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: The client's impact actions content: application/json: schema: $ref: "#/components/schemas/ImpactActionsResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/backlog: get: operationId: getBacklog summary: Get the Visibility Backlog description: | Returns every item in the client's Visibility Backlog, newest first. Backlog items come from recommendations, weekly tips, or manual entries (see source). Recommendation-sourced items are annotated with the originating recommendation's AI provider context. Completed tasks carry completedOn (date) and completedBy (the name of the user who marked the task complete). Internal collaborator notes are not exposed. tags: [Recommendations] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: The client's backlog items content: application/json: schema: $ref: "#/components/schemas/Backlog" example: total: 2 backlog: - id: 51 source: "recommendation" sourceId: 168 sessionId: "a1b2c3" title: "Create dedicated product education content targeting non-branded queries" description: "ChatGPT shows only 2 non-branded citations vs 620 competitor citations." category: "citation_improvement" aiProviderContext: "All" isCompleted: false createdAt: "2026-06-17" - id: 49 source: "manual" title: "Draft Q3 content calendar" description: "Owner: content team." category: "Planning" isCompleted: true completedOn: "2026-06-24" completedBy: "Jane Smith" createdAt: "2026-06-10" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Sources ───────────────────────────────────────────────────────── /clients/{clientID}/influential-sources: get: operationId: getInfluentialSources summary: Get influential sources description: Returns the list of influential sources configured for a client. tags: [Sources] parameters: - $ref: "#/components/parameters/clientID" - name: client in: query required: true description: Client ID schema: type: string responses: "200": description: List of influential sources content: application/json: schema: type: array items: $ref: "#/components/schemas/InfluentialSource" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/influential-sources/breakdown: get: operationId: getInfluentialSourcesBreakdown summary: Get influential sources breakdown (snapshot) description: | Ranks the client's ACTIVE influential sources (the user-curated list from Manage Influential Sources) by how many AI responses cited each across a date range. Each row reports the count of responses citing the source (exact domain or any subdomain) plus that count as a percentage of all matching responses. Sources with zero hits are still included so the response is stable across runs. Useful for "where is the AI getting its information about my industry" reports. tags: [Sources] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query description: Lower bound on analysis_date (YYYY-MM-DD, inclusive). Defaults to trailing 30 days. schema: type: string format: date - name: to in: query description: Upper bound on analysis_date (YYYY-MM-DD, inclusive). Defaults to today (UTC). schema: type: string format: date - name: provider in: query description: Filter to a single provider. Omit to roll up across all six. schema: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, GoogleAIMode, GoogleAIOverview, Grok] responses: "200": description: Source breakdown content: application/json: schema: type: object properties: client_id: type: string from: type: string format: date to: type: string format: date provider: type: string total_responses: type: integer sources: type: array description: One row per active influential source, ranked by responses_citing descending. items: $ref: "#/components/schemas/InfluentialSourceCount" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /clients/{clientID}/influential-sources/trends: get: operationId: getInfluentialSourcesTrends summary: Get influential sources trends (time series) description: | Same data as `/influential-sources/breakdown` but aggregated by date so consumers can plot "Reddit citations over time" without doing the rollup themselves. One bucket per period of the chosen granularity; every bucket lists every active source (zero-filled), so series lengths never vary between buckets. tags: [Sources] parameters: - $ref: "#/components/parameters/clientID" - name: from in: query description: Lower bound on analysis_date (YYYY-MM-DD, inclusive). Defaults to trailing 30 days. schema: type: string format: date - name: to in: query description: Upper bound on analysis_date (YYYY-MM-DD, inclusive). Defaults to today (UTC). schema: type: string format: date - name: provider in: query description: Filter to a single provider. Omit to roll up across all six. schema: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, GoogleAIMode, GoogleAIOverview, Grok] - name: granularity in: query description: Bucket width. schema: type: string enum: [day, week, month] default: day responses: "200": description: Source trend series content: application/json: schema: type: object properties: client_id: type: string from: type: string format: date to: type: string format: date provider: type: string granularity: type: string series: type: array items: type: object properties: period_start: type: string format: date total_responses: type: integer sources: type: array description: Every active influential source, zero-filled, ranked by responses_citing descending within the bucket. items: $ref: "#/components/schemas/InfluentialSourceCount" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ─── Feeds ─────────────────────────────────────────────────────────── /clients/{clientID}/feeds/refresh: post: operationId: refreshClientFeeds summary: Refresh all client feeds description: | Regenerates the feed JSON for every eligible feed belonging to the client. "Eligible" matches the dispatcher's daily refresh contract: active feeds with at least one URL and a known platform. Optional query parameters: - `includeUrls=true` first re-discovers each feed's URL list (sitemap/crawl/upload) using the parameters stored on the set, then regenerates the feed JSON. End-to-end "make every feed fresh" in one call. - `format=json|csv|tsv` returns the merged feed content in the response body. Without a `format`, the response is a JSON status summary. tags: [Feeds] parameters: - $ref: "#/components/parameters/clientID" - name: includeUrls in: query description: When true, re-discover each feed's URL list before regenerating. schema: type: boolean default: false - name: format in: query description: Return the merged feed content in the response body instead of a summary. schema: type: string enum: [json, csv, tsv] responses: "200": description: Feed refresh completed content: application/json: schema: $ref: "#/components/schemas/FeedRefreshSummary" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/FeedGenerationDisabled" /clients/{clientID}/feeds/{feedId}/refresh: post: operationId: refreshClientFeed summary: Refresh a single feed description: | Regenerates the feed JSON for a single feed belonging to the client, leaving every other feed untouched. The `feedId` path parameter accepts either the customer-facing feed GUID (shown on the feed card as "fid") or the internal integer set ID. Optional `includeUrls=true` first re-discovers the URL list for this feed using its configured discovery method, then regenerates the feed JSON. tags: [Feeds] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/feedId" - name: includeUrls in: query description: When true, re-discover the URL list before regenerating. schema: type: boolean default: false responses: "200": description: Feed regeneration completed content: application/json: schema: $ref: "#/components/schemas/FeedRegenResult" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "502": description: Feed generation failed content: application/json: schema: $ref: "#/components/schemas/FeedRegenResult" "503": $ref: "#/components/responses/FeedGenerationDisabled" /clients/{clientID}/feeds/refresh-urls: post: operationId: refreshClientFeedUrls summary: Refresh URL lists for all client feeds description: | Re-discovers URLs for every eligible URL set belonging to the client. Replaces each set's URL list using the discovery method recorded at creation (sitemap, crawl, or upload). This refreshes the URL LIST only, not the feed JSON. To regenerate the feed afterwards, call POST `/clients/{clientID}/feeds/refresh`. tags: [Feeds] parameters: - $ref: "#/components/parameters/clientID" responses: "200": description: URL list refresh completed content: application/json: schema: $ref: "#/components/schemas/URLRefreshSummary" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/FeedGenerationDisabled" /clients/{clientID}/feeds/{feedId}/refresh-urls: post: operationId: refreshClientFeedUrlsForSet summary: Refresh URL list for a single feed description: | Re-discovers URLs for a single feed belonging to the client. Per-feed form for callers that want fine-grained control. Refreshes the URL LIST only; the feed JSON is not regenerated. Call POST `/clients/{clientID}/feeds/{feedId}/refresh` afterwards to regenerate the feed. tags: [Feeds] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/feedId" responses: "200": description: URL list refresh completed content: application/json: schema: $ref: "#/components/schemas/URLRefreshResult" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "503": $ref: "#/components/responses/FeedGenerationDisabled" /clients/{clientID}/feeds/{feedId}/download: get: operationId: downloadClientFeed summary: Download a feed as a JSON file description: | Serves the stored feed JSON for a single feed as a downloadable file. Does NOT regenerate; returns whatever the last successful generation left in the feed. Call POST `/clients/{clientID}/feeds/{feedId}/refresh` first if a fresh feed is required. Browser callers see a "save file" prompt thanks to `Content-Disposition: attachment`. CLI callers (curl, scripts) get a plain JSON body that can be piped to a file. Response headers include `X-Feed-Id`, `X-Feed-Guid`, and `X-Feed-Product-Count` so automation pipelines can record the feed metadata alongside the saved file. tags: [Feeds] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/feedId" responses: "200": description: Feed JSON file headers: Content-Disposition: description: Attachment filename (slugified label, falling back to feed GUID). schema: type: string X-Feed-Id: description: Internal integer feed set ID. schema: type: string X-Feed-Guid: description: Customer-facing feed GUID. schema: type: string X-Feed-Product-Count: description: Number of products in the stored feed. schema: type: string content: application/json: schema: type: object description: Generic ACP feed envelope with a products array. "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" # ─── Prompt Check ─────────────────────────────────────────────────── /clients/{clientID}/tekka/ask: post: operationId: askTekka summary: Ask Tekka a question about your space description: | Asks Tekka, the Martekio AI assistant, a question in plain language and returns her written answer. Tekka reads your space's own analytics to answer: Share of Voice, competitors, sentiment, citations, prompt movements, AI bot traffic, recommendations and more. The endpoint is read-only and never changes your data. This is the same assistant, with the same knowledge and the same answer quality, that you get inside the Martekio app. The API exists so you can put her answers where your team already works: a reporting job, an internal dashboard, a chat bot. ### Answers are generated, not looked up Tekka composes each answer from live data using a language model, so two runs of the same question can word things differently. Do not string-match on the response text. If you need stable, machine-readable values, use the specific data endpoints instead. ### Response time Answers typically take between 4 and 21 seconds, because Tekka reads your data and then writes the answer. Set your client timeout to at least 150 seconds. ### Limits Each question consumes one of your space's daily allowance, which resets at 00:00 UTC. The allowance is claimed when the question is accepted, not when the answer comes back, so a question that times out or fails still counts. It is recorded either way and is visible to Martekio support, which is what lets us tell you what happened. The allowance is reported on the `X-Quota-*` response headers and in the `usage` object of every successful answer, so you can pace a batch without a second call. Note these are separate from the `X-RateLimit-*` headers, which carry the API-wide throughput limit. Questions asked inside the Martekio app do not count against this allowance. ### One question at a time A space may have only one question in flight. Asking again while one is still being answered returns `409`; wait for the first to finish. This keeps a single slow question from crowding out the rest of your account. tags: [Tekka] parameters: - $ref: "#/components/parameters/clientID" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TekkaAskRequest" responses: "200": description: Tekka answered the question. headers: X-Quota-Limit: description: Your space's daily question allowance. schema: { type: integer } X-Quota-Remaining: description: Questions left in the current UTC day. schema: { type: integer } X-Quota-Reset: description: Unix timestamp at which the allowance resets (next 00:00 UTC). schema: { type: integer } content: application/json: schema: $ref: "#/components/schemas/TekkaAskResponse" "400": description: The question was missing, empty, or longer than 1000 characters. content: application/json: schema: $ref: "#/components/schemas/TekkaError" "401": description: Missing or invalid API token. "403": description: | API access is not available for this space. Either the plan does not include API access, the contract has expired, your account administrator has switched API access off, or the Tekka API guard is off for this space. content: application/json: schema: $ref: "#/components/schemas/TekkaError" "409": description: | A question is already being answered for this space. Only one may be in flight at a time. Retry once the first has returned. headers: Retry-After: description: Suggested seconds to wait before retrying. schema: { type: integer } content: application/json: schema: $ref: "#/components/schemas/TekkaError" "429": description: | The daily question limit for this space has been reached. The `Retry-After` header gives the seconds until the allowance resets. headers: Retry-After: description: Seconds until the daily allowance resets. schema: { type: integer } content: application/json: schema: $ref: "#/components/schemas/TekkaError" "500": description: | Tekka failed internally while answering. The question is recorded and counts against your allowance. Quote `requestId` if you contact support. content: application/json: schema: $ref: "#/components/schemas/TekkaError" "503": description: | The request was refused rather than answered, either because your question allowance could not be determined or because the Tekka API is temporarily unavailable. Both are faults on our side, not limits: retry shortly. The `code` field distinguishes them (`quota_unavailable` or `tekka_api_unavailable`). content: application/json: schema: $ref: "#/components/schemas/TekkaError" "504": description: | Tekka did not finish answering within the server deadline. The question IS recorded and DOES count against your daily allowance, because the work was performed. Try a narrower question. content: application/json: schema: $ref: "#/components/schemas/TekkaError" /clients/{clientID}/prompt-check: post: operationId: promptCheck summary: Run a Prompt Check against AI assistants description: | Runs a single prompt through the selected AI assistants in real time and returns the visibility insights (Share of Voice, brand mentions and citations, competitor citations, sentiment) for your brand, computed against your space's brand and competitor configuration. The run is ephemeral: nothing is persisted to your dashboards or analytics. Designed for occasional, on-demand use (for example, checking a prompt from your CMS during content creation). To test many prompts at scale, set up a scheduled analysis instead. ### Assistants Specify assistants with the stable slugs below. If omitted, defaults to `chatgpt`. Google AI Overview is not available via the API (it renders only intermittently and runs on a slow asynchronous queue); it is planned for a future asynchronous endpoint. | Slug | Assistant | |------|-----------| | `chatgpt` | ChatGPT | | `claude` | Claude (Anthropic) | | `gemini` | Gemini | | `perplexity` | Perplexity | | `google_ai_mode` | Google AI Mode | | `grok` | Grok | ### Idempotency An `Idempotency-Key` header is required. Repeating a request with the same key and body returns the original result without re-running or consuming quota. Reusing a key with a different body returns `422`. ### Limits Each accepted request consumes one of your space's daily allowance (regardless of how many assistants it targets) and is reported on the `X-Quota-*` response headers. Only one test may run per space at a time; a concurrent request returns `409`. The number of assistants per request is capped per space. tags: [Prompt Check] parameters: - $ref: "#/components/parameters/clientID" - $ref: "#/components/parameters/idempotencyKeyHeader" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PromptCheckRequest" responses: "200": description: The Prompt Check ran. Returns per-assistant insights and a summary. headers: X-Quota-Limit: description: Your space's daily test allowance. schema: { type: integer } X-Quota-Remaining: description: Tests remaining today. schema: { type: integer } X-Quota-Reset: description: When the daily allowance resets (RFC 3339). schema: { type: string, format: date-time } content: application/json: schema: $ref: "#/components/schemas/PromptCheckResponse" "400": description: Invalid request, unknown or ineligible assistant, or too many assistants. content: application/json: schema: $ref: "#/components/schemas/PromptCheckError" "401": $ref: "#/components/responses/Unauthorized" "403": description: Your API token does not have access to this client. content: application/json: schema: $ref: "#/components/schemas/Error" "409": description: Another test is already running for this space (single-flight), or a request with this Idempotency-Key is in progress. content: application/json: schema: $ref: "#/components/schemas/PromptCheckError" "422": description: The Idempotency-Key was already used with a different request body. content: application/json: schema: $ref: "#/components/schemas/PromptCheckError" "429": description: The daily test quota has been reached (code `quota_exceeded`). The generic per-token throughput limiter may also return 429. content: application/json: schema: $ref: "#/components/schemas/PromptCheckError" "502": description: Every requested assistant failed to respond. No quota is consumed. content: application/json: schema: $ref: "#/components/schemas/PromptCheckError" "500": $ref: "#/components/responses/InternalError" # ─── Radar (metered partner API) ───────────────────────────────────── /radar/analyses: post: operationId: submitRadarAnalysis summary: Submit a Radar analysis description: | Submits a brand and a set of prompts for analysis and returns immediately with a `job_id`. The analysis runs asynchronously on Martekio's standard pipeline across the AI assistants enabled for your key: six as standard (ChatGPT, Perplexity, Gemini, Anthropic, Google AI Mode, Google AI Overview), plus Grok where your subscription includes the add-on. Poll `GET /radar/analyses/{jobId}` until it reports `completed`; that same response then carries the full results inline under `analysis`, so one poll loop is all you need. `GET /radar/analyses/{jobId}/results` returns the identical results and is retained for backward compatibility. Each request is independent (there is no idempotency key): submitting the same body twice runs the analysis twice and is charged twice. ### Requests The billable unit is the prompt: each prompt in a request is deducted once, whatever the number of assistants. The check is all-or-nothing: if the request needs more prompts than remain in the key's annual allowance, the whole request is rejected with `402` and nothing is charged or run. The charged / remaining counts and the reset time are returned in the `prompts` block. ### Concurrency A key may have a limited number of runs in flight at once (default 1). Submitting while that limit is reached returns `409 run_in_progress`; poll the in-flight run and retry when it completes. ### Fields All five fields are required. `website` (owned domain(s), drives brand citations) and `brand_identifier` (brand identifier(s), drives brand mentions) each accept a comma-separated list. `country` is an ISO 3166-1 alpha-2 code (e.g. `US`, `GB`, `FR`). `prompts` holds the prompts to run: 1 to the key's per-request maximum (up to 100, the size of a regular analysis), each at most 500 characters. One prompt is the billable unit, deducted once from your allowance whatever the number of assistants it reaches. (`citation_key` is accepted as a deprecated alias for `brand_identifier`.) tags: [Radar] security: - RadarAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RadarSubmitRequest" responses: "202": description: Accepted. The analysis was queued; poll the status endpoint. content: application/json: schema: $ref: "#/components/schemas/RadarSubmitResponse" "400": description: Invalid request (missing or invalid field, prompt too long, too many prompts, or unsupported country). Codes include `invalid_request`, `too_many_prompts`, `unsupported_country`. content: application/json: schema: $ref: "#/components/schemas/RadarError" "401": description: Missing or invalid API key, or a key not provisioned for Radar. content: application/json: schema: $ref: "#/components/schemas/RadarError" "402": description: Not enough prompts remaining for this request (code `quota_exceeded`). Nothing is charged or run. content: application/json: schema: $ref: "#/components/schemas/RadarQuotaError" "403": description: This API key is disabled (code `key_disabled`). content: application/json: schema: $ref: "#/components/schemas/RadarError" "409": description: The key already has its maximum number of runs in progress (code `run_in_progress`). Poll the in-flight run and retry when it completes. content: application/json: schema: $ref: "#/components/schemas/RadarError" "429": description: Rate limit exceeded (code `rate_limit_exceeded`). Maximum 60 requests per minute. content: application/json: schema: $ref: "#/components/schemas/RadarError" "503": description: The Radar API is temporarily disabled for maintenance (code `service_unavailable`). content: application/json: schema: $ref: "#/components/schemas/RadarError" "500": description: Internal error (code `internal_error`). content: application/json: schema: $ref: "#/components/schemas/RadarError" /radar/analyses/{jobId}: get: operationId: getRadarAnalysisStatus summary: Get Radar analysis status description: | Returns the current status of a submitted Radar analysis. Poll this until `status` is `completed` or `failed`. On `completed`, the response carries the full results inline under `analysis` (same shape as the results endpoint), so a single poll loop is enough and no separate results request is required. Status values: `submitted`, `running`, `completed`, `failed`. tags: [Radar] security: - RadarAuth: [] parameters: - $ref: "#/components/parameters/radarJobId" responses: "200": description: Current status. content: application/json: schema: $ref: "#/components/schemas/RadarStatusResponse" "401": description: Missing or invalid API key. content: application/json: schema: $ref: "#/components/schemas/RadarError" "403": description: This API key is disabled (code `key_disabled`). content: application/json: schema: $ref: "#/components/schemas/RadarError" "404": description: No analysis found for this job id (code `not_found`). content: application/json: schema: $ref: "#/components/schemas/RadarError" /radar/analyses/{jobId}/results: get: operationId: getRadarAnalysisResults summary: Get Radar analysis results description: | Returns the completed analysis for a job: per-assistant metrics plus brand and competitor citations. Returns `409` while the run is still in progress; poll the status endpoint until it reports `completed`. The status endpoint now also returns these same results inline under `analysis` on completion, so this endpoint is retained mainly for backward compatibility. tags: [Radar] security: - RadarAuth: [] parameters: - $ref: "#/components/parameters/radarJobId" responses: "200": description: The completed analysis results. content: application/json: schema: $ref: "#/components/schemas/RadarResultsResponse" "409": description: The analysis is not finished yet (code `not_ready`). Keep polling the status endpoint. content: application/json: schema: $ref: "#/components/schemas/RadarError" "401": description: Missing or invalid API key. content: application/json: schema: $ref: "#/components/schemas/RadarError" "403": description: This API key is disabled (code `key_disabled`). content: application/json: schema: $ref: "#/components/schemas/RadarError" "404": description: No analysis found for this job id (code `not_found`). content: application/json: schema: $ref: "#/components/schemas/RadarError" /radar/usage: get: operationId: getRadarUsage summary: Get Radar prompt usage and request history description: | Returns the partner's request balance for the current annual period (how many requests have been used and how many remain against the annual allowance) plus the recent Radar request history. Each request in the history carries the exact request and response JSON payloads, so a partner can audit everything it sent and received. Use `?limit` to bound the history length (default 50, maximum 500); requests are returned newest first. tags: [Radar] security: - RadarAuth: [] parameters: - name: limit in: query required: false description: Maximum number of requests to return, newest first. Default 50, maximum 500. schema: type: integer minimum: 1 maximum: 500 default: 50 responses: "200": description: Current request balance and request history. content: application/json: schema: $ref: "#/components/schemas/RadarUsageResponse" "401": description: Missing or invalid API key, or a key not provisioned for Radar. content: application/json: schema: $ref: "#/components/schemas/RadarError" "403": description: This API key is disabled (code `key_disabled`). content: application/json: schema: $ref: "#/components/schemas/RadarError" components: securitySchemes: BearerAuth: type: http scheme: bearer description: API token in `mkto_selector.validator` format RadarAuth: type: http scheme: bearer description: > Per-partner Martekio Radar API key in `mkto_selector.validator` format, issued by Martekio (not generated from the dashboard). The key identifies the paying partner and carries its own annual prompt allowance. parameters: radarJobId: name: jobId in: path required: true description: The `job_id` returned when the analysis was submitted. schema: type: string format: uuid clientID: name: clientID in: path required: true description: Unique identifier of the client (organisation) schema: type: string recommendationTypeFilter: name: type in: query required: false description: | Optional filter by recommendation type. Provide a single value or a comma-separated list to return only recommendations of those types (for example, `type=content_gap` or `type=content_gap,citation_improvement`). Matching is case-insensitive. Omit to return all types. An unknown value returns 400. schema: type: string example: content_gap,citation_improvement feedId: name: feedId in: path required: true description: | Feed identifier. Accepts the customer-facing feed GUID (displayed on the feed card as "fid") or the internal integer set ID. schema: type: string aiProviderQuery: name: provider in: query required: true description: AI provider name schema: type: string enum: [ChatGPT, Perplexity, Gemini, Anthropic, Grok] startDate: name: startDate in: query required: true description: Start of date range (YYYY-MM-DD) schema: type: string format: date endDate: name: endDate in: query required: true description: End of date range (YYYY-MM-DD) schema: type: string format: date idempotencyKeyHeader: name: Idempotency-Key in: header required: true description: | A unique, client-supplied key for the request. Repeating the same key with the same body replays the original result without consuming quota; reusing it with a different body returns 422. schema: type: string maxLength: 255 responses: Unauthorized: description: Missing or invalid API token content: application/json: schema: $ref: "#/components/schemas/Error" BadRequest: description: Invalid request parameters content: application/json: schema: $ref: "#/components/schemas/Error" NotFound: description: Resource not found content: application/json: schema: $ref: "#/components/schemas/Error" InternalError: description: Internal server error content: application/json: schema: $ref: "#/components/schemas/Error" FeedGenerationDisabled: description: | Product feed generation is switched off platform-wide. Temporary and nothing to do with your account or token: the four refresh endpoints stop, while every feed read endpoint keeps serving the contents last published. Distinct from the 403 returned when product feed generation is not enabled for the account. content: application/json: schema: $ref: "#/components/schemas/Error" schemas: Error: type: object properties: error: type: string description: Human-readable error message InfluentialSourceCount: type: object description: One of the client's active influential sources with the count of AI responses citing it. properties: source: type: string description: The source's label as configured in Manage Influential Sources. example: Reddit domain: type: string description: The source's domain; citations match on the exact domain or any subdomain. example: reddit.com responses_citing: type: integer description: Distinct AI responses in the window whose citations include this source. pct: type: number format: float description: responses_citing as a percentage of total_responses, two decimal places. PromptCheckError: type: object description: Structured error returned by the Prompt Check endpoint. properties: error: type: object properties: code: type: string description: Machine-readable error code (e.g. invalid_request, invalid_assistant, too_many_assistants, request_in_progress, idempotency_key_reuse, quota_exceeded, all_assistants_failed). message: type: string description: Human-readable explanation. details: type: object description: Optional structured context (e.g. the allowed assistant slugs, or the assistant cap). additionalProperties: true TekkaError: type: object description: | Structured error returned by the Tekka endpoint. Note that failures raised by the shared API middleware (an invalid token, for example) return a flat `{"error": "..."}` string instead; check the type before reading `code`. properties: error: type: object properties: code: type: string description: | Machine-readable error code. One of: `invalid_request` (missing, empty or over-length question, or a bad clientID), `tekka_api_disabled` (the Tekka API is switched off for this space), `tekka_api_unavailable` (switched off platform-wide), `quota_unavailable` (your allowance could not be read), `question_in_progress` (another question is still being answered for this space), `daily_limit_reached`, `timeout`, `internal_error`. message: type: string description: Human-readable explanation. details: type: object description: | Optional structured context. Carries `requestId` on every failure raised by the handler, plus `limit`, `used` and `resetsAt` on a `daily_limit_reached`, and `logId` plus `responseTimeMs` on a `timeout`. additionalProperties: true TekkaAskRequest: type: object required: [question] properties: question: type: string maxLength: 1000 description: | The question, in plain language. Ask it the way you would ask a colleague. Conversation history is not supported: each request is answered on its own, so include any context the question needs. example: "How did my Share of Voice change over the last 3 months, and who gained on me?" TekkaAskResponse: type: object properties: answer: type: string description: | Tekka's written answer. May contain light Markdown and usually ends with a short "Source" line naming the data behind it. Generated text: do not string-match on it. toolsUsed: type: array items: { type: string } description: | The Martekio data tools Tekka called to answer, in call order. Empty when she answered from documentation or from a generated query rather than a data tool. example: ["get_sov_summary", "get_top_competitors"] requestId: type: string description: Unique id for this request. Quote it when contacting support about an answer. logId: type: integer description: Identifier of the stored log row for this question, when it was recorded. responseTimeMs: type: integer description: | How long the answer took to produce, in milliseconds, measured server-side from the moment the request was accepted to the moment the answer was ready. example: 8432 usage: type: object description: Your space's allowance after this question was counted. properties: limit: type: integer description: Questions permitted per UTC day for this space. example: 25 remaining: type: integer description: Questions left in the current UTC day. example: 18 resetsAt: type: string format: date-time description: When the allowance resets (next 00:00 UTC). example: "2026-09-05T00:00:00Z" PromptCheckRequest: type: object required: [prompt] properties: prompt: type: string maxLength: 500 description: The prompt to test. example: where can i buy gilded wardrobe silk products assistants: type: array description: Assistant slugs to test. Defaults to ["chatgpt"] when omitted or empty. items: type: string enum: [chatgpt, claude, gemini, perplexity, google_ai_mode, grok] example: [chatgpt, claude] include: type: array description: Optional heavy fields to attach to each assistant. Insights only by default. items: type: string enum: [citations, responses] example: [citations, responses] PromptCheckResponse: type: object properties: request_id: type: string example: tp_45bb9329-68a7-496c-be17-7c4d7135e730 prompt: type: string is_branded: type: boolean description: Whether the prompt itself contains your brand. requested_at: type: string format: date-time completed_at: type: string format: date-time summary: type: object properties: assistants_tested: type: integer brand_mentioned_count: type: integer brand_cited_count: type: integer total_brand_mentions: type: integer total_brand_citations: type: integer total_competitive_citations: type: integer assistants: type: array items: $ref: "#/components/schemas/PromptCheckAssistant" PromptCheckAssistant: type: object properties: assistant: type: string description: Assistant slug. share_of_voice: type: number format: float brand_mentioned: type: boolean brand_cited: type: boolean brand_mentions: type: integer brand_citations: type: integer competitive_citations: type: integer competitors: type: array description: Competitors cited in this assistant's response, with per-competitor counts. Sums to competitive_citations. items: $ref: "#/components/schemas/PromptCheckCompetitor" sentiment: type: string nullable: true description: Sentiment toward the brand, or null when not computed (brand not present in the response). sentiment_explanation: type: string description: The reasoning behind the sentiment. Present only when include contains "responses". response_text: type: string description: The full AI response. Present only when include contains "responses". citations: type: array description: All classified citation URLs in this assistant's response. Present only when include contains "citations". items: $ref: "#/components/schemas/PromptCheckCitation" error: type: string nullable: true description: Set when this assistant failed; other assistants still return (partial success is still 200). PromptCheckCompetitor: type: object properties: name: type: string citations: type: integer PromptCheckCitation: type: object properties: url: type: string competitor_name: type: string position: type: integer is_client_owned: type: boolean is_competitor: type: boolean is_influential_source: type: boolean # ─── Industry ──────────────────────────────────────────────────── IndustryInsight: type: object properties: industryCode: type: integer industryName: type: string aiProviders: type: object description: Industry records grouped by AI provider name additionalProperties: type: array items: $ref: "#/components/schemas/IndustryRecord" IndustryRecord: type: object properties: industry_code: type: integer ai_provider: type: string domain: type: string citation_count: type: integer IndustryTopDomains: type: object properties: industryCode: type: integer aiProviders: type: object description: Top domains grouped by AI provider name additionalProperties: type: array items: $ref: "#/components/schemas/IndustryDomain" IndustryDomain: type: object properties: company_industry_code: type: integer ai_provider: type: string domain: type: string citation_count: type: integer # ─── Share of Voice ────────────────────────────────────────────── SoVMetrics: type: object properties: overallSOV: type: number format: double description: Overall Share of Voice percentage brandedCitationSOV: type: number format: double description: Branded citation Share of Voice nonBrandedCitationSOV: type: number format: double description: Non-branded citation Share of Voice analysisDate: type: string format: date description: Date of the analysis SoVHistoricResponse: type: object description: Historical SoV data keyed by provider name properties: Anthropic: type: array items: $ref: "#/components/schemas/SoVHistoricEntry" ChatGPT: type: array items: $ref: "#/components/schemas/SoVHistoricEntry" Gemini: type: array items: $ref: "#/components/schemas/SoVHistoricEntry" Perplexity: type: array items: $ref: "#/components/schemas/SoVHistoricEntry" Grok: type: array items: $ref: "#/components/schemas/SoVHistoricEntry" SoVHistoricEntry: type: object properties: refreshDate: type: string format: date overallSOV: type: number format: double totalBrandedSOV: type: number format: double totalNonBrandedSOV: type: number format: double # ─── Performance ───────────────────────────────────────────────── PerformanceResponse: type: object properties: performance: type: object description: Performance records grouped by provider name additionalProperties: type: array items: $ref: "#/components/schemas/ClientPerformance" SessionID: type: string description: Session ID for the analysis date (present when analysis_date is specified) comparison_performance: type: object description: Comparison performance records (present when comparison_date is specified) additionalProperties: type: array items: $ref: "#/components/schemas/ClientPerformance" ComparisonSessionID: type: string description: Session ID for the comparison date ClientPerformance: type: object properties: ai_provider: type: string analysis_date: type: string format: date total_queries: type: integer total_citations: type: integer average_position: type: number format: double visibility_score: type: number format: double PerformanceAllTimeResponse: type: object properties: performance: type: object description: | All-time averages bucketed by provider. Each provider key holds an array (length 0 or 1) of records. The array shape exists because the frontend chart consumer expects it; in practice there is at most one row per (client, provider) pair. properties: Anthropic: type: array items: $ref: "#/components/schemas/ClientPerformanceAllTime" ChatGPT: type: array items: $ref: "#/components/schemas/ClientPerformanceAllTime" Gemini: type: array items: $ref: "#/components/schemas/ClientPerformanceAllTime" Perplexity: type: array items: $ref: "#/components/schemas/ClientPerformanceAllTime" Grok: type: array items: $ref: "#/components/schemas/ClientPerformanceAllTime" ClientPerformanceAllTime: type: object description: | All-time aggregated metrics for one (client, AI provider) pair. Field names are PascalCase because the handler serialises the Go `ClientAveragesAllTime` model directly. properties: ID: type: integer description: Internal GORM primary key. CreatedAt: type: string format: date-time UpdatedAt: type: string format: date-time DeletedAt: type: string format: date-time nullable: true ClientID: type: integer AIProvider: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] AvgNonBrandCitations: type: number format: double description: Average brand citations per non-branded response. AvgBrandCitations: type: number format: double description: Average brand citations per branded response. AvgNonBrandReferences: type: number format: double description: Average brand references per non-branded response. AvgBrandReferences: type: number format: double description: Average brand references per branded response. PercentNonBrandedRepliesWithCitations: type: number format: double description: Percentage of non-branded responses containing a brand citation. PercentBrandedRepliesWithCitations: type: number format: double description: Percentage of branded responses containing a brand citation. PercentNonBrandedRepliesWithReferences: type: number format: double description: Percentage of non-branded responses containing a brand reference. PercentBrandedRepliesWithReferences: type: number format: double description: Percentage of branded responses containing a brand reference. AvgNoCitationsInResponse: type: number format: double description: Average total number of citations (any brand) per response. IsArchived: type: boolean description: True when the record has been archived under the data retention policy. # ─── Competitors ───────────────────────────────────────────────── Competitor: type: object properties: aiProvider: type: string competitorName: type: string totalCitationCount: type: integer CuratedCompetitorsResponse: type: object required: [tracked, threats] properties: tracked: allOf: - $ref: "#/components/schemas/CuratedCompetitorSet" description: Brands chosen for ongoing monitoring in Competition Explorer. threats: allOf: - $ref: "#/components/schemas/CuratedCompetitorSet" description: Curated real rivals, mutually exclusive with the Tracked set. CuratedCompetitorSet: type: object required: [total, competitors] properties: total: type: integer description: Number of curated competitors in this set. competitors: type: array description: Alphabetically ordered curated competitors. items: $ref: "#/components/schemas/CuratedCompetitor" CuratedCompetitor: type: object required: [competitor_name, added_at] properties: competitor_name: type: string description: Normalised competitor or brand name, commonly a domain. example: "example-rival.com" added_at: type: string format: date-time description: When the competitor was added to this curated set. CompetitorTrendsResponse: type: object properties: aiProvider: type: string results: type: array items: $ref: "#/components/schemas/CompetitorTrend" CompetitorTrend: type: object properties: ID: type: string CompetitorName: type: string BrandedCumulativeURLsCited: type: integer NonBrandedCumulativeURLsCited: type: integer CompetitorTrendsGroupedResponse: type: object description: Winners and losers grouped by provider name properties: Anthropic: $ref: "#/components/schemas/WinnersLosers" ChatGPT: $ref: "#/components/schemas/WinnersLosers" Gemini: $ref: "#/components/schemas/WinnersLosers" Perplexity: $ref: "#/components/schemas/WinnersLosers" Grok: $ref: "#/components/schemas/WinnersLosers" WinnersLosers: type: object properties: winners: type: array items: $ref: "#/components/schemas/GroupedCompetitor" losers: type: array items: $ref: "#/components/schemas/GroupedCompetitor" GroupedCompetitor: type: object properties: ID: type: string competitorName: type: string totalCumulativeURLsCited: type: integer noDaysSinceLastSeen: type: integer noTimesDetected: type: integer onWatchList: type: boolean description: True when the competitor is one of the client's Competitive Threats. onTrackedList: type: boolean description: True when the competitor is one of the client's Tracked brands. lastSeenDate: type: string providerName: type: string # ─── Analysis ──────────────────────────────────────────────────── AnalysisSummariesResponse: type: object properties: client: $ref: "#/components/schemas/ClientProfile" summary: type: array items: $ref: "#/components/schemas/AnalysisSummary" AnalysisSummary: type: object description: | High-level aggregated results for one (client, session, AI provider) tuple. Field names are PascalCase because the handler serialises the Go `AnalysisSummary` GORM model directly. properties: ID: type: integer description: Internal GORM primary key. CreatedAt: type: string format: date-time UpdatedAt: type: string format: date-time DeletedAt: type: string format: date-time nullable: true ClientID: type: integer SessionID: type: string description: Unique ID for the analysis session this summary belongs to. AIProvider: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, GoogleAIMode, GoogleAIOverview, Grok] AnalysisDate: type: string format: date CompanyIndustryCode: type: integer KeywordsProcessed: type: integer description: Total keywords evaluated in this session. PromptGenerator: type: string description: AI used to generate the search phrases (e.g. "ChatGPT"). AIProviderModel: type: string description: Specific provider model used (e.g. "claude-3-opus-20240229"). KeywordSource: type: string description: Origin of the keywords (e.g. "Import", "GSC"). NoNonBrandCitations: type: integer NoNonBrandReferences: type: integer NoBrandCitations: type: integer NoBrandReferences: type: integer TotalResponsesContainingReferences: type: integer TotalResponsesContainingCitations: type: integer TotalCitations: type: integer TotalReferences: type: integer NoNonBrandedRepliesWithCitations: type: integer NoBrandedRepliesWithCitations: type: integer NoNonBrandedRepliesWithReferences: type: integer NoBrandedRepliesWithReferences: type: integer TotalCompetitiveCitations: type: integer CitationKey: type: string description: Primary brand identifier used for matching in this session. AnalysisDuration: type: number format: double description: | Cumulative elapsed minutes from session start to when this provider's row was written. The largest value across providers in a session approximates total wall-clock time. AnalysisDurationKeyword: type: number format: double description: AnalysisDuration divided by KeywordsProcessed for this row, in minutes per keyword. SentimentScore: type: number format: double description: Average sentiment score across responses where sentiment was computed. IsArchived: type: boolean description: True when the record has been archived under the data retention policy. IsPartial: type: boolean description: | True when the row was rebuilt from analysis_details after the provider failed mid-run, so KeywordsProcessed reflects what completed rather than what was scheduled. This also covers runs that did not produce a row for every scheduled keyword, for example when a DataForSEO async task (Google AI Overview) timed out before becoming ready. Treat IsPartial=true sessions as a degraded run, not a final benchmark. RepairedAt: type: string format: date-time nullable: true description: | When a staff admin ran repair_analysis on this summary. Null means IsPartial was set by the analysis pipeline (originally broken); a value means a manual repair corrected it. AnalysisDetailsResponse: type: object properties: client: $ref: "#/components/schemas/ClientProfile" summary: type: array description: Present when pagination is not used items: $ref: "#/components/schemas/AnalysisDetail" data: type: array description: Present when pagination is used items: $ref: "#/components/schemas/AnalysisDetail" pagination: $ref: "#/components/schemas/Pagination" AnalysisDetail: type: object description: | One row per (session, provider, phrase) analysis result. Field names are PascalCase because the handler serialises the Go `AnalysisDetail` GORM model directly. Rows are wide: each one includes the AI reply text, sentiment data, and a boolean for every tracked source domain. For lightweight listings, prefer paginating and only reading the fields you need. properties: ID: type: integer CreatedAt: type: string format: date-time UpdatedAt: type: string format: date-time DeletedAt: type: string format: date-time nullable: true ClientID: type: integer CompanyIndustryCode: type: integer SessionID: type: string QueryID: type: string description: Unique ID linking this record to its citation URL entries. AIProvider: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, GoogleAIMode, GoogleAIOverview, Grok] Keyword: type: string description: Original seed keyword from the source. KeywordSource: type: string description: Origin of the keyword (e.g. "Import", "GSC"). IsBranded: type: boolean description: True if the original seed keyword was classified as Branded. IsBrandReferenced: type: boolean description: True if the client's brand was mentioned (referenced) in the AI reply. IsBrandCited: type: boolean description: True if the client's brand was linked to (cited) in the AI reply. NoBrandReferences: type: integer description: Count of unlinked text mentions of the client's brand in the AI reply. NoBrandCitations: type: integer description: Count of citation links to the client's brand in the AI reply. TotalCitationsInResponse: type: integer description: Total count of all citation links (client + competitors) in the AI reply. TotalCompetitiveCitations: type: integer Phrase: type: string description: Final search query sent to the AI provider. PhraseSOV: type: number format: double description: Citation-based Share of Voice for this specific phrase. PhraseWords: type: integer PhraseChars: type: integer Clicks: type: integer description: Clicks for the original keyword from the source, if available. AvgPosition: type: number format: double description: Average search position for the original keyword from the source, if available. CTR: type: number format: double description: Click-Through Rate for the original keyword from the source, if available. CitationKey: type: string description: Brand identifier used for matching during this analysis. ReplyWords: type: integer ReplyChars: type: integer Reply: type: string description: | The AI reply text. In listing endpoints this is truncated to the first sentence (via a Postgres regex) or the first 100 characters as a fallback. Use `/analysis/responses/{queryID}` to fetch the full untruncated reply. IsPhraseBranded: type: boolean IsArchived: type: boolean description: Listing endpoints filter out archived rows; this will always be false in responses. DomainNameEvaluated: type: string description: Domain name(s) used to identify brand citations in the response (comma-separated). IsSentimentComputed: type: boolean Sentiment: type: string nullable: true description: Sentiment classification ("Positive", "Negative", "Neutral"). SentimentScore: type: number format: double nullable: true description: Sentiment score, typically from -1 (Negative) to 1 (Positive). SentimentExplanation: type: string nullable: true PromptTokens: type: integer CompletionTokens: type: integer TotalTokens: type: integer IsCitationPathContainsBrand: type: boolean NoCitationsPathContainsBrand: type: integer IsCustomPrompt: type: boolean NoSearchQueries: type: integer description: Count of fan-out web-search queries the provider fired while generating this response. IsSourceRedditFound: type: boolean IsSourceWikipediaFound: type: boolean IsSourceQuoraFound: type: boolean IsSourceAmazonFound: type: boolean IsSourceYouTubeFound: type: boolean IsSourceTikTokFound: type: boolean IsSourceGoogleFound: type: boolean IsSourceFacebookFound: type: boolean IsSourceInstagramFound: type: boolean IsSourceWalmartFound: type: boolean IsSourceEbayFound: type: boolean IsSourceYahooFound: type: boolean IsSourcePinterestFound: type: boolean IsSourceMediumFound: type: boolean IsAIOverviewRendered: type: boolean description: | True only on Google AI Overview rows where Google actually rendered an AI Overview for the query (renders ~44-51% of the time). False on every other provider's row and on AI Overview rows where Google chose not to render. Powers AI Overview attendance trends. SystemPromptVersionID: type: integer nullable: true description: | Foreign key to system_prompt_versions identifying the AI system prompt template active when this call was made. Populated for ChatGPT (direct API), Anthropic, and Gemini (Vertex) rows. NULL for Perplexity, Google AI Mode, Google AI Overview, and any row produced by a DFS-backed route (ChatGPT-via-DFS, Gemini-via-DFS), since those paths bypass the Martekio system prompt to preserve consumer-truth. DFSTaskID: type: string description: | DataForSEO task id (tasks[0].id) for rows produced by a DFS-backed provider path: ChatGPT-via-DFS, Gemini-via-DFS, Google AI Mode, Google AI Overview. Empty string for direct-API providers and for legacy rows written before the column was added. Quote this id to DataForSEO support when troubleshooting an anomalous response, or use it to re-fetch the original result from the DFS task history endpoint without re-running the analysis. Pagination: type: object properties: page: type: integer limit: type: integer totalRecords: type: integer format: int64 totalPages: type: integer AIResponseResponse: type: object properties: reply: type: string description: The full AI-generated response text ClientProfile: type: object description: | Full client (organisation) profile, serialised from the Go `ClientProfile` GORM model directly. Field names are PascalCase. The object carries roughly 50 fields covering identity, tier and entitlement flags, billing state, agency relationships, and platform detection (Shopify / Magento / etc.). The schema below lists the fields most callers actually use; treat any extra fields the response contains as opaque operational metadata that may be added without notice. additionalProperties: true properties: ID: type: integer description: Internal GORM primary key. ClientID: type: integer description: Public client identifier used everywhere in the API path. ClientGUID: type: string description: Unique GUID for public-facing client identification (e.g. feed URLs). CompanyName: type: string CompanyCountry: type: string CompanyWWW: type: string description: Primary website URL. CompanyWWWNormalised: type: string description: Normalised primary website (e.g. "martekio.com" from "www.martekio.com"). CompanyIndustryCode: type: integer description: Foreign key to IndustrySectors. ExpirationDate: type: string format: date TierID: type: string TierName: type: string IsTrialMode: type: boolean IsExpired: type: boolean IsAgency: type: boolean IsAgencyClient: type: boolean ParentClientID: type: integer description: For agency-managed clients, the parent agency's ClientID. Zero otherwise. APIIncluded: type: boolean description: True when API access is enabled for this client. # ─── Citations ─────────────────────────────────────────────────── CitationTrendsResponse: type: object properties: trends: type: array items: $ref: "#/components/schemas/CitationTrend" providerInsights: type: array items: $ref: "#/components/schemas/ProviderInsight" topURLs: type: array items: $ref: "#/components/schemas/TopURL" CitationTrend: type: object properties: identifier: type: string sources: type: array items: $ref: "#/components/schemas/CitationSource" CitationSource: type: object properties: label: type: string url: type: string count: type: integer format: int64 subreddits: type: array items: type: string ProviderInsight: type: object properties: provider: type: string totalCount: type: integer format: int64 sources: type: array items: $ref: "#/components/schemas/CitationSource" trend: type: array items: $ref: "#/components/schemas/TrendPoint" TrendPoint: type: object properties: x: type: string format: date-time description: ISO 8601 timestamp "y": type: integer format: int64 TopURL: type: object properties: url: type: string count: type: integer format: int64 CitationsByQueryResponse: type: object properties: allCitationURLs: type: array items: $ref: "#/components/schemas/CitationURL" CitationURL: type: object properties: full_url: type: string competitor_name: type: string path_contains_brand: type: boolean is_phrase_branded: type: boolean analysis_date: type: string format: date-time is_url_client_owned: type: boolean is_url_influential_source: type: boolean is_url_competition: type: boolean position_rank: type: integer has_tech_insights: type: boolean description: True when url_technical_details exists for this (query_id, full_url) pair. Indicates the AI Response Explorer can drill into per-citation crawl data. SessionCompetitorGroup: type: object properties: aiProvider: type: string competitors: type: array items: $ref: "#/components/schemas/SessionCompetitor" SessionCompetitor: type: object properties: competitorName: type: string citationCount: type: integer # ─── Sentiment ─────────────────────────────────────────────────── SentimentAveragesResponse: type: object properties: overall_average_score: type: number format: double total_responses_overall: type: integer averages_by_provider: type: array items: $ref: "#/components/schemas/ProviderSentimentAverage" ProviderSentimentAverage: type: object properties: ai_provider: type: string average_score: type: number format: double total_responses: type: integer SentimentScoresResponse: type: object description: Daily sentiment scores keyed by provider name properties: Anthropic: type: array items: $ref: "#/components/schemas/SentimentScore" ChatGPT: type: array items: $ref: "#/components/schemas/SentimentScore" Gemini: type: array items: $ref: "#/components/schemas/SentimentScore" Perplexity: type: array items: $ref: "#/components/schemas/SentimentScore" Grok: type: array items: $ref: "#/components/schemas/SentimentScore" SentimentScore: type: object properties: analysis_date: type: string format: date sentiment_score: type: number format: double # ─── Agentic Traffic ───────────────────────────────────────────── AIBotSummary: type: object description: | Daily per-bot summary of agentic traffic. Note the mixed casing: most fields are snake_case per the Go `json:` tags, but the embedded GORM model leaks `ID`, `CreatedAt`, `UpdatedAt`, and `DeletedAt` in PascalCase. properties: ID: type: integer description: Internal GORM primary key. CreatedAt: type: string format: date-time UpdatedAt: type: string format: date-time DeletedAt: type: string format: date-time nullable: true client_id: type: integer bot_name: type: string log_date: type: string format: date-time description: Calendar date the summary covers (time component is always 00:00:00 UTC). visit_count: type: integer description: Total visits attributed to this bot for the day. unique_ip_count: type: integer description: Distinct source IPs observed for this bot on this day. first_visit_timestamp: type: string format: date-time description: Earliest visit timestamp within the day. last_visit_timestamp: type: string format: date-time description: Latest visit timestamp within the day. hourly_distribution: type: object description: Map of hour-of-day ("0".."23") to visit count. additionalProperties: type: integer request_method_counts: type: object description: Map of HTTP method (e.g. "GET", "POST") to visit count. additionalProperties: type: integer status_code_counts: type: object description: Map of HTTP status code (as string, e.g. "200") to visit count. additionalProperties: type: integer top_folders: type: object description: Top URL folders (first path segment) by visit count. additionalProperties: type: integer top_urls: type: array description: Up-to-N most-visited URLs for this bot on this day. items: type: string is_archived: type: boolean description: True when the record has been archived under the data retention policy. BotDistribution: type: object properties: bot_name: type: string visit_count: type: integer format: int64 TrafficProportion: type: object properties: name: type: string description: Bot name or "Non-AI Bot Traffic" value: type: integer format: int64 # ─── AI Referral Traffic ───────────────────────────────────────── AIReferralSummary: type: object description: | Daily summary of human visits referred from a specific AI chatbot. Most fields are snake_case per the Go `json:` tags, but the embedded GORM model leaks `ID`, `CreatedAt`, `UpdatedAt`, and `DeletedAt` in PascalCase. properties: ID: type: integer description: Internal GORM primary key. CreatedAt: type: string format: date-time UpdatedAt: type: string format: date-time DeletedAt: type: string format: date-time nullable: true client_id: type: integer log_date: type: string format: date-time source: type: string description: AI chatbot source (e.g. "ChatGPT"). visit_count: type: integer format: int64 is_archived: type: boolean ReferralDistribution: type: object properties: source: type: string description: AI chatbot source name visit_count: type: integer format: int64 ReferralTopPage: type: object properties: request_uri: type: string description: The URL path that received referral visits source: type: string description: AI chatbot source name visit_count: type: integer format: int64 # ─── Sources ───────────────────────────────────────────────────── InfluentialSource: type: object properties: id: type: integer client_id: type: integer domain: type: string description: The domain being tracked (e.g. "medium.com"). label: type: string description: Human-readable display name for the source (e.g. "Medium"). is_active: type: boolean description: Whether this source is currently being tracked. is_default: type: boolean description: True when this is a system-seeded default source. Cannot be deleted, only deactivated. is_hero: type: boolean description: True when this source appears as one of the hero trend charts on the Influential Sources dashboard (at most five). color: type: string description: Hex brand colour used for chart lines (e.g. "#FF4500" for Reddit). created_at: type: string format: date-time # ─── Performance Summary ──────────────────────────────────────── ProviderSummary: type: object properties: aiProvider: type: string description: AI provider name, or "All" for the row covering every provider together example: "ChatGPT" citationRate: type: number format: double description: Average percentage of responses containing a brand citation example: 44.05 mentionRate: type: number format: double description: Average percentage of responses containing a brand mention example: 51.89 sentimentScore: type: number format: double description: Average sentiment score (-1 to 1) example: 0.63 totalSOV: type: number format: double description: > Total Share of Voice percentage, pooled over the window: brand citations divided by all citations. Not an average of per-analysis percentages. example: 17.90 brandedSOV: type: number format: double description: Share of Voice percentage across responses to branded phrases, pooled the same way example: 42.50 nonBrandedSOV: type: number format: double description: Share of Voice percentage across responses to non-branded phrases, pooled the same way example: 0.36 analysisCount: type: integer description: > Number of analyses behind this row in the period. On the "All" row this is the total across providers, and the weight each provider carries in the averaged metrics. example: 43 PerformanceSummaryResponse: type: object properties: overall: $ref: "#/components/schemas/ProviderSummary" providers: type: array items: $ref: "#/components/schemas/ProviderSummary" # ─── Recommendations ──────────────────────────────────────────── Recommendation: type: object properties: id: type: integer description: Unique recommendation ID recommendationAction: type: string description: Short, actionable statement describing what to do recommendationText: type: string description: Supporting context explaining why this action matters recommendationType: type: string enum: [content_gap, citation_improvement, competitor_insight, sentiment_improvement, ranking_opportunity] description: Category of the recommendation priority: type: integer minimum: 1 maximum: 5 description: Priority level (1 = most urgent, 5 = lowest) aiProviderContext: type: string description: Comma-separated list of AI providers this applies to, or "All" example: "ChatGPT,Perplexity" isDismissed: type: boolean description: Whether the user has dismissed this recommendation isArchived: type: boolean description: Whether this recommendation has been archived by a newer analysis isOnBacklog: type: boolean description: Whether this recommendation has been added to the backlog RecommendationHistoryItem: type: object description: A recommendation in the full-history view (includes archived/dismissed). properties: id: type: integer sessionId: type: string description: The analysis session that produced this recommendation createdAt: type: string format: date description: Date the recommendation was generated (YYYY-MM-DD) recommendationAction: type: string recommendationText: type: string recommendationType: type: string enum: [content_gap, citation_improvement, competitor_insight, sentiment_improvement, ranking_opportunity] priority: type: integer minimum: 1 maximum: 5 aiProviderContext: type: string isDismissed: type: boolean isArchived: type: boolean isOnBacklog: type: boolean RecommendationHistory: type: object properties: total: type: integer description: Number of recommendations returned recommendations: type: array items: $ref: "#/components/schemas/RecommendationHistoryItem" BacklogItem: type: object description: One entry in the Visibility Backlog. properties: id: type: integer source: type: string enum: [recommendation, weekly_tip, manual] description: Where the item originated sourceId: type: integer description: ID of the source record (e.g. the recommendation); omitted for manual items sessionId: type: string description: Analysis session of the source; omitted for manual items title: type: string description: type: string category: type: string aiProviderContext: type: string description: Provider context, present for recommendation-sourced items isCompleted: type: boolean completedOn: type: string format: date description: Date the task was marked complete; present only for completed items completedBy: type: string description: Name of the user who marked the task complete; present only for completed items createdAt: type: string format: date Backlog: type: object properties: total: type: integer description: Number of backlog items returned backlog: type: array items: $ref: "#/components/schemas/BacklogItem" ImpactAction: type: object properties: key: type: string description: Stable identifier for this piece of work. Assigned once and never rewritten, so it can be used to follow the same action across analyses. action: type: string description: What to do, taken from the most recent analysis that raised it text: type: string description: The full recommendation text from that analysis type: type: string enum: [content_gap, citation_improvement, competitor_insight, sentiment_improvement, ranking_opportunity] priority: type: integer description: 1 most urgent, 5 lowest aiProviders: type: string description: Comma-separated assistants this applies to, or All sessionCount: type: integer description: How many SEPARATE analyses reached this same conclusion. The ranking key; read it against analysisCount. firstSeen: type: string format: date description: Date of the earliest analysis that raised it lastSeen: type: string format: date description: Date of the most recent analysis that raised it lane: type: string description: recurring, new, or unconfirmed isResolved: type: boolean description: True when recent analyses have stopped raising it ImpactActionsResponse: type: object properties: total: type: integer description: Number of impact actions returned analysisCount: type: integer description: How many analyses the grouping spans. sessionCount is meaningless without it. rankingEnabled: type: boolean description: False when there are too few analyses for the recurrence count to carry weight minToRank: type: integer description: Analyses required before recurrence is treated as evidence impactActions: type: array items: $ref: "#/components/schemas/ImpactAction" RecommendationsOverview: type: object description: All four recommendation datasets returned together by the overview endpoint. properties: latest: type: array description: Active (non-archived, non-dismissed) recommendations items: $ref: "#/components/schemas/Recommendation" all: $ref: "#/components/schemas/RecommendationHistory" backlog: $ref: "#/components/schemas/Backlog" # ─── Feeds ────────────────────────────────────────────────────── URLRefreshResult: type: object description: Per-feed result of a URL list refresh. properties: feedId: type: integer description: Internal integer set ID for the refreshed feed. feedGuid: type: string description: Customer-facing feed GUID. label: type: string description: Display label for the feed. source: type: string description: Discovery method recorded at feed creation. enum: [sitemap, crawl, upload] discoveredCount: type: integer description: Number of URLs discovered in this refresh. previousCount: type: integer description: Number of URLs the feed held before the refresh. added: type: integer description: URLs present in the new discovery but not the previous list. removed: type: integer description: URLs present previously but not in the new discovery. kept: type: integer description: URLs that appeared in both lists. durationMs: type: integer format: int64 description: Time taken to refresh this feed's URL list, in milliseconds. URLRefreshFailure: type: object description: Per-feed failure record from a URL list refresh. properties: feedId: type: integer label: type: string source: type: string error: type: string URLRefreshSummary: type: object description: Bulk URL list refresh summary across every eligible feed for the client. properties: message: type: string clientId: type: integer refreshed: type: integer description: Number of feeds whose URL list refreshed cleanly. failed: type: integer description: Number of feeds that failed to refresh. results: type: array items: $ref: "#/components/schemas/URLRefreshResult" failures: type: array items: $ref: "#/components/schemas/URLRefreshFailure" durationMs: type: integer format: int64 FeedRefreshSummary: type: object description: Summary returned by the bulk feed refresh endpoint when no `format` is requested. properties: message: type: string refreshed: type: integer description: Number of feeds queued for regeneration. durationMs: type: integer format: int64 includeUrls: type: boolean description: Echoes the request flag. Only present when true. urlRefresh: $ref: "#/components/schemas/URLRefreshSummary" FeedRegenResult: type: object description: Result of regenerating the JSON for a single feed. properties: clientId: type: integer feedId: type: integer feedGuid: type: string label: type: string includeUrls: type: boolean status: type: string description: Outcome status returned by the generator. enum: [ok, partial, error] message: type: string productCount: type: integer description: Number of products in the regenerated feed. failedUrlCount: type: integer description: Number of URLs that failed to parse during regeneration. urlRefresh: $ref: "#/components/schemas/URLRefreshResult" CitationConsistencyTrendResponse: type: object properties: client_id: type: string description: Client ID echoed from the path parameter. window: type: integer description: Trailing N analyses each anchor's score was computed over. min_prompt_samples: type: integer description: Minimum analyses a prompt must appear in within the window before it contributes to an anchor's score. Filters single-run noise out. total: type: integer description: Number of trend points in the response. trend: type: array items: $ref: "#/components/schemas/CitationConsistencyTrendPoint" CitationConsistencyTrendPoint: type: object properties: analysis_date: type: string format: date description: Date of the anchor analysis (YYYY-MM-DD). session_id: type: string description: Anchor analysis session_id. The same session_id may appear with both branding axes and across multiple providers. ai_provider: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] is_prompt_branded: type: boolean description: true for the branded-queries axis, false for non-branded. citation_score: type: number format: double minimum: 0 maximum: 1 description: Average per-prompt citation consistency in the trailing window. 1.0 means every qualifying prompt was cited in every session in the window. reference_score: type: number format: double minimum: 0 maximum: 1 description: Average per-prompt reference (mention) consistency in the trailing window. sample_size: type: integer description: Number of analyses actually present in this anchor's trailing window (<= window). prompt_count: type: integer description: Number of qualifying prompts that contributed to this anchor's score (each had at least min_prompt_samples occurrences in the window). CitationConsistencyPromptsResponse: type: object properties: client_id: type: string window: type: integer min_prompt_samples: type: integer branded: type: string enum: ["true", "false", "all"] providers: type: array items: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] total: type: integer prompts: type: array items: $ref: "#/components/schemas/CitationConsistencyPromptRow" CitationConsistencyPromptRow: type: object properties: prompt: type: string description: Free-text prompt sent to the AI provider. ai_provider: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] is_prompt_branded: type: boolean sample_size: type: integer description: Number of analyses in the window where this prompt ran (<= window). citation_score: type: number format: double minimum: 0 maximum: 1 description: Fraction of sampled analyses where the brand was cited for this prompt. reference_score: type: number format: double minimum: 0 maximum: 1 citation_pattern: type: array description: | Per-analysis citation flags across the FULL window (length = window), ordered oldest to latest. Each cell carries: `1` cited `0` ran but not cited `-1` did not run in this analysis slot Aligned 1:1 with analysis_dates and session_ids. items: type: integer enum: [-1, 0, 1] reference_pattern: type: array description: Per-analysis reference (mention) flags. Same shape and ordering as citation_pattern. items: type: integer enum: [-1, 0, 1] analysis_dates: type: array description: Analysis dates (YYYY-MM-DD) aligned 1:1 with the pattern arrays. items: type: string format: date session_ids: type: array description: Session IDs aligned 1:1 with the pattern arrays. Lets you cross-reference each cell to the analysis it came from. items: type: string # ─── Responses ────────────────────────────────────────────────── ResponsesListResponse: type: object properties: total: type: integer description: Number of response rows that match the filters, across every page. limit: type: integer description: The page size applied (after clamping). offset: type: integer description: Rows skipped before this page. returned: type: integer description: Number of rows in this page. responses: type: array items: $ref: "#/components/schemas/ResponseWithCitations" ResponseWithCitations: type: object description: One AI response with its full reply text, brand signals, and citation URLs. properties: queryId: type: string aiProvider: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] prompt: type: string description: Final search prompt sent to the provider. isBranded: type: boolean reply: type: string description: Full untruncated AI reply text. isBrandCited: type: boolean isBrandMentioned: type: boolean brandCitations: type: integer brandMentions: type: integer sentiment: type: string nullable: true description: Sentiment classification ("Positive", "Negative", "Neutral"). sentimentScore: type: number format: double nullable: true description: Sentiment score from -1 (Negative) to 1 (Positive). sessionId: type: string analysisDate: type: string format: date citations: type: array items: $ref: "#/components/schemas/ResponseCitation" ResponseDetail: type: object description: | Single-record form returned by `GET /clients/{clientID}/responses/{queryID}`. Same shape as `ResponseWithCitations` minus `sessionId` and `analysisDate` (the caller already identified the row by `queryID`). properties: queryId: type: string aiProvider: type: string enum: [Anthropic, ChatGPT, Gemini, Perplexity, Grok] prompt: type: string isBranded: type: boolean reply: type: string isBrandCited: type: boolean isBrandMentioned: type: boolean brandCitations: type: integer brandMentions: type: integer sentiment: type: string nullable: true sentimentScore: type: number format: double nullable: true citations: type: array items: $ref: "#/components/schemas/ResponseCitation" ResponseCitation: type: object description: One citation URL belonging to an AI response, with ownership and competition flags. properties: fullUrl: type: string competitorName: type: string description: Normalised competitor / domain name (e.g. "amazon.com"). positionRank: type: integer description: Order in which the citation appeared in the response (1 is first). isClientOwned: type: boolean description: True when the URL belongs to the client's own domain. isInfluentialSource: type: boolean description: True when the URL matches one of the client's tracked influential sources. isCompetition: type: boolean description: True when the URL belongs to a tracked competitor domain. pathContainsBrand: type: boolean description: True when the client's brand name appears in the URL path. # ─── Citation domains ─────────────────────────────────────────── CitationDomainsResponse: type: object properties: total: type: integer description: Number of distinct cited domains returned. domains: type: array items: $ref: "#/components/schemas/CitationDomainCount" CitationDomainCount: type: object properties: domain: type: string description: Normalised cited domain (e.g. "us.gildedwardrobe.com"). count: type: integer description: Total citation count for this domain across the requested scope. RadarSubmitRequest: type: object required: [brand, website, country, brand_identifier, prompts] properties: brand: type: string description: Brand / company name being analysed. example: "The Gilded Wardrobe" website: type: string description: Brand website(s), comma-separated. Drives brand (owned-domain) citations. example: "gildedwardrobe.com, gildedwardrobe.co.uk" country: type: string description: > ISO 3166-1 alpha-2 country code targeting the run. Must be one of Martekio's supported countries (the enum below); an unsupported value returns 400 unsupported_country. example: "GB" enum: [AE, AR, AT, AU, BE, BO, BR, CA, CH, CL, CN, CO, CR, CU, DE, DK, DO, EC, EG, ES, FI, FR, GB, GT, HK, HN, ID, IE, IL, IN, IT, JP, KE, KR, MA, MX, MY, NG, NI, NL, NO, NZ, PA, PE, PH, PL, PR, PT, PY, QA, SA, SE, SG, SV, TH, TN, TR, US, UY, VE, VN, ZA] brand_identifier: type: string description: Brand identifier(s) for mention detection, comma-separated. example: "The Gilded Wardrobe, GildedWardrobe, TheGildedWardrobe" citation_key: type: string deprecated: true description: Deprecated alias for `brand_identifier`; accepted for backward compatibility. Prefer `brand_identifier`. example: "The Gilded Wardrobe, GildedWardrobe, TheGildedWardrobe" prompts: type: array minItems: 1 description: Prompts to analyse. 1 to the key's per-request maximum, each at most 500 characters. items: type: string example: ["best luxury sleepwear brands", "which department stores near me stock The Gilded Wardrobe party dresses"] RadarSubmitResponse: type: object properties: meta: type: object properties: generated_by: { type: string } api_version: { type: string } generated_at: { type: string, format: date-time } job_id: type: string format: uuid description: Identifier for this run; use it with the status and results endpoints. status: type: string enum: [submitted] client_id: type: integer description: >- Internal id of the analysis space for this brand. The same brand and website on the same key reuse one space, so this value repeats across requests; use `job_id` (and `session_id` once complete) to address a single run. prompts: type: object properties: charged: type: integer description: Prompts charged for this request. remaining: type: integer description: Prompts remaining in the annual allowance after this request. annual_allowance: type: integer description: The key's total annual prompt allowance (base plus any Signal Boost packs). resets_at: type: string format: date-time description: When the annual allowance resets. RadarStatusResponse: type: object properties: job_id: { type: string, format: uuid } status: type: string enum: [submitted, running, completed, failed] client_id: { type: integer } session_id: type: string description: The completed analysis session id; empty until the run completes. brand: { type: string } prompt_count: type: integer description: Number of prompts submitted in this request. analysis: allOf: - $ref: "#/components/schemas/RadarAnalysisResult" description: > Present only once `status` is `completed`: the full analysis results inline (identical to the body of GET /radar/analyses/{jobId}/results). Absent while the run is still in progress. RadarResultsResponse: type: object properties: job_id: { type: string, format: uuid } status: { type: string, enum: [completed] } client_id: { type: integer } session_id: { type: string } brand: { type: string } analysis: $ref: "#/components/schemas/RadarAnalysisResult" RadarAnalysisResult: type: object description: Completed analysis results across every assistant that ran. properties: best_provider_branded: type: string description: >- The AI assistant that cited the brand most on prompts that NAMED it. Says whether the brand's own pages are what an assistant reaches for when asked about it directly. Empty when no assistant cited the brand on a branded prompt. best_provider_nonbranded: type: string description: >- The AI assistant that cited the brand most on prompts that did NOT name it. This is organic AI visibility and the harder of the two. Frequently empty, because a brand's own domain is rarely cited on a question that does not name it; treat an empty string as "no assistant stood out", not as a missing value. For a denser signal use total_references and non_branded_references on each provider row, which count the brand being mentioned rather than linked. Both fields resolve a tie by the canonical assistant order. total_citations_all_providers: { type: integer } average_sentiment: { type: number } providers: type: array description: >- One entry per assistant that ran, always in Martekio's canonical assistant order (Claude, ChatGPT, Gemini, Perplexity, Grok, Google AI Mode, Google AI Overview), never ordered by score. The order is stable across runs, so two analyses can be compared position by position; an assistant that did not run is simply absent. items: type: object properties: ai_provider: { type: string } sentiment_score: { type: number } total_citations: { type: integer } total_references: { type: integer } total_competitive_citations: { type: integer } non_branded_citations: { type: integer } non_branded_references: { type: integer } brand_citations: type: array items: type: object properties: prompt: { type: string, description: The prompt whose answer carried this citation, exactly as submitted. } url: { type: string } ai_provider: { type: string } position_rank: { type: integer } competitor_citations: type: array items: type: object properties: prompt: { type: string, description: The prompt whose answer carried this citation, exactly as submitted. } url: { type: string } competitor: { type: string } ai_provider: { type: string } position_rank: { type: integer } RadarUsageResponse: type: object description: The partner's current prompt balance and recent Radar request history. properties: meta: type: object properties: generated_by: { type: string } api_version: { type: string } generated_at: { type: string, format: date-time } prompts: type: object properties: annual_allowance: type: integer description: The key's total annual prompt allowance (base 10,500 plus 5,500 per Radar Signal Boost). used: type: integer description: Requests used so far in the current annual period. remaining: type: integer description: Prompts remaining in the current annual period. num_signal_boosts: type: integer description: Number of Radar Signal Boost packs on the key. resets_at: type: string format: date-time description: When the current annual period resets and usage returns to zero. requests: type: array description: Recent Radar requests, newest first. items: type: object properties: job_id: { type: string, format: uuid } brand: { type: string } status: type: string enum: [submitted, running, completed, failed, rejected] prompts_charged: type: integer description: Prompts charged for this request (0 for rejected requests, and 0 once a failed run has been refunded). prompts_refunded: type: integer description: Prompts charged for this request and later given back because the run failed terminally and its results can never be retrieved. Absent when nothing was refunded; when present, prompts_charged is 0. session_id: type: string description: The analysis session id once the run produced results. error_message: type: string description: Why a request was rejected; absent for accepted runs. created_at: { type: string, format: date-time } request_payload: type: object nullable: true description: The exact JSON body the partner sent for this request. response_payload: type: object nullable: true description: The exact JSON body Martekio returned for this request. RadarError: type: object description: Structured error returned by every Radar endpoint. properties: error: type: string description: Machine-readable error code (e.g. invalid_request, key_disabled, not_found, not_ready, run_in_progress, rate_limit_exceeded, service_unavailable). message: type: string description: Human-readable explanation. RadarQuotaError: type: object description: Returned with 402 when a request needs more prompts than remain. Nothing is charged or run. properties: error: { type: string, enum: [quota_exceeded] } message: { type: string } prompts_remaining: { type: integer } prompts_required: { type: integer } resets_at: { type: string, format: date-time }