diff --git a/docs/api/README.mdx b/docs/api/README.mdx
index 66b7c8f..e5eab21 100644
--- a/docs/api/README.mdx
+++ b/docs/api/README.mdx
@@ -26,6 +26,7 @@ export const SpecLinks = () => {
- New to the API? **[Quickstart](./quickstart)** — mint a key, make your first call, round-trip a resource. ~10 minutes.
- Already know the shape? **[Interactive reference](/api)** — every endpoint, rendered from the OpenAPI spec that ships with each release.
- Generating a client? Point your generator at . The same URLs feed the [API client guide](./postman) (Postman, Insomnia, Bruno, Hoppscotch).
+- Static fallback for Markdown consumers: production specs are `https://app.trakrf.id/api/openapi.json` and `https://app.trakrf.id/api/openapi.yaml`; preview deployments use the matching `app.preview.trakrf.id` host.
## Concept guides
diff --git a/docusaurus.config.ts b/docusaurus.config.ts
index d2ad0b3..8ac6bab 100644
--- a/docusaurus.config.ts
+++ b/docusaurus.config.ts
@@ -62,6 +62,27 @@ const config: Config = {
],
},
],
+ [
+ "docusaurus-plugin-llms",
+ {
+ generateLLMsTxt: true,
+ generateLLMsFullTxt: true,
+ generateMarkdownFiles: true,
+ preserveDirectoryStructure: true,
+ addMdExtension: true,
+ excludeImports: true,
+ removeDuplicateHeadings: true,
+ customLLMFiles: [
+ {
+ filename: "llms-api.txt",
+ includePatterns: ["api/**/*.md", "api/**/*.mdx"],
+ fullContent: true,
+ title: "TrakRF API Documentation",
+ description: "Integrator-focused API reference and conventions",
+ },
+ ],
+ },
+ ],
],
presets: [
diff --git a/package.json b/package.json
index 53a343f..740b74a 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,8 @@
"swizzle": "docusaurus swizzle",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
- "check:contributing": "node scripts/check-contributing.mjs"
+ "check:contributing": "node scripts/check-contributing.mjs",
+ "check:agent-docs": "node scripts/check-agent-docs.mjs"
},
"dependencies": {
"@docusaurus/core": "3.9.2",
@@ -32,6 +33,7 @@
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.9.2",
+ "docusaurus-plugin-llms": "0.6.0",
"prettier": "^3.0.0",
"typescript": "~5.6.2"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7600c8f..4f6f2ef 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -48,6 +48,9 @@ importers:
'@docusaurus/types':
specifier: 3.9.2
version: 3.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ docusaurus-plugin-llms:
+ specifier: 0.6.0
+ version: 0.6.0(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.6.3))
prettier:
specifier: ^3.0.0
version: 3.8.1
@@ -2522,6 +2525,12 @@ packages:
resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==}
engines: {node: '>=6'}
+ docusaurus-plugin-llms@0.6.0:
+ resolution: {integrity: sha512-v9I6QEJLujkmtKjfWZ/SniWu7oOGZd0JUkyxO5MaFU09+qZHjQuraYuJZ+2AoZdesrrcktgDmZtqPL/sJtHe6A==}
+ engines: {node: '>=18.0'}
+ peerDependencies:
+ '@docusaurus/core': ^3.0.0
+
docusaurus-plugin-redoc@2.5.0:
resolution: {integrity: sha512-44sDhuXvItHnUuPdKswF3cRhiN5UW3YZxmMBsQLSfCYKcYr9tgWF2qvDfQoZO9i1DwpaYbIZ/RKMrSgny/iWYA==}
engines: {node: '>=18'}
@@ -3716,6 +3725,10 @@ packages:
resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
engines: {node: '>=10'}
+ minimatch@9.0.3:
+ resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
@@ -5442,6 +5455,11 @@ packages:
resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==}
engines: {node: '>= 6'}
+ yaml@2.8.1:
+ resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -9012,6 +9030,13 @@ snapshots:
dependencies:
'@leichtgewicht/ip-codec': 2.0.5
+ docusaurus-plugin-llms@0.6.0(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.6.3)):
+ dependencies:
+ '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.6.3)
+ gray-matter: 4.0.3
+ minimatch: 9.0.3
+ yaml: 2.8.1
+
docusaurus-plugin-redoc@2.5.0(@docusaurus/utils@3.10.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(core-js@3.48.0)(mobx@6.15.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(styled-components@6.4.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)):
dependencies:
'@docusaurus/utils': 3.10.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -10581,6 +10606,10 @@ snapshots:
dependencies:
brace-expansion: 2.1.0
+ minimatch@9.0.3:
+ dependencies:
+ brace-expansion: 2.1.0
+
minimist@1.2.8: {}
mobx-react-lite@4.1.1(mobx@6.15.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
@@ -12553,6 +12582,8 @@ snapshots:
yaml@1.10.3: {}
+ yaml@2.8.1: {}
+
yargs-parser@21.1.1: {}
yargs@17.7.2:
diff --git a/scripts/check-agent-docs.mjs b/scripts/check-agent-docs.mjs
new file mode 100644
index 0000000..75abcae
--- /dev/null
+++ b/scripts/check-agent-docs.mjs
@@ -0,0 +1,54 @@
+import { readFile } from "node:fs/promises";
+import { resolve } from "node:path";
+
+const buildDir = resolve("build");
+const requiredFiles = ["llms.txt", "llms-full.txt", "llms-api.txt"];
+const markdownFiles = [
+ "docs/api/authentication.md",
+ "docs/api/errors.md",
+ "docs/api/design-notes.md",
+ "docs/api/quickstart.md",
+];
+
+async function readBuildFile(file) {
+ try {
+ return await readFile(resolve(buildDir, file), "utf8");
+ } catch (error) {
+ throw new Error(`Missing build/${file}: ${error.message}`);
+ }
+}
+
+function assert(condition, message) {
+ if (!condition) throw new Error(message);
+}
+
+const files = new Map(
+ await Promise.all(
+ [...requiredFiles, ...markdownFiles].map(async (file) => [
+ file,
+ await readBuildFile(file),
+ ]),
+ ),
+);
+
+const llms = files.get("llms.txt");
+const api = files.get("llms-api.txt");
+
+assert(
+ /\[[^\]]+\]\([^\s)]+\.md\)/.test(llms),
+ "llms.txt does not link to Markdown mirrors",
+);
+assert(api.includes("TrakRF API"), "llms-api.txt does not contain API content");
+assert(
+ !/\/docs\/(?:user-guide|app-tour)\//.test(api),
+ "llms-api.txt includes user-guide or app-tour content",
+);
+
+for (const file of markdownFiles) {
+ assert(
+ !/^\s*
{siteConfig.tagline}
+
+ Coding with an agent? Browse our machine-readable documentation at{" "}
+ /llms.txt.
+