Visibility Reports¶
A visibility report is one asynchronous analysis of the website stored in a workspace. The API accepts the run immediately, then exposes its state and result through read endpoints.
Report lifecycle¶
stateDiagram-v2
direction TB
[*] --> queued: POST report
queued --> running: processing starts
queued --> failed: run cannot start
running --> completed: result available
running --> failed: error or timeout
completed --> [*]
failed --> [*] | Status | Terminal | Meaning |
|---|---|---|
queued | No | The run was accepted and is waiting to start |
running | No | The visibility analysis is executing |
completed | Yes | result is available |
failed | Yes | result is null and error describes the failure |
Poll only while the report is queued or running. A report that has started is limited to approximately ten minutes of analysis time. Queued time is separate, so the partner should apply its own overall polling deadline and backoff policy.
Launch a report¶
POST /v1/workspaces/{workspace_id}/visibility-reports
Authorization: Bearer <partner-api-key>
Idempotency-Key: customer-visibility-2026-08-31
Required scope: reports:write
| Path parameter | Type | Description |
|---|---|---|
workspace_id | string | Workspace returned by POST /v1/workspaces |
| Header | Type | Required | Rules |
|---|---|---|---|
Idempotency-Key | string | No | 1–255 characters; unique within the workspace |
The request has no body. The report uses the website and enabled models stored on the workspace.
Example request:
curl --request POST \
--url "$NBLICK_API_BASE_URL/v1/workspaces/pws-V1StGXR8_Z5jdHi6B-myT/visibility-reports" \
--header "Authorization: Bearer $NBLICK_PARTNER_API_KEY" \
--header "Idempotency-Key: customer-visibility-2026-08-31"
Response — 202 Accepted:
{
"id": "pvr-8xR2kQpL_5jdHi6B-myT",
"workspace_id": "pws-V1StGXR8_Z5jdHi6B-myT",
"status": "queued",
"result": null,
"error": null,
"created_at": "2026-08-31T12:01:00Z",
"updated_at": null,
"completed_at": null
}
| Status | When |
|---|---|
202 | New report accepted, or existing report returned for the same idempotency key |
401 | Partner credential is missing or invalid |
403 | Credential lacks reports:write |
404 | Workspace does not exist or belongs to another partner |
422 | Path, header, or idempotency-key validation fails |
503 | Report was not queued; response code is report_not_queued |
Idempotency and retries¶
- Repeating the same key in the same workspace returns the original report with
202and does not launch another run. - The returned report may already be
running,completed, orfailed. - The same key can be used independently in another workspace.
- Omitting the key creates a new report for every accepted request.
- A
503 report_not_queuedresponse does not consume the key, so retry with the same value. - Once a report exists, including a failed report, use a new key to request another analysis.
List reports for a workspace¶
Required scope: reports:read
Query parameters¶
| Parameter | Type | Default | Rules |
|---|---|---|---|
limit | integer | 50 | Minimum 1, maximum 100 |
cursor | string or null | null | Exact pagination.next_cursor from the preceding page |
status | enum or null | null | queued, running, completed, or failed |
created_after | RFC 3339 date-time or null | null | Strict lower bound; timezone required |
created_before | RFC 3339 date-time or null | null | Strict upper bound; timezone required |
Filters are combined with AND. When both timestamps are supplied, created_after must be earlier than created_before. Results are ordered newest first.
Example request:
curl --get \
--url "$NBLICK_API_BASE_URL/v1/workspaces/pws-V1StGXR8_Z5jdHi6B-myT/visibility-reports" \
--header "Authorization: Bearer $NBLICK_PARTNER_API_KEY" \
--data-urlencode "status=queued" \
--data-urlencode "limit=50"
Response — 200 OK:
{
"data": [
{
"id": "pvr-8xR2kQpL_5jdHi6B-myT",
"workspace_id": "pws-V1StGXR8_Z5jdHi6B-myT",
"status": "queued",
"result": null,
"error": null,
"created_at": "2026-08-31T12:01:00Z",
"updated_at": null,
"completed_at": null
}
],
"pagination": {
"next_cursor": null,
"has_more": false
}
}
| Status | When |
|---|---|
200 | Page returned |
400 | Cursor is invalid for this workspace or the active filters |
401 | Partner credential is missing or invalid |
403 | Credential lacks reports:read |
404 | Workspace does not exist or belongs to another partner |
422 | Limit, status, timestamp, header, or date range is invalid |
To retrieve the latest completed report, request:
If data is empty, the workspace has no completed report.
Retrieve or poll one report¶
Required scope: reports:read
| Path parameter | Type | Description |
|---|---|---|
report_id | string | Globally unique report ID returned when the report was launched |
Example request:
curl \
--url "$NBLICK_API_BASE_URL/v1/visibility-reports/pvr-8xR2kQpL_5jdHi6B-myT" \
--header "Authorization: Bearer $NBLICK_PARTNER_API_KEY"
Response — 200 OK after completion:
{
"id": "pvr-8xR2kQpL_5jdHi6B-myT",
"workspace_id": "pws-V1StGXR8_Z5jdHi6B-myT",
"status": "completed",
"result": {
"report": {
"brand_name": "Customer",
"url": "https://customer.example/",
"category": "Remote hiring",
"description": "A platform that helps companies hire remote professionals.",
"visibility_score": 0.5,
"ai_visibility_score": 50,
"score_band": "good",
"scoring_version": 2,
"score_breakdown": {
"mention_rate": {"value": 0.5, "weight": 0.4, "points": 20.0},
"platform_coverage": {"value": 0.5, "weight": 0.2, "points": 10.0},
"avg_rank": {"value": 0.5, "weight": 0.2, "points": 10.0},
"sentiment": {"value": 0.5, "weight": 0.1, "points": 5.0},
"competitive_gap": {"value": 0.5, "weight": 0.1, "points": 5.0}
},
"answers_analyzed": 2,
"overall": {
"visibility_pct": 0.5,
"mean_reciprocal_rank": 0.5,
"citation_share": 1.0,
"sentiment_counts": {"positive": 1, "neutral": 0, "negative": 0},
"mention_count": 1,
"answers_analyzed": 2,
"branded_answers": 0
},
"per_engine": {
"openai": {
"visibility_pct": 1.0,
"mean_reciprocal_rank": 1.0,
"citation_share": 1.0,
"sentiment_counts": {"positive": 1, "neutral": 0, "negative": 0},
"mention_count": 1,
"answers_analyzed": 1,
"branded_answers": 0
},
"gemini": {
"visibility_pct": 0.0,
"mean_reciprocal_rank": 0.0,
"citation_share": null,
"sentiment_counts": {"positive": 0, "neutral": 0, "negative": 0},
"mention_count": 0,
"answers_analyzed": 1,
"branded_answers": 0
}
},
"competitors": [
{
"name": "Competitor A",
"category": "Remote hiring platform",
"mention_count": 2,
"visibility_pct": 1.0,
"share_of_voice": 0.67,
"mean_reciprocal_rank": 0.75
},
{
"name": "Customer",
"category": "Remote hiring platform",
"mention_count": 1,
"visibility_pct": 0.5,
"share_of_voice": 0.33,
"mean_reciprocal_rank": 0.5
}
],
"prompts": [
{
"text": "What is the best platform for hiring remote professionals?",
"category": "recommendation",
"responses": [
{
"engine": "openai",
"model_id": "gpt-5-mini",
"answer_text": "Customer and Competitor A are two options for hiring remote professionals.",
"citations": [
{
"url": "https://customer.example/remote-hiring",
"title": "Remote hiring",
"snippet": "Find remote professionals for your next project."
}
]
},
{
"engine": "gemini",
"model_id": "gemini-2.5-flash",
"answer_text": "Competitor A is a widely used option for remote hiring.",
"citations": []
}
]
}
],
"root_files": [
{
"name": "llms.txt",
"url": "https://customer.example/llms.txt",
"present": false,
"status_code": 404,
"content_type": "text/html",
"size_bytes": 0,
"error": null,
"blocked_agents": []
},
{
"name": "robots.txt",
"url": "https://customer.example/robots.txt",
"present": true,
"status_code": 200,
"content_type": "text/plain",
"size_bytes": 284,
"error": null,
"blocked_agents": []
}
]
},
"proposed_articles": [
{
"prompt_text": "best remote hiring platform",
"category": "comparison",
"impact": "high",
"impact_score": 0.82,
"competitors": ["Competitor A"],
"engines_missing": 1,
"engines_total": 2,
"source": "observed"
}
],
"action_list": [
{
"id": "act-fG8kdE3yP0aX7sL2mN9qR",
"workspace_id": "pws-V1StGXR8_Z5jdHi6B-myT",
"report_id": "pvr-8xR2kQpL_5jdHi6B-myT",
"type": "create_article",
"status": "recommended",
"priority": "high",
"executable": true,
"target_engines": ["gemini"],
"title": "Publish a remote hiring comparison guide",
"description": "Create a practical comparison page for remote hiring buyers.",
"why_it_matters": "The brand is absent from high-intent comparison answers.",
"expected_impact": "Improve future mentions and citation share.",
"evidence": {},
"payload": {
"article_title": "How to choose a remote hiring platform",
"category": "comparison",
"language": "en",
"word_range": "1500-2000",
"additional_information": null,
"prompt_ids": ["report:prompt:0"],
"generate_images": true,
"image_count": 2,
"include_faq": true,
"resource_urls": []
},
"first_recommended_at": "2026-08-31T12:07:00Z",
"last_recommended_at": "2026-08-31T12:07:00Z",
"created_at": "2026-08-31T12:07:00Z",
"updated_at": null
}
]
},
"error": null,
"created_at": "2026-08-31T12:01:00Z",
"updated_at": "2026-08-31T12:07:00Z",
"completed_at": "2026-08-31T12:07:00Z"
}
| Status | When |
|---|---|
200 | Report returned in its current state |
401 | Partner credential is missing or invalid |
403 | Credential lacks reports:read |
404 | Report does not exist or belongs to another partner |
422 | Header validation fails |
The report ID is globally unique, so this path does not include workspace_id. The response still includes workspace_id for association in the partner system.
Report response fields¶
| Field | Type | Nullable | Description |
|---|---|---|---|
id | string | No | Opaque report identifier |
workspace_id | string | No | Workspace analyzed by the report |
status | enum | No | queued, running, completed, or failed |
result | object | Yes | Populated only when status is completed |
error | string | Yes | Populated only when status is failed |
created_at | date-time | No | UTC creation timestamp |
updated_at | date-time | Yes | UTC timestamp of the last status or result update |
completed_at | date-time | Yes | Equal to updated_at only for a completed report |
Completed result¶
| Field | Type | Description |
|---|---|---|
result.report | object | Typed visibility analysis |
result.proposed_articles | array | Typed content opportunities |
result.action_list | array | Actions in the same shape returned by the Actions API |
result.report contains these fields:
| Field | Type | Notes |
|---|---|---|
brand_name | string | Brand identified from the website |
url | string | URL analyzed |
category | string | Brand category |
description | string | Brand description |
visibility_score | number | Weighted mention rate |
ai_visibility_score | integer | Composite 0–100 score |
score_band | enum | poor, fair, good, or excellent |
scoring_version | integer | Compare scores within the same version |
score_breakdown | object or null | mention_rate, platform_coverage, avg_rank, sentiment, and competitive_gap; each has value, weight, and points |
answers_analyzed | integer | Number of answers behind the report |
overall | object | Aggregate visibility scores |
per_engine | object | Engine name to visibility scores |
competitors | array | Competitor visibility rows |
prompts | array | Prompts, responses, and citations used in the report |
root_files | array | Root-file checks |
overall and each per_engine value contain visibility_pct, mean_reciprocal_rank, citation_share, sentiment_counts, mention_count, answers_analyzed, and branded_answers.
Each competitors[] row contains:
| Field | Type | Description |
|---|---|---|
name | string | Brand or competitor name |
category | string | Category assigned to the row |
mention_count | integer | Number of analyzed answers that mentioned it |
visibility_pct | number | Visibility rate |
share_of_voice | number | Share of weighted visibility |
mean_reciprocal_rank | number or null | Mean reciprocal rank across analyzed answers |
The array can include the audited brand's own row so the partner can compare it with competitors.
Each prompts[] item contains text, a prompt category, and responses. Each response contains engine, model_id, answer_text, and citations. A citation requires url and can also contain nullable title and snippet.
Each root_files[] item contains:
| Field | Type | Description |
|---|---|---|
name | string | Checked filename |
url | string | Checked root URL |
present | boolean | Whether the expected root file was found |
status_code | integer or null | HTTP status when available |
content_type | string or null | Returned media type |
size_bytes | integer | Response size |
error | string or null | Request failure when one occurred |
blocked_agents | array of strings | Known AI crawlers blocked by robots.txt |
proposed_articles[] contains:
| Field | Type | Values or meaning |
|---|---|---|
prompt_text | string | Query worth addressing |
category | enum | brand_discovery, category_discovery, recommendation, comparison, or buying_intent |
impact | enum | high, medium, or low |
impact_score | number | 0–1 opportunity score |
competitors | array of strings | Competitors associated with the opportunity |
engines_missing | integer | Answers that omitted the brand |
engines_total | integer | Answers analyzed for the query |
source | enum | observed or suggested |
See Actions & Generated Content for the complete action shape and execution workflow.
Failed reports¶
A failed report remains retrievable with 200 OK:
{
"id": "pvr-8xR2kQpL_5jdHi6B-myT",
"workspace_id": "pws-V1StGXR8_Z5jdHi6B-myT",
"status": "failed",
"result": null,
"error": "The visibility analysis could not be completed.",
"created_at": "2026-08-31T12:01:00Z",
"updated_at": "2026-08-31T12:05:00Z",
"completed_at": null
}
A timed-out analysis uses:
An HTTP failure describes the API request. A report with status: "failed" describes the asynchronous run. Check both.