API

SearchMention API Reference

Updated September 27, 2026

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/json on 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.
Treat the token like a password. It reads every store on your account. Keep it on a server or in your agent’s secret settings, never in browser code or a public repository.

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:

shell
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:

StatusExample bodyWhen
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.

Pagination shape
{
  "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.

MethodPathWhat it returns
GET/projectsEvery store on the account
GET/projects/{uuid}One store’s brand, market, competitors and prompt setup
GET/projects/{uuid}/catalog/categoriesWhat the store sells, by category
GET/projects/{uuid}/catalog/productsWhat the store sells, by product
GET/projects/{uuid}/promptsThe full prompt library
POST/projects/{uuid}/promptsAdd 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}/competitorsTrack 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}/runsA run’s cost, or a started run
GET/projects/{uuid}/runs/{id}A run’s progress
POST/projects/{uuid}/runs/{id}/summaryRewrite a finished run’s summary
POST/projects/{uuid}/next-stepsSuggest next steps from your last runs
GET/projects/{uuid}/reportThe full AI visibility report as data
POST/projects/{uuid}/report/shareTurn on the public report link and get it

List stores

GET /projects

Every store on your account, newest first. Archived stores are left out.

QueryWhat it does
qOnly stores whose name or domain contains this text
page, per_pageDefault 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.

