Skip to main content

AI

Crawlers

Which AI crawlers read your site, what for, and which visits were proved rather than claimed.

GET/sites/{siteId}/ai/crawlers
Crawlers
curl --request GET \
  --url 'https://truestat.io/api/v1/sites/{siteId}/ai/crawlers' \
  --header 'Authorization: Bearer <token>'
{
  "data": [
    {
      "views": 450,
      "purpose": "ai_training",
      "operator": "gpt",
      "spoofed_views": 3,
      "verified_views": 447
    },
    {
      "views": 470,
      "purpose": "ai_training",
      "operator": "claude",
      "spoofed_views": 0,
      "verified_views": 467
    }
  ],
  "meta": {
    "page": {
      "limit": 100,
      "offset": 0,
      "has_more": false
    },
    "range": {
      "to": "2026-09-16T13:40:00.000Z",
      "tz": "UTC",
      "from": "2026-09-09T13:40:00.000Z",
      "clamped": false
    },
    "site_id": "6fed8980-a119-4a9c-a4d4-53e448ef9f9e"
  }
}

Every AI crawler that fetched a page, grouped by who it was and why it came.

A user-agent is a claim, not a fact

Anything can call itself GPTBot. Setting a header takes one line, and a scraper that wants to look legitimate sets it.

So every view here is checked: TrueStat takes the address the request came from, asks DNS what name it belongs to, and asks that name what address it resolves to. A crawler that is who it says it is survives the round trip.

That check sorts your traffic into three groups, and the third is the one worth understanding:

What it means
verified_viewsThe round trip came back to the operator's own network. This really was GPTBot.
spoofed_viewsThe round trip came back to somebody else. Something wore the name and did not own it.
the restThe operator publishes nothing to check against, so nobody can verify it — us included.

That third group has no field. Subtract the two from views to get it.

Why the unverifiable group is not reported as a problem

It would be easy to show a number called "unverified" in red and let you draw a conclusion from it. It would also be misleading.

Some operators publish their address ranges and support reverse DNS. Others publish nothing at all. A crawler in the second group is not behaving worse than one in the first — it simply cannot be checked, by any tool. Counting those as suspicious would blame a crawler for its operator's documentation, and counting them as genuine would be a guess made on your behalf.

We report what was proved, what was disproved, and leave the remainder plainly unclaimed.

spoofed_views is the number to act on. On a real site in a single week: GPT sent 450 training fetches, 447 verified and 3 spoofed. Those three were something else wearing OpenAI's name.

One operator, several rows

purpose is why the crawler came, and the same operator crawls for different reasons:

  • ai_training — collecting pages to train on later

  • search — building an index

  • ai_assistant — fetching a page to answer somebody's question right now

  • other — a recognised crawler with an unrecognised purpose

Your dashboard labels that third one AI answers, which reads better on a screen. The API returns the stored value, and it is the one to filter on.

So gpt appears once under ai_training and again under search. Sum the rows for an operator's total, or call traffic split, which does that grouping and adds people as a row.

The distinction is the reason this endpoint exists. A fetch that answers a question now is traffic in the ordinary sense — somebody wanted to know something and your page was the answer. A fetch that harvests pages for a training run is not: nobody is reading, and nobody will arrive from it. Reporting both as "bot traffic" collapses the one difference that matters.

Crawler traffic is always counted here

bots=excluded is the default on the rest of the API, because a report about people that quietly counts machines is wrong.

On the /ai/* endpoints the machines are the report, so the parameter is ignored. Passing bots=excluded here does not empty the response.

The same numbers, on a screen

These are the figures the AI panel draws. If you want to see them before you write any code, which crawlers walks through the same list in the dashboard.

Authorizations

Authorizationstringheaderrequired

An API key from Settings → API keys. Scopes available: analytics:read, sites:read, sites:write, settings:read, settings:write, goals:read, goals:write, funnels:read, funnels:write, notes:read, notes:write, alerts:read, alerts:write, team:read, team:write, revenue:read, revenue:write.

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path parameters

siteIdstring (uuid)pathrequired
The site's id, from `GET /sites`. A site that is not yours and a site that does not exist both answer 403, so this cannot be used to discover other people's ids.

Query parameters

periodenum<string>query
A named window, resolved in `tz`. Cannot be combined with `from`/`to` — a request carrying both is refused rather than guessed at.
options:todayyesterdaylast24hlast7dlast30dlast12mmonthyearall
fromstring (date-time)query
Start of the window, ISO 8601 with an offset. Must be given together with `to`.
tostring (date-time)query
End of the window. Must be given together with `from`.
tzstringquerydefault: UTC
IANA timezone for day boundaries. `period=today&tz=Europe/Istanbul` means the reader's today, not the server's. Defaults to UTC.
botsenum<string>querydefault: excluded
Whether crawler traffic is counted. Excluded by default — the `/ai/*` endpoints report it separately and always include it.
options:excludedincluded
limitintegerquerydefault: 100
Rows per page, 1 to 1000.
offsetintegerquerydefault: 0
Rows to skip.
filter_pathstringquery
Narrow the result to one `path`.
filter_path_patternstringquery
Narrow the result to one `path_pattern`.
filter_hostnamestringquery
Narrow the result to one `hostname`.
filter_entry_pathstringquery
Narrow the result to one `entry_path`.
filter_exit_linkstringquery
Narrow the result to one `exit_link`.
filter_file_downloadstringquery
Narrow the result to one `file_download`.
filter_goalstringquery
Narrow the result to one `goal`.
filter_browserstringquery
Narrow the result to one `browser`.
filter_osstringquery
Narrow the result to one `os`.
filter_devicestringquery
Narrow the result to one `device`.
filter_countrystringquery
Narrow the result to one `country`.
filter_regionstringquery
Narrow the result to one `region`.
filter_citystringquery
Narrow the result to one `city`.
filter_referrerstringquery
Narrow the result to one `referrer`.
filter_channelstringquery
Narrow the result to one `channel`.
filter_utm_sourcestringquery
Narrow the result to one `utm_source`.
filter_utm_mediumstringquery
Narrow the result to one `utm_medium`.
filter_utm_campaignstringquery
Narrow the result to one `utm_campaign`.
filter_gad_campaign_idstringquery
Narrow the result to one `gad_campaign_id`.
filter_utm_termstringquery
Narrow the result to one `utm_term`.
filter_bot_kindstringquery
Narrow the result to one `bot_kind`.

Response

application/json

The answer.

dataunknown
See the example.
metaobject