Strategic Briefing
REST endpoints for Strategic Briefing. 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 the current state of this project’s strategic briefing
/v1/projects/{projectId}/strategic-briefingGet the current state of this project’s strategic briefing — the synthesized, as-of competitive read (what changed and what it means). This is the ANALYZED briefing; for the raw monitoring data use the per-dimension dashboards (e.g. /pricing), and for the monitored-competitor roster use /competitors.
This returns the LATEST run, whatever state it is in. meta.status is that run’s raw lifecycle status and is the field to branch on:
done— the run finished.item,coverage,containsandticketsare populated; this is the readable edition.running— a briefing is being generated right now.itemis null;meta.progresscarries the step it is on and when it started. A run finishes within two hours — budget rather than busy-wait. Treat it as running until this status changes, and never infer that a run is late or failed from how long it has taken.failed— the most recent attempt ended without producing an edition.itemis null.null— this project has never had a briefing run at all. This is the only value that means the project genuinely has nothing.
When meta.status is not done, THIS RUN has no content — which says nothing about whether the project has a briefing. A previously published edition is usually still readable: call GET /v1/projects/{projectId}/strategic-briefing/history to list the editions and …/history/{runId} to read one. Never report that no briefing is available on the strength of a null item — check history first.
A missing or non-accessible project returns 404 project_not_found; the briefing lifecycle itself never 404s.
Sections — lean by default. sections selects what to return and defaults to ["hub"] — the executive digest AND the navigation map. A hub diagnosis row that earned a deep-dive carries a deepDive pointer naming the dimension to read next — prefix it with deep- to form the section value (a pointer of ai-visibility → request sections=deep-ai-visibility). A row without one has no deep-dive this edition and its note says why; request a section only for a pointer that is present. Valid sections: hub, competitors, the fourteen deep-<dimension> deep-dives, or all for the full document. Charts are off by default (includeCharts=false returns each chart’s title + note only); pass includeCharts=true for the full data series.
What the edition recommends doing is not a section. Every move in the edition lands on the project’s Strategic Tickets board — as a new ticket, most important first, or on the ticket already there for that work (tickets.alreadyOnBoard). New tickets land in the triage column, and the team moves them from there; each carries the dimension and the estimate of the work where the edition gave them. Read the ones it opened with GET /v1/projects/{projectId}/tickets?origin=briefing&briefingRunId={runId}, taking {runId} from meta.runId. They are the team’s from that moment: moving, editing or dismissing one changes the board, not the edition.
Returned with any finished edition, regardless of sections: coverage (methodology and data-quality caveats — read these before quoting any figure), contains (the list of sections this edition actually holds, so you can pick what to fetch next instead of requesting slots blind), and tickets (how many tickets this edition opened on the board and how many of them sit in each column, counted as you read). All three are null whenever meta.status is not done, which says nothing about whether earlier editions exist — check …/history.
Reading the numbers: the AI Visibility Score is 0–100 (higher is better); AI-visibility presence is the share of answers analysed that named a brand (higher is named more often) and is always read with its range; on a ticket this edition opened, impact is 1–4 (higher is more critical) and briefing.estimatedMinutes is the edition’s own estimate of the work, null where it gave none — report that as unsized rather than as quick. A project’s next scheduled briefing follows its plan’s cadence: 30 days on Monitor, two weeks on Process. An edition run on request (Run an edition now) does not move it.
Path parameters
| Name | Type | Description |
|---|---|---|
projectId | string | Project ID |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
sections | array<string> | Which sections to return. Defaults to [“hub”] — the executive digest and navigation map. Pass specific sections to go deeper (e.g. a hub diagnosis pointer of ai-visibility maps to deep-ai-visibility), or all for the full document. Prefer deriving deep-<dimension> values from the hub diagnosis pointers rather than requesting slots blind — the response’s contains array lists exactly which sections exist for this edition, in this same vocabulary. | |
includeCharts | boolean | Include full chart series data. Defaults to false — each chart returns its title and note only, with no underlying numbers. Pass true for the full series. Default false. |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/strategic-briefing?sections=hub&includeCharts=false" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"meta": {
"runId": "6a3950c06bda450a1372048e",
"status": "done",
"editionNumber": 4,
"startedAt": "2026-06-22T14:20:02.110Z",
"briefingDate": "2026-06-22T15:51:24.443Z",
"progress": {
"step": "researching",
"startedAt": "2026-07-27T06:00:11.002Z",
"updatedAt": "2026-07-27T06:41:03.771Z"
}
},
"item": {
"hub": {
"kind": "hub",
"blocks": [
{
"type": "headline",
"verdict": "Pricing pressure is rising."
}
]
}
},
"coverage": {
"type": "coverage",
"items": [
"review counts are directional"
]
},
"contains": [
"hub",
"competitors",
"deep-ai-visibility",
"deep-pricing"
],
"tickets": {
"total": 9,
"byStatus": {
"triage": 5,
"todo": 3,
"in_progress": 0,
"done": 1,
"dismissed": 0
},
"opened": [
"507f1f77bcf86cd799439011",
"507f1f77bcf86cd799439012"
],
"commented": [
{
"ticketId": "507f1f77bcf86cd799439011",
"commentId": "507f1f77bcf86cd799439021",
"kind": "result",
"body": "On 14 Sep, before this ticket opened, ChatGPT named you in 2 of 8 answers. On 5 Oct, the first check after it closed, it named you in 4 of 8."
}
],
"alreadyOnBoard": [
{
"ticketId": "507f1f77bcf86cd799439011",
"move": "Get onto the 2026 buyer guide",
"status": "triage"
}
],
"recheckedUnchanged": [
"507f1f77bcf86cd799439014"
]
}
}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 · not_found | 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
}
}List this project’s briefing editions
/v1/projects/{projectId}/strategic-briefing/historyList this project’s Strategic Briefing runs, newest first — one cheap metadata row each, never briefing content.
Each row carries runId (pass it to …/history/{runId} to read that edition in full), briefingDate, status, the derived editionNumber, and headline — that edition’s one-line verdict, so you can pick which one to open without fetching every one.
Runs that failed or are still generating are included, with editionNumber, briefingDate and headline null. A gap between two editions is therefore explained rather than left unexplained.
Use this to answer “what did we say in April”, to trace how a read has changed over time, or — when GET /strategic-briefing reports a running or failed status — to find the last edition that is actually readable. For the current state use GET /strategic-briefing, not this.
Paginated with page and limit; check pagination.hasMore to fetch further pages. Scheduled briefings come every 30 days on Monitor and every two weeks on Process, plus any run on request, so this list is typically short.
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/strategic-briefing/history?page=1&limit=20" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"items": [
{
"runId": "6a3950c06bda450a1372048e",
"editionNumber": 4,
"briefingDate": "2026-06-22T15:51:24.443Z",
"documentDate": "2026-06-22",
"status": "done",
"headline": "Pricing pressure is rising in your segment."
}
],
"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 one briefing edition by id
/v1/projects/{projectId}/strategic-briefing/history/{runId}Get one strategic briefing edition by runId, in exactly the same shape as GET /strategic-briefing.
Take the runId from GET /strategic-briefing/history (or from meta.runId on a current read). Accepts the same sections and includeCharts options, with the same ["hub"] default.
A runId naming a run that failed or is still generating returns 200 with meta.status set accordingly and item null — that run genuinely produced no edition, which is an answer rather than an error. A malformed id returns 400 invalid_run_id; an id that does not belong to this project returns 404 run_not_found.
Use this to read or quote a specific past edition. For the current state use GET /strategic-briefing.
Path parameters
| Name | Type | Description |
|---|---|---|
runId | string | Briefing run ID, from meta.runId or a history row |
projectId | string | Project ID |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
sections | array<string> | Which sections to return. Defaults to [“hub”] — the executive digest and navigation map. Pass specific sections to go deeper (e.g. a hub diagnosis pointer of ai-visibility maps to deep-ai-visibility), or all for the full document. Prefer deriving deep-<dimension> values from the hub diagnosis pointers rather than requesting slots blind — the response’s contains array lists exactly which sections exist for this edition, in this same vocabulary. | |
includeCharts | boolean | Include full chart series data. Defaults to false — each chart returns its title and note only, with no underlying numbers. Pass true for the full series. Default false. |
Request
curl "https://api.competlab.com/v1/projects/507f1f77bcf86cd799439011/strategic-briefing/history/6a3950c06bda450a1372048e?sections=hub&includeCharts=false" \
-H "CL-API-Key: YOUR_COMPETLAB_API_KEY"Response 200 OK
Example
{
"meta": {
"runId": "6a3950c06bda450a1372048e",
"status": "done",
"editionNumber": 4,
"startedAt": "2026-06-22T14:20:02.110Z",
"briefingDate": "2026-06-22T15:51:24.443Z",
"progress": {
"step": "researching",
"startedAt": "2026-07-27T06:00:11.002Z",
"updatedAt": "2026-07-27T06:41:03.771Z"
}
},
"item": {
"hub": {
"kind": "hub",
"blocks": [
{
"type": "headline",
"verdict": "Pricing pressure is rising."
}
]
}
},
"coverage": {
"type": "coverage",
"items": [
"review counts are directional"
]
},
"contains": [
"hub",
"competitors",
"deep-ai-visibility",
"deep-pricing"
],
"tickets": {
"total": 9,
"byStatus": {
"triage": 5,
"todo": 3,
"in_progress": 0,
"done": 1,
"dismissed": 0
},
"opened": [
"507f1f77bcf86cd799439011",
"507f1f77bcf86cd799439012"
],
"commented": [
{
"ticketId": "507f1f77bcf86cd799439011",
"commentId": "507f1f77bcf86cd799439021",
"kind": "result",
"body": "On 14 Sep, before this ticket opened, ChatGPT named you in 2 of 8 answers. On 5 Oct, the first check after it closed, it named you in 4 of 8."
}
],
"alreadyOnBoard": [
{
"ticketId": "507f1f77bcf86cd799439011",
"move": "Get onto the 2026 buyer guide",
"status": "triage"
}
],
"recheckedUnchanged": [
"507f1f77bcf86cd799439014"
]
}
}Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_parameters · invalid_run_id · invalid_check_id · bad_request · paging_requires_compact_view · paging_requires_summary · nothing_to_return | Bad request — the payload failed validation. |
| 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 · not_found | 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": "invalid_parameters",
"message": "Domain is required",
"status": 400
}
}