Get a key
A free key, by email.
Free keys allow 10 requests an hour and 40 a day. The key is in the email and is shown once.
Quick start
Send the key in a header.
Use Authorization: Bearer <key>, or X-Citehound-Key: <key>. Keep the key out of URLs and public code.
export CITEHOUND_KEY=chk_your_key_here curl "https://getcitehound.com/v1/scan?domain=example.com" \ -H "Authorization: Bearer $CITEHOUND_KEY"
The full description is a machine-readable OpenAPI 3.1 file: /openapi.yaml.
Reference
23 endpoints.
Base address https://getcitehound.com/v1. Scan: 3. Generate: 3. Keys, account and benchmark: 3. Pro monitoring: 12.
POST /v1/keys Get a free API key by email
Sends a free key to the address. The key is in the email and nowhere else: never in this response. The answer is the same whether or not the address could be given a key. The key email is transactional only; set marketing_optin to true only if you want occasional product updates.
No key needed. Answers 202, 400, 413, 415, 429, 502, 503.
Send JSON with Content-Type: application/json.
{
"email": "you@example.com",
"marketing_optin": false
}
curl -X POST https://getcitehound.com/v1/keys \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
{
"status": "accepted",
"message": "If this address can receive a key, it is on its way. The key is sent by email only and is shown once."
}
GET /v1/me Your plan, limits and usage
The plan of the key (free or pro), its limits, how many requests it has made this hour and today (UTC), and the masked address it was sent to.
Needs a key. Answers 200, 401, 403, 429, 503.
curl https://getcitehound.com/v1/me \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"plan": "free",
"status": "active",
"email": "y***@example.com",
"created_at": "2026-10-22T12:00:00.000Z",
"limits": {
"requests_per_hour": 10,
"requests_per_day": 40,
"scans_per_day": 5,
"scans_per_domain_per_hour": 4
},
"usage": {
"this_hour": 1,
"today": 1
},
"features": {
"scan_detail": "top3",
"compare": false,
"generators": "preview",
"schema_types": [
"organization"
],
"monitoring": false,
"fixes": false,
"export": false
}
}
GET /v1/scan Scan one domain for AI readiness
Scans the public robots.txt, llms.txt, sitemap declaration and homepage of the domain (one page, not the whole site) and scores it out of 100 across three pillars. Returns the failed checks with their fixes, the state of ten AI crawlers in robots.txt, and a comparison with our benchmark. The score measures readiness, not whether any assistant names a brand.
Needs a key. Answers 200, 400, 401, 403, 429, 502, 503.
| Parameter | Required | What it is |
|---|---|---|
domain | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
curl "https://getcitehound.com/v1/scan?domain=example.com" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"domain": "example.com",
"scannedAt": "2026-10-22T12:00:00.000Z",
"score": 87,
"pillars": [
{
"id": "discover",
"name": "Discoverability",
"score": 27,
"max": 40
},
{
"id": "tech",
"name": "Technical foundation",
"score": 20,
"max": 20
},
{
"id": "trust",
"name": "Content & trust",
"score": 40,
"max": 40
}
],
"failed": [
{
"label": "AI crawler access (0/10 open)",
"pillar": "Discoverability",
"points": 13,
"max": 26,
"fix": "Review robots.txt rules for blocked AI crawlers"
}
],
"crawlers": [
{
"name": "GPTBot",
"vendor": "OpenAI",
"state": "limited",
"rule": "Disallow: /admin/ (+1 more) · bot-specific rule"
}
],
"crawlerSummary": {
"open": 0,
"limited": 10,
"blocked": 0
},
"benchmark": {
"overallAverage": 73,
"sitesScanned": 219,
"categories": 6,
"delta": 14,
"position": "above the average"
},
"note": "A readiness score: crawler access and on-page signals. It does not measure whether any assistant names a brand.",
"method": "https://getcitehound.com/methodology"
}
GET /v1/compare Compare two domains
Scans two different domains, one after the other, and returns both summaries and every check where they differ. Both domains count toward the per-domain limit.
Needs a key. Answers 200, 400, 401, 403, 429, 502, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
a | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
b | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
curl "https://getcitehound.com/v1/compare?a=example.com&b=example.org" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"a": {
"domain": "example.com",
"scannedAt": "2026-10-22T12:00:00.000Z",
"score": 87,
"pillars": [
{
"id": "discover",
"name": "Discoverability",
"score": 27,
"max": 40
},
{
"id": "tech",
"name": "Technical foundation",
"score": 20,
"max": 20
},
{
"id": "trust",
"name": "Content & trust",
"score": 40,
"max": 40
}
],
"failed": [
{
"label": "AI crawler access (0/10 open)",
"pillar": "Discoverability",
"points": 13,
"max": 26,
"fix": "Review robots.txt rules for blocked AI crawlers"
}
],
"crawlers": [
{
"name": "GPTBot",
"vendor": "OpenAI",
"state": "limited",
"rule": "Disallow: /admin/ (+1 more) · bot-specific rule"
}
],
"crawlerSummary": {
"open": 0,
"limited": 10,
"blocked": 0
},
"benchmark": {
"overallAverage": 73,
"sitesScanned": 219,
"categories": 6,
"delta": 14,
"position": "above the average"
},
"note": "A readiness score: crawler access and on-page signals. It does not measure whether any assistant names a brand.",
"method": "https://getcitehound.com/methodology"
},
"b": {
"domain": "example.org",
"scannedAt": "2026-10-22T12:00:00.000Z",
"score": 77,
"pillars": [
{
"id": "discover",
"name": "Discoverability",
"score": 27,
"max": 40
},
{
"id": "tech",
"name": "Technical foundation",
"score": 20,
"max": 20
},
{
"id": "trust",
"name": "Content & trust",
"score": 40,
"max": 40
}
],
"failed": [
{
"label": "AI crawler access (0/10 open)",
"pillar": "Discoverability",
"points": 13,
"max": 26,
"fix": "Review robots.txt rules for blocked AI crawlers"
}
],
"crawlers": [
{
"name": "GPTBot",
"vendor": "OpenAI",
"state": "limited",
"rule": "Disallow: /admin/ (+1 more) · bot-specific rule"
}
],
"crawlerSummary": {
"open": 0,
"limited": 10,
"blocked": 0
},
"benchmark": {
"overallAverage": 73,
"sitesScanned": 219,
"categories": 6,
"delta": 14,
"position": "above the average"
},
"note": "A readiness score: crawler access and on-page signals. It does not measure whether any assistant names a brand.",
"method": "https://getcitehound.com/methodology"
},
"differences": [
{
"check": "Meta description",
"a": {
"passed": true,
"points": 5
},
"b": {
"passed": false,
"points": 0
},
"max": 5
}
],
"note": "A readiness score: crawler access and on-page signals. It does not measure whether any assistant names a brand.",
"method": "https://getcitehound.com/methodology"
}
GET /v1/crawlers The state of ten AI crawlers in robots.txt
Reads robots.txt of the domain and reports, for each of ten AI crawlers, whether it is open, limited (an ordinary Disallow path applies to it) or blocked. This is a scan: it counts toward the per-domain limit.
Needs a key. Answers 200, 400, 401, 403, 429, 502, 503.
| Parameter | Required | What it is |
|---|---|---|
domain | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
curl "https://getcitehound.com/v1/crawlers?domain=example.com" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"domain": "example.com",
"scannedAt": "2026-10-22T12:00:00.000Z",
"crawlers": [
{
"name": "GPTBot",
"vendor": "OpenAI",
"state": "limited",
"rule": "Disallow: /admin/ (+1 more) · bot-specific rule"
}
],
"summary": {
"open": 0,
"limited": 10,
"blocked": 0
},
"note": "A readiness score: crawler access and on-page signals. It does not measure whether any assistant names a brand."
}
GET /v1/generate/llms-txt Generate an llms.txt
Builds an llms.txt from a name, a one-sentence description and up to 20 pages. Pure text generation: nothing is fetched. Pass each page as "page=<url>|<description>", repeated, or use POST with JSON. llms.txt is an optional convention with no demonstrated effect on Google.
Needs a key. Answers 200, 400, 401, 403, 429, 503.
| Parameter | Required | What it is |
|---|---|---|
name | Yes | |
description | Yes | |
page | No | Repeat for each page, as "<url>|<description>". |
A POST to the same path takes the same fields as a JSON body.
curl -G https://getcitehound.com/v1/generate/llms-txt \ -H "Authorization: Bearer $CITEHOUND_KEY" \ --data-urlencode "name=Example Co" \ --data-urlencode "description=Project software for small design studios." \ --data-urlencode "page=https://example.com/pricing|Plans and prices"
{
"type": "llms.txt",
"content": "# Example Co\n\n> Project software for small design studios.\n\n## Pages\n- [Plans and prices](https://example.com/pricing)"
}
GET /v1/generate/robots-txt Generate an AI-crawler robots.txt
Builds a robots.txt that explicitly allows or blocks named crawlers from the ten Citehound tracks, with an optional Sitemap line. Pure text generation: nothing is fetched. Names are comma-separated in the query; POST accepts JSON arrays.
Needs a key. Answers 200, 400, 401, 403, 429, 503.
| Parameter | Required | What it is |
|---|---|---|
allow | No | Comma-separated crawler names to allow. |
block | No | Comma-separated crawler names to block. |
sitemap | No |
A POST to the same path takes the same fields as a JSON body.
curl -G https://getcitehound.com/v1/generate/robots-txt \ -H "Authorization: Bearer $CITEHOUND_KEY" \ --data-urlencode "allow=GPTBot,ClaudeBot" \ --data-urlencode "block=Bytespider"
{
"type": "robots.txt",
"content": "User-agent: GPTBot\nAllow: /\n\nUser-agent: Bytespider\nDisallow: /\n\nSitemap: https://example.com/sitemap.xml",
"allow": [
"GPTBot"
],
"block": [
"Bytespider"
],
"sitemap": "https://example.com/sitemap.xml"
}
GET /v1/generate/schema Generate JSON-LD from a site's homepage
Reads the homepage of the domain for its real title, description and language and returns JSON-LD of the requested type. Fields the scan cannot know are left in square brackets. This reads the homepage: it counts toward the per-domain limit.
Needs a key. Answers 200, 400, 401, 403, 429, 502, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
domain | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
type | Yes |
curl "https://getcitehound.com/v1/generate/schema?domain=example.com&type=organization" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"domain": "example.com",
"type": "organization",
"json_ld": {
"@context": "https://schema.org",
"@graph": []
},
"note": "Fields the scan cannot know are left in square brackets for you to fill in."
}
GET /v1/benchmark Our archived benchmark data
Without "category": the six categories with their average score and the weighted overall average. With "category": the score spread, the per-crawler counts and the failure rate of each check for that category. This is our own archive of scans of well-known sites, from the date shown; it is not live.
Needs a key. Answers 200, 401, 403, 404, 429, 503.
| Parameter | Required | What it is |
|---|---|---|
category | No | One of the category ids from the overview. |
curl "https://getcitehound.com/v1/benchmark?category=crm" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"categories": [
{
"category": "crm",
"label": "CRM software",
"track": "B2B SaaS",
"sitesScanned": 31,
"average": 78,
"scannedAt": "2026-07-29"
}
],
"overall": {
"totalSites": 219,
"totalCategories": 6,
"overallAverage": 73
},
"note": "Our own archived scans of well-known sites."
}
POST /v1/watchlist Pro: Watch a domain
Adds a domain to the key's watchlist (up to 5 per Pro key). The first scan runs now and becomes the baseline; after that the domain is re-scanned about every 7 days by a scheduled job, and each scan is compared with the one before. The scan counts toward the per-domain limit. To get alert emails, send "alert_email" with the address this key was sent to; alerts can be switched off per domain with "alerts": false.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 201, 400, 401, 403, 409, 415, 429, 502, 503, 402.
Send JSON with Content-Type: application/json.
{
"domain": "example.com",
"alert_email": "you@example.com"
}
curl -X POST https://getcitehound.com/v1/watchlist \
-H "Authorization: Bearer $CITEHOUND_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com", "alert_email": "you@example.com"}'
{
"watch": {
"domain": "example.com",
"added_at": "2026-10-22T12:00:00.000Z",
"alerts": true,
"last_scan_at": "2026-10-22T12:00:00.000Z",
"next_scan_after": "2026-10-29T00:00:00.000Z",
"status": "ok",
"last_score": 87
},
"baseline": {
"snapshot_id": "snp_20261022120000",
"score": 87,
"scanned_at": "2026-10-22T12:00:00.000Z"
},
"alert_email": true,
"next_scan_after": "2026-10-29T00:00:00.000Z"
}
GET /v1/watchlist Pro: List the watched domains
The key's watchlist with the time of the last scan, the earliest time of the next one, the last score and whether alerts are on.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 401, 403, 429, 503, 402.
curl https://getcitehound.com/v1/watchlist \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"domains": [
{
"domain": "example.com",
"added_at": "2026-10-22T12:00:00.000Z",
"alerts": true,
"last_scan_at": "2026-10-22T12:00:00.000Z",
"next_scan_after": "2026-10-29T00:00:00.000Z",
"status": "ok",
"last_score": 87
}
],
"limit": 5,
"used": 1,
"alert_email": true
}
DELETE /v1/watchlist Pro: Stop watching a domain
Removes a domain from the key's watchlist. When no key watches the domain any more, its snapshots and events are deleted.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 400, 401, 403, 404, 429, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
domain | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
curl -X DELETE "https://getcitehound.com/v1/watchlist?domain=example.com" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"removed": true,
"domain": "example.com"
}
GET /v1/history Pro: The stored scans of a watched domain
The snapshots of one watched domain, newest first: the score, the pillar scores, the ids of the failed checks, the state of each crawler, and whether llms.txt and JSON-LD were present. Snapshots are kept 365 days.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 400, 401, 403, 404, 429, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
domain | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
since | No | Only snapshots after this date. |
limit | No |
curl "https://getcitehound.com/v1/history?domain=example.com" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"domain": "example.com",
"retention_days": 365,
"snapshots": [
{
"id": "snp_20261022120000",
"at": "2026-10-22T12:00:00.000Z",
"domain": "example.com",
"score": 87,
"pillars": [
{
"id": "discover",
"score": 27,
"max": 40
}
],
"failed": [
"ai-crawler-access"
],
"check_names": {
"ai-crawler-access": "AI crawler access"
},
"crawlers": {
"GPTBot": "open"
},
"llms_txt": true,
"schema": true
}
]
}
GET /v1/events Pro: What changed, newest first
Events from the differences between two scans of the watched domains: score_dropped, score_improved, crawler_blocked, crawler_unblocked, llms_txt_added, llms_txt_removed, check_failed, check_fixed, scan_failed. Ids are stable and sort in time order, so a polling client can pass the newest id it has as "since" and take only what is new. To read further back, pass "next_cursor" as "cursor". "score_dropped" and "score_improved" fire at a change of 5 points or more. Events are kept 365 days.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 400, 401, 403, 404, 429, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
since | No | An event id (only newer events) or a date. |
domain | No | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
type | No | A comma-separated list of event types. |
limit | No | |
cursor | No | The next_cursor of a previous answer. |
curl "https://getcitehound.com/v1/events?type=score_dropped,crawler_blocked&limit=20" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"events": [
{
"id": "evt_20261029060012_ab12cd34ef",
"type": "score_dropped",
"domain": "example.com",
"at": "2026-10-29T06:00:12.000Z",
"summary": "Readiness score dropped from 87 to 79 (-8 points).",
"snapshot_id": "snp_20261029060012",
"data": {
"from": 87,
"to": 79,
"delta": -8
}
}
],
"has_more": false,
"next_cursor": null
}
GET /v1/fixes Fix: The prioritized fix list, with files to paste
Scans the domain and returns what to change, highest points at stake first. Each finding has the plain fix and, where one applies, the file or snippet to paste (robots.txt, llms.txt, JSON-LD, head tags), built from the site's own title, description and language; anything the scan cannot know is left in square brackets. The list is remembered for 90 days so that POST /v1/fixes/recheck can show what changed.
Needs a key on Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 400, 401, 402, 403, 429, 502, 503.
| Parameter | Required | What it is |
|---|---|---|
domain | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
curl "https://getcitehound.com/v1/fixes?domain=example.com" \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"domain": "example.com",
"scannedAt": "2026-10-22T12:00:00.000Z",
"score": 62,
"items": [
{
"priority": 1,
"id": "llms-txt-present",
"label": "llms.txt present",
"pillar": "Discoverability",
"points_at_stake": 4,
"fix": "Add an llms.txt at the site root",
"files": [
{
"where": "https://example.com/llms.txt",
"kind": "llms.txt",
"content": "# Example Co\n\n> [One sentence describing the site.]"
}
]
}
],
"note": "A readiness score: crawler access and on-page signals. It does not measure whether any assistant names a brand."
}
POST /v1/fixes/recheck Fix: Re-scan after you made the changes
Scans the domain again and compares it with the fix list made by GET /v1/fixes: which findings are now fixed, which still fail, and which are new, with the score before and after. Needs a fix list first (404 no_fix_list otherwise).
Needs a key on Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 400, 401, 402, 403, 404, 415, 429, 502, 503.
Send JSON with Content-Type: application/json.
{
"domain": "example.com"
}
curl -X POST https://getcitehound.com/v1/fixes/recheck \
-H "Authorization: Bearer $CITEHOUND_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'
{
"domain": "example.com",
"checked_at": "2026-10-23T12:00:00.000Z",
"score_before": 62,
"score_after": 70,
"fixed": [
"llms-txt-present"
],
"still_failing": [
"meta-description"
],
"new_failures": [],
"note": "A readiness score: crawler access and on-page signals. It does not measure whether any assistant names a brand."
}
GET /v1/export Beyond: Export the history or the events of a watched domain
The stored snapshots (type=snapshots) or events (type=events) of a watched domain, as JSON or CSV (format=csv), as far back as the plan keeps them (730 days on Beyond).
Needs a key on Beyond (a lower plan gets 402 plan_required). Answers 200, 400, 401, 402, 403, 404, 429, 503.
| Parameter | Required | What it is |
|---|---|---|
domain | Yes | A bare domain: letters, digits and hyphens, no scheme, path, port or credentials. |
type | No | |
format | No |
curl "https://getcitehound.com/v1/export?domain=example.com&type=events&format=csv" \ -H "Authorization: Bearer $CITEHOUND_KEY" -o events.csv
{
"domain": "example.com",
"type": "snapshots",
"exported_at": "2026-10-22T12:00:00.000Z",
"retention_days": 730,
"count": 0,
"rows": []
}
POST /v1/webhooks Pro: Create a webhook
Registers an https address that receives the events you choose as signed JSON (see "Webhooks" below for the headers and how to verify them). Up to 3 per key. Leave "secret" out and one is generated and shown once; if you send one, it is never shown again. The address must be https on the default port and a public host.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 201, 400, 401, 403, 409, 415, 429, 503, 402.
Send JSON with Content-Type: application/json.
{
"url": "https://example.com/hooks/citehound",
"events": [
"score_dropped",
"crawler_blocked"
]
}
curl -X POST https://getcitehound.com/v1/webhooks \
-H "Authorization: Bearer $CITEHOUND_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/citehound", "events": ["score_dropped", "crawler_blocked"]}'
{
"webhook": {
"id": "whk_0123456789abcdef",
"url": "https://example.com/hooks/citehound",
"events": [
"score_dropped",
"crawler_blocked"
],
"enabled": true,
"consecutive_failures": 0,
"created_at": "2026-10-22T12:00:00.000Z",
"disabled_at": null,
"disabled_reason": null
},
"secret": "whsec_…",
"secret_note": "This is the only time the secret is shown. Store it now: it signs every delivery."
}
GET /v1/webhooks Pro: List webhooks
The key's webhooks with their event types, whether they are enabled and how many deliveries in a row have failed. A webhook is disabled after 5 deliveries in a row failed all their attempts. The secret is never returned.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 401, 403, 429, 503, 402.
curl https://getcitehound.com/v1/webhooks \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"webhooks": [
{
"id": "whk_0123456789abcdef",
"url": "https://example.com/hooks/citehound",
"events": [
"score_dropped"
],
"enabled": true,
"consecutive_failures": 0,
"created_at": "2026-10-22T12:00:00.000Z",
"disabled_at": null,
"disabled_reason": null
}
],
"limit": 3
}
GET /v1/webhooks/{id} Pro: Get one webhook
One webhook, without its secret.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 401, 403, 404, 429, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
id | Yes |
curl https://getcitehound.com/v1/webhooks/whk_0123456789abcdef \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"webhook": {
"id": "whk_0123456789abcdef",
"url": "https://example.com/hooks/citehound",
"events": [
"score_dropped"
],
"enabled": true,
"consecutive_failures": 0,
"created_at": "2026-10-22T12:00:00.000Z",
"disabled_at": null,
"disabled_reason": null
}
}
DELETE /v1/webhooks/{id} Pro: Delete a webhook
Deletes the webhook, its delivery log and any retries still waiting. To use an address again after it was disabled, delete the webhook and create it again.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 401, 403, 404, 429, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
id | Yes |
curl -X DELETE https://getcitehound.com/v1/webhooks/whk_0123456789abcdef \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"removed": true,
"id": "whk_0123456789abcdef"
}
GET /v1/webhooks/{id}/deliveries Pro: The delivery log of a webhook
The last 50 deliveries, newest first: the event, the status (pending, delivered or failed), the number of attempts, the HTTP status and a short error code, and when the next retry is due. Retries are made after 1 minute, 10 minutes and 1 hour, at the next run of the scheduled job.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 401, 403, 404, 429, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
id | Yes |
curl https://getcitehound.com/v1/webhooks/whk_0123456789abcdef/deliveries \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"webhook_id": "whk_0123456789abcdef",
"deliveries": [
{
"id": "dlv_20261029060013_9f8e7d6c",
"event_id": "evt_20261029060012_ab12cd34ef",
"event_type": "score_dropped",
"status": "delivered",
"attempt": 1,
"next_at": null,
"http_status": 200,
"error": null,
"created_at": "2026-10-29T06:00:13.000Z",
"delivered_at": "2026-10-29T06:00:13.000Z",
"test": false
}
]
}
POST /v1/webhooks/{id}/test Pro: Send a test event
Sends one signed test event (type "test") to the webhook now, makes one attempt and returns the result. It is logged as a test and does not count toward the failure limit.
Needs a key on Pro, Fix and Beyond (a lower plan gets 402 plan_required). Answers 200, 401, 403, 404, 429, 503, 402.
| Parameter | Required | What it is |
|---|---|---|
id | Yes |
curl -X POST https://getcitehound.com/v1/webhooks/whk_0123456789abcdef/test \ -H "Authorization: Bearer $CITEHOUND_KEY"
{
"delivery": {
"id": "dlv_20261022120005_1a2b3c4d",
"event_id": "evt_test_20261022120005_ab12cd",
"event_type": "test",
"status": "delivered",
"attempt": 1,
"next_at": null,
"http_status": 200,
"error": null,
"created_at": "2026-10-22T12:00:05.000Z",
"delivered_at": "2026-10-22T12:00:05.000Z",
"test": true
}
}
Plans
What each plan gets from the API.
The free scan on the website, the free tools and the MCP server stay free and complete. These are the limits and features of an API key. Prices and the rest of each plan are on the pricing page.
| Feature | Free | Pro | Fix | Beyond |
|---|---|---|---|---|
| Requests an hour, a day | 10, 40 | 200, 2000 | 400, 4000 | 600, 6000 |
| Scans a day (reading a site) | 5 | no extra cap | no extra cap | no extra cap |
| Scan detail | Score, pillars, crawlers, top 3 findings | Full | Full | Full |
| Compare two sites | - | Yes | Yes | Yes |
| llms.txt and robots.txt generators | Preview (first lines) | Full file | Full file | Full file |
| Schema generator | Organization only | All types | All types | All types |
| Watchlist, history, events, alerts, webhooks | - | 5 domains, 365 days | 5 domains, 365 days | 15 domains, 730 days |
| Fix list with files to paste, and the re-check | - | - | Yes | Yes |
| Export of history and events | - | - | - | Yes |
A feature the key's plan does not have answers 402 plan_required with the plan that has it. Some things are done by hand and are not part of the API: applying changes where you give us access (Fix), and researched questions, edited pieces and the monthly re-measurement (Beyond). A key gets its plan from us; there is no checkout inside the API.
Monitoring (Pro, Fix and Beyond)
Watch domains, get told when something changes.
Needs a key on Pro, Fix or Beyond. A free key gets 402 plan_required on these endpoints.
Add a domain with POST /v1/watchlist. It is scanned at once, and that scan is the baseline. A scheduled job runs once a day and re-scans the domains that are due, about 7 days after their last scan, oldest first. Each scan is compared with the one before and the differences become events. A scan that fails is tried again the next day, up to three times. A domain is scanned once for everyone who watches it.
Events
| Event | When | Alert email |
|---|---|---|
score_dropped | The score fell by 5 points or more since the last scan. | Yes |
score_improved | The score rose by 5 points or more. | No |
crawler_blocked | One of the ten AI crawlers is now blocked in robots.txt. | Yes |
crawler_unblocked | A crawler that was blocked is no longer blocked. | No |
llms_txt_added | llms.txt is now found at the site root. | No |
llms_txt_removed | llms.txt is no longer found. | Yes |
check_failed | A check that passed now fails. | No |
check_fixed | A check that failed now passes. | No |
scan_failed | The scheduled scan could not read the site (sent once when a run of failures starts). | No |
Every event has a stable id, a time, the domain and a one-line summary. Read them with GET /v1/events: pass the newest id you have as since and you get only what is new, newest first. Alert emails go to the address you give as alert_email when you add a domain (it must be the address your key was sent to). They are sent for score drops, blocked crawlers and a removed llms.txt, at most one a day for each domain, and every email has a one-click link to stop them for that domain.
Webhooks
A webhook receives each event you chose as a JSON POST. The address must be https on the default port and a public host; no redirect is followed. Each request carries X-Citehound-Timestamp (unix seconds), X-Citehound-Signature (v1= and the hex HMAC-SHA-256 of the timestamp, a dot and the raw body, keyed with your secret), X-Citehound-Event, X-Citehound-Event-Id and X-Citehound-Delivery. Any 2xx answer is a success. Otherwise it is retried after 1 minute, 10 minutes and 1 hour, at the next run of the scheduled job, so a retry can be up to a day late. After 5 deliveries in a row fail all their attempts, the webhook is disabled; delete it and create it again to start over. Use POST /v1/webhooks/{id}/test to send a test event and GET /v1/webhooks/{id}/deliveries to see the log.
const crypto = require('crypto');
// rawBody: the request body exactly as received (a string, not parsed JSON).
// headers: the lower-cased request headers. secret: the secret of the webhook.
function verify(secret, headers, rawBody) {
const ts = headers['x-citehound-timestamp'];
const want = Buffer.from('v1=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex'));
const got = Buffer.from(String(headers['x-citehound-signature'] || ''));
if (want.length !== got.length || !crypto.timingSafeEqual(want, got)) return false;
return Math.abs(Date.now() / 1000 - Number(ts)) < 300; // reject old deliveries
}
Prefer a page to curl? /watch lets you paste a Pro key to add and remove watched domains and to read the events and the score history. The key stays in the page’s memory and is never saved.
Limits
What a key can do.
Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. When a limit is reached the answer is 429 with a Retry-After header.
| Limit | Allowed | Notes |
|---|---|---|
| Free key | 10 requests an hour | 40 a day (UTC), and 5 scans a day (a scan is a request that reads a site) |
| Pro key | 200 requests an hour | 2000 a day (UTC) |
| Fix key | 400 requests an hour | 4000 a day (UTC) |
| Beyond key | 600 requests an hour | 6000 a day (UTC) |
| One domain, all keys together | 4 scans an hour | Counts scans, compare, crawlers and schema |
| One network address | 120 requests an hour | Across all keys |
| Watched domains | 5 on Pro and Fix, 15 on Beyond | Re-scanned about every 7 days (about every 2 days on Beyond) |
| Webhooks | 3 per key (5 on Beyond) | Disabled after 5 deliveries in a row fail all their attempts |
| History and events | 365 days (730 on Beyond) | Then they are deleted |
| Asking for keys | 5 an hour | 5 a day for one address |
Counters expire on their own within a day. Acceptable use: scan sites you have a reason to look at, keep to the limits, and do not use the API to probe private networks. A key that is abused is revoked.
Errors
Errors are JSON.
Every error has the shape {"error": "code", "message": "…"}. A 429 adds retry_after and limit_scope.
| Status | Code | Meaning |
|---|---|---|
400 | invalid_request | The request is not valid: a missing or malformed parameter. |
401 | invalid_key | The key is missing or not valid. A WWW-Authenticate header says how to send it. |
402 | plan_required | The endpoint, or the full output, is part of a higher plan than the key's. The body names the plan that has it (required_plan) and links the plans. |
403 | key_revoked | The key has been revoked. |
404 | not_found | No such category or endpoint. |
409 | watchlist_full | A limit of the Pro plan was reached (watchlist_full, webhook_limit) or the item already exists (webhook_exists). |
413 | too_large | The request body is larger than the limit. |
415 | unsupported_media_type | Send JSON with Content-Type: application/json. |
429 | rate_limited | A limit was reached (the key's requests, a free key's scans a day, one domain, or one network address). The Retry-After header gives the seconds to wait; the body names the limit. |
502 | scan_failed | The scan could not read the site (robots.txt or the homepage), or the key email could not be sent. |
503 | not_configured | The API is not configured on this server, or its storage is unavailable. |
Scope
What it does not do.
The API measures readiness: whether crawlers can reach a site and whether its homepage carries signals a model can read. It does not measure whether any assistant names a brand, and the benchmark is our own archive of scans, dated as shown. It is read-only: it changes nothing on a scanned site. Browser calls from other sites are not allowed (no cross-origin access), so call it from a server.
Questions
Before you build.
What does the score mean?
It is a readiness score out of 100: whether AI crawlers can reach the site and whether its homepage carries signals a model can read. It does not measure whether any assistant names a brand. The same score appears in the web scan and the MCP server.
What does a scan read?
The public robots.txt, llms.txt, sitemap declaration and homepage of the domain you name, through the same guarded fetch as the web scan. One page, not the whole site. The API never sends your key, your address or any credential to the scanned site.
What do you keep about a key?
A one-way hash of the key, a one-way hash of your address, the masked address shown by /v1/me, the plan and the dates. Your address itself is kept only if you tick the update box. Counters for the limits expire within a day. The key email is transactional only.
Can I revoke a key?
Yes. Every key email has a link that revokes that key with one click. A revoked key answers 403 from then on.
Is there a paid plan?
Yes: Pro, Fix and Beyond keys have higher limits, and add the full scan detail, compare, full generated files, monitoring (a watchlist, scheduled re-scans, events, alert emails and webhooks), the fix list and the export, as the table above shows. A key gets its plan from us, and there is no checkout inside the API yet. Write to hey@getcitehound.com to ask.
What does monitoring keep?
For a Pro key: the domains it watches, a snapshot of each scan and the events between scans for 365 days, the address for alerts if you give one, and the addresses of your webhooks (their secrets are stored encrypted and never shown again). Revoking the key deletes all of it.
Questions, or a key that will not arrive: hey@getcitehound.com