curl --request GET \
--url https://app.reputably.net/api/entities/AiTrafficDaily \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.reputably.net/api/entities/AiTrafficDaily"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.reputably.net/api/entities/AiTrafficDaily', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.reputably.net/api/entities/AiTrafficDaily",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.reputably.net/api/entities/AiTrafficDaily"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.reputably.net/api/entities/AiTrafficDaily")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.reputably.net/api/entities/AiTrafficDaily")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body[
{
"id": "<string>",
"site_id": "<string>",
"location_id": "<string>",
"date": "<string>",
"crawler_hits": 123,
"verified_hits": 123,
"spoofed_hits": 123,
"unique_agents": 123,
"by_agent": {},
"by_category": {},
"top_paths": "<array>",
"paths_by_category": {},
"referral_visits": 123,
"by_referrer": {},
"referral_paths": "<array>"
}
]List AI traffic daily rollups
Reads AI traffic daily rollups in the selected workspace. Filter with q, a JSON object of field matches. Reads only: writes through this route are refused for every entity and every credential.
curl --request GET \
--url https://app.reputably.net/api/entities/AiTrafficDaily \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.reputably.net/api/entities/AiTrafficDaily"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.reputably.net/api/entities/AiTrafficDaily', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.reputably.net/api/entities/AiTrafficDaily",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.reputably.net/api/entities/AiTrafficDaily"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.reputably.net/api/entities/AiTrafficDaily")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.reputably.net/api/entities/AiTrafficDaily")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body[
{
"id": "<string>",
"site_id": "<string>",
"location_id": "<string>",
"date": "<string>",
"crawler_hits": 123,
"verified_hits": 123,
"spoofed_hits": 123,
"unique_agents": 123,
"by_agent": {},
"by_category": {},
"top_paths": "<array>",
"paths_by_category": {},
"referral_visits": 123,
"by_referrer": {},
"referral_paths": "<array>"
}
]Authorizations
An agent key (rpk_...) created in Settings → API & MCP, sent as Authorization: Bearer rpk_.... Bearer only: a cookie session can never drive this API. An OAuth 2.1 access token obtained from the same host works identically and lands on the same ceiling.
Headers
Which workspace to read. Omit it and you get the account’s home workspace, which on an agency account is often not where the live businesses are. A workspace this credential cannot read is refused with 403 rather than quietly answered from the default.
Query Parameters
JSON filter object, for example {"needs_response":true}.
Field to sort on.
Maximum rows to return.
Rows to skip, for paging.
Response
Matching AI traffic daily rollups.
Unique id.
Denormalized from the site so the business-scope clamp (lib/businessScope.js) is a single-field query, same reason VisibilityRun carries it.
Hits whose source IP was inside the operator's published CIDR ranges (lib/aiCrawlerRanges.js). The IP itself is checked at ingest and discarded — it is never stored anywhere.
Hits where we HAD the operator's ranges and the source IP was not in them — i.e. something wearing the crawler's user-agent. Distinct from unchecked (no IP reported, or the operator publishes no ranges), which is crawler_hits - verified_hits - spoofed_hits.
{ 'GPTBot': { hits, verified, spoofed, category, operator }, ... } — only user-agents lib/aiCrawlers.js could identify. Unclassified traffic is dropped, never bucketed as 'other'.
{ ai_answers, search_index, training, other_ai } hit counts. ai_answers is the closest thing to an impression; training is high-volume and least actionable.
[{ path, hits }] capped at 25, descending. Capped because an unbounded path map on a large site is what would blow up the row size this schema exists to control.
{ ai_answers: [{ path, hits }], search_index: [...], ... } — each category independently capped at 12, so a low-volume category still shows its own pages instead of being crowded out. Per CATEGORY and not per agent on purpose: 11 bots x 25 paths would be ~275 extra entries a row, four buckets x 12 is ~48. Page data can therefore narrow to a category but never to a single crawler.
{ 'chatgpt.com': 8, ... } — humans who clicked through from an AI assistant. Only the browser beacon can see these.
[{ path, visits }] capped at 25, descending.