curl --request PATCH \
--url https://data.otterly.ai/v1/reports/brand/{reportId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"brandDomainWildcard": true,
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
'import requests
url = "https://data.otterly.ai/v1/reports/brand/{reportId}"
payload = {
"brandDomainWildcard": True,
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
brandDomainWildcard: true,
competitors: [{brand: 'Nike', brandDomain: 'nike.com'}]
})
};
fetch('https://data.otterly.ai/v1/reports/brand/{reportId}', 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/{reportId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'brandDomainWildcard' => true,
'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/{reportId}"
payload := strings.NewReader("{\n \"brandDomainWildcard\": true,\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://data.otterly.ai/v1/reports/brand/{reportId}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"brandDomainWildcard\": true,\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/{reportId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"brandDomainWildcard\": true,\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"
}Update a brand report
Changes only the fields you send; an array field you send replaces the stored one whole. A field sent with its stored value is left unchanged and is not re-validated, so a GET body can be sent back as is: its read-only fields (workspaceId, id, countries, tags, createdDate, updatedDate, countryPromptsTotal) are ignored, and the workspace of a report cannot be changed. Send brandRegex: null to clear the detection regex. Changing detection fields (brand, variations, domains, wildcard, regex or competitors) recalculates the report history for every past day, exactly like an edit in the UI, so updated numbers appear with a delay. Changing promptIds adds and removes prompts the same way as the prompt assignment endpoints.
curl --request PATCH \
--url https://data.otterly.ai/v1/reports/brand/{reportId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"brandDomainWildcard": true,
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
'import requests
url = "https://data.otterly.ai/v1/reports/brand/{reportId}"
payload = {
"brandDomainWildcard": True,
"competitors": [
{
"brand": "Nike",
"brandDomain": "nike.com"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
brandDomainWildcard: true,
competitors: [{brand: 'Nike', brandDomain: 'nike.com'}]
})
};
fetch('https://data.otterly.ai/v1/reports/brand/{reportId}', 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/{reportId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'brandDomainWildcard' => true,
'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/{reportId}"
payload := strings.NewReader("{\n \"brandDomainWildcard\": true,\n \"competitors\": [\n {\n \"brand\": \"Nike\",\n \"brandDomain\": \"nike.com\"\n }\n ]\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://data.otterly.ai/v1/reports/brand/{reportId}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"brandDomainWildcard\": true,\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/{reportId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"brandDomainWildcard\": true,\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.
Path Parameters
Brand report identifier.
1"01HX7K2YV9D3M8N0G6Q5R4S3T2"
Body
Optional. Report name shown in the app.
Optional. Brand name to track.
Optional. Other names the brand goes by, at most 100. Cannot be combined with brandRegex.
Optional. Brand website domain, e.g. acme.com. Protocol, www. and path are stripped.
Optional. Other domains the brand owns, at most 100. Each must differ from brandDomain.
Optional. 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.
IDs 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. Competitors to track, at most 100. Each competitor domain must be unique. POST /v1/competitors/suggestions returns items in this shape.
Show child attributes
Show child attributes
Response
The updated brand report. Recalculation completes asynchronously.
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