xSeek API

v1
[ Base URL: www.xseek.io/api/v1 ]

Everything xSeek measures, over REST: tracked questions, what each AI engine answered, the pages it cited, your action plan and your articles. JSON in and out, one Bearer key.

api-docs.mdopenapi.jsonMCP setup

Servers
https://www.xseek.io

Working from ChatGPT, Claude or Cursor? Connect the MCP server instead, no key needed. MCP setup

Errors and reading the numbers

Errors

  • 400Validation failed. The body says which field.
  • 401Missing, invalid or disabled key, or a missing privilege.
  • 404The record does not exist or is not yours. Same answer either way, so an id cannot be probed.
  • 500Server error. Safe to retry with backoff.

Read the numbers right

  • Unmeasured is null, never 0. No GA4 means available: false and a reason, not a row of zeros. Keep nulls out of your averages.
  • AI visits are bots. AI clicks are people. /ai-visits counts AI crawlers fetching your pages. /metrics/ai-clicks counts real visitors who came from an assistant.
  • Share of voice is against your tracked set. Your brand plus the competitors set for that website, aliases included. Topics excluded from the score stay out.

Websites & brand

GET/api/v1/websitesList websites

Returns every website tracked by your organization, newest first. Use it to find the websiteId that every other endpoint needs.

Needswebsites:read

Parameters

No parameters

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "id": "3f1c2a9e-7b4d-4c1a-9e2f-5a6b7c8d9e01",
      "name": "Acme",
      "url": "https://www.acme.com",
      "companyName": "Acme",
      "competitors": [
        "globex.com",
        "initech.com"
      ],
      "robotsTxtUrl": "https://www.acme.com/robots.txt",
      "sitemapUrl": "https://www.acme.com/sitemap.xml",
      "createdAt": "2026-09-01T10:00:00.000Z",
      "updatedAt": "2026-10-20T08:12:00.000Z"
    }
  ]
}
POST/api/v1/websitesCreate a website

Adds a new website to track and returns it with status 201. The url is reduced to its origin (https://www.acme.com/blog becomes https://www.acme.com), and xSeek then scans robots.txt, the sitemap and Search Console in the background, so robotsTxtUrl and sitemapUrl are only needed to override what it finds. Returns 403 with currentCount and maxAllowed when your plan's website limit is reached.

Needswebsites:create

Parameters

No parameters

Request body

application/json
name* required
string
Company or brand name, trimmed before it is stored. This is the name AI answers are searched for.
url* required
string
Website URL. Must be a valid URL; only the origin is stored.
robotsTxtUrl
string
robots.txt URL. Must be a valid URL. Discovered automatically when omitted.
sitemapUrl
string
Sitemap XML URL. Must be a valid URL. Discovered automatically when omitted.
competitors
string[]
Competitor names or domains to track against. Defaults to an empty list.

Responses

CodeDescription
201
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "3f1c2a9e-7b4d-4c1a-9e2f-5a6b7c8d9e01",
    "name": "Acme",
    "url": "https://www.acme.com",
    "companyName": "Acme",
    "competitors": [
      "globex.com"
    ],
    "robotsTxtUrl": null,
    "sitemapUrl": null,
    "createdAt": "2026-10-21T15:00:00.000Z",
    "updatedAt": "2026-10-21T15:00:00.000Z"
  }
}
PUT/api/v1/websites/{websiteId}Update a website

Changes a website's name, URL, robots.txt or sitemap URL, or competitor list. Send only the fields you want to change. Renaming the company also updates the tracked prompts and re-reads past AI answers for the new name. Returns the full stored website record under website, which uses storage field names such as companyName and sitemapXmlUrl.

Needswebsites:update

Parameters

websiteId* required
string
(path)
The website's id, from GET /api/v1/websites.

Request body

application/json
name
string
New company name. Trimmed and must not be empty. This is the name AI answers are searched for.
url
string
New website URL. Must be a valid, public web address (no IP, port or localhost). Unlike create, the path is kept; only a trailing slash is removed.
robotsTxtUrl
string
robots.txt URL. Must be a valid URL.
sitemapUrl
string
Sitemap XML URL. Must be a valid URL. Stored as sitemapXmlUrl.
competitors
string[]
Replaces the whole competitor list.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "website": {
    "id": "3f1c2a9e-7b4d-4c1a-9e2f-5a6b7c8d9e01",
    "organizationId": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
    "url": "https://www.acme.com",
    "companyName": "Acme",
    "competitors": [
      "globex.com",
      "initech.com"
    ],
    "robotsTxtUrl": "https://www.acme.com/robots.txt",
    "sitemapXmlUrl": "https://www.acme.com/sitemap.xml",
    "language": "en",
    "locationCode": 2840,
    "createdAt": "2026-09-01T10:00:00.000Z",
    "updatedAt": "2026-10-21T15:05:00.000Z"
  }
}
GET/api/v1/websites/{websiteId}/brand-contextGet brand context

Returns everything needed to write on-brand content for a website: company name, language and market, voice guidelines, tone, brand identity, audiences with their topics, knowledge base text, voice samples, and a short summary of the product catalog. brandVoiceGuidelines, brandTone and brandIdentity are null when never filled in, and productCatalog is null when no products were uploaded.

Needsarticles:read

Parameters

websiteId* required
string
(path)
The website's id, from GET /api/v1/websites.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "companyName": "Acme",
    "websiteUrl": "https://www.acme.com",
    "language": "en",
    "locationCode": 2840,
    "brandVoiceGuidelines": "Write plainly. Short sentences. No hype.",
    "brandTone": "conversational",
    "brandIdentity": {
      "adjectives": [
        "direct",
        "warm"
      ],
      "bannedWords": [
        "revolutionary"
      ],
      "positionsBelieve": "Small teams can win AI search."
    },
    "audiences": [
      {
        "name": "Marketing leads",
        "description": "Heads of marketing at 10 to 200 person B2B companies",
        "topics": [
          {
            "name": "AI search visibility",
            "description": "How brands get cited by ChatGPT and Perplexity"
          }
        ]
      }
    ],
    "knowledgeChunks": [
      "Acme was founded in 2019 and serves 1,200 customers."
    ],
    "brandVoiceSamples": [
      "We help you stop guessing."
    ],
    "productCatalog": {
      "count": 42,
      "facets": {
        "region": [
          "Quebec",
          "Ontario"
        ],
        "category": [
          "Chalet"
        ]
      }
    }
  }
}
GET/api/v1/websites/{websiteId}/productsSearch the product catalog

Returns active products from the catalog you uploaded, newest first, so an article only cites relevant ones. Search name and description with q, and filter on any product attribute by passing it as a query parameter (for example ?region=Quebec or ?make=Toyota), matched as a case-insensitive partial match. When a product has no image, imageUrl is filled from a custom attribute that is named like an image or holds an image URL.

Needsarticles:read

Parameters

websiteId* required
string
(path)
The website's id, from GET /api/v1/websites.
q
string
(query)
Text searched in product name and description (case-insensitive, partial match).
limit
integer
(query)
Maximum products returned. Default 50, max 200.
{attribute}
string
(query)
Any other parameter filters on the product attribute of that name (lowercased), for example region, category, make or year. Empty values are ignored. Reserved names that never filter: limit, offset, q, token.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "products": [
      {
        "id": "e3f4a5b6-c7d8-4e9f-0a1b-2c3d4e5f6a7b",
        "name": "Lakeview Chalet",
        "url": "https://www.acme.com/chalets/lakeview",
        "description": "Six-person chalet on Lac Tremblant.",
        "imageUrl": "https://www.acme.com/img/lakeview.jpg",
        "attributes": {
          "region": "Quebec",
          "category": "Chalet",
          "capacity": "6"
        }
      }
    ],
    "count": 1
  }
}
GET/api/v1/websites/{websiteId}/external-profilesList external profiles

Returns the directory and platform profiles the brand already has (for example G2, Capterra, Crunchbase), newest first, plus a flat list of their domains. Profiles come from those added in settings and from completed listing actions. Use it to see where the brand is already listed before planning new listings. label is null when none was set.

Needswebsites:read

Parameters

websiteId* required
string
(path)
The website's id, from GET /api/v1/websites.

Responses

CodeDescription
200
Successful response
Example value
{
  "profiles": [
    {
      "domain": "g2.com",
      "url": "https://www.g2.com/products/acme",
      "label": "G2"
    }
  ],
  "domains": [
    "g2.com"
  ]
}

Questions & visibility

GET/api/v1/websites/{websiteId}/promptsList tracked prompts

Lists every tracked prompt (the questions xSeek asks the AI engines) for a website, newest first, including paused ones (isActive false). The question text is in content. Each prompt carries its topicId and audienceId so you can group results by topic or join against /topics; both are null when the prompt is not attached to one. Deleted prompts are not returned.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "id": "pr_456",
      "websiteId": "web_123",
      "name": "What is the best help desk software for startups?",
      "content": "What is the best help desk software for startups?",
      "mainCompany": "Acme",
      "competitors": [
        "Zendesk",
        "Intercom"
      ],
      "audienceId": "aud_1",
      "topicId": "top_1",
      "isActive": true,
      "frequency": "daily",
      "createdAt": "2026-09-01T10:00:00.000Z",
      "updatedAt": "2026-09-01T10:00:00.000Z"
    }
  ]
}
POST/api/v1/websites/{websiteId}/promptsAdd a tracked prompt

Adds a new question for xSeek to ask the AI engines on this website. The first 100 characters of the text become its name, and the prompt is created with no topic or audience. Competitors are managed on the website, not per prompt, so the response just echoes the website's list. Note the created prompt is returned with its text under prompt, while the list endpoint calls the same field content. Returns 201, or 400 with a details list when validation fails.

Needsprompts:create

Parameters

websiteId* required
string
(path)
The unique identifier of the website

Request body

application/json
prompt* required
string
The question text to track (at least 1 character)
mainCompany
string
Brand to measure. Defaults to the website's company name
isActive
boolean
Whether the prompt runs on schedule. Default true
frequency
string
daily, weekly or monthly. Default daily

Responses

CodeDescription
201
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "pr_456",
    "websiteId": "web_123",
    "prompt": "What is the best help desk software for startups?",
    "competitors": [
      "Zendesk",
      "Intercom"
    ],
    "mainCompany": "Acme",
    "isActive": true,
    "frequency": "daily",
    "createdAt": "2026-10-21T10:00:00.000Z",
    "updatedAt": "2026-10-21T10:00:00.000Z"
  }
}
GET/api/v1/websites/{websiteId}/prompts/{promptId}Get one prompt

Returns one tracked prompt with its schedule and the time it last ran. The question text is under prompt here (the list endpoint calls it content). lastRun is null when the prompt has never run. Returns 404 if the prompt does not belong to this website.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
promptId* required
string
(path)
The unique identifier of the prompt

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "pr_456",
    "websiteId": "web_123",
    "name": "Best help desk for startups",
    "prompt": "What is the best help desk software for startups?",
    "mainCompany": "Acme",
    "competitors": [
      "Zendesk",
      "Intercom"
    ],
    "isActive": true,
    "frequency": "daily",
    "lastRun": "2026-10-20T06:00:00.000Z",
    "createdAt": "2026-09-01T10:00:00.000Z",
    "updatedAt": "2026-09-01T10:00:00.000Z"
  }
}
PUT/api/v1/websites/{websiteId}/prompts/{promptId}Update a prompt

Changes a tracked prompt's text, name, brand, schedule or active state. Send only the fields you want to change; at least one is required, otherwise the call returns 400. Pausing a prompt (isActive false) stops it from running without deleting its history. Topic, audience and competitors cannot be changed here.

Needsprompts:update

Parameters

websiteId* required
string
(path)
The unique identifier of the website
promptId* required
string
(path)
The unique identifier of the prompt

Request body

application/json
name
string
Display name, 1 to 100 characters
prompt
string
New question text (at least 1 character)
mainCompany
string
Brand to measure (at least 1 character)
isActive
boolean
true to run on schedule, false to pause
frequency
string
daily, weekly or monthly

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "pr_456",
    "websiteId": "web_123",
    "name": "Best help desk for startups",
    "prompt": "Which help desk tool is best for a 10 person startup?",
    "mainCompany": "Acme",
    "competitors": [
      "Zendesk",
      "Intercom"
    ],
    "isActive": true,
    "frequency": "weekly",
    "lastRun": "2026-10-20T06:00:00.000Z",
    "createdAt": "2026-09-01T10:00:00.000Z",
    "updatedAt": "2026-10-21T10:00:00.000Z"
  }
}
GET/api/v1/websites/{websiteId}/prompts/{promptId}/runsList a prompt's runs

Returns the answers collected for one prompt, newest first: which engine answered, which brands it named, and which pages it cited. mentions is a roster with one entry per tracked company on every run, so an entry with count 0 means the engine did not name that company; only count above 0 is a real mention. model can be null on older runs. sources lists only the pages the engine cited, in citation order.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
promptId* required
string
(path)
The unique identifier of the prompt
page
number
(query)
Page number (default 1). A page past the end returns the last page
pageSize
number
(query)
Runs per page (default 10, 1 to 100). A value outside that range returns 400

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "id": "run_1",
      "promptId": "pr_456",
      "model": "ChatGPT",
      "mentions": [
        {
          "company": "Acme",
          "count": 2,
          "context": [
            "Acme is a solid choice for small teams"
          ],
          "firstIndex": 118,
          "position": 1
        },
        {
          "company": "Zendesk",
          "count": 0,
          "context": [],
          "firstIndex": null,
          "position": null
        }
      ],
      "sources": [
        {
          "id": "src_1",
          "url": "https://example.com/best-help-desk",
          "domain": "example.com",
          "title": "Best help desk tools",
          "citationOrder": 1
        }
      ],
      "createdAt": "2026-10-20T06:00:00.000Z"
    }
  ],
  "meta": {
    "count": 1,
    "page": 1,
    "pageSize": 10,
    "total": 42,
    "totalPages": 5,
    "hasNext": true,
    "hasPrev": false
  }
}
GET/api/v1/websites/{websiteId}/prompts/leaderboardBrand leaderboard by mentions

Ranks your brand and its tracked competitors by how many times the AI engines named them across all tracked prompts (paused ones included) in the window. Only the brand set on your prompts and the website's competitor names are listed, matched by exact name; other names are left out. Prompts in topics excluded from share of voice are not counted.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
lastDays
number
(query)
Window in days (default 30, 1 to 365). A value outside that range returns 400

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "company": "Zendesk",
      "mentions": 48,
      "isCompetitor": true,
      "rank": 1
    },
    {
      "company": "Acme",
      "mentions": 31,
      "isCompetitor": true,
      "rank": 2
    }
  ],
  "meta": {
    "lastDays": 30,
    "totalMentions": 79
  }
}
GET/api/v1/websites/{websiteId}/prompts/visibilityPer-prompt visibility

For each active tracked question, how often the AI engines named your brand and who they named instead (top 10), worst first with unmeasured prompts last. losing lists measured prompts below the threshold that count toward share of voice, also grouped by topic in losingByTopic. mentionRate is null when the prompt had no runs in the window, and ourRank is null when your brand was never named; neither is zero. idle flags questions with measured zero search demand that were named in none of at least idleMinRuns answers over the last idleWindowDays days; it is a signal only and nothing is paused automatically.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
days
number
(query)
Window in days (default 30). Clamped to 1 to 180
threshold
number
(query)
Mention rate (0 to 100) below which a prompt counts as losing (default 50). Clamped to 100; 0 is treated as the default

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "days": 30,
    "threshold": 50,
    "totalPrompts": 24,
    "losingCount": 1,
    "prompts": [
      {
        "promptId": "pr_456",
        "query": "What is the best help desk software for startups?",
        "topic": {
          "id": "top_1",
          "name": "Help desk"
        },
        "audience": {
          "id": "aud_1",
          "name": "Startup founders"
        },
        "runs": 30,
        "runsMentioned": 6,
        "mentionRate": 20,
        "leaderboard": [
          {
            "company": "Zendesk",
            "mentions": 28,
            "rank": 1,
            "isUs": false
          },
          {
            "company": "Acme",
            "mentions": 6,
            "rank": 3,
            "isUs": true
          }
        ],
        "ourRank": 3,
        "excludedFromSov": false
      }
    ],
    "losing": [
      {
        "promptId": "pr_456",
        "query": "What is the best help desk software for startups?",
        "topic": {
          "id": "top_1",
          "name": "Help desk"
        },
        "audience": {
          "id": "aud_1",
          "name": "Startup founders"
        },
        "runs": 30,
        "runsMentioned": 6,
        "mentionRate": 20,
        "leaderboard": [],
        "ourRank": 3,
        "excludedFromSov": false
      }
    ],
    "losingByTopic": [
      {
        "topic": {
          "id": "top_1",
          "name": "Help desk"
        },
        "promptIds": [
          "pr_456"
        ],
        "queries": [
          "What is the best help desk software for startups?"
        ],
        "count": 1
      }
    ],
    "idleWindowDays": 30,
    "idleMinRuns": 10,
    "idleCount": 1,
    "idle": [
      {
        "promptId": "pr_789",
        "query": "Acme help desk pricing in Iceland",
        "topic": null,
        "runs": 12,
        "demand": {
          "measured": true,
          "volume": 0,
          "source": "keyword"
        }
      }
    ]
  }
}
GET/api/v1/websites/{websiteId}/sentiment/promptsSentiment per prompt

How the AI engines speak of your brand on each tracked prompt, worst first. Every answer that names a tracked brand is labelled positive, neutral, mixed or negative, with the sentence that decided it, and this rolls those labels up per prompt. Only readings at or above the 0.6 confidence floor count, the same floor the AI Visibility score uses. Prompts with no read answers in the window are not listed. Pass company to read a competitor instead.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
days
number
(query)
Window in days (default 30). Clamped to 1 to 365
label
string
(query)
positive, neutral, mixed or negative: only prompts with at least one answer carrying this label. An unknown value is ignored
company
string
(query)
A tracked brand name or alias. Default is the website's own brand. Returns 404 if it is not tracked, or if the website has no brand on its roster yet
minRead
number
(query)
Only prompts with at least this many read answers (default 1)

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "promptId": "pr_456",
      "query": "What is the best help desk software for startups?",
      "topic": {
        "id": "top_1",
        "name": "Help desk"
      },
      "audience": null,
      "read": 12,
      "counts": {
        "positive": 5,
        "neutral": 4,
        "mixed": 1,
        "negative": 2
      },
      "negativeRate": 17,
      "positiveRate": 42,
      "sentiment": 0.25,
      "latestNegative": {
        "runId": "run_1",
        "engine": "Copilot",
        "createdAt": "2026-10-18T15:53:00.000Z",
        "evidence": "Acme gets mixed reviews on support response times."
      }
    }
  ],
  "meta": {
    "websiteId": "web_123",
    "company": "Acme",
    "days": 30,
    "label": null,
    "minConfidence": 0.6,
    "prompts": 1,
    "read": 12,
    "counts": {
      "positive": 5,
      "neutral": 4,
      "mixed": 1,
      "negative": 2
    },
    "negativeRate": 17,
    "labels": {
      "positive": "recommended, or credited with a specific strength that sets it apart",
      "neutral": "named among others with no evaluative language",
      "mixed": "a specific strength and a specific limitation",
      "negative": "dismissed, warned about, described as inferior or limited, or a claim it makes is contradicted"
    }
  }
}
GET/api/v1/websites/{websiteId}/sentiment/runsAnswers behind a sentiment label

Lists the individual AI answers behind a sentiment label, newest first, each with the sentence that decided the label and the pages the engine cited. Use it to drill down from /sentiment/prompts: pick a prompt that reads negative, then see exactly what was said and which sources it leaned on. Only readings at or above the 0.6 confidence floor are listed. The full answer text is returned only with includeAnswer=true.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
label
string
(query)
positive, neutral, mixed or negative. Omit for every read answer. Any other value returns 400
promptId
string
(query)
Only answers to this prompt (a prompt UUID)
company
string
(query)
A tracked brand name or alias. Default is the website's own brand. Returns 404 if it is not tracked, or if the website has no brand on its roster yet
days
number
(query)
Window in days (default 30). Clamped to 1 to 365
page
number
(query)
Page number (default 1)
pageSize
number
(query)
Results per page (default 20). Clamped to 1 to 100
includeAnswer
boolean
(query)
true or 1 to add the full answer text as answer on each run (default false)

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "runId": "run_1",
      "promptId": "pr_456",
      "query": "What is the best help desk software for startups?",
      "engine": "Copilot",
      "createdAt": "2026-10-18T15:53:00.000Z",
      "company": "Acme",
      "label": "negative",
      "sentiment": -1,
      "confidence": 0.9,
      "evidence": "Acme gets mixed reviews on support response times.",
      "sources": [
        {
          "id": "src_1",
          "url": "https://example.com/help-desk-reviews",
          "domain": "example.com",
          "title": "Help desk reviews",
          "citationOrder": 1
        }
      ]
    }
  ],
  "meta": {
    "websiteId": "web_123",
    "company": "Acme",
    "days": 30,
    "label": "negative",
    "promptId": null,
    "minConfidence": 0.6,
    "count": 1,
    "page": 1,
    "pageSize": 20,
    "total": 1,
    "totalPages": 1
  }
}
GET/api/v1/websites/{websiteId}/sourcesList cited sources

Lists the web pages the AI engines cited when answering your tracked prompts, with how often each was cited in the window and how that compares to the previous window of the same length. citationTrend is the percent change versus the previous window, and is null when the page was not cited in the previous window (no baseline), not zero. citationPercentage is each page's share of totalCitations. The route reads at most pageSize x 5 sources in the chosen sort order, so pagination.total and totalCitations cover that set, not every source.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
page
number
(query)
Page number (default 1)
pageSize
number
(query)
Results per page (default 20). Clamped to 1 to 100
sortBy
string
(query)
citationCount (default), lastSeen, createdAt, domain or url. An unknown value falls back to citationCount
sortOrder
string
(query)
asc or desc (default desc)
search
string
(query)
Case-insensitive substring match on URL, domain or title
days
number
(query)
Window in days (default 30, minimum 1)

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "sources": [
      {
        "id": "src_1",
        "url": "https://example.com/best-help-desk",
        "domain": "example.com",
        "title": "Best help desk tools",
        "citationCount": 15,
        "citationPercentage": 12.4,
        "previousCitationCount": 9,
        "citationTrend": 66.7,
        "lastSeen": "2026-10-20T06:00:00.000Z",
        "createdAt": "2026-08-01T10:00:00.000Z"
      }
    ],
    "totalCitations": 121,
    "windowDays": 30,
    "pagination": {
      "page": 1,
      "pageSize": 20,
      "total": 100,
      "totalPages": 5,
      "hasNextPage": true,
      "hasPreviousPage": false
    }
  }
}
GET/api/v1/websites/{websiteId}/topicsList audiences and topics

Lists the website's audiences and the topics under each. Use these ids to scope /metrics/share-of-voice and to resolve the topicId and audienceId on /prompts. excludeFromSov marks informational topics where AI cites content without naming companies; they are left out of share of voice figures. location is the market a topic's questions are asked from, and is null when the topic follows the website's market.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "audiences": [
      {
        "id": "aud_1",
        "name": "Startup founders",
        "description": "Early-stage SaaS founders",
        "topics": [
          {
            "id": "top_1",
            "name": "Help desk",
            "description": null,
            "excludeFromSov": false,
            "location": {
              "country": "US"
            }
          },
          {
            "id": "top_2",
            "name": "Support guides",
            "description": null,
            "excludeFromSov": true,
            "location": null
          }
        ]
      }
    ]
  }
}
GET/api/v1/websites/{websiteId}/prompt-web-searchesList AI web searches

Lists the web searches the AI engines ran behind the scenes while answering your tracked prompts, newest first. These are the actual search queries the engines typed, which tells you what wording to target to get pulled into their answers. model is the engine that searched and can be null on older runs. There is no date window: it covers all history.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
page
number
(query)
Page number (default 1)
pageSize
number
(query)
Results per page (default 20, 1 to 100). A value outside that range returns 400

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "rows": [
      {
        "query": "best help desk software startups 2026",
        "model": "ChatGPT",
        "promptName": "Best help desk for startups",
        "promptContent": "What is the best help desk software for startups?",
        "createdAt": "2026-10-20T06:00:12.000Z"
      }
    ],
    "pagination": {
      "page": 1,
      "pageSize": 20,
      "total": 318,
      "totalPages": 16
    }
  }
}
GET/api/v1/websites/{websiteId}/metrics/share-of-voiceShare of voice over time

Your share of AI mentions over time, optionally scoped to one topic or audience. The share is a fraction from 0 to 1: your mentions divided by mentions of your brand plus tracked competitors (aliases included), the same figure the xSeek dashboard shows. Topics flagged excludeFromSov are always removed. shareOfVoice is null for a period with runs but no measured mentions, not zero, and periods with no runs at all are left out of timeseries. Returns 400 if the website has no tracked companies yet.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
startDate
string
(query)
Start date, YYYY-MM-DD. Defaults to days ago (30 days ago). Must be on or before endDate, otherwise 400
endDate
string
(query)
End date, YYYY-MM-DD, inclusive. Defaults to yesterday, because today is still filling
days
number
(query)
Trailing window, 1 to 365 (default 30). Ignored when startDate is given
granularity
string
(query)
day (default) or week. On week, each bucket date is the Monday it belongs to, so the first bucket can be a partial week
topicId
string
(query)
UUID of one topic to restrict to. Get ids from /topics. 404 if it is not on this website
audienceId
string
(query)
UUID of one audience to restrict to. 404 if it is not on this website. Ignored when topicId is set

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "startDate": "2026-09-20",
    "endDate": "2026-10-20",
    "granularity": "day",
    "denominator": "trackedCompetitors",
    "vertical": {
      "id": "top_1",
      "name": "Help desk",
      "kind": "topic"
    },
    "shareOfVoice": 0.11,
    "yourMentions": 902,
    "totalMentions": 8201,
    "timeseries": [
      {
        "date": "2026-09-20",
        "shareOfVoice": 0.11,
        "yourMentions": 29,
        "totalMentions": 264
      },
      {
        "date": "2026-09-21",
        "shareOfVoice": null,
        "yourMentions": 0,
        "totalMentions": 0
      }
    ]
  }
}

Action plan

GET/api/v1/websites/{websiteId}/opportunitiesList action plan items

Returns the actions on your action plan: the recommendations xSeek's agent wrote for this website, each with its type, rationale, evidence and the tracked questions it aims to win. Pass status to list actions by lifecycle instead (open, in_progress, completed, dismissed), for example to see what has already been done; in that mode businessValue and contentType are ignored and the rows carry fewer card fields. Fields that were never measured or never filled come back as null, which means unknown, not zero.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
status
string
(query)
One of open, in_progress, completed, dismissed. When set, returns every agent-authored action with that status (newest update first) instead of the current plan.
businessValue
string
(query)
Plan mode only. Filter by business value: critical, high, medium, low.
contentType
string
(query)
Plan mode only. Filter by suggested content type: blog, comparison, howto, faq.
page
number
(query)
Page number, starting at 1 (default 1).
pageSize
number
(query)
Results per page (default 50, max 200).
limit
number
(query)
Alias for pageSize, kept for older callers (max 200). pageSize wins when both are sent.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "opportunities": [
      {
        "id": "3f2a9c1e-7b4d-4e2a-9f1c-0a1b2c3d4e5f",
        "query": "best crm for small business",
        "title": "Rewrite the CRM comparison page around pricing tiers",
        "businessValue": "high",
        "opportunityType": "optimize_existing",
        "suggestedContentType": "comparison",
        "pageType": null,
        "archetype": "citation_gap",
        "existingPageMatch": "https://example.com/blog/crm-comparison",
        "nextAngle": null,
        "recommendationType": "rewrite",
        "channel": null,
        "target": "https://example.com/blog/crm-comparison",
        "destination": null,
        "derivedFrom": null,
        "foldUrls": null,
        "whatToChange": "Add a pricing table for the three plans and a section on setup time.",
        "postAngle": null,
        "rationale": "ChatGPT and Perplexity cite two competitors on this question and never your page.",
        "hypothesis": "Your page gets cited on this question within 6 weeks.",
        "doneWhen": "The updated page is live.",
        "evidence": [
          {
            "source": "prompts runs",
            "metric": "competitor citations (45 days)",
            "value": 12,
            "url": null
          }
        ],
        "status": "open",
        "resultType": null,
        "resultUrl": null,
        "completedAt": null,
        "promptId": "8c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
        "fromPromptIds": null,
        "createdAt": "2026-10-01T09:00:00.000Z",
        "updatedAt": "2026-10-02T14:30:00.000Z",
        "fromPrompts": [
          "What is the best CRM for a small business?"
        ]
      }
    ],
    "total": 24,
    "pagination": {
      "page": 1,
      "pageSize": 50,
      "total": 24,
      "totalPages": 1,
      "hasMore": false
    }
  }
}
PATCH/api/v1/websites/{websiteId}/opportunities/{opportunityId}Update an action

Updates one action on your plan: mark it in progress, completed or dismissed, attach the URL of what you published, or edit its card text. Completing an off-site action (listing, outreach, social, repurpose, or resultType external) requires a resultUrl, either in this request or already saved; when you complete it, that URL is registered so its AI citations and impressions get tracked. Only the fields you send are changed.

Needsprompts:update

Parameters

websiteId* required
string
(path)
The unique identifier of the website
opportunityId* required
string
(path)
The id of the action (opportunity) to update

Request body

application/json
status
string
One of open, in_progress, completed, dismissed. completed stamps completedAt, dismissed stamps dismissedAt.
resultUrl
string
Full URL (must be a valid URL) of what the action produced: published post, listing or article. Required to complete an off-site action if none is saved yet.
resultType
string
article or external. When omitted, it is set to external automatically if you send a resultUrl for an off-site action.
recommendationType
string
One of create, rewrite, consolidate, listing, outreach, social, repurpose, technical.
channel
string
Sub-channel of a social or repurpose action, for example reddit_reply, linkedin_post or medium_post. Free text, not validated.
destination
string
Consolidate actions: the canonical URL the other pages redirect to.
derivedFrom
string
Repurpose actions: the source page URL the external post is based on.
nextAngle
string
The angle that sets the new content apart.
rationale
string
Why the action is worth doing.
hypothesis
string
The expected, measurable outcome.
doneWhen
string
What counts as done.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "opportunity": {
      "id": "3f2a9c1e-7b4d-4e2a-9f1c-0a1b2c3d4e5f",
      "websiteId": "9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
      "query": "best crm for small business",
      "title": "Get listed on the G2 small business CRM category",
      "businessValue": "high",
      "recommendationType": "listing",
      "status": "completed",
      "resultType": "external",
      "resultUrl": "https://www.g2.com/products/example-crm",
      "completedAt": "2026-10-21T15:00:00.000Z",
      "dismissedAt": null,
      "rationale": "Perplexity cites G2 on 8 of 10 runs for this question.",
      "updatedAt": "2026-10-21T15:00:00.000Z"
    },
    "registeredForTracking": true
  }
}
GET/api/v1/websites/{websiteId}/cannibalizationFind pages competing for one question

Finds tracked questions where AI engines cite two or more of your own URLs, which splits your authority. For each question you get every cited URL with its citations, Google impressions, AI impressions and AI trend, plus a suggested canonical page and the pages worth folding into it (verdict canonical, keep or fold). Computed from measured data, no AI. aiTrend is null when the URL has no AI impression data in the window; gscImpressions and aiImpressions read 0 both when the page had none and when that channel is not connected.

Needsprompts:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website
days
number
(query)
Lookback window in days (default 45, clamped between 7 and 180). Google impressions use the closest stored Search Console period (7, 30 or 90 days).
min-urls
number
(query)
Minimum number of your own URLs cited on one question to count it (default 2, minimum 2).
limit
number
(query)
Maximum number of questions returned, most cited first (default 50, max 200).

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "clusters": [
      {
        "query": "How do I choose a CRM for a small team?",
        "trackedCitations": 14,
        "coverage": "9/30",
        "competitors": [
          {
            "company": "hubspot.com",
            "count": 22
          },
          {
            "company": "g2.com",
            "count": 11
          }
        ],
        "urls": [
          {
            "url": "https://example.com/blog/choose-crm",
            "citations": 9,
            "gscImpressions": 4200,
            "aiImpressions": 310,
            "aiTrend": 18,
            "verdict": "canonical"
          },
          {
            "url": "https://example.com/blog/crm-guide-2024",
            "citations": 5,
            "gscImpressions": 120,
            "aiImpressions": 8,
            "aiTrend": null,
            "verdict": "fold"
          }
        ],
        "suggestedCanonical": "https://example.com/blog/choose-crm",
        "suggestedFolds": [
          "https://example.com/blog/crm-guide-2024"
        ]
      }
    ],
    "total": 1,
    "days": 45,
    "minUrls": 2
  }
}
GET/api/v1/websites/{websiteId}/content/keyword-conflictsList articles sharing a keyword

Lists Content Studio articles on this website that were given the exact same primary keyword, so you can merge or re-target them before they compete with each other. Archived articles and translations are left out. An article's url is null until it is published.

Needsarticles:read

Parameters

websiteId* required
string
(path)
The unique identifier of the website

Responses

CodeDescription
200
Successful response
Example value
{
  "conflicts": [
    {
      "keyword": "global staffing agencies",
      "articles": [
        {
          "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "title": "Top Global Staffing Agencies for International Teams",
          "url": "https://example.com/blog/global-staffing-agencies",
          "status": "published",
          "language": "en"
        },
        {
          "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "title": "Best Affordable Recruiting Agencies for International Teams",
          "url": null,
          "status": "draft",
          "language": "en"
        }
      ]
    }
  ],
  "totalKeywords": 1,
  "totalArticles": 2
}

Content Studio

GET/api/v1/websites/{websiteId}/articlesList articles

Lists the Content Studio articles for one website, newest first, with pagination. Use view=public to get only the articles that belong on your public blog, with their last published title, description and meta fields. visualCoverage.rate and claimCoverage.rate are null when the writer reported no brands or claims, which means unmeasured, not zero.

Needsarticles:read

Parameters

websiteId* required
string
(path)
The website's id.
status
string
(query)
Only articles with this status (draft, ready, scheduled, published, archived). Ignored when view=public.
view
string
(query)
Set to "public" to return only blog-eligible articles (published, ready, or with a published snapshot; never archived, never writing briefs). Title, description, meta fields and publishedAt then come from the published snapshot.
language
string
(query)
Only articles in this language, for a localised blog. A bare code matches any market of that language ("fr" finds "fr-CA"); a full tag matches exactly. Articles with no language set count as the website's own language. Every article still carries its full alternates list.
page
number
(query)
Page number. Default 1.
pageSize
number
(query)
Articles per page. Default 20, max 100.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "articles": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "title": "Best AI visibility tools in 2026",
        "slug": "best-ai-visibility-tools-2026",
        "description": "How the top AI visibility platforms compare on price, engines tracked and setup time.",
        "metaTitle": "Best AI visibility tools (2026)",
        "metaDescription": "Side-by-side comparison of the top AI visibility platforms in 2026.",
        "status": "published",
        "tags": [
          "blog"
        ],
        "qualityScore": 87,
        "visualCoverage": {
          "named": 4,
          "captured": 3,
          "missing": 1,
          "rate": 75
        },
        "claimCoverage": {
          "stated": 6,
          "verified": 5,
          "unverified": 1,
          "rate": 83,
          "unsourcedAttributions": 0
        },
        "opportunityId": "5e6f7a8b-1c2d-4e3f-9a0b-c1d2e3f4a5b6",
        "language": "en",
        "translationOfId": null,
        "alternates": [
          {
            "language": "en",
            "slug": "best-ai-visibility-tools-2026",
            "isOriginal": true
          },
          {
            "language": "fr",
            "slug": "meilleurs-outils-visibilite-ia-2026",
            "isOriginal": false
          }
        ],
        "createdAt": "2026-05-12T14:08:00.000Z",
        "updatedAt": "2026-05-20T09:32:00.000Z",
        "publishedAt": "2026-05-15T10:00:00.000Z",
        "publishedUrl": "https://example.com/blog/best-ai-visibility-tools-2026"
      }
    ],
    "pagination": {
      "page": 1,
      "pageSize": 20,
      "total": 47,
      "totalPages": 3,
      "hasNextPage": true,
      "hasPreviousPage": false
    }
  }
}
POST/api/v1/websites/{websiteId}/articlesCreate or update an article

Creates an article in Content Studio, or updates the existing one when an article with the same slug already exists on this website (200 with updated: true instead of 201). When opportunityId is omitted, the article is matched to an open action on your plan by keyword or title. Raw HTML links in the body are converted to Markdown links. Sending status "published" snapshots the article for the public blog and fills publishedUrl from your blog base URL, but does not push it to a connected CMS; use the update endpoint for that. New articles count against your monthly article quota (429 when it is reached); updates never do.

Needsarticles:write

Parameters

websiteId* required
string
(path)
The website's id.

Request body

application/json
title* required
string
Article title.
slug
string
URL slug. Defaults to the title, lowercased and hyphenated, max 80 characters. Also the key used to find an existing article to update.
contentMarkdown
string
Article body in Markdown.
description
string
On-page lede shown under the title.
metaTitle
string
SEO title tag.
metaDescription
string
SEO meta description.
schemaMarkup
string
JSON-LD markup for the page head.
qualityScore
number
Quality score, 0 to 100.
status
string
draft (default on create), ready, published or archived. "published" snapshots the current fields for the public blog.
customGuidelines
string
Editorial guidelines stored with the article.
opportunityId
string
The action on your plan this article answers. When omitted, the API tries to match one automatically. On an update, an existing link is never overwritten by the automatic match.
keywordTerm
string
Primary target keyword. Always placed first in keywordTerms.
keywordTerms
string[]
All target keywords, deduplicated, max 10, each up to 120 characters.
tags
string[]
Article tags. When omitted, a new article takes the linked action's channel (blog, linkedin, reddit or medium), or blog when no action is linked. On an update, omitted tags are only filled in when the article has none.
visuals
object[]
One row per brand the article names: { name, url, screenshot, skipReason }. A screenshot counts only when it is an xseek.io/images URL. Ignored when empty, so it never erases an earlier record.
claims
object[]
Factual claims the article makes: { type: price | stat | feature, text, source, reason, attributedTo }. source must be an http(s) URL, otherwise the claim is stored as unverified. Ignored when empty.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "title": "Best AI visibility tools in 2026",
    "slug": "best-ai-visibility-tools-2026",
    "status": "draft",
    "qualityScore": null,
    "visualCoverage": {
      "named": 0,
      "captured": 0,
      "missing": 0,
      "rate": null
    },
    "claimCoverage": {
      "stated": 3,
      "verified": 3,
      "unverified": 0,
      "rate": 100,
      "unsourcedAttributions": 0
    },
    "createdAt": "2026-05-29T15:42:00.000Z"
  },
  "updated": false
}
GET/api/v1/websites/{websiteId}/articles/{articleId}Get an article

Returns one article with its full Markdown body, schema markup and generation details. Add view=public to read the last published version instead of the working draft; in that mode an article that is not on the public blog (or is a writing brief) returns 404.

Needsarticles:read

Parameters

websiteId* required
string
(path)
The website's id.
articleId* required
string
(path)
The article's id.
view
string
(query)
Set to "public" to return the published snapshot of title, metaDescription, contentMarkdown, schemaMarkup and publishedAt.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "title": "Best AI visibility tools in 2026",
    "slug": "best-ai-visibility-tools-2026",
    "metaDescription": "Side-by-side comparison of the top AI visibility platforms in 2026.",
    "content": null,
    "contentMarkdown": "# Best AI visibility tools in 2026\n\nAI engines now answer...",
    "brief": "Compare the top 8 platforms by price, engines tracked and setup time.",
    "schemaMarkup": "{\"@context\":\"https://schema.org\",\"@type\":\"Article\"}",
    "status": "published",
    "qualityScore": 87,
    "generationSteps": null,
    "opportunityId": "5e6f7a8b-1c2d-4e3f-9a0b-c1d2e3f4a5b6",
    "createdAt": "2026-05-12T14:08:00.000Z",
    "updatedAt": "2026-05-20T09:32:00.000Z",
    "publishedAt": "2026-05-15T10:00:00.000Z"
  }
}
PATCH/api/v1/websites/{websiteId}/articles/{articleId}Update an article

Updates any subset of an article's fields. Setting status "published", or sending a publishedUrl for an article never published before, publishes it: title, body, meta description and schema markup are snapshotted for the public blog, the article is pushed to every connected CMS (502 if every CMS fails), publishedUrl is filled from your blog base URL when the article has none, and the linked action is marked completed. Publishing needs the articles:publish privilege, which API keys holding articles:write already include. Changing the title keeps the existing slug so live URLs never break. A content or title change saves the previous version to the revision history.

Needsarticles:write

Parameters

websiteId* required
string
(path)
The website's id.
articleId* required
string
(path)
The article's id.

Request body

application/json
title
string
New title. The slug only changes if the article had none.
contentMarkdown
string
New Markdown body.
description
string
On-page lede.
metaTitle
string
SEO title tag.
metaDescription
string
SEO meta description.
schemaMarkup
string
JSON-LD markup.
status
string
draft, ready, published, scheduled or archived. published and scheduled need articles:publish. A writing brief cannot be set to published, ready or scheduled (400).
qualityScore
number
Quality score, 0 to 100.
opportunityId
string | null
Link the article to an action on your plan. null removes every link.
publishedUrl
string
The live URL of the article. Needs articles:publish. Setting it on an article never published before also sets status to published.
keywordTerm
string
Primary target keyword, placed first in keywordTerms.
keywordTerms
string[]
All target keywords, deduplicated, max 10.
visuals
object[]
Brand screenshot record, same shape as on create. Ignored when empty.
claims
object[]
Claim record, same shape as on create. Ignored when empty.
generationSteps
object
Generation pipeline metadata.
generationThinking
object
Generation reasoning trace.
revisionNote
string
Note saved with the revision history entry when content or title changes.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "title": "Best AI visibility tools in 2026",
    "slug": "best-ai-visibility-tools-2026",
    "status": "published",
    "qualityScore": 92,
    "publishedUrl": "https://example.com/blog/best-ai-visibility-tools-2026",
    "updatedAt": "2026-05-29T16:01:00.000Z"
  }
}
GET/api/v1/websites/{websiteId}/articles/by-slug/{slug}Get an article by slug

Returns one article looked up by its slug, which is what a blog front end uses to render a page. Same fields as the by-id endpoint plus language, translationOfId and alternates (every language version, itself included) for hreflang tags. With view=public, an article that is not on the public blog (or is a writing brief) returns 404, and translations that are not on the blog are left out of alternates.

Needsarticles:read

Parameters

websiteId* required
string
(path)
The website's id.
slug* required
string
(path)
The article slug.
view
string
(query)
Set to "public" to return the published snapshot instead of the working draft.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "title": "Best AI visibility tools in 2026",
    "slug": "best-ai-visibility-tools-2026",
    "metaDescription": "Side-by-side comparison of the top AI visibility platforms in 2026.",
    "content": null,
    "contentMarkdown": "# Best AI visibility tools in 2026\n\nAI engines now answer...",
    "brief": null,
    "schemaMarkup": "{\"@context\":\"https://schema.org\",\"@type\":\"Article\"}",
    "status": "published",
    "qualityScore": 87,
    "generationSteps": null,
    "opportunityId": "5e6f7a8b-1c2d-4e3f-9a0b-c1d2e3f4a5b6",
    "language": "en",
    "translationOfId": null,
    "alternates": [
      {
        "language": "en",
        "slug": "best-ai-visibility-tools-2026",
        "isOriginal": true
      },
      {
        "language": "fr",
        "slug": "meilleurs-outils-visibilite-ia-2026",
        "isOriginal": false
      }
    ],
    "createdAt": "2026-05-12T14:08:00.000Z",
    "updatedAt": "2026-05-20T09:32:00.000Z",
    "publishedAt": "2026-05-15T10:00:00.000Z"
  }
}
GET/api/v1/websites/{websiteId}/articles/{articleId}/commentsList article comments

Lists the editorial comments on an article, newest first. Each comment is anchored to a passage of the Markdown body (selectedText plus startOffset and endOffset), so a writer or an assistant can apply the change and then resolve it.

Needsarticles:read

Parameters

websiteId* required
string
(path)
The website's id.
articleId* required
string
(path)
The article's id.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "comments": [
      {
        "id": "c1d2e3f4-1111-2222-3333-aabbccddeeff",
        "articleId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "userId": "user_123",
        "userName": "Marc-Olivier",
        "selectedText": "AI visibility platforms typically charge per query",
        "startOffset": 1240,
        "endOffset": 1290,
        "comment": "Lead with the price range instead.",
        "resolved": false,
        "createdAt": "2026-05-22T11:14:00.000Z"
      }
    ]
  }
}
POST/api/v1/websites/{websiteId}/articles/{articleId}/commentsAdd an article comment

Adds an editorial comment anchored to a passage of the article's Markdown body. API keys have no user, so the comment is attributed to the organization owner. Returns the stored comment with status 201.

Needsarticles:write

Parameters

websiteId* required
string
(path)
The website's id.
articleId* required
string
(path)
The article's id.

Request body

application/json
selectedText* required
string
The passage the comment is about.
startOffset* required
number
Character offset where the passage starts in contentMarkdown.
endOffset* required
number
Character offset where the passage ends.
comment* required
string
The comment text.

Responses

CodeDescription
201
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "c1d2e3f4-1111-2222-3333-aabbccddeeff",
    "articleId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "userId": "user_owner_123",
    "parentId": null,
    "selectedText": "AI visibility platforms typically charge per query",
    "startOffset": 1240,
    "endOffset": 1290,
    "comment": "Lead with the price range instead.",
    "resolved": false,
    "resolvedByUserId": null,
    "resolvedAt": null,
    "createdAt": "2026-05-29T16:22:00.000Z"
  }
}
PATCH/api/v1/websites/{websiteId}/articles/{articleId}/commentsResolve an article comment

Marks a comment as resolved, or reopens it. Pass the comment id and the new state in the body. Returns success without the comment.

Needsarticles:write

Parameters

websiteId* required
string
(path)
The website's id.
articleId* required
string
(path)
The article's id.

Request body

application/json
commentId* required
string
The comment's id.
resolved* required
boolean
true to resolve, false to reopen.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true
}
POST/api/v1/websites/{websiteId}/imagesUpload or capture an image

