API reference
Every signature below was read directly from the installed package's type declarations and compiled source (v0.5.0-era exports — detector, schema, generators, express, next, ContentAnalyzer), not the README — a couple of real, small discrepancies from the README are noted where found.
The v0.6.0–v0.8.2 additions — ContentAnalyzer.audit(), ai-visibility/engines, /prompts, /measure, /citations, and /competitor — each have their own dedicated page rather than being appended here: see AI Readiness Engine, engine adapters, prompt discovery, brand measurement, citation analysis, and competitor gap analysis.
ai-visibility/detector
Zero runtime dependencies. Edge-safe.
detectBot(userAgent: string): BotInfo | null
Framework-agnostic function. Returns the matched bot's info, or null.
AI_CRAWLERS: BotInfo[]
The full list of 23 known crawlers across 13 vendors (as of the 0.8.2 registry audit, which added Meta-WebIndexer and Meta-ExternalFetcher), each with a name, company, User-Agent match pattern, and purpose ('training' | 'search' | 'indexing' | 'unknown'). Full detail, including verification status and sources, is on the crawler registry reference page — the short list:
GPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, Claude-User, Claude-SearchBot,
PerplexityBot, Perplexity-User, Google-Extended, Googlebot, Bingbot, CCBot,
Amazonbot, Amzn-SearchBot, Amzn-User, meta-externalagent, Meta-WebIndexer,
Meta-ExternalFetcher, Applebot-Extended, Bytespider, YouBot, cohere-ai, DiffbotClaude-Web— Anthropic's pre-2024 token — was removed in 0.4.0 after the whole registry was re-verified against vendor documentation instead of third-party SEO-blog lists; it's replaced by the current Claude-User. If you matched on Claude-Web anywhere in your own code, see the migration guide. The package README also abbreviates a few entries as "Cohere", "Meta", and "Apple" — the actual matched User-Agent substrings are cohere-ai, meta-externalagent, and Applebot-Extended, listed above as they actually appear in code.
The same registry is published as plain JSON at dist/crawlers.jsonin every release — generated from the compiled detector at build time, so it can't drift from AI_CRAWLERS. It's not in the package's exports map (so require("ai-visibility/crawlers.json") won't resolve) — it's meant for non-JS consumers to fetch over a CDN mirror at build time, e.g. cdn.jsdelivr.net/npm/ai-visibility@0.4.0/dist/crawlers.json, and vendor the result. See the crawler registry reference for the full list and the fetch-and-vendor pattern.
getUnverifiedBots(): BotInfo[]
Added in 0.4.0. Returns every crawler entry that isn't verified: true— checked directly against the installed package's runtime behavior, this is currently 4 entries: Bytespider (explicitly verified: false — no official ByteDance documentation exists at all) plus YouBot, cohere-ai, and Diffbot (no verifiedfield at all — they predate the 0.4.0 audit and haven't been re-checked yet). The package's own 0.4.0 changelog says this returns "currently just Bytespider", which undercounts it — the package README states the correct figure (4), and it matches what the installed code actually returns.
class AIBotDetector
new AIBotDetector(config?: { additionalBots?: string[]; ignoreBots?: string[] })
.detect(userAgent: string): BotInfo | null
.getBotNames(): string[]class HTMLOptimizer
Strips non-essential content from HTML for AI crawler consumption — keeps semantic structure, JSON-LD, and meaningful text.
new HTMLOptimizer(options?: AIOptimizationOptions)
.optimize(html: string): string
interface AIOptimizationOptions {
stripJs?: boolean; // remove <script> tags except JSON-LD
removeAds?: boolean;
removeTracking?: boolean;
simplifyNav?: boolean;
structureContent?: boolean; // front-load main content
}ai-visibility/schema
Dependency-free except fromHTML(), which lazily loads cheerioon first call (dynamic import — importing this subpath doesn't pull cheerio in unless you actually call it). All builders except offer()/aggregateRating() return a full JSON-LD object with @context; those two return a nested node meant to be embedded via product()'s or softwareApplication()'s offers/aggregateRating fields. As of 0.4.0, this subpath re-exports every parameter type used above (ProductSchemaData, ArticleSchemaData, etc.) — before 0.4.0 only the classes/functions were re-exported, so importing a type from ai-visibility/schema required reaching into the root barrel.
class SchemaBuilder {
static faq(items: { q: string; a: string }[]): SchemaObject
static product(data: ProductSchemaData): SchemaObject
static article(data: ArticleSchemaData): SchemaObject
static organization(data: OrganizationSchemaData): SchemaObject
static person(data: PersonSchemaData): SchemaObject
static website(data: WebSiteSchemaData): SchemaObject
static softwareApplication(data: SoftwareApplicationSchemaData): SchemaObject
static breadcrumbList(items: BreadcrumbItem[], options?: { baseUrl?: string }): SchemaObject
static definedTerm(data: DefinedTermSchemaData): SchemaObject
static definedTermSet(data: DefinedTermSetSchemaData): SchemaObject
static offer(data: OfferSchemaData): SchemaObject // nested, no @context
static aggregateRating(data: AggregateRatingSchemaData): SchemaObject // nested, no @context
static fromHTML(html: string, hints?: { author?: string; publisher?: string }): Promise<SchemaObject>
static toScriptTag(schema: SchemaObject): string
static toScriptTagMultiple(schemas: SchemaObject[]): string
}toScriptTag()/toScriptTagMultiple() return a full <script>…</script>string for raw-template injection — it doesn't fit React's dangerouslySetInnerHTML, which expects the tag's contents, not the tag itself. In React, use the plain object the builders return:
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(SchemaBuilder.faq([{ q: "...", a: "..." }]))
}}
/>ai-visibility/generators
class RobotsGenerator {
constructor(config?: RobotsConfig)
generate(): string
static allowAll(options?: { disallow?: string[]; sitemapUrl?: string }): string
static blockTraining(options?: { disallow?: string[]; sitemapUrl?: string }): string
static blockAll(options?: { disallow?: string[]; sitemapUrl?: string }): string // new in 0.5.0
}
interface RobotsConfig {
allowAI?: string[];
blockAI?: string[];
disallow?: string[]; // default: [] as of 0.3.0 — see note below
sitemapUrl?: string;
crawlDelay?: number;
}Since 0.4.0, this subpath also re-exports RobotsConfig and LLMSConfig directly — no need to import config types from the root barrel. blockAll(), new in 0.5.0, is a control mechanism for blocking every known AI crawler, not a recommendation either way — the CLI's robots --preset block-all maps to it. See the CLI reference.
0.3.0 changed the default disallow list from ['/admin', '/api', '/private', '/_next', '/static'] to []— the old default meant every Next.js site using it was telling AI crawlers not to fetch the site's own JS/CSS chunks. Confirmed directly in the compiled source while migrating this site in Phase 3. If you're upgrading from 0.2.x and relied on the default, pass your own disallow list explicitly now.
class LLMSTextGenerator {
constructor(config: LLMSConfig)
generate(): Promise<string>
static minimal(config: LLMSConfig): string // no summaries, synchronous
}
interface LLMSConfig {
siteName: string;
description: string;
baseUrl?: string;
pages: { url: string; title: string; summary?: string; priority?: "high" | "medium" | "low" }[];
contact?: { email?: string; twitter?: string; github?: string };
autoSummarize?: boolean; // fetches each page live to extract a summary if one isn't given
}ai-visibility/express
Node-only. Imports types from express (optional peer dependency).
createAIMiddleware(config?: AIMiddlewareConfig): (req, res, next) => void
optimizeResponseForAI(options?: AIOptimizationOptions): (req, res, next) => void
class AIVisitorLogger {
constructor(config?: LoggerConfig)
middleware(): (req, res, next) => void
log(entry: CrawlerLog): void // manual logging, non-Express frameworks
getLogs(filter?: { botName?: string; days?: number; url?: string }): CrawlerLog[]
getStats(days?: number): Record<string, BotStatsSerialized>
clearLogs(): void
}
interface LoggerConfig {
storage?: "file" | "memory" | "both"; // default storage strategy
logFilePath?: string; // default: ./logs/ai-crawler.json
trackCrawlers?: string[];
maxMemoryEntries?: number;
}AIVisitorLogger's file/memorystorage doesn't persist across invocations on serverless platforms like Vercel — see /built-withfor how this site logs crawler visits instead (its own Postgres table, not this class's storage). Since 0.4.0, this subpath also re-exports AIMiddlewareConfig and AIOptimizationOptions directly.
ai-visibility/next
Edge-safe. Imports types from next/server (optional peer dependency).
createNextMiddleware(options?: NextMiddlewareOptions):
(req: NextRequest, event?: NextFetchEvent) => NextResponse
interface NextMiddlewareOptions {
additionalBots?: string[];
ignoreBots?: string[];
headerName?: string; // response header marking a detected bot request, default 'x-ai-crawler'
rewrite?: string | ((bot: BotInfo, req: NextRequest) => string);
onDetect?: (bot: BotInfo, req: NextRequest) => void | Promise<void>;
}
// re-exported from ai-visibility/detector, its real home since 0.3.1:
detectAndOptimize(html: string, userAgent: string, options?: DetectAndOptimizeOptions):
{ isBot: boolean; botName: string | null; html: string }detectAndOptimize() is framework-agnostic — HTML string and User-Agent string in, transformed HTML out, no request/response objects needed. Through 0.3.0 it was importable only from this subpath, whose module scope statically imports next/server — so merely importing it in a Nuxt, Vue, or plain Node project crashed with Cannot find module 'next/server', even though the function itself never touches Next.js. Fixed in 0.3.1 by moving it to ai-visibility/detector (the zero-dependency module, its real home); this subpath still re-exports it, so existing ai-visibility/next imports keep working unchanged.
Through 0.3.x, onDetect was not awaited internally, and the returned middleware only accepted a reqparameter — no access to Next's waitUntil — so an async onDetect (a database write, say) could be torn down mid-flight the instant the response was sent. Fixed in 0.4.0: onDetect may now return a Promise<void>, the middleware accepts the NextFetchEvent Next.js always passes as its second argument, and when both are present the promise is registered with event.waitUntil() automatically. This site still calls detectBot() from ai-visibility/detector directly rather than createNextMiddleware() — not because the built-in hook is unsafe anymore, but because its own crawler-log write needs direct control over event.waitUntil() alongside other proxy logic. See the App Router recipe.
ContentAnalyzer
Exported from the root barrel. Scores HTML for AI readability.
class ContentAnalyzer {
static readonly SCORING_WEIGHTS: ScoringDimension[] // new in 0.5.0, sums to 1.0
constructor(options?: AnalyzerOptions)
analyze(html: string, context?: AnalysisContext): Promise<AIReadabilityScore>
}
interface AnalysisContext { // new in 0.5.0 — optional, backward compatible
robotsTxt?: string;
hasLlmsTxt?: boolean;
}
interface AIReadabilityScore {
overallScore: number; // 0-100
breakdown: {
answerFrontLoading: number;
factDensity: number;
headingStructure: number;
eeatSignals: number;
snippability: number;
schemaCoverage: number;
crawlerAccessibility: number; // new in 0.5.0
};
issues: { type: string; severity: "high" | "medium" | "low"; message: string; fix: string }[];
recommendations: string[];
}context is optional and additive — omitting it keeps every pre-0.5.0 call site working unchanged, it just means crawlerAccessibility can only check the page's own <meta name="robots"> tag instead of also parsing robotsTxt and checking hasLlmsTxt. The CLI's audit/lint supply this context automatically. Full rationale for the weights (and SCORING_WEIGHTS' published-JSON twin, dist/scoring-weights.json) is on the scoring methodology page, not repeated here.
AIVisitorLogger & Dashboard
Dashboard is exported from the root barrel (not ai-visibility/express — it only renders data you hand it, so it has no Express dependency itself):
class Dashboard {
constructor()
getHtml(): string
formatData(stats, logs): DashboardData
render(stats, logs, options?: { autoRefresh?: boolean; refreshInterval?: number }): string
getPath(): string
}
createDashboard(): DashboardCLI
0.5.0 added audit/lint (scoring a live URL or local directory, with an opt-in --fail-under CI gate) and top-level robots/llms aliases, alongside the pre-existing init, analyze, generate, and logs commands. Full command-by-command detail, verified by running --help and the actual binary against a live URL and a local directory, now lives on its own page: the CLI reference — not duplicated here to avoid the two drifting apart.