Request
curl "https://searchmention.com/api/v1/projects?q=trail" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response (trimmed)
{
  "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:

KeyWhat it holds
brandThe name AI answers are checked for, its aliases (other names and domains) and business_model: brand, retailer or mixed
marketcountry, language, currency, and the search_country / search_location AI answers are localized to
wizardThe prompt setup you saved: brief, chosen categories and products, competitors, goals and notes. null until you’ve set up prompts
detected_briefWhat SearchMention read from the storefront: summary, product lines, store categories, featured products, where it ships
competitorsTracked competitors with domain, name, country, reason and aliases
catalogWhether a product catalog is synced, product and category counts, and imported pages by type
readinessThe latest AI readiness score and pages scanned, or null
promptstotal, active, your plan’s cap and slots_remaining
runsThe 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_urlThe shared report link, or null

Store keys and platform credentials are never returned.

Request
curl "https://searchmention.com/api/v1/projects/$STORE" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response (trimmed)
{
  "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:

sourceWhere the rows come from
catalogYour synced product catalog
pagesCategory or product pages found on the site’s sitemap
detectedCollections and products SearchMention read from the storefront. Often all a store added by URL has
QueryWhat it does
qCategory name, or product title, SKU or handle (page title or URL for pages)
include_inactiveProducts only: 1 includes draft and archived catalog products
page, per_pageDefault 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.

Request
curl "https://searchmention.com/api/v1/projects/$STORE/catalog/products?q=backpack&per_page=20" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response (trimmed)
{
  "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.

QueryWhat it does
active1 active only, 0 paused only. Leave out for both
query_typetransactional, discovery, comparison, price, use_case, attribute or brand
qOnly prompts containing this text
page, per_pageDefault 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.

Request
curl "https://searchmention.com/api/v1/projects/$STORE/prompts?active=1" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response (trimmed)
{
  "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.

CallBodyResult
POST /promptsprompt_text (5–500 characters), query_type201. 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_active200. 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.

Request
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"}'
Response (trimmed)
{
  "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).

CallBodyResult
POST /competitorsdomain, optional name and aliases201. 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, aliases200. 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.

Request
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"]}'
Response
{
  "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:

  1. Preview. Without confirm=true nothing starts. You get 200 with the cost: prompts × engines = query_units, plus the budget left now and after the run.
  2. Confirm. With confirm=true (JSON body or query string) the run starts and you get 202 with the new run and its id.

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.

1. Preview the cost
curl -X POST "https://searchmention.com/api/v1/projects/$STORE/runs" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response · 200
{
  "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
  }
}
2. Confirm and start
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}'
Response · 202 (trimmed)
{
  "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_reasonStatusWhat to do
no_active_prompts422Turn on at least one prompt
no_engines422Enable at least one AI engine for the store
subscription403The free trial’s queries are used up. Subscribe to keep running prompts
monthly_limit403This month’s query budget is used up. It resets next month
audit_recheck_wait403The one-time AI audit ran. Its re-check opens 30 days after that run
audit_used403The one-time AI audit and its re-check are done. Subscribe to keep running prompts
not_enough_query_units403Some budget is left, but less than this run needs. Pause some prompts or wait for the reset
run_in_progress409Follow the run in running_run_id instead
Response · 409
{
  "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.

  • status is running, completed or failed. kind is batch for a full run; prompt and recheck are smaller runs started in the dashboard.
  • progress counts queries_done of queries_total (one query is one prompt on one engine), prompts_done of prompts_total, answers so far (results) and how many named you (mentioned).
  • report_ready turns true when the run has finished with answers, so the report can be fetched.
  • analysis_ready turns true a few seconds after that, once the written summary is in the report. competitor_intel_status tracks the competitor page checks that follow a run.
  • summary_refresh follows a summary refresh: status is null until you ask for one, then queued, running, completed or failed.
Request
curl "https://searchmention.com/api/v1/projects/$STORE/runs/4822" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response
{
  "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": true to 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. Default false: summary only.
  • It runs in the background. You get 202 with the run, summary_refresh.status set to queued. Poll Get a run until it is completed or failed, then fetch the report with run_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.
Request
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}'
Response · 202 (trimmed)
{
  "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:

reasonWhat to do
run_not_readyOnly a finished full run with answers has a summary to refresh
run_in_progressThe store has a run going (running_run_id). Try again when it finishes
refresh_in_progressA 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.
  • 409 with reason: no_run before the store’s first finished run. 502 with reason: failed when the steps couldn’t be written; the previous ones are kept.
Request
curl -X POST "https://searchmention.com/api/v1/projects/$STORE/next-steps" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response · 200
{
  "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.

QueryWhat it does
run_idOptional. 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.

KeyWhat it holds
store, runThe store and its market, and the run reported on with its engines
previous_runThe earlier run on the same engines that metrics.changes compares against
metricsmention_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_summaryheadline, strengths, holding_back, next_steps (a store’s first run only), competitor_insight and changes. null until written
confirmed_changesQuestions 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_stepsThe last Suggest next steps result with its date and runs. null if never asked
run_changes, intent_performanceNew wins, losses and position moves since the previous run; mention rate by prompt type
promptsEvery 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_pagesYour pages AI linked to, with the page check and the price check (below) and the prompts that linked each one
competitorstracked competitors (mentions, wins, readiness, scored pages), discovered brands and stores AI named that you don’t track, and cited_domains
sourcesThird-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.

Request
curl "https://searchmention.com/api/v1/projects/$STORE/report?run_id=4822" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response (trimmed)
{
  "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.

Share the report

POST /projects/{uuid}/report/share

Turns on the public report link, the same as Share on the report page, and returns it. Anyone with the link can open the report without an account; it always shows the store’s latest completed run. It needs a token with Can edit prompts.

  • If sharing is already on, you get the same link back with "result": "already_shared", so links you already sent keep working. A new link gives "result": "enabled".
  • The store needs at least one completed run first, or you get 422 with Run prompts at least once before sharing a report.
  • Turning sharing off stays on the report page, because it breaks every link already sent.
Request
curl -X POST "https://searchmention.com/api/v1/projects/$STORE/report/share" \
  -H "Authorization: Bearer $SEARCHMENTION_TOKEN" \
  -H "Accept: application/json"
Response
{
  "result": "enabled",
  "data": {
    "report_url": "https://searchmention.com/reports/k3Jd9…",
    "report_views": { "count": 0, "first_viewed_at": null, "last_viewed_at": null }
  }
}

Typical flow

A weekly check-in, by a script or an agent:

  1. List stores with GET /projects and pick a uuid.
  2. Review prompts with GET /projects/{uuid}/prompts. Pause or edit anything off-target with the prompt endpoints.
  3. Preview the cost with POST /projects/{uuid}/runs (no confirm).
  4. Confirm the run with confirm=true and keep the returned run.id.
  5. Poll GET /projects/{uuid}/runs/{id} until report_ready is true and competitor_intel_status is completed, failed or skipped, so the summary and competitor page scores are in (stop if status is failed).
  6. Fetch the report with GET /projects/{uuid}/report?run_id={id}.
  7. Share it with POST /projects/{uuid}/report/share when someone else should see it.
Using an AI agent? Give it a token without Can start prompt runs if it should only read. If it may run prompts, ask it to show you the preview and wait for your yes before it confirms.

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: