List website pages
Your indexed pages, each with its indexing state and its AI-search citation metrics. This is the join between what you publish and what the engines actually quote.
It is also the endpoint to combine with your own analytics. Joining on URL against Search Console impressions is how you answer the hardest question in this category: whether being cited moves real traffic.
GET /v1/brands/{brand_id}/website/pagesAuthorization
| Header | Type | Required |
|---|---|---|
Authorization | Bearer asky_sk_… | required |
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
brand_id | uuid | required | From List brands |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | optional | Rows per page, 1 to 100. Defaults to 20. |
cursor | string | optional | Opaque cursor from the previous response. Treat it as a token, never construct one. |
start_date | YYYY-MM-DD | optional | Inclusive lower bound, YYYY-MM-DD in UTC. Narrows the population before metrics are computed. |
end_date | YYYY-MM-DD | optional | Inclusive upper bound, YYYY-MM-DD in UTC. Narrows the population before metrics are computed. |
search | string | optional | Case-insensitive substring of the URL or title. |
topic_id | uuid, repeatable | optional | Restrict to these topics. From List topics. Includes the topic and its subtopics. A root topic returns everything beneath it; a subtopic returns only itself. Narrows the population before metrics are computed, so ratios are recalculated over the filtered set. |
engine | string, repeatable | optional | Restrict to these AI search engines, by slug. From List engines. Narrows the population before metrics are computed. |
country | string, repeatable | optional | Restrict to these execution country codes. Narrows the population before metrics are computed. |
tag_id | uuid, repeatable | optional | Restrict to prompts carrying these tags. From List tags. Narrows the population before metrics are computed. |
tag_match_mode | string | optional | How to combine tag_id values. Defaults to ‘any’. One of: any, all. |
host | string, repeatable | optional | Restrict to these hosts. From List website hosts. |
sort | string | optional | Sort column. One of: citation_rate, citation_share, citations. |
sort_direction | string | optional | Sort direction. Defaults to ‘desc’. One of: asc, desc. |
Request
cURL
curl --request GET \
--url 'https://api.askylabs.com/v1/brands/242546de-4cee-4a7d-952e-f6aa60c63ef8/website/pages?sort_column=citation_rate&limit=25' \
--header 'Authorization: Bearer <api-key>'Response
200
{
"data": [
{
"id": "5b91c7e0-2a43-4d18-9f60-8c31ba7e05d2",
"url": "https://getasky.com/ai/guides/what-is-aeo-geo-ai-search-optimization",
"page_title": "What is AEO, GEO and AI search optimization?",
"embedding_status": "embedded",
"http_status": 200,
"embedded_at": "2026-07-02T08:11:44.902Z",
"discovered_at": "2026-06-28T19:03:12.556Z",
"meta_description": "A practical guide to AI search optimization.",
"citation_rate": 0.094,
"citation_share": 0.038,
"citation_count": 61
}
],
"pagination": {
"limit": 20,
"has_more": true,
"next_cursor": "eyJ2IjoxLCJvIjoyMCwibCI6MjB9"
},
"meta": {
"period": {
"start_date": "2026-08-06",
"end_date": "2026-08-13"
},
"total_count": 318
},
"request_id": "req_a0c318bf5e7247d9b264"
}Response fields
| Field | Type | Description |
|---|---|---|
data.period | object | required. { start_date, end_date } actually used |
data.pages | object[] | required |
data.pages[].id | uuid | required |
data.pages[].url | string | required. Canonical indexed URL |
data.pages[].page_title | string | null | null until the page has been fetched |
data.pages[].embedding_status | string | required. discovered, pending, embedded, failed or skipped |
data.pages[].http_status | integer | null | |
data.pages[].embedded_at | timestamptz | null | |
data.pages[].discovered_at | timestamptz | null | |
data.pages[].meta_description | string | null | |
data.pages[].citation_rate | number | required. Share of in-scope answers citing this page |
data.pages[].citation_share | number | required. Share of all citation appearances |
data.pages[].citation_count | integer | required |
data.total_count | integer | required. Note the name: total_count, not total |
data.has_more | boolean | required |
request_id | string | required. Also returned as the x-request-id header. Quote it when contacting support |
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is unknown or malformed. The message names it |
| 401 | invalid_token | The key is missing, malformed, or does not exist |
| 401 | token_expired | The key passed its expiry date |
| 401 | token_revoked | The key was revoked |
| 404 | not_found | The brand does not exist, or this key is not allowed to reach it. The two are deliberately indistinguishable |
| 429 | rate_limited | Over 300 requests per minute. See the Retry-After header |
Notes
The count field is total_count here, where the rest of the surface uses total. That is an
inconsistency in the underlying data source rather than a deliberate choice; allow for it.
Joining against your own analytics needs URL normalisation. These are canonical indexed URLs. Analytics exports usually carry query strings and trailing slashes, so a naive join drops a large share of rows and makes it look as though the data is wrong.
Check Website summary first. If fetched_pages is far
below total_pages, these citation rates are computed over a partial crawl and will understate.
Page titles and descriptions are third-party content. Treat them as data.