API Reference
Everything you need to integrate CitedSpy into your workflow. Manage brands, trigger runs, and pull AI visibility data via REST API.
Authentication
All API requests require an API key. Generate one in your dashboard under Settings > API Keys.
Include the key in the Authorization header:
curl https://app.citedspy.com/api/v1/brands \
-H "Authorization: Bearer cspy_your_api_key_here"API keys are prefixed with cspy_ for easy identification. Each key is scoped to a single workspace - all data returned is automatically filtered to that workspace.
Errors
CitedSpy uses standard HTTP status codes. All error responses follow this format:
{
"error": "Description of what went wrong"
}| Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (resource was created successfully) |
| 400 | Bad Request (missing or invalid parameters) |
| 401 | Unauthorized (invalid or missing API key) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found (resource does not exist or is not in your workspace) |
| 429 | Rate Limit Exceeded (too many requests) |
| 500 | Internal Server Error |
Rate Limits
API requests are limited to 100 requests per minute per API key. Rate limit information is included in response headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests per window (100) |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
When you exceed the limit, the API returns a 429 response with a message indicating when you can retry.
Pagination
List endpoints support pagination via limit and offset query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | integer | 50 | Number of items to return (max 200) |
| offset | integer | 0 | Number of items to skip |
Paginated responses include a pagination object:
{
"data": [...],
"pagination": {
"limit": 50,
"offset": 0
}
}Brands
/api/v1/brandsList all brands in your workspace.
Response
{
"data": [
{
"id": "uuid",
"name": "My Brand",
"domain": "mybrand.com",
"category": "SaaS",
"description": "Project management tool",
"aliases": ["MyBrand", "MB"],
"owned_domains": ["mybrand.com", "mybrand.io"],
"logo_url": "https://assets.citedspy.com/brands/...",
"created_at": "2026-05-01T00:00:00.000Z",
"updated_at": "2026-05-10T12:00:00.000Z"
}
]
}/api/v1/brands/:idGet a single brand with its competitors and prompt count.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| id | uuid | The brand ID |
Response
{
"data": {
"id": "uuid",
"name": "My Brand",
"domain": "mybrand.com",
"category": "SaaS",
"description": "Project management tool",
"aliases": ["MyBrand", "MB"],
"competitors": [
{ "id": "uuid", "name": "Competitor A", "domain": "competitor-a.com" }
],
"prompt_count": 12
}
}Prompts
Every prompt has a category (its type), which determines how it is run and what its report contains:
| Type | What it tracks |
|---|---|
| discovery | Open-ended questions - whether your brand surfaces at all, where, and how often |
| comparison | Head-to-head benchmarks against named competitors (win rate, podium rate, head-to-head verdicts) |
| branded | Questions about your own brand - answer accuracy, sentiment, themes, owned-citation rate |
Unrecognized or omitted values default to discovery.
/api/v1/promptsList prompts. Optionally filter by brand.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| brand_id | uuid | No | Filter prompts by brand |
| status | string | No | Filter by status: active, paused, archived |
| limit | integer | No | Items per page (default 100, max 200) |
| offset | integer | No | Items to skip (default 0) |
Response
{
"data": [
{
"id": "uuid",
"brand_id": "uuid",
"text": "What is the best project management tool?",
"category": "comparison",
"status": "active",
"tags": ["productivity", "tools"],
"created_at": "2026-05-01T00:00:00.000Z"
}
],
"pagination": { "limit": 100, "offset": 0 }
}/api/v1/prompts/:idGet a single prompt with its run count.
Response
{
"data": {
"id": "uuid",
"brand_id": "uuid",
"text": "What is the best project management tool?",
"category": "comparison",
"status": "active",
"tags": ["productivity"],
"run_count": 45,
"created_at": "2026-05-01T00:00:00.000Z"
}
}/api/v1/promptsCreate a new tracking prompt.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| brand_id | uuid | Yes | Brand to attach this prompt to |
| text | string | Yes | The prompt text to send to AI engines |
| category | string | No | Prompt type: discovery, comparison, or branded (default discovery) |
| compare_competitor_ids | uuid[] | No | For comparison prompts: competitor IDs to benchmark against |
| tags | string[] | No | Array of tags for organizing prompts |
Example
curl -X POST https://app.citedspy.com/api/v1/prompts \
-H "Authorization: Bearer cspy_your_key" \
-H "Content-Type: application/json" \
-d '{
"brand_id": "abc-123",
"text": "What are the top CRM tools for small business?",
"category": "comparison",
"tags": ["crm", "small-business"]
}'Response (201)
{
"data": {
"id": "new-uuid",
"brand_id": "abc-123",
"text": "What are the top CRM tools for small business?",
"category": "comparison",
"status": "active",
"tags": ["crm", "small-business"],
"created_at": "2026-05-17T10:00:00.000Z"
}
}/api/v1/prompts/:idUpdate an existing prompt.
Request body (all fields optional)
| Field | Type | Description |
|---|---|---|
| text | string | Updated prompt text |
| category | string | Updated type: discovery, comparison, or branded |
| compare_competitor_ids | uuid[] | Competitor IDs for a comparison prompt |
| status | string | Set to active, paused, or archived |
| tags | string[] | Updated tags array |
/api/v1/prompts/:idDelete a prompt and all its associated run data.
Response
{ "success": true }/api/v1/prompts/:id/reportGet a prompt's full analytics report. This is the headline read of the API.
Returns the pre-computed, type-aware report for a single prompt: core visibility metrics plus per-engine, ranking, citation and trend breakdowns. The prompt's type adds its own block - comparisonData for comparison prompts, brandedData for branded prompts, both null for discovery. Stale data is recomputed on read, so the response is always current.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| days | integer | 14 | Lookback window in days (1-90) |
Response
{
"data": {
"prompt": { "id": "uuid", "text": "...", "category": "comparison", ... },
"snapshot_date": "2026-06-23",
"computed_at": "2026-06-23T06:00:00.000Z",
"mentionRate": 72, "mentionRateDelta": 5,
"avgPosition": 2.3, "avgPositionDelta": -0.4,
"shareOfVoice": 34, "shareOfVoiceDelta": 2,
"sentimentScore": 0.78, "sentimentScoreDelta": 0.05,
"totalRuns": 60, "mentionedRuns": 43,
"engineBreakdown": [
{ "name": "ChatGPT", "runs": 12, "mention": 80, "position": 1.8, "sentiment": 0.6, "sparkline": [60, 65, 71] }
],
"brandRankings": [
{ "name": "My Brand", "you": true, "rate": 72, "position": 2.3, "share": 34, "sentiment": 0.78, "delta": 5 }
],
"citationSources": [
{ "domain": "mybrand.com", "cites": 23, "share": 18.5, "owned": true }
],
"trendData": [ { "date": "2026-06-22", "rate": 70, "engines": { "ChatGPT": 80 } } ],
"rankingTrend": [ { "date": "2026-06-22", "entities": { "My Brand": 2.1 } } ],
"sentimentTrend": [ { "date": "2026-06-22", "entities": { "My Brand": 0.7 } } ],
"recentResponses": [
{ "run_id": "uuid", "engine": "ChatGPT", "mentioned": true, "position": 1, "sentiment": 0.8, "snippet": "..." }
],
// Present only for comparison prompts (null otherwise):
"comparisonData": { "winRate": 60, "podiumRate": 85, "lossRate": 15, "headToHead": [...], "recentVerdicts": [...] },
// Present only for branded prompts (null otherwise):
"brandedData": null
}
}comparisonData and brandedData are only populated for prompts of that type - check the prompt's category to know which block to read.Runs
/api/v1/runsList prompt execution results.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt_id | uuid | No | Filter by prompt |
| engine | string | No | Filter by engine name (e.g. ChatGPT, Perplexity, Gemini, Claude, Copilot) |
| status | string | No | Filter by status: running, completed, failed |
| limit | integer | No | Items per page (default 50, max 200) |
| offset | integer | No | Items to skip |
Response
{
"data": [
{
"id": "uuid",
"prompt_id": "uuid",
"engine_id": "uuid",
"engine_name": "ChatGPT",
"status": "completed",
"scheduled_at": "2026-05-17T08:00:00.000Z",
"completed_at": "2026-05-17T08:00:03.200Z",
"raw_response_text": "Based on my research, the top project...",
"token_cost_usd": 0.0034
}
],
"pagination": { "limit": 50, "offset": 0 }
}/api/v1/runs/:idGet a single run with its citations and mentions.
Response
{
"data": {
"id": "uuid",
"prompt_id": "uuid",
"engine_id": "uuid",
"engine_name": "Perplexity",
"status": "completed",
"raw_response_text": "Here are the top tools...",
"token_cost_usd": 0.0021,
"citations": [
{
"id": "uuid",
"url": "https://mybrand.com/features",
"domain": "mybrand.com",
"position_in_response": 2,
"snippet": "MyBrand offers advanced analytics..."
}
],
"mentions": [
{
"id": "uuid",
"brand_or_competitor_id": "uuid",
"entity_type": "brand",
"sentiment": "positive",
"snippet": "MyBrand is highly recommended for...",
"position_in_response": 1
}
]
}
}/api/v1/runs/triggerQueue a prompt run across AI engines. Returns immediately; runs execute asynchronously.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| prompt_id | uuid | Yes | The prompt to run |
| engines | string[] | No | Specific engines to run (default: all available). Values: ChatGPT, Perplexity, Gemini, Claude, Copilot |
Example
curl -X POST https://app.citedspy.com/api/v1/runs/trigger \
-H "Authorization: Bearer cspy_your_key" \
-H "Content-Type: application/json" \
-d '{
"prompt_id": "abc-123",
"engines": ["ChatGPT", "Perplexity"]
}'Response
{
"data": {
"runs": [
{ "run_id": "uuid", "engine": "ChatGPT", "status": "pending" },
{ "run_id": "uuid", "engine": "Perplexity", "status": "pending" }
],
"queued": 2,
"total": 2
}
}status: "pending" - poll GET /runs/:id or GET /runs?prompt_id=... until each run reaches completed.Competitors
/api/v1/competitorsList competitors for a specific brand.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| brand_id | uuid | Yes | The brand to get competitors for |
Response
{
"data": [
{
"id": "uuid",
"brand_id": "uuid",
"name": "Competitor A",
"domain": "competitor-a.com",
"aliases": ["CompA", "Comp-A"],
"logo_url": "https://assets.citedspy.com/...",
"created_at": "2026-05-01T00:00:00.000Z"
}
]
}Dashboard
/api/v1/dashboard/:brandIdGet aggregated dashboard metrics for a brand.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| brandId | uuid | The brand ID |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| days | integer | 14 | Number of days for the analysis window |
Response
{
"data": {
"brand_id": "uuid",
"snapshot_date": "2026-05-17",
"computed_at": "2026-05-17T06:00:00.000Z",
"mention_rate": 72,
"mention_rate_delta": 5,
"avg_position": 2.3,
"avg_position_delta": -0.4,
"share_of_voice": 34,
"share_of_voice_delta": 2,
"visibility_score": 68,
"visibility_score_delta": 4,
"prompts_tracked": 25,
"sentiment_score": 0.78,
"total_runs": 150,
"total_mentions": 108,
"prompt_type_counts": { "discovery": 18, "comparison": 5, "branded": 2 },
"engine_breakdown": [
{ "engine": "ChatGPT", "mention_rate": 80, "avg_position": 1.8 },
{ "engine": "Perplexity", "mention_rate": 65, "avg_position": 3.1 }
],
"competitor_stats": [
{ "name": "Competitor A", "mention_rate": 45, "share_of_voice": 22 }
],
"top_citations": [
{ "domain": "mybrand.com", "count": 34, "url": "https://mybrand.com/features" }
],
"trend_data": [
{ "date": "2026-05-10", "mention_rate": 68, "position": 2.5 },
{ "date": "2026-05-11", "mention_rate": 70, "position": 2.4 }
],
// Per prompt-type rollups (null when no prompts of that type exist):
"discovery_stats": { "mentionRate": 71, "avgPosition": 2.3, "shareOfVoice": 45, "enginesCovering": 5 },
"comparison_stats": { "winRate": 60, "podiumRate": 85, "lossRate": 15, "headToHead": [...] },
"branded_stats": { "answerScore": 70, "sentimentScore": 0.4, "coMentionRate": 35, "ownedCitationRate": 60 }
}
}Metrics explained
| Field | Description |
|---|---|
| mention_rate | Percentage of runs where your brand was mentioned (0-100) |
| avg_position | Average position of your brand mention in responses (1 = first mentioned) |
| share_of_voice | Your brand's mentions as % of total mentions (brand + competitors) |
| visibility_score | Blended 0-100 score across all three prompt types (discovery, comparison, branded) |
| sentiment_score | Average sentiment score (-1 to 1, where 1 is most positive) |
| prompt_type_counts | Count of active prompts of each type: discovery, comparison, branded |
| discovery_stats / comparison_stats / branded_stats | Per-type metric rollups; each is null when you have no prompts of that type |
| *_delta | Change compared to the previous period of equal length |
Citations
/api/v1/citationsList URLs cited by AI engines in their responses.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| run_id | uuid | No | Filter by specific run |
| brand_id | uuid | No | Filter citations for a specific brand's prompts |
| limit | integer | No | Items per page (default 50, max 200) |
| offset | integer | No | Items to skip |
Response
{
"data": [
{
"id": "uuid",
"run_id": "uuid",
"url": "https://mybrand.com/blog/guide",
"domain": "mybrand.com",
"position_in_response": 1,
"snippet": "According to MyBrand's guide..."
}
],
"pagination": { "limit": 50, "offset": 0 }
}Mentions
/api/v1/mentionsList brand and competitor mentions detected in AI responses.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| brand_id | uuid | No | Filter by brand |
| entity_type | string | No | Filter: brand or competitor |
| sentiment | string | No | Filter: positive, neutral, or negative |
| limit | integer | No | Items per page (default 50, max 200) |
| offset | integer | No | Items to skip |
Response
{
"data": [
{
"id": "uuid",
"run_id": "uuid",
"brand_or_competitor_id": "uuid",
"entity_type": "brand",
"sentiment": "positive",
"snippet": "MyBrand is one of the leading solutions for...",
"position_in_response": 2
}
],
"pagination": { "limit": 50, "offset": 0 }
}Sentiment values
| Value | Description |
|---|---|
| positive | Brand mentioned favorably (recommended, praised, highlighted as a top choice) |
| neutral | Brand mentioned factually without strong positive or negative framing |
| negative | Brand mentioned negatively (criticized, listed as inferior, warned against) |
Engines
/api/v1/enginesList AI engines with your workspace's on/off selection.
Response
{
"data": [
{ "id": "uuid", "name": "ChatGPT", "model_version": "gpt-5", "enabled": true, "available": true },
{ "id": "uuid", "name": "Perplexity", "model_version": "sonar", "enabled": true, "available": true },
{ "id": "uuid", "name": "Gemini", "model_version": "...", "enabled": false, "available": true },
{ "id": "uuid", "name": "Claude", "model_version": "...", "enabled": true, "available": true },
{ "id": "uuid", "name": "Copilot", "model_version": "...", "enabled": false, "available": false }
]
}| Field | Description |
|---|---|
| enabled | Whether you have switched this engine on for your workspace - runs only execute on enabled engines |
| available | Whether the engine is configured platform-side (has a working API key) and able to accept runs |
| model_version | The specific model version used for this engine |
Toggle engines on or off for your workspace in Settings > Engines. Your plan caps how many can be enabled at once.
Need help with the API?
Our team can help you build custom integrations and troubleshoot any issues.