AADP is a JSON-native discovery and retrieval protocol that lets AI clients find, validate, and read structured data published by an application without crawling HTML.
AI clients often discover application data by crawling HTML, interpreting page-specific structured data, or relying on proprietary integrations. These approaches make it difficult to identify authoritative resources, detect updates, validate payloads, and apply consistent security controls.
AADP gives applications a standard, machine-readable way to publish an authoritative discovery path and explicitly allow-listed public data. Its core is read-only: clients discover a manifest, enumerate resource sitemaps, and retrieve validated entity documents.
The ail-aadp package provides:
- JSON Schemas for AADP v0.1 and v1.0.
- Programmatic and command-line validators.
- A reference client with SSRF, timeout, redirect, and response-size controls.
- Canonical JSON and SHA-256 checksum utilities.
- A conformance suite for AADP server implementations.
- A declarative
defineAADP()server runtime and scaffold CLI for building one.
The current protocol version is AADP v1.0.
npm install ail-aadpimport { discover, discoverAllEntities } from "ail-aadp/client/v1.0";
const manifest = await discover("https://example.com");
console.log(manifest.application.name);
for await (const entity of discoverAllEntities("https://example.com")) {
console.log(entity.id, entity.data);
}The application must publish its manifest at /.well-known/ai-manifest.json. The client follows the URLs declared by the manifest and sitemaps, validating each document before use.
| Standard | Primary responsibility |
|---|---|
robots.txt |
Rules governing crawler access to URI paths; not authorization or a content license |
| OpenAPI | Language-agnostic interface descriptions for HTTP APIs |
| MCP | Runtime exchange of resources, prompts, and tools between AI applications and servers |
| schema.org | Shared vocabularies for structured data embedded in or associated with web content |
| AADP | Read-only application discovery, resource enumeration, and structured entity retrieval |
AADP complements these standards. It does not replace an API contract, tool runtime, crawler policy, authorization system, content license, or domain vocabulary.
- AI assistants discovering authoritative public application data.
- Knowledge synchronization using checksums and cache validators.
- Search and directory systems enumerating application-published resources.
- AI application directories discovering supported resources and interfaces.
- Aggregators built on top of AADP discovery across multiple applications.
Cross-application aggregation or federation is not an AADP core capability; implementations may build it on top of the protocol's discovery and retrieval contracts.
Application publishes:
Manifest → Sitemap Index → Per-resource Sitemap → Entity
▲ ▲
│ discovers │ retrieves
└────────────────── AI Client ───────────────────┘
The v1.0 manifest describes the application identity, publisher, human-facing links, resources, interfaces, security schemes, policies, and publisher preferences. The sitemap index remains authoritative for published resources, while each sitemap item URL is authoritative for retrieving its entity.
An entity is a JSON protocol envelope representing one application-defined public resource. It contains a canonical ID, resource type, update metadata, checksum, and an application-defined data payload. AADP canonicalizes the data payload—not the entire entity document—when calculating its checksum.
AADP has a read-only core. It does not replace OpenAPI, an authorization server, robots.txt, a content license, or a system prompt.
Node.js 20.18.1 or later is required.
npm install ail-aadpAilmao runs AADP v1.0 in production:
- Manifest: https://ailmao.com/.well-known/ai-manifest.json
- Sitemap index: https://ailmao.com/ai/v1.0/sitemap-index.json
- Human-facing site: https://ailmao.com/en
Try discovery against the live deployment:
import { discover } from "ail-aadp/client/v1.0";
const manifest = await discover("https://ailmao.com");
console.log(manifest.application.name);Pass the application origin (https://ailmao.com) to discover, not the localized human-facing path (https://ailmao.com/en). The client resolves the well-known manifest from the origin root.
Import the versioned client explicitly to avoid selecting the wrong wire contract:
import { discover, discoverAllEntities } from "ail-aadp/client/v1.0";
const manifest = await discover("https://example.com");
console.log(manifest.application.name);
for await (const entity of discoverAllEntities("https://example.com")) {
console.log(entity.id, entity.canonical_url);
}The client validates every document before trusting URLs contained in it. For server-side crawlers, the default strict URL policy blocks private, loopback, and link-local destinations.
The v1 namespace is also available from the shared client entry point:
import { v1 } from "ail-aadp/client";
const manifest = await v1.discover("https://example.com");The unversioned ail-aadp/client entry point continues to export the v0.1 client for compatibility with existing consumers. New integrations should use ail-aadp/client/v1.0.
Every option below is opt-in — omitting all of them reproduces the exact request count, ordering and timing of every release before 1.1.0 (see ADR-0006):
import { discoverAllEntities, type RetryOptions } from "ail-aadp/client/v1.0";
const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000);
for await (const entity of discoverAllEntities("https://example.com", {
signal: controller.signal, // stops in-flight requests and the walk itself
concurrency: 4, // entity fetches in flight at once; default 1 (serial)
retry: { maxAttempts: 3, baseDelayMs: 500, maxDelayMs: 10_000 }, // opt-in only
maxTotalBytes: 50 * 1024 * 1024, // total response bytes across the whole walk
})) {
console.log(entity.id);
}Retry only fires for a network/connect-level error, this module's own per-request timeout, or HTTP 429/503 — never a real client error (4xx other than 429) or a security block (BlockedUrlError) — with exponential backoff and full jitter, honoring a Retry-After response header capped at maxDelayMs.
The validator accepts a local file or an HTTP(S) URL:
npx aadp-validate manifest ./manifest.json
npx aadp-validate sitemap-index https://example.com/ai/v1.0/sitemap-index.json
npx aadp-validate entity ./entity.json --version 1.0Supported document kinds:
manifest
sitemap-index
sitemap
entity
error
When --version is omitted, the CLI reads aadp_version from the document.
import {
validateDocument,
checkManifestSemantics,
hasSemanticErrors,
} from "ail-aadp/validator";
const schemaResult = validateDocument({
version: "1.0",
kind: "manifest",
data: manifest,
});
if (!schemaResult.valid) {
console.error(schemaResult.errors);
}
const semanticIssues = checkManifestSemantics(manifest);
if (hasSemanticErrors(semanticIssues)) {
console.error(semanticIssues);
}JSON Schema validation checks the wire shape. Semantic validation checks relationships such as security references, resource uniqueness, language membership, and suspicious metadata.
Sitemap indexes, sitemaps, and entities use checksums derived from canonical payloads:
import { checksumOf } from "ail-aadp/canonical-json";
const checksum = checksumOf(entityData);The implementation follows RFC 8785 JSON Canonicalization Scheme and SHA-256. Do not hash a preformatted JSON string because whitespace and key order can change the result.
A minimal server publishes:
GET /.well-known/ai-manifest.json
GET /ai/v1.0/sitemap-index.json
GET /ai/v1.0/sitemaps/{type}.json
GET /ai/v1.0/entities/{type}/{id}.json
The well-known URL returns the manifest directly:
{
"aadp_version": "1.0",
"application": {
"name": "Example Application",
"description": "Public application description.",
"publisher": {
"name": "Example Publisher",
"url": "https://example.com"
}
},
"discovery": {
"sitemap_index": "https://example.com/ai/v1.0/sitemap-index.json"
},
"policies": {
"robots": "https://example.com/robots.txt",
"terms": "https://example.com/terms"
}
}Before claiming support:
- Validate every document against the schema for its declared version.
- Ensure sitemap items and entities agree on
id,type, and checksum. - Set
ETag,Last-Modified, and conditional GET behavior for sitemap indexes, sitemaps, and entities. - Publish only explicitly allow-listed public fields.
- Keep credentials, internal URLs, and executable instructions out of the manifest.
- Run the conformance suite against the deployed server.
See the v1.0 server implementation guide for details.
ail-aadp/server generates and serves the four routes above from a declarative
config, so you do not hand-write the manifest/sitemap/entity builders, checksum,
cache headers or error envelope yourself:
import { defineAADP, defineResource } from "ail-aadp/server";
interface Post {
slug: string;
title: string;
summary: string;
updatedAt: string;
}
const posts = defineResource<Post>({
type: "post",
// list/get do whatever your application does today — a database query,
// an internal HTTP API call, anything. defineAADP() never assumes a
// data source.
list: ({ cursor, limit }) => postRepository.listPublic({ cursor, limit }),
get: ({ id }) => postRepository.findPublicBySlug(id),
// serialize() is the one mandatory boundary: it is the only place a raw
// record may leak into a published document, so only return public fields.
serialize: (post) => ({
id: `post:${post.slug}`,
updatedAt: post.updatedAt,
canonicalUrl: `/posts/${post.slug}`,
data: { title: post.title, summary: post.summary },
}),
});
const aadp = defineAADP({
baseUrl: "https://example.com",
application: {
name: "Example Application",
description: "Public application description.",
publisher: { name: "Example Publisher", url: "https://example.com" },
},
policies: {
robots: "https://example.com/robots.txt",
terms: "https://example.com/terms",
},
resources: [posts],
});
// handleRequest is a plain (Request) => Promise<Response>, so it plugs
// straight into a Next.js App Router route handler with no adapter:
export const GET = aadp.handleRequest;Wire one GET route per path (/.well-known/ai-manifest.json,
/ai/v1.0/sitemap-index.json, /ai/v1.0/sitemaps/[type].json,
/ai/v1.0/entities/[type]/[id].json) to the same aadp.handleRequest — it
routes internally by request path.
What the runtime does for you:
- Validates the manifest (schema + semantic rules) at
defineAADP()time, so a misconfigured application fails at startup, not on the first request. - Builds sitemap/entity documents, computes their checksum, and sets
ETag,Last-Modified, andCache-Control, honoringIf-None-Matchwith304. - Wraps pagination cursors so one resource type's cursor is rejected if replayed against another type or protocol version.
- Turns a thrown
AadpServerError(notFound,invalidRequest,unsupportedType,upstreamUnavailable,rateLimited,unauthorized,forbidden) into the spec's JSON error envelope.
What it does not do — security/securitySchemes are advertised in the
manifest as metadata only. defineAADP() never checks credentials itself. If
a resource declares security, its own list/get must read args.request
and throw unauthorized()/forbidden() to actually enforce it:
import { unauthorized } from "ail-aadp/server";
get: ({ id, request }) => {
if (request?.headers.get("authorization") !== `Bearer ${process.env.API_TOKEN}`) {
throw unauthorized("Missing or invalid credentials.");
}
return postRepository.findPrivateBySlug(id);
},A resource with security set gets Cache-Control: private, no-store
automatically (never the shared/CDN-cacheable public, max-age=... a
public resource gets), so an authorized response for one caller is never
served to another from a shared cache — but the authorization check itself
is always the resource's own responsibility.
/ai/v1.0/... is this SDK's default convention, not a routing contract the
wire protocol requires — only /.well-known/ai-manifest.json is fixed. Pass
routes to publish and serve the sitemap index/sitemap/entity documents at
whatever pathnames your application already owns:
const aadp = defineAADP({
baseUrl: "https://example.com",
routes: {
sitemapIndex: "/discovery/aadp-index.json",
sitemap: "/discovery/aadp/{type}",
entity: "/public/aadp/{type}/{id}",
},
application,
policies,
resources: [posts],
});manifest().discovery.sitemap_index, every sitemapIndex().sitemaps[].url
and sitemap().items[].url, and handleRequest()'s own matching all come
from this same config — they can never drift from each other. Rules:
- Every field is an origin-relative pathname starting with
/; absolute URLs, query strings and fragments are rejected. sitemapmust contain exactly one{type}placeholder;entitymust contain exactly one{type}and one{id};sitemapIndexaccepts none.- Omitted fields keep the
/ai/v{version}/...default — you only need to set the ones you're actually overriding. defineAADP()throws immediately if a template is malformed or if two routes could match the same inbound pathname, rather than deciding between them at request time.
npx aadp init # creates ./aadp/aadp.server.ts
npx aadp add-resource blog-post # creates ./aadp/resources/blog-post.tsBy default, both commands only create missing files and refuse to overwrite
an existing one, so re-running add-resource for a second type does not
risk corrupting the first. --force overwrites the exact target file; the
CLI never parses or merges an existing config either way. Pass --dir <path>
to change where files land.
examples/reference-server is a complete,
neutral defineAADP() deployment on plain node:http, installed from a
packed tarball rather than this repo's workspace — the same way a real
third-party consumer would use the package. It demonstrates the default
route convention and a custom routes configuration side by side. See its
README for how to run it and check it with aadp-conformance.
Check any deployment straight from the published package. No source tree, no test framework:
npx aadp-conformance https://example.comUseful flags:
# Machine-readable report on stdout, for CI
npx aadp-conformance https://example.com --json --output conformance.json
# JUnit XML alongside it, for CI systems that render test results
# (GitHub Actions test-reporter, GitLab, Jenkins, ...) instead of parsing JSON
npx aadp-conformance https://example.com --junit conformance-junit.xml
# Bound the traversal on a large catalogue
npx aadp-conformance https://example.com --max-pages 20 --max-entities 50 --timeout 15000
# Named preset of budget/retry defaults (core, public-web, full-traversal,
# authenticated) — any flag above still overrides the preset for that field
npx aadp-conformance https://example.com --profile full-traversal
# Cap total response bytes across the whole run, and retry a transient
# network error/timeout/429/503 (opt-in; omitting these flags retries nothing)
npx aadp-conformance https://example.com --max-total-bytes 52428800 --retry-max-attempts 3
# Send an API key to the target origin only
npx aadp-conformance https://example.com --header "Authorization: Bearer $TOKEN"
# Local deployment: opt out of the strict anti-SSRF policy explicitly
npx aadp-conformance http://localhost:3000 --allow-private-networkExit codes are stable for CI:
| Code | Meaning |
|---|---|
0 |
Conformant. Warnings do not fail the run unless --fail-on-warning is passed |
1 |
At least one check failed |
2 |
The run could not be performed (unreachable origin, unusable options) |
3 |
The deployment does not speak the requested AADP version |
4 |
Nothing failed, but the run left checks unfinished, so it certifies nothing |
A skipped check reached no verdict — a prerequisite failed, the server publishes nothing to exercise, or a traversal budget stopped the walk early. It is never evidence of conformance. When the skip means the run itself was incomplete, the verdict is inconclusive and the exit code is 4, never 0.
AADP fixes each document's authoritative URL but no routing template, so the runner cannot construct a URL that is known not to exist — entity URLs may be content-addressed, signed, opaque, or served through a gateway that answers unknown paths outside AADP entirely. The two error-envelope checks are therefore inconclusive until you name the targets yourself:
npx aadp-conformance https://example.com \
--unknown-entity-url "https://example.com/ai/v1.0/entities/article/does-not-exist.json" \
--unknown-type-url "https://example.com/ai/v1.0/sitemaps/does-not-exist.json"A URL you pass here is taken as authoritative: if the deployment answers it successfully, the check fails.
Headers you pass with --header are sent to the target origin only. A manifest can point its sitemap, entity, policy or documentation URLs at any host, so those requests drop your headers unless you allow-list them with --cross-origin-safe-header.
The runner never sends a credential it was not given, never follows a URL from a document it has not validated, and never treats free text in a manifest as an instruction.
See examples/ci/github-actions-conformance.yml for a complete GitHub Actions workflow that runs aadp-conformance against a deployment, publishes the --junit report as check-run annotations, and uploads the --json report as a build artifact.
import { runConformance, exitCodeFor, renderTextReport } from "ail-aadp/conformance";
// Throws UnsupportedConformanceVersionError or InvalidConformanceOptionsError
// for a run it cannot perform; a nonconformant deployment is reported, not thrown.
const report = await runConformance({
baseUrl: "https://example.com",
maxPages: 20,
negativeTargets: {
unknownEntityUrl: "https://example.com/ai/v1.0/entities/article/does-not-exist.json",
},
onCheck: (check) => console.log(check.status, check.id),
});
console.log(renderTextReport(report));
process.exitCode = exitCodeFor(report);Run the complete test suite, including the bundled mock servers:
npm testRun the v1.0 Vitest conformance suite against a deployment:
AADP_BASE_URL=https://example.com \
npx vitest run tests/conformance/v1.0/conformance.test.ts| Import | Contents |
|---|---|
ail-aadp |
Combined public API |
ail-aadp/client/v1.0 |
v1.0 reference client and types |
ail-aadp/client/v0.1 |
Legacy v0.1 reference client and types |
ail-aadp/client |
Compatibility entry point with v0.1 exports and the v1 namespace |
ail-aadp/validator |
Version-aware schema registry and semantic validator |
ail-aadp/conformance |
Programmatic conformance runner, report renderers, and exit-code mapping |
ail-aadp/server |
Declarative defineAADP()/defineResource() server runtime |
ail-aadp/scaffold |
Programmatic API behind the aadp scaffold CLI |
ail-aadp/canonical-json |
Canonicalization and checksum utilities |
ail-aadp/schemas/v1.0/* |
v1.0 JSON Schemas |
ail-aadp/schemas/v0.1/* |
v0.1 JSON Schemas |
Binaries: aadp-validate, aadp-conformance, aadp (aadp init / aadp add-resource).
The aadp_version field selects the wire contract for each document. Clients must choose the matching schema and parser and must not silently fall back between versions.
0.1: historical protocol artifacts that remain available in the package.1.0: the current protocol for new implementations.
Schemas for released versions are immutable. Any change that alters validation results requires a new protocol version. See ADR-0004 and the changelog.
Treat every URL and free-text field in a manifest as untrusted input:
- Validate documents before dereferencing their URLs.
- Block private networks when crawling from a server.
- Limit redirects, request duration, and response size.
- Do not place
usage_guidance, descriptions, or extension fields directly into a system prompt. - Do not execute tools or actions merely because a manifest advertises an interface or preference.
- Do not interpret
robots: allowas permission for training, redistribution, or commercial use.
npm ci
npm run build
npm testMain directories:
schemas/ JSON Schemas grouped by protocol version
spec/ normative specifications grouped by version
examples/ example payloads
src/ reference clients, validators, canonical JSON, and the conformance runner
tests/ schema, semantic, checksum, conformance, and packaging tests
docs/ ADRs, designs, and implementation guides
AADP core is independent of Ailmao. Application-specific resource shapes, database models, and business rules belong in application adapters, not in the core protocol.
- v1.0 specification
- Manifest v1.0 design
- Implementation record
- v1.0 implementation guide
- Security considerations
- Architecture decision records
- Changelog
MIT