curl --request POST \
--url https://data.otterly.ai/v1/reports/brand \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"reportTitle": "Adidas Visibility Report",
"brand": "Adidas",
"brandDomain": "adidas.com",
"promptIds": [
"01HXP1DRTM5G8Z2N3KQ7VAW4PA"
],
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
'import requests
url = "https://data.otterly.ai/v1/reports/brand"
payload = {
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"reportTitle": "Adidas Visibility Report",
"brand": "Adidas",
"brandDomain": "adidas.com",
"promptIds": ["01HXP1DRTM5G8Z2N3KQ7VAW4PA"],
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
workspaceId: '01HX7K2YV9D3M8N0G6Q5R4S3T2',
reportTitle: 'Adidas Visibility Report',
brand: 'Adidas',
brandDomain: 'adidas.com',
promptIds: ['01HXP1DRTM5G8Z2N3KQ7VAW4PA'],
competitors: [{brand: 'Nike', brandDomain: 'nike.com'}]
})
};
fetch('https://data.otterly.ai/v1/reports/brand', 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/reports/brand",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'workspaceId' => '01HX7K2YV9D3M8N0G6Q5R4S3T2',
'reportTitle' => 'Adidas Visibility Report',
'brand' => 'Adidas',
'brandDomain' => 'adidas.com',
'promptIds' => [
'01HXP1DRTM5G8Z2N3KQ7VAW4PA'
],
'competitors' => [
[
'brand' => 'Nike',
'brandDomain' => 'nike.com'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://data.otterly.ai/v1/reports/brand"
payload := strings.NewReader("{\n \"workspaceId\": \"01HX7K2YV9D3M8N0G6Q5R4S3T2\",\n \"reportTitle\": \"Adidas Visibility Report\",\n \"brand\": \"Adidas\",\n \"brandDomain\": \"adidas.com\",\n \"promptIds\": [\n \"01HXP1DRTM5G8Z2N3KQ7VAW4PA\"\n ],\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://data.otterly.ai/v1/reports/brand")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"workspaceId\": \"01HX7K2YV9D3M8N0G6Q5R4S3T2\",\n \"reportTitle\": \"Adidas Visibility Report\",\n \"brand\": \"Adidas\",\n \"brandDomain\": \"adidas.com\",\n \"promptIds\": [\n \"01HXP1DRTM5G8Z2N3KQ7VAW4PA\"\n ],\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://data.otterly.ai/v1/reports/brand")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"workspaceId\": \"01HX7K2YV9D3M8N0G6Q5R4S3T2\",\n \"reportTitle\": \"Adidas Visibility Report\",\n \"brand\": \"Adidas\",\n \"brandDomain\": \"adidas.com\",\n \"promptIds\": [\n \"01HXP1DRTM5G8Z2N3KQ7VAW4PA\"\n ],\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "01HXBR1DGM5XY8Z2N3KQ7VAW4P",
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"brand": "Adidas",
"brandVariations": [
"adidas",
"Adidas AG"
],
"brandDomain": "adidas.com",
"brandDomainVariations": [
"adidas.de",
"adidas.co.uk"
],
"brandDomainWildcard": false,
"brandRegex": null,
"countries": [
"us",
"de",
"uk"
],
"competitors": [
{
"brand": "Nike",
"brandVariations": [
"nike",
"Nike Inc."
],
"brandDomain": "nike.com",
"brandDomainVariations": [
"nike.de",
"nike.co.uk"
],
"brandDomainWildcard": false,
"brandRegex": null
}
],
"reportTitle": "Adidas Visibility Report",
"createdDate": "2024-11-01T12:00:00.000Z",
"updatedDate": "2025-05-01T08:30:00.000Z",
"promptIds": [
"01HXP1DRTM5G8Z2N3KQ7VAW4PA",
"01HXP2DRTM5G8Z2N3KQ7VAW4PB"
],
"countryPromptsTotal": {
"us": 50,
"de": 35,
"uk": 28
},
"tags": [
{
"id": "01HX8AB3CDE4FG5HJ6KL7MN8PQ",
"name": "Running Shoes",
"color": "orange"
}
]
}{
"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"
}{
"message": "Report not found"
}Create a brand report
Creates a brand report in a workspace, the same as creating it in the UI. The prompts must already exist in that workspace — create them with POST /v1/workspaces/{id}/prompts — and the report tracks their countries. Domains are normalized (protocol, www. and path stripped). Detect the brand either by brand and brandVariations, or by brandRegex together with reportTitle. Competitors can come straight from POST /v1/competitors/suggestions. Data for the report is collected asynchronously. Sending the same request again within 2 minutes returns the report the first request created instead of a second one; if that report is still being set up, the response is a 409 carrying its ID.
curl --request POST \
--url https://data.otterly.ai/v1/reports/brand \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"reportTitle": "Adidas Visibility Report",
"brand": "Adidas",
"brandDomain": "adidas.com",
"promptIds": [
"01HXP1DRTM5G8Z2N3KQ7VAW4PA"
],
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
'import requests
url = "https://data.otterly.ai/v1/reports/brand"
payload = {
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"reportTitle": "Adidas Visibility Report",
"brand": "Adidas",
"brandDomain": "adidas.com",
"promptIds": ["01HXP1DRTM5G8Z2N3KQ7VAW4PA"],
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
workspaceId: '01HX7K2YV9D3M8N0G6Q5R4S3T2',
reportTitle: 'Adidas Visibility Report',
brand: 'Adidas',
brandDomain: 'adidas.com',
promptIds: ['01HXP1DRTM5G8Z2N3KQ7VAW4PA'],
competitors: [{brand: 'Nike', brandDomain: 'nike.com'}]
})
};
fetch('https://data.otterly.ai/v1/reports/brand', 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/reports/brand",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'workspaceId' => '01HX7K2YV9D3M8N0G6Q5R4S3T2',
'reportTitle' => 'Adidas Visibility Report',
'brand' => 'Adidas',
'brandDomain' => 'adidas.com',
'promptIds' => [
'01HXP1DRTM5G8Z2N3KQ7VAW4PA'
],
'competitors' => [
[
'brand' => 'Nike',
'brandDomain' => 'nike.com'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://data.otterly.ai/v1/reports/brand"
payload := strings.NewReader("{\n \"workspaceId\": \"01HX7K2YV9D3M8N0G6Q5R4S3T2\",\n \"reportTitle\": \"Adidas Visibility Report\",\n \"brand\": \"Adidas\",\n \"brandDomain\": \"adidas.com\",\n \"promptIds\": [\n \"01HXP1DRTM5G8Z2N3KQ7VAW4PA\"\n ],\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://data.otterly.ai/v1/reports/brand")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"workspaceId\": \"01HX7K2YV9D3M8N0G6Q5R4S3T2\",\n \"reportTitle\": \"Adidas Visibility Report\",\n \"brand\": \"Adidas\",\n \"brandDomain\": \"adidas.com\",\n \"promptIds\": [\n \"01HXP1DRTM5G8Z2N3KQ7VAW4PA\"\n ],\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://data.otterly.ai/v1/reports/brand")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"workspaceId\": \"01HX7K2YV9D3M8N0G6Q5R4S3T2\",\n \"reportTitle\": \"Adidas Visibility Report\",\n \"brand\": \"Adidas\",\n \"brandDomain\": \"adidas.com\",\n \"promptIds\": [\n \"01HXP1DRTM5G8Z2N3KQ7VAW4PA\"\n ],\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "01HXBR1DGM5XY8Z2N3KQ7VAW4P",
"workspaceId": "01HX7K2YV9D3M8N0G6Q5R4S3T2",
"brand": "Adidas",
"brandVariations": [
"adidas",
"Adidas AG"
],
"brandDomain": "adidas.com",
"brandDomainVariations": [
"adidas.de",
"adidas.co.uk"
],
"brandDomainWildcard": false,
"brandRegex": null,
"countries": [
"us",
"de",
"uk"
],
"competitors": [
{
"brand": "Nike",
"brandVariations": [
"nike",
"Nike Inc."
],
"brandDomain": "nike.com",
"brandDomainVariations": [
"nike.de",
"nike.co.uk"
],
"brandDomainWildcard": false,
"brandRegex": null
}
],
"reportTitle": "Adidas Visibility Report",
"createdDate": "2024-11-01T12:00:00.000Z",
"updatedDate": "2025-05-01T08:30:00.000Z",
"promptIds": [
"01HXP1DRTM5G8Z2N3KQ7VAW4PA",
"01HXP2DRTM5G8Z2N3KQ7VAW4PB"
],
"countryPromptsTotal": {
"us": 50,
"de": 35,
"uk": 28
},
"tags": [
{
"id": "01HX8AB3CDE4FG5HJ6KL7MN8PQ",
"name": "Running Shoes",
"color": "orange"
}
]
}{
"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"
}{
"message": "Report not found"
}Authorizations
Provide your API key as a Bearer token: Authorization: Bearer YOUR_API_KEY.
Body
Workspace to create the report in. The API key must have access to it.
1"01HX7K2YV9D3M8N0G6Q5R4S3T2"
Brand website domain, e.g. acme.com. Protocol, www. and path are stripped.
1IDs of prompts that already exist in the report workspace — look them up with GET /v1/workspaces/{id}/prompts. The report tracks the countries of these prompts.
1 - 1000 elements1 - 64Optional. Report name shown in the app. Defaults to brand. Required when brandRegex is set.
1Brand name to track. Required unless brandRegex is set, in which case reportTitle is used as the brand label.
1Optional. Other names the brand goes by, at most 100. Cannot be combined with brandRegex.
1001Optional. Other domains the brand owns, at most 100. Each must differ from brandDomain.
1001Optional. When true, subdomains of brandDomain also count as the brand domain.
Optional. Regular expression used to detect the brand in AI answers, instead of the name and its variations (so it cannot be combined with brandVariations). Up to 1000 characters. Send null to clear it.
Optional. Competitors to track, at most 100. Each competitor domain must be unique. POST /v1/competitors/suggestions returns items in this shape.
100Show child attributes
Show child attributes
Response
The created brand report.
1"01HX7K2YV9D3M8N0G6Q5R4S3T2"
Regular expression used to detect the brand in AI answers, instead of brand and brandVariations. null when not set.
Lowercase ISO 3166-1 alpha-2 country codes the report tracks (e.g. us, de), with uk used in place of gb.
Total prompt count keyed by country code (lowercase ISO 3166-1 alpha-2; uk instead of gb).
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes