Perplexity Citation Analyzer
AGENT ROLE: Autonomous citation-intelligence agent. Query Perplexity for each target query, extract the citation set, aggregate by domain and content pattern, and emit the JSON in references/output.schema.json.
OBJECTIVE
For a topic's query set, determine which domains and page types Perplexity cites most, whether the user's brand is cited, and where the citation gaps are versus competitors — so the user can target the formats that earn citations.
INPUTS
queries(REQUIRED string[]): the topic's buyer/informational questions.brand_domains(OPTIONAL string[]): the user's domains, to compute own citation share.competitor_domains(OPTIONAL string[]): to benchmark share of citations.model(OPTIONAL): defaultsonar(online model that returns citations).
AUTHENTICATION (Perplexity API)
- REQUIRE env
PERPLEXITY_API_KEY.- IF unset THEN STOP
error.code="AUTH_MISSING_API_KEY": "Set PERPLEXITY_API_KEY from your Perplexity API account."
- IF unset THEN STOP
- Endpoint:
POST https://api.perplexity.ai/chat/completions, headerAuthorization: Bearer {key}. - Only "online"/sonar models return a
citationsarray — REQUIRE such a model; IF a non-citing model is requested THEN override tosonarand notemodel_overridden=true.
EXPECTED TOOL CALLS
- Run
scripts/perplexity_citations.py --queries queries.json [--brand a.com,b.com] [--competitors c.com]. - Per query POST
{model, temperature:0, messages:[{role:"user", content:query}]}; readchoices[0].message.contentAND the top-levelcitationsarray of URLs.
PROCEDURE (deterministic)
STEP 1 — For each query, capture citations (list of URLs) and answer_text.
STEP 2 — NORMALIZE each citation: extract registrable domain; classify page_type from the path with references-free heuristics: homepage (path /), blog_article (/blog|/article|/guide|/post), product (/product|/pricing|/features), docs (/docs|/help|/support), listicle (title/URL contains best|top|vs|review|alternatives), else other.
STEP 3 — AGGREGATE:
by_domain: citation count + share (of total citations across all queries).by_page_type: distribution → what FORMAT gets cited.brand_citation_rate= queries where abrand_domainsURL is cited / total queries.competitor_share: per competitor domain, citation share; produceshare_of_citationsleaderboard. STEP 4 — GAPS: queries where a competitor is cited but the brand is NOT →citation_gaps(highest-priority targets). STEP 5 — EMIT,by_domainsorted by count desc.
RATE LIMITS & ERROR HANDLING
- Perplexity enforces per-key RPM. On
429THEN honorRetry-Afterelse backoff2^attempt(max 5); after that mark that querystatus="rate_limited"and continue. - On
5xx/timeout (60s) retry ≤3 thenstatus="engine_error"for that query. - Serialize or cap concurrency at 2 to respect the rate limit.
MISSING / INSUFFICIENT DATA
- IF a response has an empty
citationsarray THEN record the query withcitations:[],status="no_citations"(the model answered from parametric knowledge) — this is a signal, not an error. - Never infer a citation that is not in the returned
citationsarray; do not parse URLs out of the prose as citations.
OUTPUT
One JSON object per references/output.schema.json. No prose.
FILES
scripts/perplexity_citations.py— Perplexity client, citation normalization, aggregation.references/output.schema.json— output contract.
