curl --request GET \
--url https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scores \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scores"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scores', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": [
{
"date": "2023-12-25",
"score": 123,
"score_version": "<string>",
"panel_version": "<string>",
"presence_pct": 123,
"avg_position": 123,
"avg_sentiment": 123,
"source_visibility_pct": 123,
"composite_score": 123,
"total_mentions": 0,
"total_runs": 0,
"citation_count": 0
}
],
"pagination": {
"next_cursor": "<string>",
"has_more": true
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}Get score series
Score time series.
Ascending by date over the window. Without country: omit platform for the AIS Score series (each point carrying the scoring generation PERSISTED with it, or null where that was not recorded); set it for one platform’s composite series (score_version always null); competitor ids are accepted — sentiment is null by design, a brand scored under several own brands keeps the latest pairing’s numbers (this series is the persisted row verbatim, so that caveat covers an own brand tracked as a sibling’s rival as well), and score_version follows the same rule as an own brand’s, since the same writer scores competitors, so a competitor point scored since that generation was first persisted carries it and only older points are null. With country: the per-market series — every item is a CountryScorePoint whose score is visibility_index (score_kind: "visibility_index"), NOT the headline AIS Score; platform narrows it to one platform’s rows; own brands only, and an unsupported market is 400 validation_error (unsupported_country). The two series issue distinct cursors.
curl --request GET \
--url https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scores \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scores"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scores', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": [
{
"date": "2023-12-25",
"score": 123,
"score_version": "<string>",
"panel_version": "<string>",
"presence_pct": 123,
"avg_position": 123,
"avg_sentiment": 123,
"source_visibility_pct": 123,
"composite_score": 123,
"total_mentions": 0,
"total_runs": 0,
"citation_count": 0
}
],
"pagination": {
"next_cursor": "<string>",
"has_more": true
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}Authorizations
A Surfais API key — an organisation key or a partner key. Issued by Surfais; shown once. Rotate by creating a new key, then revoking the old one.
Path Parameters
Organisation id. Org keys: the key's own org. Partner keys: any org with an active link. Anything else is 404.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$Brand id within the org (own brand or competitor row); 404 when not in the org. Writes that add tracking need an own (400 validation_error with details[].code = "own_brand_only"), non-archived (409 brand_archived) brand.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$Query Parameters
Page size (1–200). Default 50.
1 <= x <= 200Opaque cursor from a previous page's pagination.next_cursor. It pins the walk — its sort anchor, the resolved date window and the FILTERS the first page ran under — so continue with the cursor alone: omitted filters are restored from it. Naming a different value for one of them is 400 invalid_cursor (details.reason = "filter_changed", details.parameter the one that differs); start again from the first page to change a filter.
2048Window start (inclusive). Default: to minus 30 days.
^\d{4}-\d{2}-\d{2}$Window end (inclusive). Default: today (UTC). Max span 366 days; from after to is 400 validation_error. On a paginated endpoint the resolved window is pinned by the first page and carried in the cursor — continue with the cursor alone; naming a different from/to on a continuation is 400 invalid_cursor (details.reason = "window_changed").
^\d{4}-\d{2}-\d{2}$Omit for the all-platform aggregate series (the AIS Score; with country, the market's all-platform visibility_index); set for one platform's series (its composite score; with country, that platform's visibility_index in the market).
perplexity, chatgpt, gemini, claude, ai_overviews Per-market series: data items are CountryScorePoint (score = visibility_index, score_kind = visibility_index) instead of ScorePoint. Must be a market Surfais scans (400 validation_error, details[].code = "unsupported_country" otherwise); a supported market with no rows is an empty series. Own brands only (400 validation_error with details[].code = "own_brand_only" for a competitor id). Bound into that series' cursor: continue with the cursor alone and the market is restored, and naming a different one is 400 invalid_cursor with details.reason = "filter_changed".
^[A-Za-z]{2}$