Mine People Also Ask for keyword research that still works
People Also Ask (PAA) is the expandable box of related questions Google shows on many results pages. Each entry is a question searchers ask around your query, together with the page or AI-generated answer Google chose for it. Collected across a keyword list, PAA gives you two things keyword volume tools lack: the questions in searchers’ phrasing, and who currently owns each answer. Below: extracting PAA as typed data, expanding it level by level, clustering it into a question set, and reusing the map for AI-answer monitoring. Turning those clusters into pages and sections is covered in People Also Ask for SEO.
Definitions
- People Also Ask entry: one question in the PAA box, with Google’s chosen answer source.
- Answer owner: the page (for
LINKentries) or the set of cited sources (forAIOVERVIEWentries) Google uses to answer a PAA question. - PAA expansion: running the questions returned for one query as new queries, to collect the next level of questions.
- Question cluster: a group of PAA questions that one page can answer, found by normalizing and grouping similar phrasings.
The data
A Google Search request returns PAA as a typed array.
curl -X POST https://api.answerline.dev/v1/monitor/google \
-H "Authorization: Bearer $ANSWERLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "iphone 17 vs galaxy s26", "country": "US" }'
{
"success": true,
"result": {
"organicResults": [ { "position": 1, "title": "…", "link": "…", "snippet": "…" } ],
"peopleAlsoAsk": [
{ "question": "Is the iPhone 17 better than the Galaxy S26?", "type": "LINK", "title": "…", "link": "…", "snippet": "…" },
{ "question": "Which phone has better battery life?", "type": "AIOVERVIEW" }
],
"relatedSearches": [ { "query": "…", "link": "…" } ]
}
}
The questions above are placeholders; your output depends on the query and market.
Field reference
| Field | Present on | Meaning |
|---|---|---|
question |
all | the question text |
type |
all | LINK, AIOVERVIEW or UNKNOWN |
title, link, snippet |
LINK |
the answering page’s title, URL and the snippet Google shows |
markdown |
AIOVERVIEW, with include.paaAioverview |
the AI-generated answer as Markdown |
sources[] |
AIOVERVIEW, with include.paaAioverview |
cited sources: label, url, description, position |
redirectLink |
when Google served the link as a redirect | the /goto link; the link field holds the resolved destination |
Two other arrays in the same response feed keyword research: relatedSearches[] (query, link) and, when Google shows the module, peopleAreSaying[] (position, title, link, date), which often points at forum threads.
Targeting
The request takes query plus country (or gl), and location or uule for a city. Collect each market you publish for separately. For local nuances, see local rank tracking.
Building the question map
1. Seed set
Pick 20 to 100 head terms per topic area: product category terms, comparison pairs, and the problems your product solves. Seeds you already rank for are useful too, because their PAA shows the follow-up questions your page does not yet answer.
2. Expand level by level
Run the seeds, collect every question, deduplicate, and run the new questions as the next level. Stop when a level adds few questions you have not seen, or at a fixed depth of two or three. The code below does this with async tasks.
import os
import re
import time
from collections import Counter, defaultdict
from urllib.parse import urlparse
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['ANSWERLINE_API_KEY']}"}
COUNTRY = "US"
def norm(q):
return re.sub(r"[^a-z0-9 ]", "", q.lower()).strip()
def run_level(queries, level):
tasks = [
{
"taskType": "GOOGLE",
"payload": {"query": q, "country": COUNTRY},
"idempotencyKey": f"paa-{COUNTRY}-{level}-{norm(q)[:80].replace(' ', '-')}",
}
for q in queries
]
ids = []
for i in range(0, len(tasks), 500): # batch limit is 500 tasks
r = requests.post(f"{API}/v1/async/task/batch", json=tasks[i:i + 500], headers=HEADERS, timeout=60)
r.raise_for_status()
ids += [x["task"]["id"] for x in r.json()["results"] if x["success"]]
done = {}
while len(done) < len(ids):
for tid in ids:
if tid not in done:
d = requests.get(f"{API}/v1/async/task/{tid}", headers=HEADERS, timeout=30).json()
if d["task"]["status"] in ("COMPLETED", "FAILED"):
done[tid] = d
time.sleep(10)
return [d["response"]["result"] for d in done.values() if d["task"]["status"] == "COMPLETED"]
seen = set()
frequency = Counter() # how many collected SERPs showed the question
owners = defaultdict(Counter) # question -> answering domains
frontier = ["iphone 17 vs galaxy s26", "best android phone for photography"]
for level in range(3):
seen |= {norm(q) for q in frontier}
results = run_level(frontier, level)
new = {}
for result in results:
for paa in result.get("peopleAlsoAsk", []):
key = norm(paa["question"])
frequency[key] += 1
if paa.get("type") == "LINK" and paa.get("link"):
owners[key][urlparse(paa["link"]).hostname] += 1
if key not in seen:
new[key] = paa["question"]
print(f"level {level}: {len(results)} SERPs, {len(new)} new questions")
if len(new) < 10:
break
frontier = list(new.values())
Idempotency keys built from market, level and normalized question make a re-run of the same script safe: a question already submitted is not charged again. Keep keys short; the example truncates the normalized question.
3. Cluster
Group questions that one page can answer. Start simple and refine by hand:
- Normalize (lowercase, strip punctuation, as
normdoes). - Group by the entity pair or head noun (“battery life”, “camera”, “price”).
- Merge near-duplicates (“is X better than Y”, “which is better X or Y”).
- Order clusters by summed
frequency: a question that appears on many of the results pages you collected is central to the topic.
frequency measures centrality within your own collection, not search volume; pair it with volume data from your keyword tool if you have it.
4. Assign owners and pick targets
For each cluster, look at the answer owners:
| Owner situation | Action |
|---|---|
Your domain owns most LINK answers |
defend: keep the page current, answer the adjacent questions on it |
| A strong competitor or publisher owns them | target only if you can publish a clearly better answer |
| Forums, thin pages or no consistent owner | easiest targets: a focused answer page |
Mostly AIOVERVIEW entries |
aim to be among the cited sources[]; enable include.paaAioverview to see them |
5. Turn clusters into briefs
Each cluster becomes one page or one section: the cluster’s most frequent question as the heading, the other questions as subheadings or an FAQ, and the current owners as the pages to beat. Write the direct answer in the first sentence under each heading.
6. Re-run monthly
Run the same seeds each month and diff: new questions, disappeared questions, and owner changes. Rising questions are early content opportunities. SERP feature change tracking covers diffing Google results over time.
Worked cost example
The question counts here are invented; the prices are not (Starter tier). A Google Search task costs $0.00103. With 50 seeds, level 0 costs 50 x $0.00103 = $0.0515. If those results yield 180 new unique questions, level 1 costs 180 x $0.00103 = $0.1854. If level 1 yields 400 new questions and you stop at level 2, that level costs $0.412. Total: 630 tasks, $0.6489. Adding include.paaAioverview raises each task to $0.00174.
Deduplication is the main cost control: without the seen set, the same popular questions come back at every level and get paid for repeatedly.
PAA and AI answers
PAA questions and AI assistant prompts overlap: both are full questions in searchers’ own words. That makes a PAA map a good starting point for an AI monitoring prompt set. Take the top cluster heads, run them as ChatGPT or Gemini prompts, and compare who is cited there against who owns the PAA answer on Google. Where the same domain wins both, you are competing with an entrenched source; where they differ, there is room.
Through ChatGPT, the link does not run the other way. Its result.searchQueries[] (with include.searchQueries, which is free) comes back empty on the answers served today, so it gives no seeds to feed back into the expansion above. The cited sources are the signal to compare. See query fan-out and prompt set design.
Pitfalls
- Paying for duplicates. Normalize and deduplicate before every level.
- Treating frequency as search volume. It counts appearances in the results you collected.
- One market for all content. PAA for
country: "GB"can differ from the US; collect what you publish for. - Ignoring
AIOVERVIEWentries. Withoutinclude.paaAioverviewthey carry no answer data, so they look ownerless. Request the answers for a sample before deciding they are easy targets. - Unbounded depth. Each level can multiply queries. Set a maximum depth and a minimum new-question threshold.
- Polling too hard. For large runs, add a
webhookto each task instead of polling; see async tasks and webhooks.
Related
For the underlying data model, see typed SERP data and the Google Search engine page. The use case is described on keyword research; batch mechanics for large keyword sets are in rank tracking with batches.
Start with the quickstart.
Questions
How can I pull Google's People Also Ask questions with code?
Send a Google Search request (POST /v1/monitor/google with query and country, or a GOOGLE async task). The response lists them in result.peopleAlsoAsk[], each with question and type, plus title, link and snippet for LINK-type entries.
How do I avoid paying twice for the same PAA question?
Normalize each question by lowercasing it and stripping punctuation, and deduplicate against every level already run before submitting the next one. Derive each task's idempotencyKey from market, level and the normalized question, so a resubmission answers RESOURCE_ALREADY_EXISTS instead of creating a second task.
How much does PAA extraction cost?
A one-page Google Search request costs $0.00103 on every tier. Enabling include.paaAioverview is free and makes responses slower.
How deep should I expand PAA questions?
Two or three levels is usually enough to map a niche: run seeds, then run the new questions they return, and stop when a level adds few questions you have not seen. Deduplicate before each level so you never pay for the same query twice.
Do PAA questions differ by market?
Yes, and the request takes country or gl, so you can collect each market separately and compare; location or uule narrows it to a city. Run the markets you publish for rather than assuming one list fits all.