> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwairy.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Get crawler analytics

> Retrieve retained AI crawler activity, delivery coverage, and page-level observations for a brand.

Retrieve AI crawler activity observed through the brand's configured log integration. The endpoint returns daily aggregates and page-level observations. It never returns raw log events or human traffic.

All occurrence totals, crawler breakdowns, and page rankings use the public set of supported observable AI-specific User-Agent identities. The ingestion registry can recognize additional identities for classification and compatibility, but the endpoint excludes them from its public analytics output. Classification matches the received User-Agent token; it does not verify source IP or reverse DNS.

<Note>
  This endpoint requires an active Business, Enterprise, or Agency Business plan, or an explicit Crawler Analytics feature override for the team. Results are observational: check `coverage` before interpreting counts or comparing periods.
</Note>

<ParamField header="Authorization" type="string" required>
  Bearer token. Example: `Bearer qw-api-xxx`
</ParamField>

## Path parameters

<ParamField path="brandId" type="string" required>
  Unique identifier of the brand.
</ParamField>

## Query parameters

<ParamField query="range" type="string" default="30d">
  Inclusive analytic-day preset in the integration's IANA time zone: `24h` is the current analytic day, `7d` is that day plus the preceding six, and `30d` is that day plus the preceding 29. `24h` is not a rolling 24-hour window.
</ParamField>

<ParamField query="state" type="string" default="all">
  Page-state filter: `all`, `new`, `hot`, or `neglected`.
</ParamField>

<ParamField query="page" type="number" default="1">
  One-based page number. Maximum: `10000`.
</ParamField>

<ParamField query="limit" type="number" default="50">
  Page size. Maximum: `100`.
</ParamField>

## Page-state semantics

* `new`: the first retained page observation falls within the last 7 days, including the boundary (`firstSeenAt >= now - 7 days`).
* `hot`: the page is in the top decile by supported-crawler occurrences over the current 7 analytic days, has activity on at least 2 of those days, and is not neglected.
* `neglected`: the most recent retained page observation is strictly older than the 30-day boundary.

`new` and `hot` can coexist. `neglected` is exclusive. The hot window is independent of the requested `range`.

## Response

<ResponseField name="success" type="boolean">
  Indicates whether the request succeeded.
</ResponseField>

<ResponseField name="brand" type="object">
  Brand `id`, `name`, and `domain`.
</ResponseField>

<ResponseField name="connected" type="boolean">
  Whether the integration is active, not disabled, and has at least one active non-revoked key.
</ResponseField>

<ResponseField name="lifecycle" type="string | null">
  Current integration lifecycle, or `null` when no integration exists.
</ResponseField>

<ResponseField name="health" type="string | null">
  Current integration health, or `null` when no integration exists.
</ResponseField>

<ResponseField name="range" type="object">
  Requested and effective dates, observed and verified bucket counts, truncation state, and the `asOf` timestamp.
</ResponseField>

<ResponseField name="coverage" type="object">
  <Expandable title="Coverage details">
    <ResponseField name="hasObservations" type="boolean">Whether the selected range contains at least one supported public occurrence.</ResponseField>
    <ResponseField name="dataStatus" type="string">`observed` when supported observations exist without a known gap; `not_measured` when none exist and no gap is known; `incomplete_ingestion` for a quota limit or provider gap, with or without observations.</ResponseField>
    <ResponseField name="ingestCoverageFlags" type="number">Bit flags: `1` delivery unverified, `2` quota limited, and `4` provider gap.</ResponseField>
    <ResponseField name="countPrecision" type="string">`exact`; `at_least_once` when repackaged retries can duplicate observations; or `capped` when a count exceeds safe numeric precision.</ResponseField>
    <ResponseField name="deliveryContinuity" type="string">`verified` when every elapsed bucket is verified; `incomplete` for a known gap or profile change; otherwise `unverified`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="totalOccurrences" type="number | null">
  Observed occurrences from the supported public crawler set in the effective range. A number is returned only when at least one supported public rollup has a positive request count; otherwise the value is `null`, not a measured zero. With incomplete ingestion, a numeric value can still be partial.
