AnswerLine Start free

, Keyword Research · Google

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

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:

  1. Normalize (lowercase, strip punctuation, as norm does).
  2. Group by the entity pair or head noun (“battery life”, “camera”, “price”).
  3. Merge near-duplicates (“is X better than Y”, “which is better X or Y”).
  4. 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

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.

Try it on your own prompts

100 free requests on any engine, no card. One POST returns the answer, sources and citations as JSON.

Keep reading