AI Agent Traffic
Tinylytics records visits from known AI assistants and crawlers separately from human page views. ChatGPT fetching /pricing to answer a question does not inflate your visitor counts.
The embed script cannot see most of this traffic. Crawlers request raw HTML and skip JavaScript. Server-side requests (the API or a backend snippet) are what capture them.
What gets recorded
Known user agents are classified into:
- AI answers — a user asked an assistant, and it fetched your page (
ChatGPT-User,Claude-User,Perplexity-User) - Indexing — search crawlers for AI or classic search surfaces (
OAI-SearchBot,PerplexityBot) - Training — crawlers collecting public pages (
GPTBot,ClaudeBot,Google-Extended) - AI crawler — other named AI bots
Generic scrapers and spoofed Chrome user agents stay out of this list. Those are still dropped from human hits.
Where to see it
On a site overview, the AI agents card shows counts by agent and by page for the current date filter. It is hidden by default. Turn it on under UI settings → Stats sections. It is not included in views, unique visitors, or public stats.
Capture crawlers that skip JavaScript
If the request never reaches Tinylytics, we cannot record it. Send the request from your backend, middleware, or edge:
curl -X POST "https://tinylytics.app/api/v1/sites/SITE_ID/agent_visits" \
-H "Authorization: Bearer tly-fa-your-api-key" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"path": "/pricing",
"url": "https://example.com/pricing",
"user_agent": "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0"
}'
Use a full-access API key. Unknown browser user agents are ignored on this endpoint. Posting the same payload to POST /sites/:id/hits also classifies known crawlers as agent visits instead of human hits.
Rails middleware example:
class TrackAiCrawlers
def initialize(app)
@app = app
end
def call(env)
request = ActionDispatch::Request.new(env)
track(request) if request.get? || request.head?
@app.call(env)
end
private
def track(request)
Thread.new do
Faraday.post("#{ENV.fetch("TINYLYTICS_API_BASE_URL")}/api/v1/sites/#{ENV.fetch("TINYLYTICS_SITE_ID")}/agent_visits") do |req|
req.headers["Authorization"] = "Bearer #{ENV.fetch("TINYLYTICS_API_KEY")}"
req.headers["Content-Type"] = "application/json"
req.body = {
path: request.path,
url: request.original_url,
user_agent: request.user_agent,
referrer: request.referer
}.to_json
end
end
end
end
Do not wait on that HTTP call in the request. Fire and forget, or enqueue a job.
API
GET /api/v1/sites/:id/agent_visits— list visits (read-only key is enough)POST /api/v1/sites/:id/agent_visits— record one visit (full-access key)
See the API documentation for parameters.