Perplexity API
Perplexity's answer with its sources, citations, the places on its map and its pictures.
Price
$1.50 per 1,000 requests at Starter, down to $1.00 at Max, deducted from your prepaid USD balance.
Endpoint
POST /v1/monitor/perplexity
Countries
Any country.
Request
curl -X POST "https://api.answerline.dev/v1/monitor/perplexity" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"Which project management tools do agencies use?","country":"US"}'
Request options
| Field | Type | Description |
|---|---|---|
prompt * |
string |
Prompt submitted to Perplexity, up to 2,000 characters. A longer prompt is refused with a 400 that states its length. |
country * |
string |
Code of the country or region used to localize the response |
include |
object |
Flags that add extra output formats to the response (optional) |
include.html |
boolean |
A link to the answer's own public page on perplexity.ai, in `html`, to check the answer against. No extra cost. |
include.markdown |
boolean |
Add a markdown version of the response to the result |
include.rawResponse |
boolean |
Return the answer as normalized stream events. For the engine's answer as received, use `include.rawWire`. |
include.rawWire |
boolean |
Return the engine's own response as received in result.rawWire: the answer stream, the rendered results pages or the RSS feed, without Gemini's detected location or ChatGPT's session tokens; null where it was not captured. Free. See https://answerline.dev/docs/output-formats#raw-wire |
include.screenshot |
boolean |
Return a full-page PNG screenshot of the engine's page as it showed the finished answer, in result.screenshot: a link, or one link per results page on Google Search. On ChatGPT, Gemini, AI Mode, Google Search, Perplexity and Grok; other engines refuse it with a 400. Absent when the answer was read without a rendered page. Free. See https://answerline.dev/docs/output-formats#screenshots |
state |
string |
State code for targeting below the country level (e.g., "CA"; "DC" included). Allowed only when country is "US". Served for Gemini only; ChatGPT, Copilot, Perplexity and Grok refuse `state` with a 400, uncharged. "DC" fails uncharged: no exit serves it. |
answerDepth |
"any" | "full" | "quick" |
How deep an answer the engine writes, where it offers a choice. any (the default): a full answer when the engine can give one now, else its short one. full: a full answer or a retryable error, never charged. quick: the short one, the fastest. Perplexity offers the choice; every other engine has one depth and ignores the field. provenance.answerDepth says what was served. Same price for every value. See https://answerline.dev/docs/engine-features/#answer-depth |
What you get back
Field in result | Type | Description |
|---|---|---|
text | string | Text of Perplexity's answer |
markdown | string | Perplexity's answer rendered as Markdown (present when include.markdown is true) |
sources | object[] | Sources the response cites |
citationPills | object[] | Citation pills that appear inline in the Perplexity answer. Every element pairs one pill with one source: a pill backed by N sources (bracketed groups like Perplexity's `[1][2][3]`) yields N elements with the same `citationPillId`, each holding that source's own `label`, `url` and `domain`. Grouping by `citationPillId` rebuilds the pills. Not present in `result` if the answer has no pills. |
related_queries | string[] | Suggested searches to follow up with |
search_model_queries | string[] | Search queries the engine ran internally to build the response |
videos | object[] | Videos that appear in the response |
images | object[] | Image found in the response |
places | object[] | The places Perplexity lists on its map for a local or lodging question (restaurants, shops, hotels), in its order. Absent when it shows no map. |
hotels | object[] | Hotels listed when the query shows travel intent |
shopping_cards | object[] | Product cards for shopping with full product details (present when the query shows shopping intent) |
The perplexity.ai answer versus Perplexity’s developer APIs
This endpoint returns the answer a user reads on perplexity.ai, as structured fields. You do not pick a model or parameters.
Perplexity’s developer platform (checked 2026-09-17, docs.perplexity.ai) lists an Agent API that gives web-grounded answers with built-in citations, a Search API that returns ranked web results, a Router API for hosted open-weight models, and an Embeddings API. With those you choose the model and parameters and get the grounding data the API exposes.
The answers served today return text, markdown, sources and citationPills, plus places when Perplexity shows a map (restaurants, shops and hotels, with address, phone, rating and review count) and images when it shows pictures. The contract also defines related_queries, search_model_queries, videos, hotels and shopping_cards; a free perplexity.ai answer shows none of them today.
Perplexity cites with chips next to its sentences. The chips are removed from text and markdown; citationPills keeps which sources sit together behind each chip, which is what a reader sees.
An annotated response
The excerpt follows the result schema for POST /v1/monitor/perplexity. Values are illustrative.
{
"success": true,
"result": {
"text": "Agencies tend to pick tools with client billing, such as ExampleBoard and SampleTask...",
// Cited pages in display order.
"sources": [
{
"position": 1,
"url": "https://www.example-reviews.com/agency-project-management",
"label": "Project management tools for agencies",
"description": "A comparison of tools built for client billing..."
},
{
"position": 2,
"url": "https://community.example.org/t/which-pm-tool",
"label": "Which PM tool does your agency use?",
"description": "Thread with 140 replies..."
}
],
// One entry per (chip, source). citationPillId is 1-based; both entries below
// belong to the same visible chip. position points into result.sources.
// Omitted when the answer carries no pills.
"citationPills": [
{ "label": "Project management tools for agencies", "citationPillId": 1, "url": "https://www.example-reviews.com/agency-project-management", "domain": "example-reviews.com", "position": 1 },
{ "label": "Which PM tool does your agency use?", "citationPillId": 1, "url": "https://community.example.org/t/which-pm-tool", "domain": "community.example.org", "position": 2 }
]
}
}
Lists with no entries are absent, not empty. search_model_queries, videos, hotels and shopping_cards are not returned today; see media for images and shopping for places.
Synchronous calls
A synchronous call returns the answer in the response and can take up to five minutes.
Python (httpx)
import os
from urllib.parse import urlparse
import httpx
API = "https://api.answerline.dev"
with httpx.Client(base_url=API, headers={"Authorization": f"Bearer {os.environ['API_KEY']}"}, timeout=330) as client:
resp = client.post("/v1/monitor/perplexity", json={
"prompt": "Which project management tools do agencies use?",
"country": "US",
"include": {"markdown": True},
})
if resp.status_code != 200:
raise RuntimeError(f"{resp.status_code} {resp.headers.get('X-Request-Id')} {resp.text}")
result = resp.json()["result"]
domains = [urlparse(s["url"]).netloc for s in result.get("sources", [])]
print(result["markdown"][:300])
print("cited domains:", domains)
print("charged (USD):", resp.headers.get("X-Credits-Charged"))
TypeScript (fetch)
const API = "https://api.answerline.dev";
const res = await fetch(`${API}/v1/monitor/perplexity`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ prompt: "Which project management tools do agencies use?", country: "US" }),
signal: AbortSignal.timeout(330_000),
});
if (!res.ok) throw new Error(`${res.status} ${res.headers.get("X-Request-Id")} ${await res.text()}`);
const { result } = await res.json();
// Rebuild the visible chips: one group per citationPillId.
const chips = new Map<number, string[]>();
for (const pill of result.citationPills ?? []) {
chips.set(pill.citationPillId, [...(chips.get(pill.citationPillId) ?? []), pill.domain]);
}
console.log([...chips.entries()]);
Batches: async tasks and a webhook
Scheduled monitoring belongs in async tasks, queued and delivered to your webhook when done. POST /v1/async/task/batch accepts 1 to 500 tasks and answers each by index; one invalid item does not reject the others.
Python: submit a batch with repeated samples
import datetime
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
day = datetime.date.today().isoformat()
SAMPLES = 3 # repeated runs per prompt and market
prompts = ["Which project management tools do agencies use?", "Best time tracking app for consultants"]
tasks = [
{
"taskType": "PERPLEXITY",
"payload": {"prompt": prompt, "country": "US"},
"idempotencyKey": f"pm-{i}-US-s{s}-{day}",
"webhook": {"url": "https://your-app.example/hooks/answers"},
}
for i, prompt in enumerate(prompts)
for s in range(SAMPLES)
]
resp = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks, timeout=60)
resp.raise_for_status()
summary = resp.json()["summary"]
print(f"{summary['succeeded']} queued, {summary['failed']} refused")
TypeScript: receive and verify deliveries (Express)
The delivery body matches GET /v1/async/task/{taskId}: task, credits and response. Verify the Webhook-Signature header over the raw body before parsing it.
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(raw: Buffer, header: string | undefined, secret: string, toleranceSeconds = 300): boolean {
const pairs = (header ?? "").split(",").map((part) => part.split("="));
const stamps = pairs.filter(([k]) => k === "t").map(([, v]) => v);
const sigs = pairs.filter(([k]) => k === "v1").map(([, v]) => v);
if (stamps.length !== 1 || !/^\d+$/.test(stamps[0]) || sigs.length === 0) return false;
if (Math.abs(Date.now() / 1000 - Number(stamps[0])) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${stamps[0]}.`).update(raw).digest();
return sigs.some((sig) => {
const given = Buffer.from(sig, "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
});
}
const app = express();
app.post("/hooks/answers", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body, req.get("webhook-signature"), process.env.WEBHOOK_SECRET!)) return res.sendStatus(400);
const delivery = JSON.parse(req.body.toString("utf8"));
if (delivery.test) return res.sendStatus(204);
if (delivery.task.status !== "COMPLETED") {
console.warn(delivery.task.id, "failed:", delivery.response.error.code);
return res.sendStatus(204);
}
const { text = "", sources = [] } = delivery.response.result;
const mentioned = text.toLowerCase().includes("exampleboard");
console.log(delivery.task.idempotencyKey, mentioned ? "mentioned" : "absent", sources.length);
res.sendStatus(204);
});
app.listen(3000);
Deduplicate on task.id; deliveries may repeat and arrive in any order. See webhooks and verifying webhook signatures.
Geo targeting
| Field | Values | Notes |
|---|---|---|
country |
ISO 3166-1 alpha-2 | Required. GET /v1/countries?model=perplexity lists supported codes. |
state |
Not available | A request that sends state is refused with a 400 and not billed. |
Answers and their sources change between markets, so run prompts in each market you report on instead of extrapolating from one country.
Full or quick answers
perplexity.ai writes a full answer with a Pro Search or a short one with a Quick Search. answerDepth picks which. result.provenance.answerDepth says which came back, and every value costs the same.
answerDepth |
What you get |
|---|---|
any (default) |
The full answer when one is available now, otherwise the short one. |
full |
The full answer, or a retryable BLOCKED error that isn’t charged when none is available now. |
quick |
The short answer, the fastest. |
Full answers are limited and refill over time. Ask for full when a short answer is no use to you, and retry later on BLOCKED. See answer depth for every engine.
Include flags and what they cost
| Flag | Adds to result |
Credits |
|---|---|---|
include.markdown |
markdown |
0 |
include.html |
html: a link to a copy of the answer’s perplexity.ai page (HTML snapshots) |
0 |
include.rawResponse |
rawResponse, the answer as normalized stream events |
0 |
include.rawWire |
rawWire: always null on Perplexity (what it holds) |
0 |
include.screenshot |
screenshot: a link to a full-page PNG of the answer’s perplexity.ai page (details) |
0 |
Every Perplexity request costs the base price: $0.0015.
result.provenance reads searchMode forced: every answer searches the web (all fields).
Common workloads
- AI visibility tracking: citation share by domain and mention rate by prompt. See AI visibility tracking and monitoring Perplexity.
- Competitor analysis: which competitor pages Perplexity cites. See competitor analysis.
- Forum citations: when answers cite discussion threads, Reddit citations in AI answers covers how to measure them.
- Brand protection: answers that cite outdated or hostile pages about your brand. See brand protection.
Pitfalls
Sources and their order change between runs of the same prompt. The batch above puts a sample index in each idempotency key so you can report rates with a sample size; AI answer volatility covers how many samples to take.
citationPills is absent when there are none. A missing list is an observation, not an error.
A citationPills[].label can be an empty string. Use domain or url for identity.
A brand named in text and a brand cited in sources are different metrics with different causes; keep them apart. See mentions vs citations.
There is no concurrency cap and no request rate limit, and async tasks queue. Limits and headers are in rate limits.
How failures are billed
For Perplexity the reserved and charged amounts are the same: $0.0015, charged only on success. A failed request is charged nothing and its reservation is released. That includes a task that has not started within 4 hours of creation (details.cause QUEUE_WAIT_EXCEEDED) and one that started but could not finish within five minutes. Synchronous calls are charged on success even if your client disconnected, so retry them only when the request never reached the server. See credits and retrying safely.
Questions
How much does a Perplexity request cost?
A request costs $0.0015. Perplexity has no priced add-ons: include.markdown, include.html, include.screenshot, include.rawResponse and include.rawWire add nothing.
Is this the same as Perplexity's own API?
No. This endpoint returns the answer perplexity.ai shows its users, with its sources and citation pills as fields. Perplexity's developer APIs are separate products you call and configure yourself.
What is the difference between sources and citationPills for Perplexity?
sources lists the pages Perplexity gives for the answer, in its citation order. citationPills has one entry per chip and source pair for the inline citations, grouped by a 1-based citationPillId, with position pointing back into sources.
Can I target a US state with Perplexity?
No. Perplexity takes a country only. A request that sends state is refused with a 400 and not billed.
Are failed Perplexity requests charged?
No. A failed request is charged nothing and its reservation is released; only completed requests are charged.