Content
REST endpoints for Content. Base URL https://api.competlab.com; every request needs a CL-API-Key header (see Authentication). Responses wrap in { item } or { items }; errors in { error }.
Get latest content intelligence data
/v1/projects/{projectId}/contentGet the latest content intelligence data for all competitors in a project. Returns URL counts, content categorization, strategic URL analysis, and competitive gap assessment. Use this to understand the content landscape across competitors. Fields we could not measure are null, with a sibling availability object giving the reason; never read a null as zero, false or absent, and note that a measured 0 / false / empty list is reported as itself and is a real finding. The four gap lists carry a distinction worth respecting: a null list means no comparison ran, while an EMPTY list means we compared and found none — reading the first as the second reports a verdict about a comparison that never happened. A project with no completed run for this dimension yet answers 404 no_data_available — not a missing project and not a failed measurement, just nothing measured here so far; the project itself is fine and get_project still describes it. Distinguish it from project_not_found, which means the id is wrong.
Path parameters
| Name | Type | Description |
|---|---|---|
projectId | string | Project ID |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/content" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"item": {
"lastUpdatedAt": "2026-03-15T10:00:00.000Z",
"summary": {
"sitemapsChecked": 8,
"sitemapsSuccessful": 7,
"customer": {
"domain": "yoursite.com",
"totalUrls": 350,
"strategicUrls": 280,
"categorizedCounts": {
"blog": 120,
"docs": 80,
"tools": 5,
"landing": 30,
"legal": 10,
"caseStudies": 0,
"comparison": 3,
"integrations": 25,
"changelog": 15,
"webinars": 2,
"other": 60
},
"contentAnalysisAvailable": {
"available": false,
"reason": "content_fetch_failed"
}
},
"topCompetitor": {
"domain": "rival.com",
"strategicUrls": 420
},
"strategicUrlGap": -140,
"overallRank": 3,
"comparableCompetitors": 4,
"criticalGaps": [
{
"category": "caseStudies",
"categoryLabel": "Case Studies",
"competitorsWithContent": 3,
"leader": {
"domain": "competitor.com",
"count": 42,
"exampleUrls": [
"https://competitor.com/blog/post-1",
"https://competitor.com/blog/post-2"
]
},
"actionHint": "Create 3-5 case studies showcasing customer success stories"
}
],
"significantGaps": [
{
"category": "blog",
"categoryLabel": "Blog Posts",
"customerCount": 5,
"competitorAverage": 25,
"competitorMax": 42,
"gapPercentage": -80,
"leader": {
"domain": "competitor.com",
"count": 42,
"exampleUrls": [
"https://competitor.com/blog/post-1",
"https://competitor.com/blog/post-2"
]
},
"actionHint": "Increase blog output to at least match competitor average of 25 posts"
}
],
"advantages": [
{
"category": "docs",
"categoryLabel": "Documentation",
"customerCount": 120,
"competitorMax": 45,
"leadAmount": 75
}
],
"onTrack": [
{
"category": "landing",
"categoryLabel": "Landing Pages",
"customerCount": 30,
"competitorMax": 35,
"differencePercent": -14
}
],
"totalCriticalGaps": 2,
"totalSignificantGaps": 1,
"totalAdvantages": 3,
"totalOnTrack": 4
},
"competitors": [
{
"domain": "competitor.com",
"isOwn": false,
"totalUrls": 450,
"strategicUrls": 380,
"categorizedCounts": {
"blog": 150,
"docs": 100,
"tools": 10,
"landing": 10,
"caseStudies": 10,
"comparison": 10,
"integrations": 10,
"changelog": 10,
"webinars": 10,
"legal": 10,
"programmatic": 27996,
"other": 65
},
"programmaticExampleUrls": [
"https://competitor.com/ncm/01012100",
"https://competitor.com/cnae/0119-9-01",
"https://competitor.com/cfop/1101"
],
"sitemapCount": 2,
"contentDataAvailable": {
"available": false,
"reason": "no_sitemap_published"
}
}
]
}
}Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_missing · api_key_invalid · api_key_revoked · api_key_expired · insufficient_scope | The CL-API-Key is missing, malformed, revoked, expired, or lacks the required scope. |
| 404 | project_not_found · no_data_available | No project matches the id in the path. |
Every error uses the shared { error: { code, message, status } } envelope; code is one of the values listed above.
{
"error": {
"code": "api_key_invalid",
"message": "Invalid API key",
"status": 401
}
}Get content intelligence run history
/v1/projects/{projectId}/content/historyGet paginated history of content intelligence monitoring runs for a project. Each entry includes run timing and summary statistics with gap analysis. Fields we could not measure are null, with a sibling availability object giving the reason; never read a null as zero, false or absent, and note that a measured 0 / false / empty list is reported as itself and is a real finding.
Path parameters
| Name | Type | Description |
|---|---|---|
projectId | string | Project ID |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | number | Page number (1-indexed). Default 1. Min 1. | |
limit | number | Number of items per page. Default 20. Range 1–100. |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/content/history?page=1&limit=20" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"items": [
{
"runId": "507f1f77bcf86cd799439011",
"completedAt": "2026-03-15T10:00:00.000Z",
"summary": {
"sitemapsChecked": 8,
"sitemapsSuccessful": 7,
"customer": {
"domain": "yoursite.com",
"totalUrls": 350,
"strategicUrls": 280,
"categorizedCounts": {
"blog": 120,
"docs": 80,
"tools": 5,
"landing": 30,
"legal": 10,
"caseStudies": 0,
"comparison": 3,
"integrations": 25,
"changelog": 15,
"webinars": 2,
"other": 60
},
"contentAnalysisAvailable": {
"available": false,
"reason": "content_fetch_failed"
}
},
"topCompetitor": {
"domain": "rival.com",
"strategicUrls": 420
},
"strategicUrlGap": -140,
"overallRank": 3,
"comparableCompetitors": 4,
"criticalGaps": [
{
"category": "caseStudies",
"categoryLabel": "Case Studies",
"competitorsWithContent": 3,
"leader": {
"domain": "competitor.com",
"count": 42,
"exampleUrls": [
"https://competitor.com/blog/post-1",
"https://competitor.com/blog/post-2"
]
},
"actionHint": "Create 3-5 case studies showcasing customer success stories"
}
],
"significantGaps": [
{
"category": "blog",
"categoryLabel": "Blog Posts",
"customerCount": 5,
"competitorAverage": 25,
"competitorMax": 42,
"gapPercentage": -80,
"leader": {
"domain": "competitor.com",
"count": 42,
"exampleUrls": [
"https://competitor.com/blog/post-1",
"https://competitor.com/blog/post-2"
]
},
"actionHint": "Increase blog output to at least match competitor average of 25 posts"
}
],
"advantages": [
{
"category": "docs",
"categoryLabel": "Documentation",
"customerCount": 120,
"competitorMax": 45,
"leadAmount": 75
}
],
"onTrack": [
{
"category": "landing",
"categoryLabel": "Landing Pages",
"customerCount": 30,
"competitorMax": 35,
"differencePercent": -14
}
],
"totalCriticalGaps": 2,
"totalSignificantGaps": 1,
"totalAdvantages": 3,
"totalOnTrack": 4
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 47,
"totalPages": 3,
"hasMore": true
}
}Paginated — pass page and limit query parameters and follow pagination.hasMore to page through the full set.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_missing · api_key_invalid · api_key_revoked · api_key_expired · insufficient_scope | The CL-API-Key is missing, malformed, revoked, expired, or lacks the required scope. |
Every error uses the shared { error: { code, message, status } } envelope; code is one of the values listed above.
{
"error": {
"code": "api_key_invalid",
"message": "Invalid API key",
"status": 401
}
}Get content intelligence data for a specific run
/v1/projects/{projectId}/content/history/{runId}Get full content intelligence data for a specific historical monitoring run, including per-competitor URL counts, content categorization, and strategic URL metrics. Fields we could not measure are null, with a sibling availability object giving the reason; never read a null as zero, false or absent, and note that a measured 0 / false / empty list is reported as itself and is a real finding. A run that finished but produced no summary answers 404 run_not_summarized, which is a different fact from run_not_found: the run exists, it just has nothing to report. It used to answer 200 with an all-zero summary invented from the missing one.
Path parameters
| Name | Type | Description |
|---|---|---|
runId | string | Run ID |
projectId | string | Project ID |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/content/history/507f1f77bcf86cd799439012" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"item": {
"runId": "507f1f77bcf86cd799439011",
"completedAt": "2026-03-15T10:00:00.000Z",
"summary": {
"sitemapsChecked": 8,
"sitemapsSuccessful": 7,
"customer": {
"domain": "yoursite.com",
"totalUrls": 350,
"strategicUrls": 280,
"categorizedCounts": {
"blog": 120,
"docs": 80,
"tools": 5,
"landing": 30,
"legal": 10,
"caseStudies": 0,
"comparison": 3,
"integrations": 25,
"changelog": 15,
"webinars": 2,
"other": 60
},
"contentAnalysisAvailable": {
"available": false,
"reason": "content_fetch_failed"
}
},
"topCompetitor": {
"domain": "rival.com",
"strategicUrls": 420
},
"strategicUrlGap": -140,
"overallRank": 3,
"comparableCompetitors": 4,
"criticalGaps": [
{
"category": "caseStudies",
"categoryLabel": "Case Studies",
"competitorsWithContent": 3,
"leader": {
"domain": "competitor.com",
"count": 42,
"exampleUrls": [
"https://competitor.com/blog/post-1",
"https://competitor.com/blog/post-2"
]
},
"actionHint": "Create 3-5 case studies showcasing customer success stories"
}
],
"significantGaps": [
{
"category": "blog",
"categoryLabel": "Blog Posts",
"customerCount": 5,
"competitorAverage": 25,
"competitorMax": 42,
"gapPercentage": -80,
"leader": {
"domain": "competitor.com",
"count": 42,
"exampleUrls": [
"https://competitor.com/blog/post-1",
"https://competitor.com/blog/post-2"
]
},
"actionHint": "Increase blog output to at least match competitor average of 25 posts"
}
],
"advantages": [
{
"category": "docs",
"categoryLabel": "Documentation",
"customerCount": 120,
"competitorMax": 45,
"leadAmount": 75
}
],
"onTrack": [
{
"category": "landing",
"categoryLabel": "Landing Pages",
"customerCount": 30,
"competitorMax": 35,
"differencePercent": -14
}
],
"totalCriticalGaps": 2,
"totalSignificantGaps": 1,
"totalAdvantages": 3,
"totalOnTrack": 4
},
"competitors": [
{
"domain": "competitor.com",
"isOwn": false,
"totalUrls": 450,
"strategicUrls": 380,
"categorizedCounts": {
"blog": 150,
"docs": 100,
"tools": 10,
"landing": 10,
"caseStudies": 10,
"comparison": 10,
"integrations": 10,
"changelog": 10,
"webinars": 10,
"legal": 10,
"programmatic": 27996,
"other": 65
},
"programmaticExampleUrls": [
"https://competitor.com/ncm/01012100",
"https://competitor.com/cnae/0119-9-01",
"https://competitor.com/cfop/1101"
],
"sitemapCount": 2,
"contentDataAvailable": {
"available": false,
"reason": "no_sitemap_published"
}
}
]
}
}Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_missing · api_key_invalid · api_key_revoked · api_key_expired · insufficient_scope | The CL-API-Key is missing, malformed, revoked, expired, or lacks the required scope. |
| 404 | run_not_found · run_not_summarized | No run matches the id in the path. |
Every error uses the shared { error: { code, message, status } } envelope; code is one of the values listed above.
{
"error": {
"code": "api_key_invalid",
"message": "Invalid API key",
"status": 401
}
}Get content changelog
/v1/projects/{projectId}/content/changelogGet paginated changelog of detected URL changes across competitor sitemaps. Each item shows numeric counts per category plus up to 3 sample URLs per category by default — safe for any token budget. Supports filtering by competitorId and category query parameters. Pass allUrlsPerCategory=true for the full URL list per category (warning: high-activity competitors can produce large responses; combine with filters to scope, and check the truncated flag in the response — when true, the byte cap fired and items/URLs were trimmed).
Path parameters
| Name | Type | Description |
|---|---|---|
projectId | string | Project ID |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | number | Page number (1-indexed). Default 1. Min 1. | |
limit | number | Number of items per page. Default 20. Range 1–100. | |
competitorId | string | Filter by competitor ID | |
category | string | Filter by content category (blog, docs, tools, landing, caseStudies, comparison, integrations, changelog, webinars, legal, programmatic, other). Changelog rows carry the same category the content dashboard reports for the same URL, decided over the competitor’s whole sitemap rather than over the changed URLs alone — so a page added into a templated catalog filters under programmatic, not other. Values: blog, docs, tools, landing, caseStudies, comparison, integrations, changelog, webinars, legal, programmatic, other. | |
allUrlsPerCategory | boolean | By default each item returns up to 3 sample URLs per category. Set true to return the full URL list per category. High-activity competitors can produce large responses — combine with category and competitorId filters to scope. Subject to an internal byte cap; check truncated in the response. |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/content/changelog?page=1&limit=20&competitorId=507f1f77bcf86cd799439011&category=blog&allUrlsPerCategory=false" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"items": [
{
"competitorDomain": "competitor.com",
"sitemapUrl": "https://competitor.com/sitemap.xml",
"checkedAt": "2026-03-15T10:00:00.000Z",
"totalAdded": 5,
"totalRemoved": 2,
"addedByCategory": {
"blog": [
"https://competitor.com/blog/new-post"
],
"docs": [
"https://competitor.com/docs/new-guide"
],
"programmatic": [
"https://competitor.com/ncm/01012100"
]
},
"removedByCategory": {
"landing": [
"https://competitor.com/old-landing"
]
},
"addedCounts": {
"blog": 1,
"docs": 1
},
"removedCounts": {
"landing": 1
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 47,
"totalPages": 3,
"hasMore": true
},
"truncated": false
}Paginated — pass page and limit query parameters and follow pagination.hasMore to page through the full set.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_missing · api_key_invalid · api_key_revoked · api_key_expired · insufficient_scope | The CL-API-Key is missing, malformed, revoked, expired, or lacks the required scope. |
Every error uses the shared { error: { code, message, status } } envelope; code is one of the values listed above.
{
"error": {
"code": "api_key_invalid",
"message": "Invalid API key",
"status": 401
}
}