curl --request PATCH \
--url https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"domain": "<string>",
"isDirectCompetitor": true,
"addVariations": [
"<string>"
],
"removeVariations": [
"<string>"
]
}
'import requests
url = "https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}"
payload = {
"name": "<string>",
"domain": "<string>",
"isDirectCompetitor": True,
"addVariations": ["<string>"],
"removeVariations": ["<string>"]
}
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({
name: '<string>',
domain: '<string>',
isDirectCompetitor: true,
addVariations: ['<string>'],
removeVariations: ['<string>']
})
};
fetch('https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}', 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.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}",
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([
'name' => '<string>',
'domain' => '<string>',
'isDirectCompetitor' => true,
'addVariations' => [
'<string>'
],
'removeVariations' => [
'<string>'
]
]),
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://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"domain\": \"<string>\",\n \"isDirectCompetitor\": true,\n \"addVariations\": [\n \"<string>\"\n ],\n \"removeVariations\": [\n \"<string>\"\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://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"domain\": \"<string>\",\n \"isDirectCompetitor\": true,\n \"addVariations\": [\n \"<string>\"\n ],\n \"removeVariations\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}")
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 \"name\": \"<string>\",\n \"domain\": \"<string>\",\n \"isDirectCompetitor\": true,\n \"addVariations\": [\n \"<string>\"\n ],\n \"removeVariations\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"competitor": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"domain": "<string>",
"isDirectCompetitor": true
},
"variations": [
"<string>"
],
"added": [
{
"name": "<string>",
"movedFrom": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>"
}
}
],
"removed": [
"<string>"
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}Edit a competitor
Edit a tracked competitor as the in-app competitor sheet does: rename it, change its website domain, make it a direct or a SERP competitor, and add or remove the other names it goes by (name variations), which is how its mentions are recognised in AI answers. Changing the domain, or making it direct, re-matches its past mentions in the background. Everything is checked before anything is written: a variation that is another competitor’s name is refused with 409 conflict (merge the two in the app instead), and removing a name the competitor does not have is refused with 400. A name confirmed as another competitor’s variation moves to this one. A body with no fields changes nothing and answers with the competitor and its name variations. Requires the competitor:match capability and a manage-tier role, as in the app. A pitch project that is not currently active refuses with 409 INACTIVE_PITCH_PROMPT_MUTATION.
curl --request PATCH \
--url https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"domain": "<string>",
"isDirectCompetitor": true,
"addVariations": [
"<string>"
],
"removeVariations": [
"<string>"
]
}
'import requests
url = "https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}"
payload = {
"name": "<string>",
"domain": "<string>",
"isDirectCompetitor": True,
"addVariations": ["<string>"],
"removeVariations": ["<string>"]
}
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({
name: '<string>',
domain: '<string>',
isDirectCompetitor: true,
addVariations: ['<string>'],
removeVariations: ['<string>']
})
};
fetch('https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}', 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.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}",
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([
'name' => '<string>',
'domain' => '<string>',
'isDirectCompetitor' => true,
'addVariations' => [
'<string>'
],
'removeVariations' => [
'<string>'
]
]),
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://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"domain\": \"<string>\",\n \"isDirectCompetitor\": true,\n \"addVariations\": [\n \"<string>\"\n ],\n \"removeVariations\": [\n \"<string>\"\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://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"domain\": \"<string>\",\n \"isDirectCompetitor\": true,\n \"addVariations\": [\n \"<string>\"\n ],\n \"removeVariations\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.searchable.com/api/mcp/projects/{projectId}/competitors/{competitorId}")
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 \"name\": \"<string>\",\n \"domain\": \"<string>\",\n \"isDirectCompetitor\": true,\n \"addVariations\": [\n \"<string>\"\n ],\n \"removeVariations\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"competitor": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"domain": "<string>",
"isDirectCompetitor": true
},
"variations": [
"<string>"
],
"added": [
{
"name": "<string>",
"movedFrom": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>"
}
}
],
"removed": [
"<string>"
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "unauthorized",
"error": "<string>",
"message": "<string>",
"howToFix": "<string>",
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requiresUpgrade": true,
"retryable": true,
"timeout": true,
"quotaExceeded": true,
"current": 123,
"limit": 123,
"bulkLimitExceeded": true,
"requested": 123,
"maxAllowed": 123,
"details": {
"formErrors": [
"<string>"
],
"fieldErrors": {}
}
}Authorizations
A Searchable API key, created under Settings → Workspace → Integrations. Send as Authorization: Bearer sea_xxxxx. Every key carries one or more scopes (read, write, admin) and may optionally be bound to a single project — see Getting started.
Path Parameters
The project id.
The competitor's id.
Body
The competitor's new name.
1 - 200Its website domain, e.g. example.com.
1 - 253true makes it a direct competitor, explicitly tracked; false makes it a SERP competitor, one that appears when AI results mention it.
Other names it goes by, to add.
201 - 200Name variations to remove. Each must be one it has.
201 - 200Response
Competitor updated.
Show child attributes
Show child attributes
Its name variations after the edit.
The variations added. movedFrom names the competitor a confirmed variation moved off.
Show child attributes
Show child attributes
The variations removed.