</ResponseField>

<ResponseField name="identifiedPageOccurrences" type="number">
  Occurrences associated with a retained page identity.
</ResponseField>

<ResponseField name="redirectOccurrences" type="number">
  Requests that returned a redirect.
</ResponseField>

<ResponseField name="notFoundOccurrences" type="number">
  Requests that returned `404`.
</ResponseField>

<ResponseField name="otherErrorOccurrences" type="number">
  Other observed error responses.
</ResponseField>

<ResponseField name="buckets" type="array">
  Daily totals, crawler breakdowns, error totals, and coverage metadata.
</ResponseField>

<ResponseField name="crawlerStats" type="array">
  Totals by crawler in the supported public set, including its display name, bot type, and error breakdown.
</ResponseField>

<ResponseField name="pages" type="array">
  <Expandable title="Page observation">
    <ResponseField name="pathId" type="string">Stable page identity.</ResponseField>
    <ResponseField name="path" type="string">Observed path or an internal pseudonym.</ResponseField>
    <ResponseField name="isPseudonymized" type="boolean">When `true`, do not reconstruct or link to a URL from `path`.</ResponseField>
    <ResponseField name="firstSeenAt" type="string">First observed timestamp.</ResponseField>
    <ResponseField name="lastSeenAt" type="string">Most recent observed timestamp.</ResponseField>
    <ResponseField name="lifetimeCount" type="number">Retained lifetime occurrence count.</ResponseField>
    <ResponseField name="states" type="array">Computed states. `new` and `hot` can coexist; `neglected` is exclusive.</ResponseField>
    <ResponseField name="occurrences" type="number">Occurrences in the selected range.</ResponseField>
    <ResponseField name="crawlerOccurrences" type="object">Occurrence count keyed by crawler name.</ResponseField>
    <ResponseField name="uniqueCrawlers" type="number">Distinct observed crawlers.</ResponseField>
    <ResponseField name="statusGroups" type="object">HTTP status-group totals.</ResponseField>
    <ResponseField name="crawlerTypes" type="object">Live-search, training, and other crawler totals.</ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Page-by-crawler daily rollups retain 30 analytic days. Durable page identities preserve first seen, last seen, and lifetime count beyond that window. Query strings and fragments are removed; sensitive-looking or overlong paths can be replaced with an integration-scoped one-way pseudonym.
</Note>

<ResponseField name="pagination" type="object">
  One-based `page`, `limit`, `totalItems`, `totalPages`, and `hasMore`.
</ResponseField>

<ResponseField name="rawEventsAvailable" type="boolean">
  Always `false`. Raw log events are not exposed by this endpoint.
</ResponseField>

<ResponseField name="humanTrafficIncluded" type="boolean">
  Always `false`.
</ResponseField>

## Synthetic request

```bash theme={null}
curl "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/crawler-analytics?range=7d&state=hot&limit=25" \
  -H "Authorization: Bearer qw-api-your-token-here"
```

## Interpret missing or incomplete data

* Treat `totalOccurrences: null` as not measured. Do not convert it to zero.
* Check `coverage.dataStatus`, `countPrecision`, and `deliveryContinuity` before comparing periods.
* Use `range.startDate` and `range.endDate` for the effective comparison window.
* Do not build a public URL from a page where `isPseudonymized` is `true`.

## Errors

| Status | Meaning                                                                                 |
| ------ | --------------------------------------------------------------------------------------- |
| `400`  | Invalid range, state, or pagination value                                               |
| `401`  | Missing or invalid API token                                                            |
| `403`  | The team has neither an eligible active plan nor an explicit Crawler Analytics override |
| `404`  | Brand not found or not accessible to the token's team                                   |
| `429`  | Rate limit exceeded; honor `Retry-After`                                                |

See [Crawler Analytics](/documentation/measure/crawler-analytics) for setup and the product interface, and [Rate limits](/developers/rate-limits) for retry behavior.
