Competitors are the brands Topify.ai tracks alongside yours across AI responses. This endpoint returns active competitors with aggregated visibility, sentiment, and position metrics.
List competitors
GET /projects/{project_id}/competitors
Returns all active competitors for a project with aggregated metrics for the given date range.
Path parameters
| Param | Type | Description |
|---|
project_id | string (UUID) | The project ID |
Query parameters
| Param | Type | Default | Description |
|---|
duration_days | integer | 7 | Days to look back |
date_from | string | — | Start date (YYYY-MM-DD) |
date_to | string | — | End date (YYYY-MM-DD) |
providers | string | — | Comma-separated provider filter |
curl "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors?duration_days=7" \
-H "X-API-Key: tk_live_..."
resp = client.get(f"/projects/{project_id}/competitors", params={"duration_days": 7})
const resp = await fetch(`${BASE}/projects/${projectId}/competitors?duration_days=7`, { headers });
Response
{
"success": true,
"data": {
"active_competitors": [
{
"competitor_id": "00000000-0000-0000-0000-000000000000",
"project_id": "a1b2c3d4-...",
"name": "Acme Corp",
"website": "https://acme.com",
"icon_url": "https://logo.clearbit.com/acme.com",
"icon_urls": [
"https://logo.clearbit.com/acme.com",
"https://www.google.com/s2/favicons?domain=acme.com&sz=64"
],
"state": "active",
"is_own_brand": true,
"metrics": {
"visibility": 85.0,
"sentiment": 72.0,
"position": 1.8,
"mention_count": null,
"days_with_data": 7,
"period": "2026-02-27 to 2026-03-06"
}
},
{
"competitor_id": "c1d2e3f4-...",
"project_id": "a1b2c3d4-...",
"name": "Competitor A",
"website": "https://competitor-a.com",
"icon_url": "https://logo.clearbit.com/competitor-a.com",
"icon_urls": [
"https://logo.clearbit.com/competitor-a.com",
"https://www.google.com/s2/favicons?domain=competitor-a.com&sz=64"
],
"state": "active",
"is_own_brand": false,
"metrics": {
"visibility": 72.0,
"sentiment": 65.0,
"position": 3.1,
"mention_count": null,
"days_with_data": 7,
"period": "2026-02-27 to 2026-03-06"
}
}
],
"total_active": 8
}
}
CompetitorResponse fields
| Field | Type | Nullable | Description |
|---|
competitor_id | string (UUID) | No | Competitor ID. 00000000-... for injected own brand |
project_id | string (UUID) | No | Parent project ID |
name | string | No | Competitor brand name |
website | string | No | Competitor website URL |
icon_url | string | Yes | Primary logo URL |
icon_urls | string[] | No | Multiple logo URL sources (Clearbit + Google favicon) |
state | string | Yes | Always active in this endpoint |
is_own_brand | boolean | No | true if this is your own brand |
metrics | CompetitorMetrics | Yes | Aggregated metrics for the date range |
CompetitorMetrics fields
| Field | Type | Nullable | Description |
|---|
visibility | float | Yes | Average visibility score across the period |
sentiment | float | Yes | Average sentiment score across the period |
position | float | Yes | Average position in AI responses |
mention_count | integer | Yes | Total mention count (may be null) |
days_with_data | integer | Yes | Number of days with collected data |
period | string | No | Human-readable date range (e.g., 2026-02-27 to 2026-03-06) |
Your own brand is always included in the response with is_own_brand: true. If your brand doesn’t already exist as a competitor, it is injected automatically with zeroed metrics so you can compare directly.
Create competitors
POST /projects/{project_id}/competitors
Add one or more competitor brands to a project. Each competitor begins tracking immediately.
Requires an admin-scoped API key. Viewer keys receive a 403 response.
Path parameters
| Param | Type | Description |
|---|
project_id | string (UUID) | The project to add competitors to |
Request body
| Field | Type | Required | Description |
|---|
competitors | object[] | Yes | Array of competitors to add (1—20 per request) |
competitors[].name | string | Yes | Brand name |
competitors[].website | string | Yes | Website domain or URL (e.g. acme.com or https://acme.com) |
curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors" \
-H "X-API-Key: tk_live_..." \
-H "Content-Type: application/json" \
-d '{
"competitors": [
{"name": "Acme Corp", "website": "acme.com"},
{"name": "Globex Inc", "website": "globex.net"}
]
}'
resp = client.post(f"/projects/{project_id}/competitors", json={
"competitors": [
{"name": "Acme Corp", "website": "acme.com"},
{"name": "Globex Inc", "website": "globex.net"},
],
})
const resp = await fetch(`${BASE}/projects/${projectId}/competitors`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
competitors: [
{ name: "Acme Corp", website: "acme.com" },
{ name: "Globex Inc", website: "globex.net" },
],
}),
});
Response
{
"code": 200,
"message": "200 OK",
"data": {
"projectId": "a1b2c3d4-...",
"created": 2,
"competitors": [
{
"competitorId": "c1d2e3f4-...",
"projectId": "a1b2c3d4-...",
"name": "Acme Corp",
"website": "https://acme.com",
"iconUrl": "https://img.logo.dev/acme.com?token=...",
"iconUrls": ["https://img.logo.dev/acme.com?token=..."],
"state": "active",
"isOwnBrand": false,
"metrics": null
}
]
}
}
Error responses
| Status | Cause |
|---|
400 | Empty competitors array, empty name, invalid website URL, or duplicate domain in request |
403 | API key has read-only (viewer) access |
409 | A competitor with the same domain already exists in this project |
429 | Active competitor limit reached (max 10) |
Update competitor
PATCH /projects/{project_id}/competitors/{competitor_id}
Update the name or website of an existing competitor.
Requires an admin-scoped API key.
Path parameters
| Param | Type | Description |
|---|
project_id | string (UUID) | The project ID |
competitor_id | string (UUID) | The competitor to update |
Request body
All fields are optional. Include only the fields you want to change.
| Field | Type | Description |
|---|
name | string | New brand name (cannot be empty) |
website | string | New website domain or URL |
curl -X PATCH "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors/{competitor_id}" \
-H "X-API-Key: tk_live_..." \
-H "Content-Type: application/json" \
-d '{"name": "Acme Inc", "website": "acmeinc.com"}'
resp = client.patch(f"/projects/{project_id}/competitors/{competitor_id}", json={
"name": "Acme Inc",
})
const resp = await fetch(`${BASE}/projects/${projectId}/competitors/${competitorId}`, {
method: "PATCH",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ name: "Acme Inc" }),
});
Response
Returns the updated competitor with all fields (same shape as items in list competitors).
Error responses
| Status | Cause |
|---|
400 | Empty request body, whitespace-only name, invalid website URL, or no fields changed |
403 | API key has read-only access |
404 | Competitor not found in this project |
Delete competitor
DELETE /projects/{project_id}/competitors/{competitor_id}
Permanently remove a competitor from your project and stop tracking it.
This action cannot be undone. Requires an admin-scoped API key.
Path parameters
| Param | Type | Description |
|---|
project_id | string (UUID) | The project ID |
competitor_id | string (UUID) | The competitor to delete |
curl -X DELETE "https://topify-customer-api-production.up.railway.app/api/public/v1/projects/{project_id}/competitors/{competitor_id}" \
-H "X-API-Key: tk_live_..."
resp = client.delete(f"/projects/{project_id}/competitors/{competitor_id}")
const resp = await fetch(`${BASE}/projects/${projectId}/competitors/${competitorId}`, {
method: "DELETE",
headers,
});
Response
{
"code": 200,
"message": "200 OK",
"data": {
"competitorId": "c1d2e3f4-...",
"message": "Competitor deleted successfully"
}
}
Error responses
| Status | Cause |
|---|
403 | API key has read-only access |
404 | Competitor not found in this project |