Get a content check
curl --request GET \
--url https://data.otterly.ai/v1/audits/geo/content-checks/{checkId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://data.otterly.ai/v1/audits/geo/content-checks/{checkId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://data.otterly.ai/v1/audits/geo/content-checks/{checkId}', 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://data.otterly.ai/v1/audits/geo/content-checks/{checkId}",
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://data.otterly.ai/v1/audits/geo/content-checks/{checkId}"
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://data.otterly.ai/v1/audits/geo/content-checks/{checkId}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://data.otterly.ai/v1/audits/geo/content-checks/{checkId}")
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": "01HXCONTENT5XY8Z2N3KQ7VAW4",
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"url": "https://www.adidas.com/us/running",
"status": "completed",
"createdDate": "2026-01-13T09:46:46.251Z",
"structuralAnalysis": {
"overallScore": 68.3,
"categoryScores": {
"metadata": 92,
"technical": 100,
"structure": 64,
"content": 46
},
"metadata": {
"score": 92,
"breakdown": {
"titleTag": {
"maxScore": 30,
"score": 30
},
"openGraphTags": {
"maxScore": 15,
"score": 12
},
"metaDescription": {
"maxScore": 30,
"score": 25
},
"structuredData": {
"maxScore": 25,
"score": 25
}
}
},
"technical": {
"score": 100,
"breakdown": {
"mobileFriendlyIndicators": {
"maxScore": 30,
"score": 30
},
"validHtmlStructure": {
"maxScore": 40,
"score": 40
},
"robotsAndCrawling": {
"maxScore": 20,
"score": 20
},
"languageDeclaration": {
"maxScore": 10,
"score": 10
}
}
},
"structure": {
"score": 64,
"breakdown": {
"listsAndStructuredContent": {
"maxScore": 20,
"score": 0
},
"paragraphStructure": {
"maxScore": 15,
"score": 10
},
"headingHierarchy": {
"maxScore": 30,
"score": 30
},
"semanticElements": {
"maxScore": 25,
"score": 14
},
"navigationStructure": {
"maxScore": 10,
"score": 10
}
}
},
"content": {
"score": 46,
"breakdown": {
"richContentElements": {
"maxScore": 60,
"score": 36
},
"contentVariety": {
"maxScore": 40,
"score": 10
}
}
}
},
"dynamicContent": {
"differenceDescription": "Found 1/2 matching chunks. Missing 1 chunks from dynamic content.",
"score": 50,
"staticLength": 9738,
"dynamicLength": 4834,
"isPotentiallyBlocked": false
},
"contentAnalysis": {
"overallScore": 68,
"analysis": {
"semanticRepetition": {
"score": 70,
"description": "Key ideas are repeated appropriately, but some terms recur without variation. Reinforce semantic signals by adding natural variants of the most frequent phrases."
},
"summaryBlock": {
"score": 50,
"description": "There is no explicit 'Key Takeaways' recap. Add a short summary block (3–5 bullets) covering the problem, approach, top results, and primary recommendation."
},
"clarityAndAssertiveness": {
"score": 70,
"description": "Some hedged phrasing reduces authority. Replace hedges with direct claims backed by data and use active voice."
},
"specificity": {
"score": 60,
"description": "Numeric outcomes are present but lack context such as baselines, sample sizes, and timelines. Add concrete ranges and dates to strengthen credibility."
}
}
},
"crawlerIdentity": "ChatGPT-User",
"sendOtterlyHeader": false
}{
"message": "Validation failed",
"target": "query",
"errors": [
{
"path": "country",
"message": "Required",
"code": "invalid_type"
}
]
}{
"message": "Report not found"
}{
"message": "Report not found"
}{
"message": "Report not found"
}Audits
Get a content check
GET
/
v1
/
audits
/
geo
/
content-checks
/
{checkId}
Get a content check
curl --request GET \
--url https://data.otterly.ai/v1/audits/geo/content-checks/{checkId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://data.otterly.ai/v1/audits/geo/content-checks/{checkId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://data.otterly.ai/v1/audits/geo/content-checks/{checkId}', 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://data.otterly.ai/v1/audits/geo/content-checks/{checkId}",
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://data.otterly.ai/v1/audits/geo/content-checks/{checkId}"
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://data.otterly.ai/v1/audits/geo/content-checks/{checkId}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://data.otterly.ai/v1/audits/geo/content-checks/{checkId}")
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": "01HXCONTENT5XY8Z2N3KQ7VAW4",
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"url": "https://www.adidas.com/us/running",
"status": "completed",
"createdDate": "2026-01-13T09:46:46.251Z",
"structuralAnalysis": {
"overallScore": 68.3,
"categoryScores": {
"metadata": 92,
"technical": 100,
"structure": 64,
"content": 46
},
"metadata": {
"score": 92,
"breakdown": {
"titleTag": {
"maxScore": 30,
"score": 30
},
"openGraphTags": {
"maxScore": 15,
"score": 12
},
"metaDescription": {
"maxScore": 30,
"score": 25
},
"structuredData": {
"maxScore": 25,
"score": 25
}
}
},
"technical": {
"score": 100,
"breakdown": {
"mobileFriendlyIndicators": {
"maxScore": 30,
"score": 30
},
"validHtmlStructure": {
"maxScore": 40,
"score": 40
},
"robotsAndCrawling": {
"maxScore": 20,
"score": 20
},
"languageDeclaration": {
"maxScore": 10,
"score": 10
}
}
},
"structure": {
"score": 64,
"breakdown": {
"listsAndStructuredContent": {
"maxScore": 20,
"score": 0
},
"paragraphStructure": {
"maxScore": 15,
"score": 10
},
"headingHierarchy": {
"maxScore": 30,
"score": 30
},
"semanticElements": {
"maxScore": 25,
"score": 14
},
"navigationStructure": {
"maxScore": 10,
"score": 10
}
}
},
"content": {
"score": 46,
"breakdown": {
"richContentElements": {
"maxScore": 60,
"score": 36
},
"contentVariety": {
"maxScore": 40,
"score": 10
}
}
}
},
"dynamicContent": {
"differenceDescription": "Found 1/2 matching chunks. Missing 1 chunks from dynamic content.",
"score": 50,
"staticLength": 9738,
"dynamicLength": 4834,
"isPotentiallyBlocked": false
},
"contentAnalysis": {
"overallScore": 68,
"analysis": {
"semanticRepetition": {
"score": 70,
"description": "Key ideas are repeated appropriately, but some terms recur without variation. Reinforce semantic signals by adding natural variants of the most frequent phrases."
},
"summaryBlock": {
"score": 50,
"description": "There is no explicit 'Key Takeaways' recap. Add a short summary block (3–5 bullets) covering the problem, approach, top results, and primary recommendation."
},
"clarityAndAssertiveness": {
"score": 70,
"description": "Some hedged phrasing reduces authority. Replace hedges with direct claims backed by data and use active voice."
},
"specificity": {
"score": 60,
"description": "Numeric outcomes are present but lack context such as baselines, sample sizes, and timelines. Add concrete ranges and dates to strengthen credibility."
}
}
},
"crawlerIdentity": "ChatGPT-User",
"sendOtterlyHeader": false
}{
"message": "Validation failed",
"target": "query",
"errors": [
{
"path": "country",
"message": "Required",
"code": "invalid_type"
}
]
}{
"message": "Report not found"
}{
"message": "Report not found"
}{
"message": "Report not found"
}Authorizations
Provide your API key as a Bearer token: Authorization: Bearer YOUR_API_KEY.
Path Parameters
Content check identifier.
Minimum string length:
1Example:
"01HX7K2YV9D3M8N0G6Q5R4S3T2"
Response
The content check.
Minimum string length:
1Example:
"01HX7K2YV9D3M8N0G6Q5R4S3T2"
Available options:
pending, completed, failed The crawler identity that was impersonated when fetching the URL.
Whether the Otterly identifying header was sent when fetching the URL.
How well the page is structured for machine reading, with per-category breakdowns.
Show child attributes
Show child attributes
Comparison of the static (no-JS) page against the JavaScript-rendered page.
Show child attributes
Show child attributes
AI content-readiness analysis: per-dimension scores and recommendations for making the page easier for AI to quote and understand.
Show child attributes
Show child attributes