Documentation Index
Fetch the complete documentation index at: https://docs.topify.ai/llms.txt
Use this file to discover all available pages before exploring further.
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 |