File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -13,6 +13,7 @@ Seneschal lets a receiver get an HMAC-signed webhook the instant a Monero (XMR)
1313- Free UFVK derivation from a seed phrase (rate-limited): POST https://api.seneschal.space/v1/private/derive-viewkey
1414
1515## API discovery
16+ - OpenAPI 3.1 document (all routes, x402 prices per route): https://api.seneschal.space/openapi.json
1617- x402 payment manifest: https://api.seneschal.space/.well-known/x402
1718- Health + table sizes: https://api.seneschal.space/v1/health
1819- MCP server (Streamable HTTP transport): https://mcp.seneschal.space/
Original file line number Diff line number Diff line change @@ -632,6 +632,29 @@ export async function buildApp(options = {}) {
632632 return reply . send ( faviconCache ) ;
633633 } ) ;
634634
635+ // `/llms.txt` — the llmstxt.org convention agent crawlers fetch from
636+ // the API origin (the docs site serves its own copy, but indexers
637+ // that found us via openapi.json look here, not on the docs host).
638+ // Same lazy-load-and-cache pattern as the favicon.
639+ let llmsTxtCache ;
640+ app . get ( '/llms.txt' , async ( req , reply ) => {
641+ if ( llmsTxtCache === undefined ) {
642+ try {
643+ llmsTxtCache = await readFile ( new URL ( '../docs/llms.txt' , import . meta. url ) , 'utf8' ) ;
644+ }
645+ catch {
646+ llmsTxtCache = null ;
647+ }
648+ }
649+ if ( ! llmsTxtCache ) {
650+ reply . code ( 404 ) ;
651+ return { error : { code : 'not_found' , message : 'no llms.txt' } } ;
652+ }
653+ reply . header ( 'content-type' , 'text/plain; charset=utf-8' ) ;
654+ reply . header ( 'cache-control' , 'public, max-age=3600' ) ;
655+ return reply . send ( llmsTxtCache ) ;
656+ } ) ;
657+
635658 // `/.well-known/x402` — opt-in discovery manifest matching the
636659 // emerging convention that x402 service-discovery agents (e.g.
637660 // Agorion, coinbase/x402 #1379) scrape from a provider's apex
Original file line number Diff line number Diff line change @@ -109,6 +109,22 @@ describe('routing & shape', () => {
109109 expect ( r . statusCode ) . toBe ( 404 ) ;
110110 expect ( r . json ( ) . error . code ) . toBe ( 'not_found' ) ;
111111 } ) ;
112+
113+ // Agent crawlers fetch these from the API origin (not the docs
114+ // host); a 404 here loses the listing.
115+ test ( 'GET /llms.txt serves the agent-facing service summary' , async ( ) => {
116+ const r = await call ( 'GET' , '/llms.txt' ) ;
117+ expect ( r . statusCode ) . toBe ( 200 ) ;
118+ expect ( r . headers [ 'content-type' ] ) . toMatch ( / t e x t \/ p l a i n / ) ;
119+ expect ( r . body ) . toMatch ( / S e n e s c h a l / ) ;
120+ expect ( r . body ) . toMatch ( / o p e n a p i \. j s o n / ) ;
121+ } ) ;
122+
123+ test ( 'GET /favicon.ico serves an icon' , async ( ) => {
124+ const r = await call ( 'GET' , '/favicon.ico' ) ;
125+ expect ( r . statusCode ) . toBe ( 200 ) ;
126+ expect ( r . headers [ 'content-type' ] ) . toMatch ( / i m a g e \/ x - i c o n / ) ;
127+ } ) ;
112128} ) ;
113129
114130describe ( '/v1/ops/health (watchdog state surface)' , ( ) => {
You can’t perform that action at this time.
0 commit comments