Stores an image for use in an article and returns a public xseek.io URL to embed as ![alt](url). Send multipart/form-data with a file, or JSON with a url to have xSeek take a screenshot of that page (or, with mode "product", fetch the page's own product image first). PNG, JPEG, WebP, GIF and AVIF up to 5 MB. A failed capture returns 502 with captured: false; carry on without the image.

Needsimages:write

Parameters

websiteId* required
string
(path)
The website's id.

Request body

application/json
file
file
Multipart only. The image bytes. Required when sending multipart/form-data. Max 5 MB.
url
string
JSON only. The page to capture. Required when sending application/json.
mode
string
JSON only. "product" tries the page's own product image (og:image or JSON-LD Product.image) before falling back to a screenshot.
fullPage
boolean
JSON only. Capture the full page instead of the first screen. Default false.
delaySeconds
number
JSON only. Seconds to wait before the capture.
alt
string
Alt text. Also used to build the file name in the returned URL.
source
string
Free-form label for where the image came from, echoed back. Defaults to screenshot:<url> or product-<origin>:<imageUrl> for captures.

Responses

CodeDescription
201
Successful response
Example value
{
  "data": {
    "id": "7f3a2b1c-4d5e-4f60-8a9b-0c1d2e3f4a5b",
    "url": "https://www.xseek.io/images/7f3a2b1c-4d5e-4f60-8a9b-0c1d2e3f4a5b/hubspot-pricing-page.jpg",
    "pathname": "web_123/article-images/9c1e2f3a4b5d-screenshot.jpg",
    "contentType": "image/jpeg",
    "size": 184213,
    "alt": "HubSpot pricing page",
    "source": "screenshot:https://www.hubspot.com/pricing"
  }
}

AI traffic

GET/api/v1/websites/{websiteId}/ai-visitsList AI crawler visits by page

Returns the pages AI crawlers (GPTBot, ClaudeBot, PerplexityBot and others) fetched on this website, one row per URL with its visit count, last visit time and the bots that came, most recently visited first. These are bot fetches, not people: for real visitors arriving from an AI assistant use /metrics/ai-clicks.

Needsai_visits:read

Parameters

websiteId* required
string
(path)
The website's id. It must belong to the API key's organization.
page
number
(query)
Page number. Default 1, max 1000000.
pageSize
number
(query)
Rows per page. Default 10, max 100.
botName
string
(query)
Only visits from this exact bot name, case-sensitive, as recorded at tracking time. The xSeek snippets record the catalog names from /api/v1/robots, e.g. GPTBot or claudebot. At most 200 characters once URL-encoded.
searchQuery
string
(query)
Only URLs containing this text (case-insensitive). At most 1000 characters once URL-encoded.
startDate
string
(query)
Start of the window, an ISO 8601 date-time with Z or an offset, e.g. 2026-09-01T00:00:00Z. A bare date (2026-09-01) is rejected. Must be between 1900 and 2299.
endDate
string
(query)
End of the window, inclusive, same format as startDate. Must be on or after startDate.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "url": "https://example.com/blog/ai-search",
      "visitCount": 42,
      "lastVisitDate": "2026-10-20T08:14:03.000Z",
      "botNames": [
        "GPTBot",
        "claudebot"
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 10,
    "totalCount": 150,
    "totalPages": 15
  }
}
POST/api/v1/websites/{websiteId}/ai-visitsRecord an AI crawler visit

Sends one AI crawler hit from your own server or edge logs to xSeek, so it shows up in AI visit tracking. Use it when you detect bots yourself instead of using the xSeek tracking snippet. The visit is timestamped when xSeek receives it. Works on every plan; website-scoped agent keys are refused. Returns 201.

Needsai_visits:push

Parameters

websiteId* required
string
(path)
The website's id. It must belong to the API key's organization.

Request body

application/json
botName* required
string
Name of the bot. Must not be empty. Use the catalog name from /api/v1/robots (e.g. GPTBot, claudebot) so the botName filter on GET matches what the xSeek snippets record.
userAgent* required
string
The full User-Agent header of the request. Must not be empty.
url* required
string
The full URL the bot fetched. Must be a valid URL.
ip
string
The bot's IP address, if you have it.
referer
string
The HTTP Referer header, if any.

Responses

CodeDescription
201
Successful response
Example value
{
  "success": true
}
GET/api/v1/websites/{websiteId}/metrics/ai-clicksGet AI referral clicks

Returns real visitors who landed on the site from an AI assistant (GA4 sessions whose source is ChatGPT, Perplexity, Gemini and so on): the total, its share of all traffic, a per-engine split and a daily series. This is not crawler traffic; that is /ai-visits. When GA4 is not connected or Google Analytics rejects the request, the response says available false with a reason and omits every count rather than returning zeros. aiShareOfTraffic is null when total traffic could not be read or is 0, and totalSessions is null when it could not be read. Days with no AI clicks are left out of timeseries.

Needsai_visits:read

Parameters

websiteId* required
string
(path)
The website's id. It must belong to the API key's organization.
startDate
string
(query)
Start date, YYYY-MM-DD. Defaults to days days before today (30 if days is not set), not before endDate. Must be on or before endDate.
endDate
string
(query)
End date, YYYY-MM-DD. Defaults to yesterday, because today's GA4 data is still incomplete.
days
number
(query)
Trailing window in days, 1 to 365. Default 30. Ignored when startDate is given.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "metric": "aiReferralClicks",
    "startDate": "2026-09-20",
    "endDate": "2026-10-20",
    "available": true,
    "unavailableReason": null,
    "totalAiClicks": 7751,
    "aiShareOfTraffic": 0.073,
    "totalSessions": 106178,
    "enginesCount": 2,
    "byEngine": [
      {
        "engine": "ChatGPT",
        "clicks": 6867,
        "share": 0.886
      },
      {
        "engine": "Perplexity",
        "clicks": 884,
        "share": 0.114
      }
    ],
    "timeseries": [
      {
        "date": "2026-09-20",
        "engines": [
          {
            "engine": "ChatGPT",
            "clicks": 210
          },
          {
            "engine": "Perplexity",
            "clicks": 31
          }
        ]
      }
    ]
  }
}
GET/api/v1/websites/{websiteId}/metrics/top-pagesRank pages by AI referral clicks

Lists your pages ranked by AI referral clicks (GA4 visitors from an AI assistant), with each page's Search Console impressions and the assistant that sends it the most traffic. Clicks follow your date range, but impressions come from a fixed 7, 30 or 90 day Search Console window, reported in impressionsWindowDays. impressions is null when the page has no Search Console row. When GA4 is not connected or Google Analytics rejects the request, the response says available false with a reason instead of zeros.

Needsai_visits:read

Parameters

websiteId* required
string
(path)
The website's id. It must belong to the API key's organization.
startDate
string
(query)
Start date, YYYY-MM-DD. Defaults to days days before today (30 if days is not set), not before endDate. Must be on or before endDate.
endDate
string
(query)
End date, YYYY-MM-DD. Defaults to yesterday, because today's GA4 data is still incomplete.
days
number
(query)
Trailing window in days, 1 to 365. Default 30. Ignored when startDate is given.
pathPrefix
string
(query)
Only pages at or under this path, e.g. /blog matches /blog and /blog/anything.
sortBy
string
(query)
aiClicks (default) or impressions. Pages with null impressions sort last.
page
number
(query)
Page number. Default 1.
pageSize
number
(query)
Rows per page. Default 50, max 200.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "metric": "aiReferralClicks",
    "startDate": "2026-09-20",
    "endDate": "2026-10-20",
    "available": true,
    "unavailableReason": null,
    "impressionsWindowDays": 30,
    "pages": [
      {
        "url": "/blog/ai-agent-customer-support",
        "aiClicks": 84,
        "impressions": 52500,
        "topEngine": "ChatGPT"
      },
      {
        "url": "/pricing",
        "aiClicks": 41,
        "impressions": null,
        "topEngine": "Perplexity"
      }
    ],
    "pagination": {
      "page": 1,
      "pageSize": 50,
      "total": 128,
      "totalPages": 3
    }
  }
}

