Public API Reference
Access your LogLens analytics data programmatically. Build custom dashboards, integrate with your tools, or automate reporting.
Base URL
https://api.loglens.ai
1 Authentication
All API requests require authentication using an API key. API keys can be created in your Organization Settings.
Pass your API key in the Authorization header:
Authorization: Bearer llapi_your_api_key_here
Note: API keys start with llapi_ (ingest keys for your CDN or log drain start with ll_ and cannot call this API). The rest of the key is shown only once, when it is created.
2 Rate Limits
Rate limits are applied per organization, per hour. The limit depends on your plan:
| Plan | Requests/Hour |
|---|---|
| Free | No API access |
| Starter | 100 |
| Growth | 500 |
| Scale | 2,000 |
| Unlimited | 10,000 |
| Professional (legacy) | 500 |
| Enterprise (legacy) | 2,000 |
Write endpoints (see Actions & key scopes) have a separate bucket of 120 writes per hour per organisation.
Rate limit headers are included in every response:
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
X-RateLimit-Reset: 2024-01-21T18:00:00Z
3 Error Handling
The API uses standard HTTP status codes and returns JSON error responses:
| Status | Description |
|---|---|
200 |
Success |
401 |
Invalid or missing API key |
403 |
API access not available on your plan |
404 |
Website not found |
429 |
Rate limit exceeded |
500 |
Internal server error |
{
"detail": "Rate limit exceeded",
"limit": 100,
"reset_at": "2024-01-21T18:00:00Z"
}
4 Date Ranges
Many log analytics endpoints accept a time range. Use hours for a rolling window, or start and end for a specific date range (ISO 8601 format).
GET /websites/{id}/bots?hours=168
GET /websites/{id}/bots?start=2026-03-05&end=2026-03-27
If neither is provided, the default is hours=24. When start/end are present, hours is ignored. Defaults differ by endpoint (24, 168 or 720 hours), and snapshot endpoints take no time range: Site Checks (checks/history takes days), URL search, Page Importance, health and alert configuration. Index coverage's hours is a crawl-recency threshold measured back from now, not a historical selection. Each endpoint below lists its parameters and openapi.json carries the reviewed set; do not assume a parameter applies where it is not listed.
Common Query Parameters
Two more parameters: max_rows works on every JSON GET /public/v1/websites/{id}/… read; segment only on reads whose log queries apply it:
| Parameter | Description |
|---|---|
segment=<segment_id> |
Narrow the read to one saved segment (page group). Ids come from GET segments. It is currently applied by summary, traffic, bots, bots/access, paths, status-codes, referrers, devices, live, llms, ai-funnel, ai-landing, crawl-report, crawl-audit, crawl-links and the logs section of url. Other reads — including geography, ips, the seo/… family, requests, recommendations, Search Console, Site Checks, index coverage and URL search — ignore it and return unfiltered data. Pre-aggregated rollups are bypassed while a segment is active, so long windows are a little slower. An id that doesn't belong to the site is ignored and the response is unfiltered. |
max_rows=N |
Caps every list in the response to its first N entries and adds a _truncated map — {"max_rows", "original_counts": {"<field path>": <original length>}, "note"} — so you can tell what was cut. It exists to keep payloads small for MCP / agent tooling: the big detail endpoints (seo, llms, ips) return multi-megabyte bodies over long windows. Omit it for full data, or use Create Export for large datasets. |
GET /websites/{id}/paths?hours=720&segment=seg_ab12cd34ef&max_rows=50
{
"paths": [ … first 50 … ],
"_truncated": { "max_rows": 50, "original_counts": { "paths": 4812 }, "note": "Lists capped at max_rows entries. Omit max_rows for full data, or use create_export for large datasets." }
}
Googlebot Split Variants
The bots endpoint accepts split_variants=true to show Googlebot Desktop and Googlebot Smartphone as separate entries instead of merged:
GET /websites/{id}/bots?hours=168&split_variants=true
Command-Line Interface (CLI) NEW
Access your LogLens analytics from the terminal. Query traffic, bots, SEO data, and more — with table, JSON, and CSV output.
Installation
npm install -g @salience/lens-cli
Setup
# Save your API key
salience config set-key YOUR_API_KEY
# Set a default website (optional)
salience config set-website YOUR_WEBSITE_ID
Examples
# List websites
salience websites
# Bot breakdown (last 7 days, JSON output)
salience bots -w <id> -h 168 --json
# SEO crawl budget by directory
loglens seo budget-urls -w <id> --dir /blog/
# Export paths as CSV
salience paths -w <id> --csv > paths.csv
# Pipe to jq
salience bots -w <id> --json | jq '.[].name'
# Query a specific date range
salience bots -w <id> --start 2026-03-05 --end 2026-03-27
# Show Googlebot Desktop and Smartphone separately
salience bots -w <id> --split-variants
Run salience --help (or salience <command> --help) for commands and their options. Coding agents can use the bundled skill: salience skills install <new-directory> copies it without touching existing files, and a copied skill is not updated when the CLI is upgraded. See the help docs for detailed usage.
MCP Server Integration NEW
Connect LogLens to AI assistants like Claude, Cursor, and other tools that support the
Model Context Protocol (MCP).
The MCP server exposes public API reads and read_write actions as tools that AI assistants can call. Parameters are tool-specific: many log tools take hours or start/end, while snapshot tools such as Site Checks, index coverage and URL search take none.
MCP Endpoint
https://mcp-logs.salience.com/mcp?apiKey=YOUR_API_KEY
Connect with OAuth (Claude)
No API key needed. Add https://mcp-logs.salience.com/mcp as a custom connector in Claude.ai, or run
claude mcp add --transport http salience https://mcp-logs.salience.com/mcp in Claude Code. Claude opens a Salience page in your browser:
sign in with Google or an emailed code (a new email creates the account and offers Free or a 14-day trial), pick the organisation and approve the scope.
The connection is listed under Organization → API Access as a key named after the client; revoke it there to disconnect. The key-based setup below still works for every client.
Setup
Install the mcp-remote bridge (requires Node.js 18+):
npm install -g mcp-remote
Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"salience": {
"command": "npx",
"args": [
"mcp-remote",
"https://mcp-logs.salience.com/mcp?apiKey=YOUR_API_KEY"
]
}
}
}
If npx isn't found, use the full path to node and mcp-remote. Run which node and which mcp-remote to find them.
Cursor
Go to Settings → MCP Servers and add the endpoint URL:
https://mcp-logs.salience.com/mcp?apiKey=YOUR_API_KEY
Other MCP Clients
Any MCP-compatible client can connect using the endpoint URL. Clients that don't support remote servers directly can use the mcp-remote bridge as shown above.
Available Tools
Read tools cover traffic, bots, SEO, crawl budget, index coverage, URL evidence, Site Checks, segments, crawls, recommendations and more, alongside a smaller set of write tools. Clients discover the current catalogue when they connect; the MCP setup guide lists every tool and its parameters. Write tools need a read_write key (see Actions & key scopes).
| Tool | Description |
|---|---|
list_websites | List all websites in your account |
get_summary | Traffic summary for a website |
get_traffic | Traffic time-series data |
get_bots | Bot and crawler breakdown (supports split_variants for Googlebot Desktop/Smartphone) |
get_seo | SEO crawler analytics |
get_budget_urls | Per-URL crawl budget breakdown |
get_url_patterns | Auto-detected URL patterns |
get_index_coverage | Google index coverage summary |
Plus more read tools (paths, geography, status codes, IPs, referrers, devices, LLM crawlers, AI funnel and landing pages, Search Console, Google Analytics, URL detail and search, segments, crawl join / audit / links, recommendations, insights, health, Site Checks, page importance, robots.txt, sitemap history, site events, exports, alerts) and write tools (acknowledge alerts, add site events, suppress / unsuppress bots, resolve / re-open recommendations, create / delete segments, start / cancel crawls). max_rows and segment are offered only on the tools that list them.
List Websites
Returns all websites accessible to your API key.
GET /public/v1/websites
Response
{
"websites": [
{
"id": "ws_abc123",
"domain": "example.com",
"name": "Main Website",
"created_at": "2024-01-01T00:00:00Z"
}
],
"count": 1
}
Create Website
NEW
Register a website in the key's organisation, mint its ingest key and detect the hosting platform. This is what the MCP create_website tool calls. Requires a key with the read_write scope (a read-only key gets a 403).
POST /public/v1/websites
Request Body
| Name | Type | Description |
|---|---|---|
domain |
string | Required. The site's hostname; scheme and path are stripped. |
name |
string | Display name. Defaults to the domain. |
source_type |
string | Force a platform: cloudflare, cloudfront, vercel, vector, kinsta, shopify, netlify. Otherwise detected from the domain, falling back to cloudflare. |
Response
{
"website_id": "…", "domain": "example.com", "name": "example.com", "source_type": "cloudflare",
"ingest": { "url": "https://ingest.salience.com/", "header": "X-API-Key", "api_key": "…shown once…" },
"detected": { "cdn": "cloudflare", "display_name": "Cloudflare", "compatible": true },
"guide_url": "https://salience.com/help#cloudflare"
}
Setup Status
NEW
Platform, ingest endpoint, whether logs have arrived and what to do next. Backs the MCP get_setup_instructions and get_setup_status tools. The ingest key is only returned by Create Website; this endpoint never repeats it.
GET /public/v1/websites/{website_id}/setup
Response
{
"website_id": "…", "domain": "example.com", "source_type": "cloudflare",
"ingest": { "url": "https://ingest.salience.com/", "header": "X-API-Key" },
"logs": { "state": "received", "ever_received": true, "last_received_date": "2026-09-18", "records_last_30d": 120483 },
"receiving": true, "next_steps": ["Logs are arriving. …"], "guide_url": "…", "checked_at": "…"
}
Billing Status
NEWThe organisation's plan, trial state, whether a card is on file, a Stripe customer-portal link when a customer exists, and the paid plans a trial or checkout can target.
GET /public/v1/billing
Response
{
"org_id": "…", "plan": "free", "plan_name": "Free", "subscription_status": null,
"trial": null, "card_on_file": false, "portal_url": null,
"available_plans": [{ "id": "basic", "name": "Solo", "price_monthly_usd": 19, "trial_period_days": 14 }, …]
}
Start Trial
NEW
Start the one 14-day no-card trial of a paid plan for a Free organisation. Same rules as the dashboard: one trial per user account and per organisation; a 409 with a code explains a refusal. Requires a key with the read_write scope (a read-only key gets a 403).
POST /public/v1/billing/trial
Request Body
| Name | Type | Description |
|---|---|---|
plan_id |
string | Required. A public paid plan id, e.g. basic, starter, growth, scale. |
Response
{ "plan": "growth", "subscription_status": "trial_nocard", "trial_ends_at": "2026-10-02T…" }
Start Checkout
NEW
Create a Stripe Checkout session for a plan and return its URL. The customer opens the link in a browser and enters card details on Stripe's page; nothing about the card passes through this API. During a no-card trial the remaining trial days and the first-month discount apply; an organisation with a live subscription is switched in place instead and gets no URL. Requires a key with the read_write scope (a read-only key gets a 403).
POST /public/v1/billing/checkout
Request Body
| Name | Type | Description |
|---|---|---|
plan_id |
string | Required. A purchasable plan id. |
interval |
string | monthly (default) or yearly. |
Response
{ "checkout_url": "https://checkout.stripe.com/c/pay/…" }
Get Summary
Returns summary statistics for a website.
GET /public/v1/websites/{website_id}/summary
Parameters
| Name | Type | Description |
|---|---|---|
hours |
integer | Time period in hours (default: 24, max: 8760) |
Get Traffic
Returns traffic data with hourly breakdown.
GET /public/v1/websites/{website_id}/traffic?hours=24
Get Bots
Returns bot analytics including identification and verification status.
GET /public/v1/websites/{website_id}/bots?hours=24
Get Bot Access
Served vs rejected, per bot. For each bot: served (2xx/3xx) and rejected (401/403/429/451) requests, each split into verified and unverified identity; rejected_by_status (which codes the site used); rejected_paths (top 5 paths where rejections happened, with status); access = mixed (served on some requests, rejected on others — listed first), rejected, served or none (only 404s/errors); and rejected_who = impersonators_only, verified_only, both or unverifiable. Blocking only impersonators is the healthy pattern; verified_only means a rule is turning away the real bot. hours defaults to 168; limit up to 500.
GET /public/v1/websites/{website_id}/bots/access?hours=168
Get Paths
Returns path/URL analytics sorted by request count.
GET /public/v1/websites/{website_id}/paths?hours=24&limit=100
Parameters
| Name | Type | Description |
|---|---|---|
hours |
integer | Time period (default: 24) |
limit |
integer | Max results (default: 100, max: 1000) |
Get Geography
Returns geographic distribution of traffic by country and city.
GET /public/v1/websites/{website_id}/geography?hours=24
Get Status Codes
Returns HTTP status code distribution with hourly breakdown.
GET /public/v1/websites/{website_id}/status-codes?hours=24
Get IPs
Returns IP address and IP range analytics.
GET /public/v1/websites/{website_id}/ips?hours=24
Get Referrers
Returns referrer domain analytics - see which sites are sending traffic to you.
GET /public/v1/websites/{website_id}/referrers?hours=24&limit=100
Query Parameters
hours- Time period (1-8760, default: 24)limit- Maximum referrers to return (1-500, default: 100)
Get Devices
Returns device, browser, and operating system analytics.
GET /public/v1/websites/{website_id}/devices?hours=24
Response includes
browsers- Browser breakdown (Chrome, Safari, Firefox, etc.)operating_systems- OS breakdown (Windows, macOS, iOS, Android, etc.)device_types- Device type breakdown (Desktop, Mobile, Tablet)
Get SEO Stats
Returns search engine crawler statistics - Googlebot, Bingbot, etc.
GET /public/v1/websites/{website_id}/seo?hours=24&bot=googlebot
Query Parameters
hours- Time period (1-8760, default: 24)bot- Filter by specific bot (optional, e.g. "googlebot", "bingbot")
Response includes
crawler_requests- Total crawler requestsverified_requests- Verified (legitimate) crawler requestsunverified_suspicious- Potentially spoofed crawler requestsavg_response_time_ms- Average response time to crawlerstop_crawlers- Breakdown by crawler
Actions & key scopes
API keys have a scope: read (the default — every endpoint above) or read_write, which can also perform the actions below. Create a read & write key under Organization → API Access. A read-only key gets 403 with "This API key is read-only". Writes are limited to 120 per hour per organisation, and every write is recorded as a site event on the timeline naming the key. Nothing here deletes data or touches your hosting.
Two conveniences for clients: PATCH is accepted alongside PUT on segments/{segment_id} (both replace the segment with the body you send), and GET recommendations/snippet accepts days=N as well as hours (days is converted to hours, clamped to 1–365, and only used when hours is absent).
POST alerts/{alert_id}/acknowledge
POST site-events {"title", "date": "YYYY-MM-DD", "time"?, "category"?, "description"?}
DELETE site-events/{event_id}
POST bots/{bot_name}/suppress {"days"?: 1-365, "reason"?} — stops SEO-error alerts for that bot; expires after `days`
DELETE bots/{bot_name}/suppress
POST recommendations/resolve {"key"} — key from GET recommendations
DELETE recommendations/resolve/{key}
POST segments {"name", "rules": [{"type","value"}], "exclude_rules"?, "parent_id"?, "colour"?}
PUT segments/{segment_id} same body as POST — PATCH is accepted as an alias of PUT
DELETE segments/{segment_id} · POST segments/library (see Segments)
POST crawls/run {"max_pages"?: 100-50000}
POST crawls/{crawl_id}/cancel · GET crawls (see Crawls)
curl -X POST https://api.loglens.ai/public/v1/websites/{website_id}/bots/ByteSpider/suppress \
-H "Authorization: Bearer llapi_…" -H "Content-Type: application/json" \
-d '{"days": 30, "reason": "known scraper, reviewed"}'
{"crawler": "ByteSpider", "suppressed": true, "until": "2026-10-04T10:12:00+00:00"}
Deploy snippets
Turns a recommendation into the exact rule for your platform. type: block_ips (abusive / unverified-bot IPs), block_bots (fake bot user agents — prefer a challenge to a hard block) or gone_404s (paths to return 410 Gone). target: cloudflare (WAF expression), cloudfront (CloudFront Function), nginx, apache, netlify, vercel or robots; defaults to the site's platform. Lists are capped at 500 entries and every response carries notes with the caveats. Nothing is applied automatically — review, then paste.
Time window: hours, or start/end, or days=N (1–365; converted to hours when hours is not given). Pass one of them explicitly: without a window the underlying recommendation queries use the API's default of 24 hours for the IP and bot lists, not 30 days.
GET /public/v1/websites/{website_id}/recommendations/snippet?type=block_ips&target=cloudflare
{
"type": "block_ips", "target": "cloudflare", "filename": "cloudflare-block-ips.txt", "count": 37,
"snippet": "(ip.src in {203.0.113.7 198.51.100.22 …})",
"notes": ["Security → WAF → Custom rules → Create rule → Edit expression; action Block (or Managed Challenge to be safe)."]
}
Segments
Saved page groups defined in the dashboard (SEO → Segments): a name and rules — prefix, contains, exact, regex on the path, or query on the query string — with optional exclusions and nesting. They are evaluated on the logs at query time, so they apply to all history. Add ?segment=<segment_id> to a segment-aware read — /summary, /traffic, /bots, /paths, /llms, /ai-funnel, /ai-landing, /crawl-report, /crawl-audit, /url and the others listed under Common Query Parameters — to filter it to one group; other reads ignore it. /segments-breakdown returns every segment (plus _all) with requests, human visits, verified search-bot hits and share, Googlebot, AI bots, unique paths, crawl coverage and errors; compare=1 adds the previous period of the same length with percentage deltas, and segments=<id>,<id> restricts the breakdown to that subset of segments.
GET /public/v1/websites/{website_id}/segments
GET /public/v1/websites/{website_id}/segments-breakdown?hours=720&compare=1
GET /public/v1/websites/{website_id}/segments-breakdown?hours=720&segments=seg_ab12cd34ef,seg_9f8e7d6c5b
GET /public/v1/websites/{website_id}/crawl-audit?hours=720&segment=seg_ab12cd34ef
{
"segments": [
{ "segment_id": "seg_ab12cd34ef", "name": "Products", "colour": "#0ea5e9", "parent_id": null,
"rules": [{ "type": "prefix", "value": "/products" }], "exclude_rules": [{ "type": "query", "value": "sort=" }] }
]
}
Creating and editing segments (read_write key)
POST segments creates a segment; PUT segments/{segment_id} (or PATCH, an alias) replaces it with the same body shape. name (≤60 chars) and at least one rule are required; up to 30 rules and 30 exclusions; regexes are validated; colour, parent_id (nest under another segment), notes and order are optional. Max 100 segments per site. Both return {"segment": {…}}. DELETE segments/{segment_id} returns {"deleted": "<id>"}.
POST /public/v1/websites/{website_id}/segments — or — PUT|PATCH /public/v1/websites/{website_id}/segments/{segment_id}
{
"name": "Products",
"rules": [{ "type": "prefix", "value": "/products" }, { "type": "regex", "value": "^/p/[0-9]+" }],
"exclude_rules": [{ "type": "query", "value": "sort=" }],
"colour": "#0ea5e9", "parent_id": null, "notes": "PDPs only", "order": 10
}
Segment library (read_write key)
POST segments/library adds the eight common page groups in one call — Blog (/blog, /news, /articles, /posts), Products (/product, /products, /p/, /shop), Categories (/category, /categories, /c/, /collections), Guides & help (/guides, /help, /support, /docs, /knowledge-base), Pagination (/page/N, page=, p=), Parameter URLs (utm_, sort=, filter=, sessionid=), Search results (/search, q=, s=) and Account & checkout (/account, /login, /cart, /checkout, /my-account). The body is empty (send {}). Segments whose name already exists on the site are skipped, so it is safe to call again; the response lists what was actually created.
POST /public/v1/websites/{website_id}/segments/library body: {}
{ "added": ["Blog", "Pagination", "Parameter URLs", "Search results"] }
{
"compare": true,
"segments": [
{ "segment_id": "_all", "name": "All pages", "search_hits": 18420, "delta": { "search_hits": -4.2 } },
{ "segment_id": "seg_ab12cd34ef", "name": "Products", "search_hits": 9110, "search_share": 49.5, "crawl_coverage": 71.3, "delta": { "search_hits": 12.8, "errors_5xx": 0 } }
]
}
Crawl Audit
Technical findings from the latest crawl, each row crossed with the logs for the period (search_hits from verified search bots, human_hits, last_search_hit). findings is ordered by severity and bot hits: broken_links, links_to_redirects, redirect_chains, canonical_issues, noindex_pages, robots_blocked, duplicate_titles, duplicate_descriptions, missing_titles, missing_descriptions, long_titles, missing_h1, multiple_h1, thin_pages, hreflang_issues, slow_pages, large_pages, sitemap_issues. totals.bot_hits_on_findings is the crawl budget spent on things to fix. Defaults to the latest ready crawl; pass crawl_id= (from GET crawls) to audit a specific one. limit caps rows per finding (default 100).
GET /public/v1/websites/{website_id}/crawl-audit?hours=720&limit=100
GET /public/v1/websites/{website_id}/crawl-audit?hours=720&crawl_id=20260901T020000_1a2b3c4d
{
"totals": { "high": 12, "medium": 58, "low": 140, "bot_hits_on_findings": 431 },
"findings": [
{ "key": "broken_links", "severity": "high", "count": 9, "bot_hits": 212,
"rows": [{ "path": "/old-offers", "status": 404, "linked_from": { "count": 37 }, "search_hits": 118 }] }
]
}
Crawl Join
The latest site crawl (an uploaded export from your site crawler, a URL list, or Salience's own crawl — managed in the dashboard under SEO → Crawl Join) crossed with the logs for the period. summary counts pages, active (fetched by a verified search bot), ignored (indexable and linked but never fetched) and orphans (fetched but not linked from the site). ignored is sorted by Search Console impressions, inlinks and depth; by_depth / by_inlinks give crawl attention per bucket. Returns available: false, reason: "no_crawl" until a crawl exists. Defaults to the latest ready crawl; pass crawl_id= (from GET crawls) to join a specific one. limit caps the ignored / orphans lists (default 200).
GET /public/v1/websites/{website_id}/crawl-report?hours=720&limit=200
GET /public/v1/websites/{website_id}/crawl-report?hours=720&crawl_id=20260901T020000_1a2b3c4d
{
"available": true,
"crawl": { "source": "salience_crawler", "url_count": 1842, "completed_at": "2026-09-04T11:02:10Z" },
"summary": { "pages_in_crawl": 1842, "indexable_pages": 1610, "active": 1204, "ignored": 406, "orphans": 57, "active_pct": 74.8 },
"ignored": [{ "path": "/guides/spa-days", "depth": 4, "inlinks": 2, "impressions_28d": 1180, "search_hits": 0 }],
"orphans": [{ "path": "/old-offers", "search_hits": 312, "last_status": 200 }],
"by_depth": [{ "bucket": "3", "pages": 640, "crawled_pct": 61.2, "hits_per_page": 1.9 }]
}
Link Map (crawl-links)
The internal link graph of the latest Salience crawl, crossed with your logs. Every page is a node with an internal pagerank (0–1, the most-linked-into page = 1), inlinks, outlinks, click depth, HTTP status, and the verified search-bot / AI / human hits in the period; edges are [source_id, target_id] pairs between the returned nodes. Nodes are the top limit by PageRank (public API default 200, clamped to 50–3,000); the homepage is always included. The prefix and max_depth filters are not currently applied by the public API, so filter the returned nodes yourself. Two lists come with it: linked_but_ignored (indexable pages with the most link equity that no search bot fetched) and fetched_but_barely_linked (pages search bots fetched that have two or fewer inlinks). Needs a Salience crawl — uploaded exports carry no link edges (reason: "no_link_graph"). Pass crawl_id= for a specific crawl. Standard time parameters apply to the hit counts.
GET /public/v1/websites/{website_id}/crawl-links?hours=720&limit=400
{
"totals": { "pages": 1842, "edges": 61230, "matched": 612, "shown": 400, "shown_edges": 14880 },
"nodes": [ { "id": 0, "path": "/", "depth": 0, "inlinks": 1790, "outlinks": 212, "status": 200, "pagerank": 1.0, "search_hits": 318, "google_hits": 301, "ai_hits": 12, "human_hits": 4410 }, … ],
"edges": [ [0, 17], [0, 18], … ],
"linked_but_ignored": [ { "path": "/blog/old-guide", "pagerank": 0.41, "inlinks": 96, "search_hits": 0 } ],
"fetched_but_barely_linked": [ { "path": "/tools/checker", "search_hits": 140, "inlinks": 1 } ]
}
Crawls
The 20 most recent crawls of the site, newest first, plus latest — the newest crawl with status: "ready", which is what crawl-report and crawl-audit use unless you pass crawl_id=. Each crawl has source (upload — a crawl export or URL list — or salience_crawler, our own crawler), status (queued, processing, ready, failed, blocked, cancelled; uploads also pass through pending_upload), page counts (url_count, indexable_count, status_200, unlinked_count, max_depth), an audit_summary of findings by severity, timestamps (created_at, started_at, completed_at) and, for our crawler, trigger, seeds, render_js, progress and a delta against the previous crawl. The site-level crawl settings (auto_crawl, auto_crawl_max_pages, crawl_render_js, crawl_query_keys) and the fixed crawler_egress_ip to allow-list are returned alongside. Start or stop crawls with POST crawls/run / POST crawls/{crawl_id}/cancel (see Actions). No time-range parameters.
GET /public/v1/websites/{website_id}/crawls
{
"crawls": [
{ "crawl_id": "20260904T020000_1a2b3c4d", "source": "salience_crawler", "status": "ready", "trigger": "scheduled",
"url_count": 1842, "indexable_count": 1610, "status_200": 1790, "unlinked_count": 57, "max_depth": 6,
"audit_summary": { "high": 12, "medium": 58, "low": 140 },
"created_at": "2026-09-04T02:00:00+00:00", "started_at": "2026-09-04T02:00:04+00:00", "completed_at": "2026-09-04T02:41:10+00:00" },
{ "crawl_id": "20260828T101512_9f8e7d6c", "source": "upload", "status": "ready", "filename": "internal_all.csv", "url_count": 1799, "completed_at": "2026-08-28T10:16:02+00:00" },
{ "crawl_id": "20260821T020000_5e4d3c2b", "source": "salience_crawler", "status": "blocked", "error": "403 from origin — allow-list the crawler IP" }
],
"latest": { "crawl_id": "20260904T020000_1a2b3c4d", "status": "ready", … },
"auto_crawl": true, "auto_crawl_max_pages": 10000, "crawl_render_js": false, "crawl_query_keys": ["page"],
"crawler_egress_ip": "18.132.26.88"
}
AI Funnel
Two reports on what AI gives back. operators: per AI company, the search/answer fetches it made (fetches), its training crawls (training_requests), the human visitors who arrived from its answers (referrals, detected from the referrer host or a utm_source=chatgpt.com-style tag) and fetches_per_visit — the scrape-to-referral ratio. discovery: Search Console pages with at least min_impressions impressions (28 days) that no AI search or answer bot fetched in the period — invisible to AI search. Requires a connected Search Console property; otherwise discovery.gsc_connected is false.
GET /public/v1/websites/{website_id}/ai-funnel?hours=720&min_impressions=50
{
"operators": [
{ "operator": "OpenAI", "bots": ["ChatGPT User", "GPTBot", "OpenAI SearchBot"],
"fetches": 1240, "training_requests": 9860, "referrals": 729, "fetches_per_visit": 1.7,
"top_landing_pages": [{ "path": "/gifts/for-him", "visits": 112 }] }
],
"totals": { "fetches": 2210, "training_requests": 14400, "referrals": 1357, "fetches_per_visit": 1.6 },
"discovery": { "gsc_connected": true, "min_impressions": 50, "checked": 312, "never_fetched": 41,
"pages": [{ "path": "/experiences/spa-days", "impressions_28d": 4810, "clicks_28d": 96, "avg_position": 6.2, "ai_fetches": 0 }] }
}
AI Landing Pages
Which pages AI assistants (ChatGPT, Perplexity, Claude, Gemini, Copilot …) send people to, and what those visitors do next. A visit is AI-referred when a human request’s referrer host is an AI assistant (chatgpt.com, perplexity.ai, claude.ai, gemini.google.com, copilot.microsoft.com …) or its URL carries a utm_source naming one (utm_source=chatgpt.com). totals: ai_referred_visits, unique_paths, operators, humans_total, share_of_human_traffic_pct and previous (the equal period before, with delta_pct). by_operator: visits, unique paths and the most-cited page per assistant. pages (top 200 by visits, limit up to 500): operators {name: visits}, humans_total (all human visits to the page), ai_share_pct, bounce_proxy_pct (share of AI-referred visits whose IP made no other page request in the period — a heuristic), next_paths (top 3 pages the same IPs requested within 30 minutes, counted once per IP; assets and API routes excluded), first_seen, last_seen. series: visits per assistant per bucket at the period’s chart granularity. Accepts hours (default 720) or start/end, countries, segment and operator (one assistant name, e.g. OpenAI). data_buffering: true means the Athena queries are still running — repeat the call shortly.
GET /public/v1/websites/{website_id}/ai-landing?hours=720&operator=OpenAI
{
"totals": { "ai_referred_visits": 412, "unique_paths": 57, "operators": 4, "humans_total": 188420, "share_of_human_traffic_pct": 0.22,
"previous": { "ai_referred_visits": 301, "delta_pct": 36.9 } },
"by_operator": [ { "operator": "OpenAI", "visits": 260, "unique_paths": 41, "top_path": "/gifts/for-him" } ],
"pages": [
{ "path": "/gifts/for-him", "visits": 88, "operators": { "OpenAI": 71, "Perplexity": 17 },
"humans_total": 2140, "ai_share_pct": 4.1, "bounce_proxy_pct": 38.6, "bounced": 34,
"next_paths": [ { "path": "/gifts/for-him/driving", "visits": 21 } ],
"first_seen": "2026-08-09T10:12:44Z", "last_seen": "2026-09-05T18:40:02Z" }
],
"series": [ { "hour": "2026-09-05-00-00", "OpenAI": 9, "Perplexity": 2 } ],
"granularity": "1 day",
"detection": { "referrer_hosts": ["chatgpt.com", "perplexity.ai", "claude.ai", …], "utm_sources": ["chatgpt.com", …], "follow_on_minutes": 30 }
}
LLM / AI Crawler Analytics
NEW
Dedicated analytics for LLM and AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, etc.). Every crawler carries an intent: training (model-training corpus crawl), search (AI-search index crawl) or fetcher (a person asked the AI about a page; fetchers ignore robots.txt by design). intent_totals gives the request split. pages_by_intent provides ranked URL paths for each intent.
Returns per-bot time series, crawler verification status, and the pages those bots are reading.
GET /public/v1/websites/{website_id}/llms?hours=168
Query Parameters
| Name | Type | Description |
|---|---|---|
hours |
integer | Rolling window in hours (default: 24, max: 8760) |
start |
string | ISO 8601 start timestamp (use with end instead of hours) |
end |
string | ISO 8601 end timestamp |
countries |
string | Comma-separated ISO country codes to filter by (e.g. US,GB,DE) |
Response
{
"total_requests": 184201,
"total_ai_requests": 21084,
"ai_percentage": 11.4,
"unique_crawlers": 7,
"unique_pages_crawled": 3419,
"intent_totals": { "training": 9860, "search": 2210, "fetcher": 430 },
"period_hours": 168,
"granularity": "hourly",
"crawlers": [
{
"bot_name": "GPTBot",
"requests": 12847,
"unique_pages": 2105,
"intent": "training",
"verified_count": 12840,
"unverified_count": 7,
"verification_status": "verified"
}
],
"bot_hourly": [
{ "time_bucket": "2026-05-09-14-00", "GPTBot": 432, "ClaudeBot": 118 }
],
"bot_names": ["GPTBot", "ClaudeBot", "PerplexityBot"],
"pages": [
{
"path": "/blog/launch",
"requests": 412,
"unique_bots": 5,
"status_2xx": 410,
"status_4xx": 2
}
]
}
Available on Starter and above.
Crawl Budget URLs
Per-URL crawl budget breakdown within a directory. Shows which paths search engine crawlers are spending budget on, so you can spot waste.
GET /public/v1/websites/{website_id}/seo/budget-urls?hours=168&directory=/blog/
Query Parameters
| Name | Type | Description |
|---|---|---|
hours | integer | Time window (default: 24, max: 8760) |
directory | string | Directory prefix to drill into (default: /) |
bot | string | Filter to a single crawler (e.g. googlebot) |
page | integer | Page number (default: 1) |
page_size | integer | Results per page (default: 50, max: 500) |
params_filter | string | Filter by URL params (e.g. with_params, no_params) |
file_type | string | Filter by file extension (e.g. html, pdf) |
Response
{
"urls": [
{
"path": "/blog/seo-guide",
"requests": 842,
"percentage": 12.4,
"daily_avg": 120.3
}
],
"total": 317,
"page": 1,
"page_size": 50,
"directory": "/blog/"
}
Available on Starter and above.
URL Patterns
Auto-detected URL pattern templates with crawl frequency. Variable segments (numbers, UUIDs, dates) are replaced with placeholders so you can see how crawlers treat each route family.
GET /public/v1/websites/{website_id}/seo/url-patterns?hours=168&min_urls=3
Query Parameters
| Name | Type | Description |
|---|---|---|
hours | integer | Time window (default: 168, max: 8760) |
bot | string | Filter to a single crawler |
min_urls | integer | Minimum unique URLs per pattern to be returned (default: 3) |
params_filter | string | Filter by URL params |
file_type | string | Filter by file extension |
Response
{
"patterns": [
{
"pattern": "/products/{id}",
"total_requests": 14820,
"unique_urls": 1042,
"daily_avg": 2117.1,
"pct_of_crawl": 22.5,
"status_2xx": 14501,
"status_3xx": 12,
"status_4xx": 301,
"status_5xx": 6,
"pattern_type": "numeric_id",
"flags": ["high_cardinality"],
"example_url": "/products/8421"
}
],
"total_crawl_requests": 65820,
"days_in_period": 7.0,
"min_urls": 3
}
Available on Starter and above.
Path Explorer
Hierarchical view of crawled paths with per-bot and per-status breakdowns. Use to build a directory tree of crawler activity.
GET /public/v1/websites/{website_id}/seo/path-explorer?hours=24
Query Parameters
| Name | Type | Description |
|---|---|---|
hours | integer | Time window (default: 24, max: 8760) |
bot | string | Filter to a single crawler |
params_filter | string | Filter by URL params |
file_type | string | Filter by file extension |
Response
{
"paths": [
{
"path": "/blog/seo-guide",
"requests": 842,
"bots": { "Googlebot": 621, "Bingbot": 221 },
"status_2xx": 840,
"status_3xx": 0,
"status_4xx": 2,
"status_5xx": 0
}
],
"top_bots": ["Googlebot", "Bingbot", "GPTBot"],
"total_paths": 3219
}
Available on Starter and above.
Status Consistency
URLs whose HTTP status changed across crawls — e.g. mostly 200
but occasionally 404 or 5xx.
Useful for surfacing flapping pages and intermittent errors that hit crawlers.
GET /public/v1/websites/{website_id}/seo/status-consistency?hours=168&min_requests=3
Query Parameters
| Name | Type | Description |
|---|---|---|
hours | integer | Time window (default: 24, max: 8760) |
bot | string | Filter to a single crawler |
min_requests | integer | Only include pages with at least this many crawls (default: 3) |
params_filter | string | Filter by URL params |
file_type | string | Filter by file extension |
Response
{
"pages": [
{
"path": "/checkout",
"total_requests": 214,
"status_2xx": 198,
"status_3xx": 0,
"status_4xx": 12,
"status_5xx": 4,
"statuses": { "200": 198, "404": 12, "503": 4 },
"dominant_class": "2xx",
"consistency_pct": 92.5,
"non_2xx_pct": 7.5,
"impact_score": 16,
"last_non_2xx": "2026-05-09T18:42:00Z",
"status_classes": 3
}
],
"summary": {
"total_pages_checked": 2418,
"total_inconsistent": 147,
"critical": 12,
"warning": 38,
"healthy": 97
}
}
Available on Starter and above.
Raw Request Log
The request-level log behind the dashboard's Log Explorer — every stored request
(bots and humans), newest first, with the full enriched row: geography, ASN,
bot verification verdict, protocol, device and timing. All filters are optional and
combine freely: bot_name, is_bot, bot_verified,
client_ip, path (substring), status,
status_category, method, host,
country, user_agent (substring). Time range via
hours or start/end (ISO 8601). Up to 500 rows
per call (limit); continue with the returned next_cursor or
page. A response with data_buffering: true means the archive
query is still running — retry the same call in a few seconds. For bulk pulls, use
create export with
export_type=requests.
GET /public/v1/websites/{website_id}/requests?hours=24&bot_name=GPTBot&status_category=4xx&limit=100
Available on all plans.
Crawler Request Log
Raw crawler request log with filtering. Each row is one crawler hit — timestamp, path, status, bot identity and verification, and response time.
GET /public/v1/websites/{website_id}/seo/requests?hours=24&filter_type=4xx&page=1
Query Parameters
| Name | Type | Description |
|---|---|---|
hours | integer | Time window (default: 24, max: 8760) |
bot | string | Filter to a single crawler |
filter_type | string | One of: all, verified, unverified, 2xx, 3xx, 4xx, 5xx, slowest |
page | integer | Page number (default: 1) |
page_size | integer | Results per page (default: 50, max: 500) |
params_filter | string | Filter by URL params |
file_type | string | Filter by file extension |
Response
{
"items": [
{
"timestamp": "2026-05-09T18:42:11Z",
"path": "/blog/old-post",
"status": 404,
"method": "GET",
"client_ip": "66.249.66.1",
"user_agent": "Mozilla/5.0 (compatible; Googlebot/2.1; ...)",
"bot_name": "Googlebot",
"bot_verified": true,
"bot_verification_status": "verified",
"time_taken_ms": 182.4
}
],
"total_count": 312,
"page": 1,
"page_size": 50,
"total_pages": 7,
"has_next": true,
"has_prev": false,
"filter": "4xx"
}
Available on Starter and above.
robots.txt Audit
Fetches your live robots.txt, parses the rule groups, and
cross-references them against actual crawler activity to surface violations
(bots hitting paths they were told to skip).
GET /public/v1/websites/{website_id}/seo/robots?hours=168
Query Parameters
| Name | Type | Description |
|---|---|---|
hours | integer | Time window for violation lookup (default: 24, max: 8760) |
bot | string | Limit violation check to a single crawler |
Response
{
"domain": "example.com",
"status": 200,
"fetch_blocked": false,
"using_saved": false,
"raw_content": "User-agent: *\nDisallow: /admin/\n...",
"rule_groups": [
{
"user_agent": "*",
"rules": [
{ "type": "disallow", "path": "/admin/" }
]
}
],
"sitemaps": ["https://example.com/sitemap.xml"],
"violations": [
{
"bot_name": "SemrushBot",
"path": "/admin/login",
"requests": 42,
"rule": "Disallow: /admin/"
}
],
"violations_total": 42,
"violation_summary": {
"SemrushBot": { "total_violations": 1, "total_requests": 42, "unique_paths": 1 }
},
"history": [],
"is_new_version": false,
"current_hash": "a1b2c3..."
}
Available on Starter and above.
Get Sitemap Coverage
Returns sitemap coverage data - tracks which URLs from your sitemap have been crawled by search engines, their crawl frequency, and current status.
GET /public/v1/websites/{website_id}/seo/sitemap?page=1&page_size=50&status=never_crawled
Query Parameters
page- Page number (default: 1)page_size- Results per page (1-500, default: 50)status- Filter: all, never_crawled, recently_crawled, stale, not_in_sitemapsort- Sort by: path, times_crawled, last_crawled, first_seensort_dir- Sort direction: asc, desc
Response fields (per URL)
url- Full URL from sitemapstatus- Crawl status: crawled or not_crawledlast_crawl_date- Timestamp of most recent crawlcrawl_count- Total number of times this URL has been crawledcontent_type- Content type of the URL (e.g. text/html)response_code- HTTP response code from last crawl
URL Crawl History
Per-URL crawl history — every individual crawl event for a single sitemap URL, including which bot, status code, response time, and country.
GET /public/v1/websites/{website_id}/seo/sitemap/url-history?url_path=/blog/post-1&hours=720
Query Parameters
| Name | Type | Description |
|---|---|---|
url_path | string | Required. Path of the URL to look up (e.g. /blog/post-1) |
hours | integer | Time window (default: 168, max: 8760) |
bot | string | Filter to a single crawler |
page | integer | Page number (default: 1) |
page_size | integer | Results per page (default: 50, max: 500) |
Response
{
"url_path": "/blog/post-1",
"total_crawls": 42,
"hours": 720,
"events": [
{
"timestamp": "2026-05-09 14:22:00",
"bot_name": "Googlebot",
"status": 200,
"method": "GET",
"user_agent": "Mozilla/5.0 (compatible; Googlebot/2.1; ...)",
"response_time_ms": 182,
"country": "US"
}
],
"page": 1,
"page_size": 50,
"total_pages": 1,
"bot_summary": {
"Googlebot": { "count": 38, "last_crawled": "2026-05-09 14:22:00" }
}
}
Available on Starter and above.
Index Coverage
Google index coverage for your current sitemap URLs plus any URL whose last inspection says indexed, crossed with recorded Googlebot crawls. crawled means a Googlebot crawl was recorded within hours of now (default 720) — a recency threshold, not a historical selection. indexed / not_indexed come from the last stored Search Console URL Inspection result, not a live check. pending_inspection counts URLs without a stored indexed or not_indexed result, including URLs never inspected; it is not evidence either way. directories (paged with dir_page and dir_page_size) break the buckets down by first path segment, and data_freshness describes each source. Returns gsc_connected: false with a message when Search Console is not connected, and 503 when the stored records cannot be read.
GET /public/v1/websites/{website_id}/seo/index-coverage
Response
{
"gsc_connected": true, "property_url": "sc-domain:example.com", "last_sync_at": "2026-09-14T02:10:00+00:00",
"buckets": {
"crawled_indexed": 1842,
"crawled_not_indexed": 356,
"not_crawled_indexed": 23,
"not_crawled_not_indexed": 491,
"pending_inspection": 118
},
"total_urls": 2830,
"directories": [{ "directory": "/blog", "crawled_indexed": 640, "crawled_not_indexed": 88, "not_crawled_indexed": 4, "not_crawled_not_indexed": 51, "pending_inspection": 12 }],
"total_directories": 14, "dir_page": 1, "dir_page_size": 20,
"data_freshness": { "crawl_data": "Recorded Googlebot crawl recency (last 30 days)", "index_data": "Last recorded Google inspection (normally refreshed after 14 days)" }
}
Index Coverage URLs
Paginated URLs from index coverage, sorted by 28-day impressions, with the inspection provenance for each row. index_status is the last stored inspection coverage (pending when never inspected); inspected_at dates it; inspected_url is the exact URL Google inspected (protocol, www/apex, trailing slash) and is null for older inspections that did not record it; google_canonical is Google's choice and not proof of indexing. crawl_count and last_crawled_at come from Googlebot crawls recorded within hours of now. Returns 400 when Search Console is not connected; sitemap_status explains an empty list when no sitemap URLs have synced.
GET /public/v1/websites/{website_id}/seo/index-coverage/urls?directory=/blog&page=1&page_size=50
Parameters
| Name | Type | Description |
|---|---|---|
bucket |
string | One of: crawled_indexed, crawled_not_indexed, not_crawled_indexed, not_crawled_not_indexed, pending_inspection. Omit for every bucket; an unrecognised name returns no rows. |
directory |
string | Path prefix filter, e.g. /blog |
page |
integer | Page number (default: 1) |
page_size |
integer | Results per page (default: 50). 0 returns every matching row; prefer bounded pages. |
hours |
integer | Googlebot crawl-recency window measured back from now (default: 720) |
Response
{
"urls": [
{
"url_path": "/blog/post-1", "bucket": "crawled_indexed",
"crawl_count": 38, "last_crawled_at": "2026-09-13T14:22:00", "last_crawled_by": "Googlebot Smartphone",
"index_status": "indexed", "index_status_reason": "Submitted and indexed", "inspection_verdict": "PASS",
"inspected_at": "2026-09-02T10:00:00+00:00", "inspected_url": "https://www.example.com/blog/post-1/", "google_canonical": "https://www.example.com/blog/post-1/",
"impressions_28d": 5120, "clicks_28d": 212, "avg_position": 8.4
},
{
"url_path": "/blog/new-post", "bucket": "pending_inspection",
"crawl_count": 2, "index_status": "pending",
"inspected_at": null, "inspected_url": null, "google_canonical": null
}
],
"page": 1, "page_size": 50,
"total": 783, "total_pages": 16,
"bucket_filter": null, "directory_filter": "/blog"
}
Search Performance
Search Console impressions, clicks, CTR and average position as a daily series for the period, totals against the previous equal period, site-wide top queries and top pages. Requires a Search Console connection; returns gsc_connected: false otherwise. Data lags Google by 2–3 days and is synced daily.
GET /public/v1/websites/{website_id}/search/performance?hours=720
Parameters
| Name | Type | Description |
|---|---|---|
hours |
integer | Period length in hours; 24 when omitted (MCP and CLI send 720). Rounded up to whole days; the window ends on the latest day Search Console has data for. start/end ISO timestamps are also accepted. |
Response
{
"gsc_connected": true,
"window": { "start": "2026-08-05", "end": "2026-09-03", "days": 30, "data_through": "2026-09-03" },
"series": [
{ "day": "2026-08-05", "impressions": 61230, "clicks": 1842, "ctr": 0.0301, "position": 12.4 }
],
"totals": { "impressions": 1712340, "clicks": 52110, "ctr": 0.0304, "position": 11.9, "days_with_data": 28 },
"previous": { "impressions": 1650012, "clicks": 49870, "ctr": 0.0302, "position": 12.3, "days_with_data": 28 },
"top_queries": [ { "query": "signal checker", "impressions": 14020, "clicks": 1255, "ctr": 0.0895, "position": 2.1 } ],
"top_pages": [ { "path": "/network-status", "impressions": 437347, "clicks": 18211, "ctr": 0.0416, "position": 6.8 } ],
"top_pages_window": "28d (all pages)",
"last_sync_at": "2026-09-06T14:00:43Z",
"last_sync_counts": { "search_analytics_requests": 206, "pages_with_queries": 200 }
}
Before the first sync the response carries available: false, reason: "not_synced_yet". A data-store failure returns 503 with retryable: true — never an empty series.
Search Queries (per page)
The Search Console queries that showed one page over the trailing 28 days (up to 10, synced for the top 200 pages by impressions) plus the page's daily impressions/clicks series (top 500 pages). The same fields are embedded in the URL detail response under index_status.top_queries and index_status.daily.
GET /public/v1/websites/{website_id}/search/queries?path=/network-status
Response
{
"gsc_connected": true,
"path": "/network-status",
"top_queries": [
{ "query": "is sky mobile down", "impressions": 21044, "clicks": 1399, "ctr": 0.0665, "position": 3.2 }
],
"daily": [ { "day": "2026-08-07", "impressions": 15230, "clicks": 612, "ctr": 0.0402, "position": 6.9 } ],
"queries_synced_at": "2026-09-06T14:00:31"
}
Index vs Fetch Reconciliation
Search Console index status per URL crossed with verified Googlebot fetches from your logs over the period. Four buckets: indexed_fetched, indexed_not_fetched (Google is not revisiting), not_indexed_fetched (fetched but refused) and fetched_not_inspected (status unknown). Counts cover every path in the union; examples are capped at 50 per bucket. Static assets are excluded from the fetch side. Cached for one hour per period.
GET /public/v1/websites/{website_id}/search/reconcile?hours=168
Response
{
"gsc_connected": true,
"period_hours": 168,
"counts": { "indexed_fetched": 3074, "indexed_not_fetched": 27957, "not_indexed_fetched": 137, "fetched_not_inspected": 528, "not_indexed_not_fetched": 1479 },
"inspected_paths": 32647,
"fetched_paths": 3739,
"buckets": {
"not_indexed_fetched": [
{ "path": "/oxford-oxfordshire", "fetches": 5, "last_fetch": "2026-09-06T02:50:11Z", "error_fetches": 0, "index_status": "not_indexed", "index_status_reason": "Crawled - currently not indexed", "impressions_28d": 1012, "clicks_28d": 49 }
]
},
"examples_cap": 50,
"data_buffering": false
}
While the log scan is still running the response has data_buffering: true and counts: null — poll the same call again.
Google Analytics Overview
Google Analytics 4 is a connected source, like Search Console — not an ingest path. It adds the human outcomes the logs cannot see (sessions, active users, engagement, key events, revenue) next to the request stream. This endpoint returns the period totals against the previous equal period with deltas, a daily series that carries log_humans alongside the GA numbers, the top 25 traffic sources, ai_sources (sessions and outcomes from AI assistants; operator names match AI Landing Pages) and measurement_gap: human page requests in your logs against GA pageviews, with unmeasured_pct — the share of real human page requests GA never recorded. Synced nightly from the GA4 Data API; lags about a day; aggregates only, no visitor identifiers and no bots. Requires a connected GA4 property (Settings → Integrations → Google Analytics); returns ga_connected: false otherwise.
GET /public/v1/websites/{website_id}/ga/overview?hours=720
Parameters
| Name | Type | Description |
|---|---|---|
hours |
integer | Period length in hours; 24 (one GA day) when omitted (MCP and CLI send 720). Whole GA days; the window ends on the latest day GA4 has finalised, which is normally yesterday (window.lag_days). start/end ISO timestamps are also accepted. |
Response
{
"ga_connected": true,
"property_id": "123456789",
"property_name": "Signal Checker - GA4",
"last_sync_at": "2026-09-06T03:12:40Z",
"available": true,
"currency": "GBP",
"window": { "from": "2026-08-06", "to": "2026-09-04", "days": 30, "lag_days": 1 },
"totals": { "sessions": 48210, "active_users": 39880, "engaged_sessions": 29553, "engagement_rate": 0.613, "key_events": 812, "revenue": 3140.5, "avg_engagement_secs": 74.2, "pageviews": 71004 },
"previous": { "window": { "from": "2026-07-07", "to": "2026-08-05" }, "sessions": 45395, "active_users": 37944, "engaged_sessions": 27191, "engagement_rate": 0.599, "key_events": 731, "revenue": 2884.1, "pageviews": 67817 },
"deltas": { "sessions_pct": 6.2, "active_users_pct": 5.1, "key_events_pct": 11.1, "revenue_pct": 8.9, "pageviews_pct": 4.7, "engagement_rate_pp": 1.4 },
"series": [
{ "day": "2026-08-06", "sessions": 1588, "active_users": 1320, "pageviews": 2361, "key_events": 27, "log_humans": 3104 }
],
"sources": [
{ "source": "google", "medium": "organic", "sessions": 31205, "engaged_sessions": 20283, "engagement_rate": 0.65, "key_events": 540, "revenue": 2105.0 }
],
"ai_sources": [
{ "operator": "OpenAI", "source": "chatgpt.com", "sessions": 412, "engaged_sessions": 301, "engagement_rate": 0.731, "key_events": 19, "revenue": 96.0 },
{ "operator": "Perplexity", "source": "perplexity.ai", "sessions": 138, "engaged_sessions": 97, "engagement_rate": 0.703, "key_events": 4, "revenue": 0 }
],
"measurement_gap": { "available": true, "ga_pageviews": 71004, "log_human_page_requests": 93410, "unmeasured_pct": 24.0, "days_compared": 30, "note": "Share of human page requests in the logs that GA4 did not record (blockers, declined consent, tag failures)." }
}
Not connected: ga_connected: false, available: false, reason: "ga_not_connected". Connected but the first sync has not finished: reason: "not_synced_yet"; nothing in the window: reason: "no_data_in_window" (totals, previous, deltas and window are null in all three). deltas values are null when the previous period was zero. While the log side of the gap is still computing, measurement_gap is { available: false, data_buffering: true } and the top-level data_buffering: true is set — poll the same call again; with no log data it is { available: false, reason: "no_log_data" }. currency is the property's reporting currency or null. Modelled data from consent mode is included in GA's figures as GA reports it.
Google Analytics Pages
GA4 landing pages for the period, sorted by sessions. Each row carries sessions, engaged sessions, engagement rate, key events (GA4 conversions), revenue, pageviews, active users and average engagement time, plus ai_sessions (sessions that landed here from an AI assistant) and search_sessions (from organic search). path is the GA landing-page path without the query string, so it lines up with URL detail, whose ga section holds the same figures for one page together with its own measurement gap.
GET /public/v1/websites/{website_id}/ga/pages?hours=720&limit=100&q=/blog/
Parameters
| Name | Type | Description |
|---|---|---|
hours |
integer | Period length in hours; 24 (one GA day) when omitted (MCP and CLI send 720). Whole GA days; the window ends on the latest day GA4 has finalised, which is normally yesterday (window.lag_days). start/end ISO timestamps are also accepted. |
limit |
integer | Maximum pages returned (default 100). total is the count before the limit. |
q |
string | Case-insensitive substring filter on path. |
Response
{
"ga_connected": true,
"available": true,
"currency": "GBP",
"window": { "from": "2026-08-06", "to": "2026-09-04", "days": 30, "lag_days": 1 },
"pages": [
{ "path": "/network-status", "sessions": 9120, "engaged_sessions": 6201, "engagement_rate": 0.68, "key_events": 144, "revenue": 512.0, "pageviews": 12877, "active_users": 8410, "avg_engagement_secs": 81.5, "ai_sessions": 96, "search_sessions": 7710 }
],
"total": 1842
}
Same ga_connected / available / reason contract as the overview; pages is empty and window is null when unavailable. MCP: get_ga_overview / get_ga_pages. CLI: salience ga overview / salience ga pages [-l N] [-q filter].
Site Events
Annotated, site-wide events that overlay onto charts in the dashboard — deploys, algorithm updates, marketing campaigns, anything you want to correlate against traffic shifts.
GET /public/v1/websites/{website_id}/site-events?start_date=2026-04-01&end_date=2026-05-10
Query Parameters
| Name | Type | Description |
|---|---|---|
start_date | string | Optional. Earliest event date (YYYY-MM-DD) |
end_date | string | Optional. Latest event date (YYYY-MM-DD). Use with start_date. |
Response
{
"events": [
{
"website_id": "ws_abc123",
"event_id": "evt_71d2...",
"date": "2026-05-08",
"time": "14:30",
"title": "Deployed v2.4",
"category": "deploy",
"description": "New caching layer rolled out to all regions",
"created_by": "user_abc",
"created_at": "2026-05-08T14:31:09Z",
"updated_at": "2026-05-08T14:31:09Z"
}
]
}
Available on Starter and above.
Operations
Background exports, alert history, and alert configuration for orchestrating LogLens from your own systems.
List Exports
NEWList your background export jobs for a website with status, row count, file size, and a presigned download URL for completed jobs (URL valid for 7 days).
GET /public/v1/websites/{website_id}/exports
Response
{
"exports": [
{
"job_id": "9c1f...e31",
"export_type": "seo-requests",
"status": "completed",
"row_count": 12842,
"file_size_bytes": 4129083,
"created_at": "2026-05-09T10:30:00Z",
"completed_at": "2026-05-09T10:31:48Z",
"error": null,
"filters": "{\"hours\":\"168\",\"filter\":\"4xx\"}",
"description": "4xx filter | last 7d",
"download_url": "https://...s3.amazonaws.com/exports/9c1f...e31.csv?X-Amz-..."
}
]
}
Available on Starter and above. Only your own exports are returned.
Bot scope by export type
requests covers all bot traffic at request level (narrow with bot_scope); seo-requests covers search-engine bots only (Googlebot, Bing, DuckDuckBot, etc. — no AI or scraper bots); bot-history covers all bots. Request-level exports include a host column for multi-hostname feeds.
Filters an export type does not support are never silently applied-looking: they are ignored and reported back in the creation response's ignored_filters field with a warning.
Completed exports also have a short, stable download link: GET /public/v1/websites/{website_id}/exports/{job_id}/download (302-redirects to the file; add ?redirect=false for JSON). All read endpoints accept ?max_rows=N to cap every list in the response (a _truncated map reports what was cut) — useful for very large windows in agent tooling.
Create Export
NEWQueue a new background export job. The export runs asynchronously; poll List Exports for status, or wait for the email notification when it's ready. Files are CSV and links expire after 7 days.
POST /public/v1/websites/{website_id}/exports
Request Body
| Name | Type | Description |
|---|---|---|
export_type |
string | Required. One of: traffic, paths, status-codes, bots, bot-history, referrers, geography, devices, ips, sitemap-coverage, crawl-budget-urls, requests, seo-requests, url-pattern-urls, recommendations-paths-404, recommendations-slow-paths, recommendations-unverified-bots |
hours |
integer | Rolling window in hours. Use this or start/end. |
start / end |
string | ISO 8601 timestamps for an explicit range. |
bot / bot_name |
string | Filter to a single crawler. Naming a bot on a request-level export overrides the type's default bot scope. |
bot_scope |
string | For the requests export: search, ai, or all (default) bot traffic. |
countries |
string | Comma-separated ISO country codes. |
status / status_code / status_category |
string | Filter by HTTP status (e.g. 404) or category (4xx). |
filter |
string | Sub-filter passed through to the underlying endpoint (e.g. verified, slowest for seo-requests). |
directory |
string | Directory prefix (used by crawl-budget-urls). |
pattern |
string | URL pattern template (used by url-pattern-urls). |
params_filter / file_type / path_filter |
string | Optional URL/path filters, mirror the underlying SEO endpoints. |
notify |
boolean | Default true — the key's owner is emailed when the file is ready. Send false for scripted / agent use and poll List Exports instead. |
Response
HTTP/1.1 201 Created
{
"job_id": "9c1f...e31",
"status": "pending",
"message": "Export started. You'll receive an email when it's ready."
}
Available on Starter and above.
Alert History
NEWFired alerts for a website (anomaly detector, error spikes, new-bot detection, etc.). Sorted most-recent first; includes a count of unacknowledged alerts in the last 30 days.
GET /public/v1/websites/{website_id}/alerts?limit=50&type=traffic_spike
GET /public/v1/websites/{website_id}/alerts?view=incidents
Query Parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Max alerts to return (default: 50, max: 500) |
type | string | Filter to a specific alert_type (e.g. traffic_spike, error_rate, new_bot) |
acknowledged | boolean | Filter by acknowledgement state (true/false) |
view | string | Set to incidents to additionally return incidents: the returned page of alerts grouped by fingerprint (alert_type|severity|subject, e.g. offending IP or crawler name) within a rolling 6-hour join window. Each incident carries fingerprint, alert_type, severity, subject, title, first_seen, last_seen, duration_seconds, count, alerts[] (alert ids), latest_alert, status (active / cleared — no repeat for longer than the type’s clear window, 2h by default and 24h for nightly checks / snoozed) and acknowledged_all. In this view unacknowledged_count counts incidents; the per-alert figure is unacknowledged_alert_count. Every alert also carries its fingerprint, plus last_seen / occurrences when repeats were coalesced into it. |
Response
{
"alerts": [
{
"website_id": "ws_abc123",
"alert_id": "2026-05-09T14:22:00Z#a1b2",
"alert_type": "traffic_spike",
"severity": "warning",
"message": "Traffic 340% above baseline for the last hour",
"details": {
"current_value": 12842,
"baseline_value": 2914,
"deviation_pct": 340.6
},
"acknowledged": false,
"created_at": "2026-05-09T14:22:00Z"
}
],
"count": 1,
"unacknowledged_count": 3
}
Available on Starter and above.
Alert Configuration
NEWReturns the website's current alert configuration — which alert types are enabled, how often each runs, suppression lists, email/webhook settings, and the anomaly-detector baseline status.
GET /public/v1/websites/{website_id}/alerts/config
Response
{
"config": {
"alerts_enabled": true,
"traffic_alerts_enabled": true,
"traffic_frequency_minutes": 60,
"error_alerts_enabled": true,
"error_frequency_minutes": 5,
"bot_alerts_enabled": true,
"bot_frequency_minutes": 1440,
"seo_alerts_enabled": true,
"seo_frequency_minutes": 60,
"suppressed_crawlers": [],
"suppressed_bots": [],
"email_notifications_enabled": true,
"email_disabled_types": [],
"min_email_severity": 5,
"digest_enabled": true,
"min_digest_severity": 3,
"digest_send_hour_utc": 8,
"webhook_notifications_enabled": false,
"webhook_urls": []
},
"baseline_status": {
"status": "ready",
"message": "Baseline established. Anomaly detection is active.",
"data_points": 1248,
"ready": true
}
}
Available on Starter and above.
Search IP Requests
Returns all requests made by a specific IP address. Useful for investigating suspicious activity.
GET /public/v1/websites/{website_id}/ips/{ip_address}/requests?hours=24&page=1
Query Parameters
hours- Time period (1-168, default: 24, max 7 days)page- Page number (default: 1)page_size- Results per page (1-500, default: 50)
Response includes (for each request)
timestamp- Request timestamppath- URL path requestedmethod- HTTP methodstatus- HTTP status codeuser_agent- User agent stringis_bot- Whether request was from a bot
Example Usage
$ cURL
curl -X GET \
"https://api.loglens.ai/public/v1/websites" \
-H "Authorization: Bearer llapi_your_key_here"
JS JavaScript / Node.js
const response = await fetch(
'https://api.loglens.ai/public/v1/websites',
{
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
);
const data = await response.json();
console.log(data.websites);
PY Python
import requests
response = requests.get(
"https://api.loglens.ai/public/v1/websites",
headers={"Authorization": f"Bearer {api_key}"}
)
data = response.json()
print(data["websites"])
Recommendations, Insights & Health NEW
Programmatic access to the same actionable recommendations, AI-generated insights,
real-time feed, and site/ingestion health you see in the dashboard. All read-only
(GET), authenticated with your API key like every other endpoint.
| Endpoint | Description |
|---|---|
GET /public/v1/websites/{website_id}/recommendations |
All current recommendations — 404 hotspots, slow paths, unverified bots, IPs to block. |
GET /public/v1/websites/{website_id}/recommendations/paths-404 |
Top 404 (not-found) paths worth fixing or redirecting. |
GET /public/v1/websites/{website_id}/recommendations/slow-paths |
Slowest paths (highest response time) — performance hotspots. |
GET /public/v1/websites/{website_id}/recommendations/unverified-bots |
Bots claiming a verified identity whose IP failed verification (possible impersonation). |
GET /public/v1/websites/{website_id}/insights |
AI-generated insights — traffic / SEO / anomaly narratives. |
GET /public/v1/websites/{website_id}/health |
Current site + ingestion health — whether data is flowing, error rates, feed misconfiguration. |
GET /public/v1/websites/{website_id}/health/history |
Historical health-status trend over time. |
GET /public/v1/websites/{website_id}/live |
Real-time request feed (most recent requests). Optional ?type=all|bots|human|errors. |
GET /public/v1/websites/{website_id}/checks |
Site Safety Check verdicts — nightly checks on crawler access, security and serving quality, each pass/warn/fail with evidence. |
GET /public/v1/websites/{website_id}/checks/history |
Daily check-verdict history and transitions. Optional ?days=1-90 (default 30). |
GET /public/v1/websites/{website_id}/page-importance |
Page Importance — per-page verified-crawler attention scores (0–10, log scale, 90-day window). Optional ?bot=google|bing&limit=N. |
GET /public/v1/websites/{website_id}/url |
URL detail — every signal for one path (?path= required, URL-encoded): logs, search-bot fetches, crawl, sitemap, Search Console index status, Importance. Accepts hours or start/end, countries, segment. |
POST /public/v1/websites/{website_id}/reports/seo |
Generate a Technical SEO Report (async; appears in exports). Body: {"period_days": 7-90, "prepared_for": "..."}. Soft limit 10/month. |
Each of these is documented below. The time-scoped ones take hours or start/end, and all of them accept segment and max_rows (see Common Query Parameters).
Recommendations
Actionable items distilled from the site's alerts over the window: ips_to_block, paths_404, unverified_bots and slow_paths, each scored and sorted (score, reason_badges). Window: hours or start/end. When neither is sent the API uses 24 hours; pass hours=720 for the last 30 days. Every item carries a key (ip:<ip>, 404:<path>, unverified:<bot>:<ip>, slow:<path>) — that is the value POST recommendations/resolve {"key"} takes to mark it done and DELETE recommendations/resolve/{key} (URL-encode it) takes to un-resolve. Resolved items drop out of the list until they reappear in newer alerts; resolved_count says how many are hidden.
The three detail endpoints — recommendations/paths-404, recommendations/slow-paths, recommendations/unverified-bots — go back to the logs for the same window (same hours / start / end) and return {"items": [...], "window_days": N} with fuller per-row detail (hit counts, verified-bot share, sample user agents, last seen). On an Athena timeout they return an empty items with "error": "timeout" — retry rather than treating it as "nothing found". Turn a list into a firewall rule with Deploy snippets.
GET /public/v1/websites/{website_id}/recommendations?hours=720
GET /public/v1/websites/{website_id}/recommendations/paths-404?start=2026-08-01&end=2026-08-31
GET /public/v1/websites/{website_id}/recommendations/slow-paths?hours=168
GET /public/v1/websites/{website_id}/recommendations/unverified-bots?hours=168
{
"window_days": 30, "resolved_count": 4,
"ips_to_block": [
{ "key": "ip:203.0.113.7", "ip": "203.0.113.7", "country": "SG", "request_count": 18420, "alert_types": ["hacking_probe", "scraper"],
"sample_user_agent": "python-requests/2.31", "sample_paths": "/wp-login.php, /.env", "last_seen": "2026-09-03T22:14:00Z", "score": 0.91, "reason_badges": ["probing", "high volume"] }
],
"paths_404": [{ "key": "404:/old-offers", "path": "/old-offers", "request_count": 612, "score": 0.74, "reason_badges": ["googlebot hitting"] }],
"unverified_bots": [{ "key": "unverified:Googlebot:198.51.100.22", "claimed_bot": "Googlebot", "ip": "198.51.100.22", "request_count": 940 }],
"slow_paths": [{ "key": "slow:/search", "path": "/search", "avg_latency_ms": 2860 }]
}
Insights
The AI-written narratives already generated for the site in the dashboard (traffic, SEO and anomaly write-ups). This endpoint reads the cache — it does not generate new ones. Insights are keyed by the dashboard page they were written for (default /; e.g. /seo, /bots) and the JSON filters that were active (default {}), so pass both exactly as the dashboard did to find a match. Returns up to 10, newest first, each with id, page, period, filters, content (Markdown), tools_used, token counts and created_at. An empty insights array means nothing has been generated for that page/filters combination.
GET /public/v1/websites/{website_id}/insights?page=/seo&filters=%7B%22bot%22%3A%22googlebot%22%7D
{
"insights": [
{ "id": 1482, "page": "/seo", "period": "30d", "filters": { "bot": "googlebot" },
"content": "Googlebot spent 38% of its crawl on paginated category pages…", "tools_used": ["get_seo", "get_url_patterns"],
"input_tokens": 18210, "output_tokens": 640, "created_at": "2026-09-02T08:11:45Z" }
]
}
Health
GET health is the current picture with no parameters: per-metric current value against a rolling baseline_mean / baseline_std with a z_score and status (normal, warning, critical or insufficient_data), whether data is flowing, the baseline's maturity (building, warming_up, mature) and recent alerts. GET health/history returns one hourly series for one metric: metric=traffic|errors|bots|crawlers|not_found|latency (default traffic) over period=24h|7d|30d (default 24h), each point with the value and the baseline band so you can plot it. latency is an average in ms; the rest are request counts.
GET /public/v1/websites/{website_id}/health
GET /public/v1/websites/{website_id}/health/history?metric=errors&period=7d
{
"metrics": {
"traffic": { "current": 4210, "baseline_mean": 3980.5, "baseline_std": 410.2, "z_score": 0.56, "status": "normal" },
"errors": { "current": 96, "current_rate": 0.0228, "z_score": 3.4, "status": "critical" },
"latency": { "current": 412.7, "unit": "ms", "status": "normal" }
},
"baseline_maturity": { "status": "mature", "observations": 612 },
"evaluation_window_minutes": 60,
"active_alerts": [{ "alert_type": "error_spike_5xx", "severity": "critical", "created_at": "2026-09-04T09:00:00Z" }]
}
Site Safety Checks
Standing checks on crawler access, security and serving quality (robots.txt and sitemap delivery, crawler blocking and rate limiting, exposed files, redirects and so on), evaluated nightly. GET checks takes no parameters and returns the current verdict for every check: id, title, category, verdict, weight, an evidence object, updated_at (when that verdict was evaluated) and, after a change, previous_verdict and flipped_at. Verdicts are pass, warn, fail, unknown (could not be evaluated this run — for example our probe was blocked or skipped; never a pass) and not_applicable (does not apply to this site, for example too little traffic). Sorted fail, warn, unknown, pass, not_applicable, then by weight. summary counts only the verdicts that occur (a missing key means zero); evaluated: false means no evaluation has been stored yet, which is not a pass.
evidence keys vary by check. Delivery probes report requested_url, final_url (after redirects), redirects, status and an outcome: ok, partial, missing, blocked, server_error, http_error, invalid, unreachable or unconfirmed. Retrieval and content outcomes decide warn / fail; a warn with outcome: "ok" and advisories (cookies, caching, content type) is delivery advice, not a retrieval failure, and notes are informational. A 403 or 429 answered to our checker is reported as blocked for our checker; it does not prove search crawlers are blocked. GET checks/history takes only days (1–90, default 30; no hours/start/end) and returns the stored daily verdict per check — date as YYYYMMDD in UTC, with days that have no stored verdict absent — plus derived changes, newest first, including changes into or out of unknown.
GET /public/v1/websites/{website_id}/checks
GET /public/v1/websites/{website_id}/checks/history?days=60
{
"evaluated": true, "summary": { "pass": 15, "warn": 2, "fail": 1, "unknown": 1, "not_applicable": 3 },
"checks": [
{ "id": "robots_txt_served_statically", "title": "robots.txt delivery", "category": "crawler_access", "verdict": "fail", "weight": 5,
"evidence": { "requested_url": "https://example.com/robots.txt", "final_url": "https://www.example.com/robots.txt", "status": 503, "outcome": "server_error",
"note": "robots.txt returned HTTP 503. Google may pause crawling your whole site while robots.txt returns server errors." },
"previous_verdict": "pass", "flipped_at": "2026-09-04T01:40:12Z", "updated_at": "2026-09-04T01:40:12Z" },
{ "id": "sitemap_served_properly", "verdict": "warn",
"evidence": { "requested_url": "https://www.example.com/sitemap.xml", "final_url": "https://www.example.com/sitemap.xml", "status": 200, "outcome": "ok",
"advisories": ["sets cookies (session), which can stop a CDN caching it; crawlers generally ignore them"] }, "updated_at": "2026-09-04T01:40:12Z" },
{ "id": "ab_test_crawler_exposure", "verdict": "unknown",
"evidence": { "note": "Our probe was blocked or failed, so this check could not be evaluated. See the 'Site reachable by our checker' check; allow the SalienceBot user agent to enable it." }, "updated_at": "2026-09-04T01:40:12Z" },
{ "id": "ai_crawlers_access", "verdict": "not_applicable", "evidence": { "note": "no AI crawler traffic" }, "updated_at": "2026-09-04T01:40:12Z" }
]
}
{
"days": 60,
"history": {
"robots_txt_served_statically": [{ "date": "20260903", "verdict": "pass" }, { "date": "20260904", "verdict": "fail" }],
"ab_test_crawler_exposure": [{ "date": "20260903", "verdict": "pass" }, { "date": "20260904", "verdict": "unknown" }]
},
"changes": [
{ "check_id": "robots_txt_served_statically", "date": "20260904", "from": "pass", "to": "fail" },
{ "check_id": "ab_test_crawler_exposure", "date": "20260904", "from": "pass", "to": "unknown" }
]
}
Live Feed
The most recent requests as they arrive — the same feed as the dashboard's Live view. No time range; type=all|bots|human|errors (default all) filters what comes back. Each row carries timestamp, client_ip, method, path, status / status_category, bytes_sent, time_taken (ms), user_agent and the classification (is_bot, bot_name, bot_category, bot_verified, bot_verification_status). Poll it rather than streaming; use max_rows to keep the payload small.
GET /public/v1/websites/{website_id}/live?type=errors&max_rows=20
{
"feed_type": "errors", "count": 20,
"requests": [
{ "timestamp": "2026-09-04T10:41:52Z", "client_ip": "203.0.113.7", "method": "GET", "path": "/wp-login.php", "status": 404, "status_category": "4xx",
"is_bot": true, "bot_name": "python-requests", "bot_category": "scripting", "bot_verified": null, "bot_verification_status": null,
"bytes_sent": 1204, "time_taken": 38.0, "user_agent": "python-requests/2.31" }
]
}
Page Importance
Per-page crawl-attention scores: how much of a verified crawler's attention each URL gets, on a 0–10 log scale, computed nightly over a rolling window (window_days in the response, currently 90). bot=google|bing|… picks the crawler family (default google; the families available for the site are listed in bots — an unknown one is a 400). Pages are sorted by crawl count; limit (1–50,000, default 200) caps them, or full=1 returns every page regardless of limit. distribution is the histogram of scores. Until the first nightly build has run you get available: false, reason: "not_built_yet". No time-range parameters — the window is fixed by the build.
GET /public/v1/websites/{website_id}/page-importance?bot=google&limit=500
GET /public/v1/websites/{website_id}/page-importance?bot=bing&full=1
{
"available": true, "bot": "google", "bots": ["bing", "google"], "generated_at": "2026-09-04T00:52:10Z", "window_days": 90,
"page_count": 4812, "total_crawls": 61240,
"distribution": { "0": 2210, "1": 1180, …, "10": 3 },
"pages": [{ "path": "/", "crawls": 2140, "score": 10 }, { "path": "/products", "crawls": 890, "score": 8.7 }]
}
URL detail
Everything known about one path on the site, in one call. path is required (URL-encoded, matched exactly and case-sensitively; a query string or fragment is dropped). Time range as elsewhere: hours (default 24) or start/end, plus optional countries and segment. Each top-level section is independently nullable so a missing source never blanks the rest: logs (totals, status mix, avg/p95 response, first/last seen, a bucketed series, top_bots, top_referrers, top_countries, and a variant block when the trailing-slash twin also received traffic), search_bots (verified Googlebot/Bingbot fetches and last fetch), crawl (from the latest ready crawl, with its crawl_id and completed_at: status, depth, inlinks, outlinks, PageRank, indexable, the page's declared canonical, redirect target — or in_crawl: false, or available: false with reason no_crawl or missing_results when saved crawl files are gone), sitemap, index_status, ga and importance (per-family score, crawls, interval, last crawl, generated_at). If the log scan is still running the response carries data_buffering: true with logs null — poll again in a few seconds; it is not zero traffic.
Each source keeps its own date: the selected window applies to logs, search_bots and the Search Console period, while the crawl, sitemap snapshot, inspection and importance build are as of their own timestamps. sitemap.membership_status is present (confirmed in the sitemap), removed (confirmed, then removed; see removed_at), not_found (no confirmed membership and the last sitemap fetch was complete) or unknown (no confirmed membership and completeness not established — not proof of absence); read it with fetch_complete, last_successful_fetch_at and last_checked_at. in_sitemap is true only for confirmed, unremoved membership, and reason: "not_in_sitemap" only means no inventory record. index_status is the last stored URL Inspection result: index_status, index_status_reason, inspection_verdict, inspected_at and inspected_url — the exact URL Google inspected, which may be the www/apex or trailing-slash variant, and null for older inspections that did not record it. google_canonical is Google's choice, not proof of indexing. reason: "not_inspected" means no stored inspection, not "not indexed"; available: false with gsc_not_connected or lookup_failed means the source could not be used.
GET /public/v1/websites/{website_id}/url?path=%2Fpricing&hours=168
{
"path": "/pricing", "period_hours": 168,
"logs": { "requests": 6204, "humans": 5065, "bots": 1139, "verified_search": 628,
"status_mix": { "2xx": 5712, "3xx": 365, "4xx": 127, "5xx": 0 },
"avg_time_ms": 17.4, "p95_time_ms": 73, "granularity": "1 hour",
"series": [{ "time": "2026-09-06T10:00:00Z", "requests": 41, "bots": 9, "verified_search": 4 }, …],
"top_bots": [{ "name": "Bingbot", "requests": 609, "verified": 609, "last_seen": "2026-09-06T10:20:06Z" }],
"top_referrers": [{ "domain": "www.google.com", "requests": 2887 }], "top_countries": [{ "country": "GB", "requests": 4739 }], "variant": null },
"search_bots": { "google": { "fetches": 16, "last_fetch": "2026-09-06T04:33:17Z" }, "bing": { "fetches": 609, "last_fetch": "2026-09-06T10:20:06Z" } },
"crawl": { "available": true, "in_crawl": true, "status": 200, "depth": 1, "inlinks": 597, "outlinks_internal": 12, "pagerank": 0.95, "indexable": true, "canonical": "https://example.com/pricing", "redirect_to": null, "completed_at": "2026-09-05T03:10:00Z" },
"sitemap": { "membership_status": "present", "membership_confirmed": true, "in_sitemap": true, "removed": false, "last_seen": "2026-09-06T03:00:00Z",
"fetch_complete": true, "last_successful_fetch_at": "2026-09-06T03:00:00Z", "times_crawled": 8909, "last_crawled_by": "Bingbot" },
"index_status": { "available": true, "index_status": "indexed", "inspection_verdict": "PASS", "inspected_at": "2026-09-02T10:00:00Z",
"inspected_url": "https://www.example.com/pricing", "google_canonical": "https://www.example.com/pricing", "impressions_28d": 437347, "clicks_28d": 14735, "avg_position": 37.4 },
"importance": { "available": true, "families": { "google": { "score": 6, "crawls": 93, "interval_days": 1.0 }, "bing": { "score": 10, "crawls": 2748 } } }
}
URL search
Typeahead over the URLs Salience already knows for the site: the sitemap inventory, search-bot crawl records and your latest Site Crawler run. Use it to find the exact path for a page you half-remember, then call URL detail with it. q is a path fragment, prefix or full URL (the origin and any query string are stripped server-side; an empty q returns the site's most-crawled URLs); limit defaults to 15 (max 50). Results are ranked prefix matches first, then substring matches, then by crawls descending. Each result has path, source (sitemap = confirmed sitemap membership, crawl_discovered = an inventory record without confirmed membership, crawl = seen only in the latest crawl), membership_status (present, removed, not_found — only after a complete sitemap fetch — or unknown, which is not proof of absence), membership_confirmed, in_sitemap, removed / removed_at, crawls, last_crawled, last_bot, and from the latest crawl crawl_status_code and indexable (both null without a crawl). total_matches counts every match, not just the page returned; sources splits it by origin; sitemap_snapshot gives fetch_complete, last_successful_fetch_at and last_checked_at for the inventory as a whole. Powers the search box on the URL detail page (URL Lookup). No time-range parameters. MCP: search_urls; CLI: salience urls network.
GET /public/v1/websites/{website_id}/urls/search?q=network&limit=15
{
"query": "network",
"results": [
{ "path": "/networks/three", "source": "sitemap", "membership_status": "present", "in_sitemap": true, "removed": false, "crawls": 412,
"last_crawled": "2026-09-06T04:33:17Z", "last_bot": "Googlebot", "crawl_status_code": 200, "indexable": true },
{ "path": "/network-status", "source": "crawl_discovered", "membership_status": "not_found", "in_sitemap": false, "removed": false, "crawls": 97,
"last_crawled": "2026-09-05T22:10:02Z", "last_bot": "Bingbot", "crawl_status_code": 200, "indexable": true }
],
"total_matches": 23,
"sources": { "sitemap_urls": 18, "crawl": 5 },
"sitemap_snapshot": { "fetch_complete": true, "last_successful_fetch_at": "2026-09-06T03:00:00Z", "last_checked_at": "2026-09-06T03:00:00Z" }
}
Technical SEO Report
Generates the client-ready Technical SEO Report (AI narrative over the log evidence) as an asynchronous job. Body: period_days (7–90, default 30, clamped) and optional prepared_for (a client or site name, ≤120 chars, printed on the cover). Returns 201 with a job_id; the report lands in List Exports as export_type: "seo-report" a couple of minutes later — poll for status: "completed" and fetch it via the export's download URL. Soft-capped at 10 reports per organisation per month; the 11th returns 429. Works with a read-only key (it produces a file, it doesn't change anything) but is not counted against the write bucket.
POST /public/v1/websites/{website_id}/reports/seo
curl -X POST https://api.loglens.ai/public/v1/websites/{website_id}/reports/seo \
-H "Authorization: Bearer llapi_…" -H "Content-Type: application/json" \
-d '{"period_days": 30, "prepared_for": "Acme Ltd"}'
HTTP/1.1 201 Created
{ "job_id": "9c1f…e31", "status": "pending", "message": "Report generating — it will appear in Downloads in a couple of minutes." }
GET /public/v1/websites/{website_id}/exports
{ "exports": [{ "job_id": "9c1f…e31", "export_type": "seo-report", "status": "completed", "description": "Technical SEO Report | last 30d | for Acme Ltd", "download_url": "https://…" }] }