The SearchMention API lets your scripts and AI agents, such as Claude, read every store on your account: store details, what each store sells, the prompts you track, run progress and the full AI visibility report. One token covers every store you own. On an agency account, that is every client store.
At a glance
- Base URL
https://searchmention.com/api/v1- Auth
Authorization: Bearer sm_acct_…- Format
- JSON. Send
Accept: application/jsonon every request. - Access
- Read only by default. Starting prompt runs is opt-in per token.
Get a token
In SearchMention, open Account settings → API, give the token a name (for example “Claude” or “Weekly report”) and click Create token.
- Tick Can edit prompts if this token should add, edit, pause or delete prompts, add, edit or remove tracked competitors, and turn on report sharing, and Can start prompt runs only if it should be able to spend your query budget. With neither, the token is read only: it can’t change anything or start runs.
- The token is shown once. Copy it straight into your secret store; SearchMention can’t show it again.
- Revoke a token from the same page at any time. Anything using it stops working immediately.
- An account can hold up to 10 tokens. Use one per script or agent so you can revoke one without breaking the others.
Authentication
Send the token as a bearer token on every request. Stores are addressed by their uuid, which List stores returns.
The examples on this page read two shell variables, so you can paste them as they are:
export SEARCHMENTION_TOKEN="sm_acct_your_token_here"
export STORE="9b1f6c2e-4d7a-4e0b-a5f3-2c8d1e6b7a90"
curl "https://searchmention.com/api/v1/projects" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
Errors and limits
Errors come back as JSON with an error message:
| Status | Example body | When |
|---|---|---|
401 | {"error": "Invalid API token."} | The token is missing, wrong or revoked, or it is a store key (sm_live_…) instead of an account token. |
403 | {"error": "This token is missing the \"run\" scope."} | The token wasn’t allowed to start runs or refresh a summary. A confirmed run blocked by your plan’s query budget is also 403 (see Start a run). |
404 | {"error": "Store not found."} | The uuid isn’t one of your stores, or the store is archived. Unknown run ids return Run not found. |
409 | {"error": "A run is already in progress.", …} | A run is already going for that store, you asked for the report of a run that hasn’t finished, or a summary refresh can’t start yet. |
422 | {"message": "…", "errors": {"per_page": ["…"]}} | A query parameter is invalid, or a confirmed run can’t start because the store has no active prompts or engines. |
Rate limits. 120 requests a minute, 30 a minute for starting runs, and 10 a minute for summary refreshes. Over the limit you get 429 with a Retry-After header (seconds to wait).
Pagination
List endpoints return rows in data, page numbers in meta and ready-made page URLs in links. Pass page and per_page, or follow links.next until it is null.
{
"data": [ … ],
"meta": { "current_page": 1, "per_page": 50, "last_page": 3, "total": 128 },
"links": {
"first": "https://searchmention.com/api/v1/projects?page=1",
"last": "https://searchmention.com/api/v1/projects?page=3",
"previous": null,
"next": "https://searchmention.com/api/v1/projects?page=2"
}
}
Some lists add their own top-level keys, such as source on the catalog and totals on prompts.
Endpoints
All paths are relative to https://searchmention.com/api/v1.
| Method | Path | What it returns |
|---|---|---|
| GET | /projects | Every store on the account |
| GET | /projects/{uuid} | One store’s brand, market, competitors and prompt setup |
| GET | /projects/{uuid}/catalog/categories | What the store sells, by category |
| GET | /projects/{uuid}/catalog/products | What the store sells, by product |
| GET | /projects/{uuid}/prompts | The full prompt library |
| POST | /projects/{uuid}/prompts | Add a prompt |
| PATCH | /projects/{uuid}/prompts/{id} | Edit, pause or resume a prompt |
| DELETE | /projects/{uuid}/prompts/{id} | Delete a prompt that never ran |
| POST | /projects/{uuid}/competitors | Track a competitor |
| PATCH | /projects/{uuid}/competitors/{domain} | Rename a competitor or change its aliases |
| DELETE | /projects/{uuid}/competitors/{domain} | Stop tracking a competitor |
| POST | /projects/{uuid}/runs | A run’s cost, or a started run |
| GET | /projects/{uuid}/runs/{id} | A run’s progress |
| POST | /projects/{uuid}/runs/{id}/summary | Rewrite a finished run’s summary |
| POST | /projects/{uuid}/next-steps | Suggest next steps from your last runs |
| GET | /projects/{uuid}/report | The full AI visibility report as data |
| POST | /projects/{uuid}/report/share | Turn on the public report link and get it |
List stores
GET /projects
Every store on your account, newest first. Archived stores are left out.
| Query | What it does |
|---|---|
q | Only stores whose name or domain contains this text |
page, per_page | Default 50 per page, max 100 |
Each row has the store’s uuid, name, domain, plan and status, prompt counts, the latest_run (in any state, or null), the shared report_url (null when sharing is off) and report_views.
curl "https://searchmention.com/api/v1/projects?q=trail" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": [
{
"uuid": "9b1f6c2e-4d7a-4e0b-a5f3-2c8d1e6b7a90",
"name": "Trail Co.",
"domain": "trailco.example",
"site_type": "online_store",
"plan": "growth",
"status": "active",
"created_at": "2026-08-03T14:12:09+00:00",
"prompts": { "total": 48, "active": 42 },
"latest_run": {
"id": 4821,
"status": "completed",
"prompts_executed": 168,
"total_queries": 168,
"started_at": "2026-09-22T09:00:04+00:00",
"completed_at": "2026-09-22T09:14:51+00:00"
},
"report_url": "https://searchmention.com/reports/k3Jd9…",
"report_views": {
"count": 6,
"first_viewed_at": "2026-09-22T10:02:11+00:00",
"last_viewed_at": "2026-09-25T16:40:37+00:00"
}
}
],
"meta": { "current_page": 1, "per_page": 50, "last_page": 1, "total": 1 },
"links": { … }
}
Get a store
GET /projects/{uuid}
Everything behind one store’s prompts, in data:
| Key | What it holds |
|---|---|
brand | The name AI answers are checked for, its aliases (other names and domains) and business_model: brand, retailer or mixed |
market | country, language, currency, and the search_country / search_location AI answers are localized to |
wizard | The prompt setup you saved: brief, chosen categories and products, competitors, goals and notes. null until you’ve set up prompts |
detected_brief | What SearchMention read from the storefront: summary, product lines, store categories, featured products, where it ships |
competitors | Tracked competitors with domain, name, country, reason and aliases |
catalog | Whether a product catalog is synced, product and category counts, and imported pages by type |
readiness | The latest AI readiness score and pages scanned, or null |
prompts | total, active, your plan’s cap and slots_remaining |
runs | The latest run, whether one is running, the engines a run uses (active_models), remaining_query_units (null = unlimited) and block_reason if a run can’t start now |
report_url | The shared report link, or null |
Store keys and platform credentials are never returned.
curl "https://searchmention.com/api/v1/projects/$STORE" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": {
"uuid": "9b1f6c2e-4d7a-4e0b-a5f3-2c8d1e6b7a90",
"name": "Trail Co.",
"domain": "trailco.example",
"platform": "shopify",
"plan": "growth",
"status": "active",
"busy": false,
"brand": {
"name": "Trail Co.",
"aliases": { "names": ["TrailCo"], "domains": [] },
"business_model": "brand",
"business_model_source": "brief"
},
"market": {
"country": "US",
"language": "en",
"currency": "USD",
"search_country": "US",
"search_location": "United States"
},
"wizard": { "brief": { … }, "selected_categories": [ … ], "competitors": [ … ] },
"detected_brief": { "status": "ready", "built_at": "2026-08-03T14:13:40+00:00", "data": { … } },
"competitors": [
{
"domain": "ridgeline.example",
"name": "Ridgeline Outfitters",
"country": "US",
"reason": "Sells ultralight packs at a similar price",
"aliases": []
}
],
"catalog": { "synced": true, "product_count": 214, "category_count": 18, … },
"readiness": { "overall_score": 72.5, "pages_scanned": 40, "completed_at": "2026-09-20T08:31:02+00:00" },
"prompts": { "total": 48, "active": 42, "cap": 50, "slots_remaining": 8 },
"runs": {
"latest": { "id": 4821, "status": "completed", … },
"running": false,
"active_models": ["chatgpt", "ai_overview", "gemini", "perplexity"],
"remaining_query_units": 3620,
"block_reason": null
},
"report_url": "https://searchmention.com/reports/k3Jd9…"
}
}
Catalog: categories and products
GET /projects/{uuid}/catalog/categories
GET /projects/{uuid}/catalog/products
What the store sells, from the best source it has. The top-level source says which:
source | Where the rows come from |
|---|---|
catalog | Your synced product catalog |
pages | Category or product pages found on the site’s sitemap |
detected | Collections and products SearchMention read from the storefront. Often all a store added by URL has |
| Query | What it does |
|---|---|
q | Category name, or product title, SKU or handle (page title or URL for pages) |
include_inactive | Products only: 1 includes draft and archived catalog products |
page, per_page | Default 100 per page, max 200 |
A category row has name, url, parent, product_count and description. A product row has title, url, vendor, product_type, categories, tags, price, price_min, price_max, currency, status and description. Fields a source doesn’t have are null or [], and descriptions are plain text cut at 300 characters.
curl "https://searchmention.com/api/v1/projects/$STORE/catalog/products?q=backpack&per_page=20" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": [
{
"title": "Ridge 40L Ultralight Backpack",
"url": "https://trailco.example/products/ridge-40l",
"vendor": "Trail Co.",
"product_type": "Backpacks",
"categories": ["Backpacks", "Ultralight"],
"tags": ["hiking", "ultralight"],
"price": "189.00",
"price_min": "189.00",
"price_max": "209.00",
"currency": "USD",
"status": "active",
"description": "A 40-litre frameless pack that weighs 780 g…"
}
],
"source": "catalog",
"meta": { "current_page": 1, "per_page": 20, "last_page": 1, "total": 6 },
"links": { … }
}
List prompts
GET /projects/{uuid}/prompts
The whole prompt library, including paused prompts and prompts that have never run, so you can review a set before spending query budget on it.
| Query | What it does |
|---|---|
active | 1 active only, 0 paused only. Leave out for both |
query_type | transactional, discovery, comparison, price, use_case, attribute or brand |
q | Only prompts containing this text |
page, per_page | Default 100 per page, max 200 |
Each row has id, prompt_text, category, query_type and its label, buyer intent (high, medium or low), is_active, and seeded with seeded_by: the prompt names your brand or a tracked competitor, so a mention there isn’t organic. latest_run shows how many engines answered it and how many named you in the latest completed run, or null if it wasn’t in that run.
The response also carries totals for the whole library (not just the page), the latest_run it compares against, and health: the same checks the Prompts tab shows, such as catalog categories no prompt covers, prompts with an out-of-date year, and questions Google shows next to your prompts.
curl "https://searchmention.com/api/v1/projects/$STORE/prompts?active=1" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": [
{
"id": 118,
"prompt_text": "best ultralight backpack for a weekend hike",
"category": "Backpacks",
"query_type": "discovery",
"query_type_label": "Discovery",
"intent": "low",
"source": "manual",
"is_active": true,
"seeded": false,
"seeded_by": [],
"created_at": "2026-08-03T14:20:12+00:00",
"latest_run": { "engines": 4, "mentioned": 2 }
}
],
"totals": { "total": 48, "active": 42, "seeded": 5, "cap": 50, "slots_remaining": 8 },
"latest_run": { "id": 4821, "completed_at": "2026-09-22T09:14:51+00:00" },
"health": { "issues": [ … ], "suggestions": [ … ] },
"meta": { "current_page": 1, "per_page": 100, "last_page": 1, "total": 42 },
"links": { … }
}
Add, edit and delete prompts
POST /projects/{uuid}/prompts
PATCH /projects/{uuid}/prompts/{id}
DELETE /projects/{uuid}/prompts/{id}
Changes the prompt library with the same rules as the Prompts tab. It needs a token with Can edit prompts. Nothing changes while the store is busy (a run, import or prompt generation): you get 409, so a run never asks a half-edited set.
| Call | Body | Result |
|---|---|---|
POST /prompts | prompt_text (5–500 characters), query_type | 201. If a paused prompt has the same text it’s resumed instead (200, result: restored). Same text as an active prompt: 409. At your plan’s prompt limit: 403 |
PATCH /prompts/{id} | Any of prompt_text, query_type, is_active | 200. is_active: false pauses, true resumes (within the limit). Text another prompt uses: 422 |
DELETE /prompts/{id} | — | 200. Only for a prompt that never ran; one with results gets 409, so pause it instead and it stays in past reports |
query_type is one of discovery, use_case, attribute, transactional, price, comparison, brand. Editing the text keeps the prompt’s id, so its earlier results stay with it; for a different question, pause the old prompt and add a new one.
curl -X PATCH "https://searchmention.com/api/v1/projects/$STORE/prompts/118" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"prompt_text": "where to buy ultralight backpacks online", "query_type": "transactional"}'
{
"data": {
"id": 118,
"prompt_text": "where to buy ultralight backpacks online",
"query_type": "transactional",
"query_type_label": "Transactional",
"intent": "high",
"is_active": true,
…
},
"result": "updated",
"totals": { "active": 42, "cap": 50, "slots_remaining": 8 }
}
Track, edit and remove competitors
POST /projects/{uuid}/competitors
PATCH /projects/{uuid}/competitors/{domain}
DELETE /projects/{uuid}/competitors/{domain}
Changes the store’s tracked competitors, the same way as the Track button on the Visibility page and the Prompts & Competitors screen. It needs a token with Can edit prompts. A competitor is addressed by its tracked domain, as GET /projects/{uuid} lists it. Like prompt edits, nothing changes while the store is busy (409).
| Call | Body | Result |
|---|---|---|
POST /competitors | domain, optional name and aliases | 201. Already tracked, or already counted as a tracked competitor (same brand on another domain): 409. Your own domain: 422. At your plan’s competitor limit: 403 |
PATCH /competitors/{domain} | Any of name, aliases | 200. aliases replaces the list; [] clears it |
DELETE /competitors/{domain} | — | 200. The competitor is no longer tracked |
aliases are other names AI uses for the competitor (up to 10). A domain in the list, like velotric.com for a competitor tracked as velotricbike.com, counts as another site of the same brand, so mentions of either count once. Changes apply to counts in your reports right away.
curl -X PATCH "https://searchmention.com/api/v1/projects/$STORE/competitors/velotricbike.com" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "Velotric", "aliases": ["velotric.com"]}'
{
"data": {
"domain": "velotricbike.com",
"name": "Velotric",
"country": null,
"reason": null,
"aliases": ["velotric.com"]
},
"result": "updated",
"totals": { "tracked": 6, "cap": 10, "slots_remaining": 4 }
}
Start a run
POST /projects/{uuid}/runs
Asks every active prompt on every active engine, like the dashboard’s Run prompts button. It needs a token with Can start prompt runs, and it takes two calls, so a script or agent never spends query budget by accident:
- Preview. Without
confirm=truenothing starts. You get200with the cost:prompts×engines=query_units, plus the budget left now and after the run. - Confirm. With
confirm=true(JSON body or query string) the run starts and you get202with the new run and itsid.
Runs draw on the same plan budget as the Run prompts button, so a token can never spend more than you could by clicking. remaining_query_units is null on plans without a limit.
curl -X POST "https://searchmention.com/api/v1/projects/$STORE/runs" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": {
"started": false,
"prompts": 42,
"engines": ["chatgpt", "ai_overview", "gemini", "perplexity"],
"query_units": 168,
"remaining_query_units": 3620,
"remaining_after_run": 3452,
"can_run": true,
"block_reason": null,
"message": null,
"running_run_id": null
}
}
curl -X POST "https://searchmention.com/api/v1/projects/$STORE/runs" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'
{
"data": {
"started": true,
"prompts": 42,
"engines": ["chatgpt", "ai_overview", "gemini", "perplexity"],
"query_units": 168,
"remaining_query_units": 3620,
"remaining_after_run": 3452,
"can_run": true,
"run": {
"id": 4822,
"kind": "batch",
"status": "running",
"started_at": "2026-09-27T10:05:00+00:00",
"completed_at": null,
"progress": { "queries_done": 0, "queries_total": 168, … },
"report_ready": false
}
}
}
When a run can’t start, the preview says so with can_run: false, a block_reason and a readable message. Confirming anyway returns the error below with the same data:
block_reason | Status | What to do |
|---|---|---|
no_active_prompts | 422 | Turn on at least one prompt |
no_engines | 422 | Enable at least one AI engine for the store |
subscription | 403 | The free trial’s queries are used up. Subscribe to keep running prompts |
monthly_limit | 403 | This month’s query budget is used up. It resets next month |
audit_recheck_wait | 403 | The one-time AI audit ran. Its re-check opens 30 days after that run |
audit_used | 403 | The one-time AI audit and its re-check are done. Subscribe to keep running prompts |
not_enough_query_units | 403 | Some budget is left, but less than this run needs. Pause some prompts or wait for the reset |
run_in_progress | 409 | Follow the run in running_run_id instead |
{
"error": "A run is already in progress.",
"data": {
"started": false,
"can_run": false,
"block_reason": "run_in_progress",
"message": "A run is already in progress.",
"running_run_id": 4822,
…
}
}
Get a run
GET /projects/{uuid}/runs/{id}
Progress of one run. Poll it every 30 seconds or so after starting a run.
statusisrunning,completedorfailed.kindisbatchfor a full run;promptandrecheckare smaller runs started in the dashboard.progresscountsqueries_doneofqueries_total(one query is one prompt on one engine),prompts_doneofprompts_total, answers so far (results) and how many named you (mentioned).report_readyturnstruewhen the run has finished with answers, so the report can be fetched.analysis_readyturnstruea few seconds after that, once the written summary is in the report.competitor_intel_statustracks the competitor page checks that follow a run.summary_refreshfollows a summary refresh:statusisnulluntil you ask for one, thenqueued,running,completedorfailed.
curl "https://searchmention.com/api/v1/projects/$STORE/runs/4822" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": {
"id": 4822,
"kind": "batch",
"status": "completed",
"started_at": "2026-09-27T10:05:00+00:00",
"completed_at": "2026-09-27T10:19:42+00:00",
"progress": {
"queries_done": 168,
"queries_total": 168,
"prompts_done": 42,
"prompts_total": 42,
"results": 168,
"mentioned": 61
},
"models_queried": 4,
"engines": ["ai_overview", "chatgpt", "gemini", "perplexity"],
"analysis_ready": true,
"competitor_intel_status": "running",
"summary_refresh": { "status": null, "requested_at": null, "finished_at": null },
"report_ready": true
}
}
Refresh a run’s summary
POST /projects/{uuid}/runs/{id}/summary
Rewrites the summary of a finished run (headline, what’s working, what’s holding you back, what changed) from the answers it already has. Only a store’s first run gets next steps in its summary; after that, ask for them with Suggest next steps. No AI engine is asked again, so it uses no query units. It needs a token with Can start prompt runs.
- Send
"recheck_prices": trueto fix price checks first: the prices AI quoted are read again from its answers (a range such as “$21–$23” is kept as a range instead of $21), and your pages AI linked are checked again against every price they sell at. Then the summary is rewritten. Defaultfalse: summary only. - It runs in the background. You get
202with the run,summary_refresh.statusset toqueued. Poll Get a run until it iscompletedorfailed, then fetch the report withrun_id. - If a new summary can’t be written, the run keeps the one it had and the refresh shows
failed. You can ask again.
curl -X POST "https://searchmention.com/api/v1/projects/$STORE/runs/4822/summary" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"recheck_prices": true}'
{
"data": {
"id": 4822,
"kind": "batch",
"status": "completed",
"analysis_ready": true,
"summary_refresh": {
"status": "queued",
"requested_at": "2026-09-27T11:02:10+00:00",
"finished_at": null
},
"report_ready": true,
…
}
}
When a refresh can’t start you get 409 with a reason:
reason | What to do |
|---|---|
run_not_ready | Only a finished full run with answers has a summary to refresh |
run_in_progress | The store has a run going (running_run_id). Try again when it finishes |
refresh_in_progress | A refresh of this run is already queued or running. Poll Get a run |
Suggest next steps
POST /projects/{uuid}/next-steps
AI picks 1 to 3 next steps from what kept coming back over your last 4 comparable runs (the same engines and ChatGPT model): questions no engine mentioned you for in every run and who it mentioned instead, pages AI kept using in answers that didn’t mention you, competitors it named in most runs that you don’t track, your pages ChatGPT’s search kept finding without citing them, and your cited pages that are broken or show another price. Uses no query units and needs a token with Can start prompt runs.
- The result is saved with the date and the runs it covered, and stays until you ask again. It comes back in the report as
suggested_next_steps. 409withreason: no_runbefore the store’s first finished run.502withreason: failedwhen the steps couldn’t be written; the previous ones are kept.
curl -X POST "https://searchmention.com/api/v1/projects/$STORE/next-steps" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": {
"suggested_at": "2026-10-21T15:04:12+00:00",
"run_ids": [4719, 4760, 4791, 4822],
"latest_run_id": 4822,
"next_steps": ["Publish a guide answering “best energy gels for marathon runners” that compares Näak with GU and Maurten."],
"candidates": [{ "id": "Q1", "type": "lost_question", "subject": "best energy gels for marathon runners", "ref": 337 }]
}
}
Get the report
GET /projects/{uuid}/report
The same data as the shareable AI visibility report, for the latest completed run. Pass run_id for an earlier run. It works with report sharing turned off.
| Query | What it does |
|---|---|
run_id | Optional. A full run of this store. 404 if it isn’t one, 409 with its status if it hasn’t completed |
Before the store’s first completed run you get 404 with This store has no completed run to report on yet.
| Key | What it holds |
|---|---|
store, run | The store and its market, and the run reported on with its engines |
previous_run | The earlier run on the same engines that metrics.changes compares against |
metrics | mention_rate (% of prompts where any engine named you), avg_position, visibility_score, share_of_voice, sentiment, the same per engine, changes since the previous run, and over_time: questions mentioned in each of your recent comparable runs and their average (null after one run) |
analysis_summary | headline, strengths, holding_back, next_steps (a store’s first run only), competitor_insight and changes. null until written |
confirmed_changes | Questions won or lost per engine, and competitors new in or gone from answers, that held for your last 2 runs after 2 runs the other way. null before 4 comparable runs |
suggested_next_steps | The last Suggest next steps result with its date and runs. null if never asked |
run_changes, intent_performance | New wins, losses and position moves since the previous run; mention rate by prompt type |
prompts | Every prompt with each engine’s result, and history: per engine, mentioned or not in each of your last 4 comparable runs, oldest first (null after one run). outcome (won, lost or no_answer), your brand_position and brand_sentiment, who_won when you lost or also_named when you won, and the engine’s full answer in response_text. ChatGPT answers also carry search (whether it searched the web, the queries it ran and every page it read in sources) and read_your_page (true when ChatGPT read one of your pages but didn’t mention or cite you); both are null for other engines and answers before October 2026 |
cited_pages | Your pages AI linked to, with the page check and the price check (below) and the prompts that linked each one |
competitors | tracked competitors (mentions, wins, readiness, scored pages), discovered brands and stores AI named that you don’t track, and cited_domains |
sources | Third-party pages behind the answers: mentions_you (pages that name your store) and outreach (pages AI relied on where you weren’t named, with the brands they recommended) |
Price check on cited pages. When an AI answer quotes a price for one of your pages, the report compares it with the price on the page: ai_price and page_price (with currencies and _display strings), ai_price_max when the AI gave a range (ai_price is the low end, the display reads “$21–$23”, and any page price inside the range is a match), price_matches, ai_price_quote (the AI’s own words), matched_variant when the AI’s price matches one variant, and ai_price_is_compare_at when the AI quoted your compare-at (before sale) price. Pages that no longer load show unreachable with a reason, and redirect_host when a URL now redirects to another site. When the store's host turned our check away (HTTP 429), the page shows not_checked: we don't know whether it loads, so it isn't marked unreachable and has no readiness or page price.
curl "https://searchmention.com/api/v1/projects/$STORE/report?run_id=4822" \
-H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
-H "Accept: application/json"
{
"data": {
"store": { "uuid": "9b1f6c2e-…", "name": "Trail Co.", "domain": "trailco.example", "business_model": "brand", … },
"run": {
"id": 4822,
"status": "completed",
"completed_at": "2026-09-27T10:19:42+00:00",
"engines": ["ai_overview", "chatgpt", "gemini", "perplexity"],
"prompts": 42
},
"previous_run": { "id": 4821, "completed_at": "2026-09-22T09:14:51+00:00" },
"report_url": "https://searchmention.com/reports/k3Jd9…",
"metrics": {
"mention_rate": 38,
"prompts_total": 42,
"prompts_mentioned": 16,
"avg_position": 2.4,
"visibility_score": 31,
"share_of_voice": { "pct": 22, … },
"sentiment": { "score": 78, "label": "Positive", … },
"engines": [
{ "engine": "chatgpt", "label": "ChatGPT", "mention_rate": 45, "mentioned": 19, "answers": 42, "asked": 42, "avg_position": 2.1, … }
],
"changes": { "visibility": { "value": 4, "label": "+4pt", "direction": "up" }, … }
},
"analysis_summary": {
"headline": "Trail Co. is named for ultralight packs but missing from price questions.",
"strengths": [ … ],
"holding_back": [ … ],
"next_steps": [ … ],
"competitor_insight": "…"
},
"prompts": [
{
"prompt_id": 118,
"prompt_text": "best ultralight backpack for a weekend hike",
"query_type": "discovery",
"mentioned_anywhere": true,
"results": [
{
"engine": "chatgpt",
"label": "ChatGPT",
"no_answer": false,
"outcome": "won",
"brand_mentioned": true,
"brand_position": 2,
"brand_sentiment": "recommended",
"who_won": [],
"also_named": [
{ "domain": "ridgeline.example", "name": "Ridgeline Outfitters", "position": 1, "tracked": true, … }
],
"response_text": "For a weekend hike, look at… 2. Trail Co. Ridge 40L…",
"executed_at": "2026-09-27T10:06:13+00:00"
}
]
}
],
"cited_pages": [
{
"url": "https://trailco.example/products/ridge-40l",
"title": "Ridge 40L Ultralight Backpack",
"page_type": "product",
"http_status": 200,
"unreachable": false,
"not_checked": false,
"ai_price": 169.0,
"ai_price_max": null,
"ai_price_display": "$169.00",
"page_price": 189.0,
"page_price_display": "$189.00",
"price_matches": false,
"ai_price_quote": "the Ridge 40L sells for about $169",
"matched_variant": null,
"ai_price_is_compare_at": false,
"prompts": [ { "prompt_id": 118, "prompt_text": "best ultralight backpack for a weekend hike", "engines": ["chatgpt"] } ]
}
],
"competitors": { "tracked": [ … ], "discovered": [ … ], "cited_domains": [ … ], "locked": false },
"sources": { "mentions_you": [ … ], "outreach": [ … ] }
}
}
Answers are the engines’ plain text, without the report page’s highlighting.
Typical flow
A weekly check-in, by a script or an agent:
- List stores with
GET /projectsand pick auuid. - Review prompts with
GET /projects/{uuid}/prompts. Pause or edit anything off-target with the prompt endpoints. - Preview the cost with
POST /projects/{uuid}/runs(noconfirm). - Confirm the run with
confirm=trueand keep the returnedrun.id. - Poll
GET /projects/{uuid}/runs/{id}untilreport_readyistrueandcompetitor_intel_statusiscompleted,failedorskipped, so the summary and competitor page scores are in (stop ifstatusisfailed). - Fetch the report with
GET /projects/{uuid}/report?run_id={id}. - Share it with
POST /projects/{uuid}/report/sharewhen someone else should see it.
Store keys are different
Each store also has its own key: sm_live_… on that store’s Settings page, or sm_pixel_…, which the Shopify app sets up when you connect. Store keys reach one store and are for storefront integrations such as AI Traffic tracking. They don’t work with this API, and an account token doesn’t work in those integrations. To connect a store, follow its setup guide:
- Cloudflare Worker: AI Traffic for any store behind Cloudflare
- Magento 2: AI Traffic connector
- WooCommerce: catalog sync
- Shopify: app install