Technical checks

GET/api/v1/websites/{websiteId}/robots-scansList robots.txt scans

Returns every stored robots.txt scan for this website, most recent first. Each scan lists which AI bots the file allows and which it blocks, by their lower-case robots.txt token, so you can see when a change started blocking a crawler. A bot counts as blocked only when its rules disallow the whole site (Disallow: /); one blocked folder still leaves it allowed. A failed fetch is stored too, with status failed and an errorMessage.

NeedsAny valid key

Parameters

websiteId* required
string
(path)
The website's id. It must belong to the API key's organization.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "id": "3f1c2b8e-1a2b-4c3d-9e8f-0a1b2c3d4e5f",
      "websiteId": "web_123",
      "organizationId": "org_123",
      "scanDate": "2026-10-20T06:00:00.000Z",
      "robotsTxtContent": "User-agent: *\nDisallow: /admin\n\nUser-agent: CCBot\nDisallow: /",
      "allowedBots": [
        "gptbot",
        "oai-searchbot"
      ],
      "disallowedBots": [
        "ccbot"
      ],
      "status": "success",
      "errorMessage": null
    }
  ]
}
GET/api/v1/websites/{websiteId}/robots-scans/latestGet latest robots.txt scan

Returns the most recent robots.txt scan for this website: the file content and which AI bots it allows or blocks, by lower-case robots.txt token. A bot counts as blocked only when its rules disallow the whole site. If the site has never been scanned, the response is {"success": true} with no data field.

NeedsAny valid key

Parameters

websiteId* required
string
(path)
The website's id. It must belong to the API key's organization.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "3f1c2b8e-1a2b-4c3d-9e8f-0a1b2c3d4e5f",
    "websiteId": "web_123",
    "organizationId": "org_123",
    "scanDate": "2026-10-20T06:00:00.000Z",
    "robotsTxtContent": "User-agent: *\nDisallow: /admin\n\nUser-agent: CCBot\nDisallow: /",
    "allowedBots": [
      "gptbot",
      "claudebot"
    ],
    "disallowedBots": [
      "ccbot"
    ],
    "status": "success",
    "errorMessage": null
  }
}
GET/api/v1/tools/llms-txtGenerate an llms.txt file

Generates draft llms.txt content for any website URL, a plain-text summary that tells AI assistants what the site is and which pages matter. Returns the text as a string you can review and publish at /llms.txt. Returns 502 if the generator could not produce it.

NeedsAny valid key

Parameters

url* required
string
(query)
The site to generate for, e.g. https://example.com.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": "# Example Inc\n\n> Example builds scheduling software for clinics.\n\n## Pages\n- [Pricing](https://example.com/pricing): Plans and prices"
}
GET/api/v1/tools/robots-txtCheck any site's robots.txt

Fetches the robots.txt of any website (it reads /robots.txt at the URL's origin) and tells you which AI bots it allows and which it blocks, by lower-case robots.txt token. A bot counts as blocked only when its rules disallow the whole site. Use it to audit a site you do not track, such as a competitor. If the file cannot be fetched you still get a 200 with status failed and an errorMessage.

NeedsAny valid key

Parameters

url* required
string
(query)
Any URL on the site to check, including the scheme, e.g. https://example.com. The path is replaced with /robots.txt unless it already ends in /robots.txt. A URL that cannot be parsed comes back as a failed scan.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "8a7b6c5d-4e3f-4a1b-9c2d-1e0f9a8b7c6d",
    "url": "https://example.com",
    "scanDate": "2026-10-21T15:00:00.000Z",
    "robotsTxtContent": "User-agent: GPTBot\nDisallow: /",
    "allowedBots": [
      "claudebot",
      "perplexitybot"
    ],
    "disallowedBots": [
      "gptbot"
    ],
    "status": "success",
    "errorMessage": null,
    "ipAddress": "203.0.113.10",
    "userAgent": "curl/8.4.0"
  }
}
GET/api/v1/robotsList known AI bot user agents

Returns xSeek's catalog of known AI crawlers and assistants, grouped by company, with each bot's user agent string, a regex pattern to detect it and what it is used for. No API key needed, CORS is open and responses are cached at the edge for an hour, so you can call it from your own bot detection code. The ips arrays are currently always empty.

NeedsNo key needed

Parameters

format
string
(query)
full (default): grouped by company. compact: one flat agents list with a company field. patterns-only: just name, pattern and patternFlags. Any other value returns full.
company
string
(query)
Only this company's bots, case-insensitive, by catalog key, e.g. openai, anthropic, google, perplexity, microsoft, meta, apple, xai, mistral, common_crawl. An unknown name returns empty results.

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "companies": {
      "openai": {
        "userAgents": [
          {
            "name": "GPTBot",
            "userAgent": "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.1; +https://openai.com/gptbot)",
            "pattern": "GPTBot",
            "patternFlags": "i",
            "description": "OpenAI crawler for training ChatGPT"
          }
        ],
        "ips": []
      },
      "anthropic": {
        "userAgents": [
          {
            "name": "claudebot",
            "userAgent": "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ClaudeBot/1.0; +claudebot@anthropic.com)",
            "pattern": "ClaudeBot",
            "patternFlags": "i",
            "description": "ClaudeBot training crawler for web data collection"
          }
        ],
        "ips": []
      }
    },
    "totalCompanies": 26,
    "totalAgents": 48,
    "lastUpdated": "2026-10-21T15:00:00.000Z"
  }
}

Account & keys

GET/api/v1/accountGet account info

Returns the organization the API key belongs to, its plan, and the enabled API keys connected to it (devices), with isCurrent marking the key making the call. Useful to check which account a key points to.

NeedsAny valid key

Parameters

No parameters

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": {
    "organization": {
      "id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
      "name": "Acme Inc.",
      "plan": "growth"
    },
    "devices": [
      {
        "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
        "name": "Claude Code",
        "note": null,
        "createdAt": "2026-09-02T09:00:00.000Z",
        "lastUsedAt": "2026-10-21T14:58:00.000Z",
        "isCurrent": true
      }
    ]
  }
}
GET/api/v1/api-keysList API keys

Lists every API key in your organization, newest first, including disabled ones. The secret key value is never returned, only its id, name, creation date, last use and whether it is enabled.

NeedsAny valid key

Parameters

No parameters

Responses

CodeDescription
200
Successful response
Example value
{
  "success": true,
  "data": [
    {
      "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
      "name": "Server Key",
      "createdAt": "2026-09-02T09:00:00.000Z",
      "lastUsedAt": "2026-10-21T14:58:00.000Z",
      "enabled": true
    }
  ]
}
POST/api/v1/api-keysCreate an API key

Creates a new API key for your organization and returns its secret value once, with status 201. Store it right away: it is not shown again. A key can only grant privileges it holds itself, and a temporary key cannot create keys (403). A key created with no privileges can only call endpoints that need none, such as /api/v1/account.

NeedsAny valid key

Parameters

No parameters

Request body

application/json
name* required
string
Display name for the key. Must not be empty.
note
string
Free-text note stored with the key.
privileges
string[]
Privileges to grant, for example ["websites:read","prompts:read"]. Valid values: ai_visits:push, ai_visits:read, llm_queries:read, websites:create, websites:read, websites:update, prompts:create, prompts:read, prompts:update, articles:read, articles:write, articles:publish, images:write, brand:write, integrations:write. Each must be held by the calling key. Duplicates are removed. Defaults to none.

Responses

CodeDescription
201
Successful response
Example value
{
  "success": true,
  "data": {
    "id": "d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f6a",
    "name": "Reporting script",
    "key": "aist_4f9c2b7e1a3d4c5b8e6f7a9b0c1d2e3f",
    "createdAt": "2026-10-21T15:10:00.000Z"
  }
}