From ccad2c801652b4351610f818e2ccd0b5e86bb5fb Mon Sep 17 00:00:00 2001
From: Will
Date: Fri, 21 Aug 2026 20:47:45 +0000
Subject: [PATCH 1/3] Add machine-readable discovery surfaces for AI agents
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The site already served llms.txt, per-page markdown mirrors, canonicals, a
sitemap and an RSS feed, but an agent arriving cold had no way to find the
security contact, the HTTP APIs behind the product, or the MCP server, and
nothing it fetched was readable cross-origin.
- /.well-known/security.txt (RFC 9116), /.well-known/api-catalog (RFC 9727,
linking both published OpenAPI specs to their docs pages), and
/.well-known/mcp.json describing the stdio `agent-relay mcp` server. The App
Router skips dot-prefixed directories, so these live under app/well-known/
and next.config rewrites the canonical paths onto them; the internal path
carries X-Robots-Tag: noindex and a robots.txt disallow.
- CORS (Access-Control-Allow-Origin: *) on every machine-readable endpoint —
llms.txt, the markdown mirrors, feed.xml, sitemap.xml, robots.txt and the
well-known documents — plus X-Content-Type-Options: nosniff site-wide.
- Site-wide Organization / WebSite / SoftwareApplication JSON-LD in the root
layout, cross-referenced by @id so any single page resolves its publisher.
- for llms.txt, llms-full.txt and the feed on every
page, so the plain-text mirrors are discoverable from any entry point.
- robots.txt names the AI crawlers and agent fetchers that read their own
user-agent (Perplexity, Google-Extended, Applebot, Meta, Amazon, CCBot,
Mistral, Cohere and others) alongside the OpenAI and Anthropic entries.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_01VV1b4tXaTcM2EqLLwKpL4u
---
web/app/layout.tsx | 70 ++++++++++-
web/app/robots.ts | 50 +++++---
web/app/well-known/api-catalog/route.ts | 14 +++
web/app/well-known/mcp.json/route.ts | 14 +++
web/app/well-known/security.txt/route.ts | 18 +++
web/lib/agent-discovery.ts | 143 +++++++++++++++++++++++
web/lib/test/agent-discovery.test.ts | 45 +++++++
web/next.config.mjs | 48 ++++++++
8 files changed, 388 insertions(+), 14 deletions(-)
create mode 100644 web/app/well-known/api-catalog/route.ts
create mode 100644 web/app/well-known/mcp.json/route.ts
create mode 100644 web/app/well-known/security.txt/route.ts
create mode 100644 web/lib/agent-discovery.ts
create mode 100644 web/lib/test/agent-discovery.test.ts
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
index 1d8b277..0dd8e93 100644
--- a/web/app/layout.tsx
+++ b/web/app/layout.tsx
@@ -4,7 +4,7 @@ import { Geist_Mono, Inter, Sora } from 'next/font/google';
import type { ReactNode } from 'react';
import { defaultOgImage } from '../lib/og-meta';
-import { POSTHOG_HOST, SITE_URL } from '../lib/site';
+import { absoluteUrl, POSTHOG_HOST, SITE_EMAIL, SITE_NAME, SITE_URL } from '../lib/site';
import { WebsitePostHogPageView } from './PostHogPageView';
import './globals.css';
@@ -62,6 +62,53 @@ export const metadata: Metadata = {
},
};
+// Site-wide structured data. Individual pages add their own Article/Service/
+// CollectionPage nodes and reference these two by @id, so an agent reading any
+// single page still resolves the publisher and the site root.
+const siteStructuredData = {
+ '@context': 'https://schema.org',
+ '@graph': [
+ {
+ '@type': 'Organization',
+ '@id': `${SITE_URL}/#organization`,
+ name: SITE_NAME,
+ url: SITE_URL,
+ email: SITE_EMAIL,
+ logo: {
+ '@type': 'ImageObject',
+ url: absoluteUrl('/agent-relay-logo-white.svg'),
+ },
+ description:
+ 'Agent Relay is the messaging layer for AI agents: channels, DMs, durable delivery, event listeners, and typed actions for any agent runtime.',
+ sameAs: [
+ 'https://github.com/agentworkforce/relay',
+ 'https://twitter.com/agent_relay',
+ 'https://discord.gg/RJGE7CHV',
+ ],
+ },
+ {
+ '@type': 'WebSite',
+ '@id': `${SITE_URL}/#website`,
+ name: SITE_NAME,
+ url: SITE_URL,
+ inLanguage: 'en-US',
+ publisher: { '@id': `${SITE_URL}/#organization` },
+ },
+ {
+ '@type': 'SoftwareApplication',
+ '@id': `${SITE_URL}/#software`,
+ name: SITE_NAME,
+ url: SITE_URL,
+ applicationCategory: 'DeveloperApplication',
+ operatingSystem: 'Any',
+ publisher: { '@id': `${SITE_URL}/#organization` },
+ description:
+ 'Add channels, DMs, durable delivery, event listeners, and Zod-typed actions to any agent runtime.',
+ softwareHelp: { '@type': 'CreativeWork', url: absoluteUrl('/docs') },
+ },
+ ],
+};
+
export default function RootLayout({ children }: { children: ReactNode }) {
const postHogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
const content = postHogKey ? (
@@ -83,7 +130,28 @@ export default function RootLayout({ children }: { children: ReactNode }) {
return (
+
+ {/* Machine-readable mirrors of this site, advertised on every page so an
+ agent that lands anywhere can find the plain-text and feed formats. */}
+
+
+
+
+
{content}
diff --git a/web/app/robots.ts b/web/app/robots.ts
index 2fde2af..b525930 100644
--- a/web/app/robots.ts
+++ b/web/app/robots.ts
@@ -2,13 +2,44 @@ import type { MetadataRoute } from 'next';
import { absoluteUrl, SITE_URL } from '../lib/site';
+// Named allow rules for the crawlers and agent fetchers that read their own
+// user-agent out of robots.txt. Everything here is already covered by the
+// permissive `*` rule below; the explicit entries exist because several of
+// these bots treat "no rule for me" more conservatively than an explicit
+// Allow, and because a scanner reading robots.txt can only see the agents that
+// are named.
const AI_CRAWLERS = [
+ // OpenAI
+ 'GPTBot',
'OAI-SearchBot',
'ChatGPT-User',
- 'GPTBot',
+ // Anthropic
+ 'ClaudeBot',
'Claude-SearchBot',
'Claude-User',
- 'ClaudeBot',
+ 'anthropic-ai',
+ // Google
+ 'Google-Extended',
+ 'GoogleOther',
+ // Perplexity
+ 'PerplexityBot',
+ 'Perplexity-User',
+ // Apple
+ 'Applebot',
+ 'Applebot-Extended',
+ // Meta
+ 'meta-externalagent',
+ 'Meta-ExternalFetcher',
+ // Microsoft / Bing
+ 'bingbot',
+ // Others
+ 'Amazonbot',
+ 'Bytespider',
+ 'CCBot',
+ 'cohere-ai',
+ 'DuckAssistBot',
+ 'MistralAI-User',
+ 'YouBot',
];
export default function robots(): MetadataRoute.Robots {
@@ -20,17 +51,10 @@ export default function robots(): MetadataRoute.Robots {
})),
{
userAgent: '*',
- allow: [
- '/',
- '/skill',
- '/skill.md',
- '/openclaw',
- '/openclaw/skill',
- '/agents',
- '/agents/',
- '/docs/',
- '/blog/',
- ],
+ allow: ['/'],
+ // Internal target of the /.well-known rewrite; the dot-prefixed paths
+ // are the canonical ones.
+ disallow: ['/well-known/'],
},
],
sitemap: absoluteUrl('/sitemap.xml'),
diff --git a/web/app/well-known/api-catalog/route.ts b/web/app/well-known/api-catalog/route.ts
new file mode 100644
index 0000000..e199599
--- /dev/null
+++ b/web/app/well-known/api-catalog/route.ts
@@ -0,0 +1,14 @@
+import { getApiCatalog } from '../../../lib/agent-discovery';
+
+// RFC 9727 API catalog, served at /.well-known/api-catalog via the rewrite in
+// next.config.mjs.
+export const revalidate = 86400;
+
+export function GET() {
+ return new Response(JSON.stringify(getApiCatalog(), null, 2), {
+ headers: {
+ 'Content-Type': 'application/linkset+json',
+ 'Cache-Control': 'public, max-age=86400',
+ },
+ });
+}
diff --git a/web/app/well-known/mcp.json/route.ts b/web/app/well-known/mcp.json/route.ts
new file mode 100644
index 0000000..57bfa7a
--- /dev/null
+++ b/web/app/well-known/mcp.json/route.ts
@@ -0,0 +1,14 @@
+import { getMcpManifest } from '../../../lib/agent-discovery';
+
+// MCP server manifest, served at /.well-known/mcp.json via the rewrite in
+// next.config.mjs.
+export const revalidate = 86400;
+
+export function GET() {
+ return new Response(JSON.stringify(getMcpManifest(), null, 2), {
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'Cache-Control': 'public, max-age=86400',
+ },
+ });
+}
diff --git a/web/app/well-known/security.txt/route.ts b/web/app/well-known/security.txt/route.ts
new file mode 100644
index 0000000..5ded7df
--- /dev/null
+++ b/web/app/well-known/security.txt/route.ts
@@ -0,0 +1,18 @@
+import { getSecurityTxt } from '../../../lib/agent-discovery';
+
+// Served at /.well-known/security.txt via the rewrite in next.config.mjs — the
+// App Router skips dot-prefixed directories, so the route lives at
+// /well-known/ and the canonical path rewrites onto it.
+//
+// ISR rather than force-static so the Expires field rolls forward instead of
+// freezing at build time.
+export const revalidate = 86400;
+
+export function GET() {
+ return new Response(getSecurityTxt(new Date()), {
+ headers: {
+ 'Content-Type': 'text/plain; charset=utf-8',
+ 'Cache-Control': 'public, max-age=86400',
+ },
+ });
+}
diff --git a/web/lib/agent-discovery.ts b/web/lib/agent-discovery.ts
new file mode 100644
index 0000000..11c6baf
--- /dev/null
+++ b/web/lib/agent-discovery.ts
@@ -0,0 +1,143 @@
+// Machine-readable discovery documents served under /.well-known/.
+//
+// Agents that land on agentrelay.com without prior knowledge use these to find
+// the security contact, the HTTP APIs behind the product, and the MCP server
+// that exposes Relay as tools. Everything here points at surfaces that already
+// exist — the docs pages and the published OpenAPI specs — so the manifests
+// stay true without a separate source of truth to keep in sync.
+
+import { SITE_EMAIL, SITE_URL, absoluteUrl } from './site';
+
+const RELAYCAST_OPENAPI_URL =
+ 'https://raw.githubusercontent.com/AgentWorkforce/relaycast/main/openapi.yaml';
+const RELAYFILE_OPENAPI_URL =
+ 'https://raw.githubusercontent.com/AgentWorkforce/relayfile/main/openapi/relayfile-v1.openapi.yaml';
+
+/** RFC 9116 requires an absolute, machine-parseable expiry no more than a year out. */
+export function securityTxtExpiry(now: Date): string {
+ const expires = new Date(now);
+ expires.setUTCFullYear(expires.getUTCFullYear() + 1);
+ expires.setUTCMilliseconds(0);
+ return expires.toISOString().replace('.000Z', 'Z');
+}
+
+/** RFC 9116 security.txt. */
+export function getSecurityTxt(now: Date): string {
+ return [
+ '# Agent Relay security contact (RFC 9116).',
+ '# Report a vulnerability by email; we acknowledge within two business days.',
+ '',
+ `Contact: mailto:${SITE_EMAIL}`,
+ `Expires: ${securityTxtExpiry(now)}`,
+ 'Preferred-Languages: en',
+ `Canonical: ${absoluteUrl('/.well-known/security.txt')}`,
+ `Policy: ${absoluteUrl('/terms')}`,
+ '',
+ ].join('\n');
+}
+
+type LinksetLink = { href: string; type: string; title: string };
+
+type LinksetEntry = {
+ anchor: string;
+ 'service-desc'?: LinksetLink[];
+ 'service-doc'?: LinksetLink[];
+ 'service-meta'?: LinksetLink[];
+};
+
+/**
+ * RFC 9727 API catalog: one linkset entry per HTTP API, each pointing at its
+ * OpenAPI description (`service-desc`) and its human documentation
+ * (`service-doc`).
+ */
+export function getApiCatalog(): { linkset: LinksetEntry[] } {
+ return {
+ linkset: [
+ {
+ anchor: 'https://api.agentrelay.com/v1',
+ 'service-desc': [
+ {
+ href: RELAYCAST_OPENAPI_URL,
+ type: 'application/yaml',
+ title: 'Relaycast v1 OpenAPI 3.0 description',
+ },
+ ],
+ 'service-doc': [
+ {
+ href: absoluteUrl('/docs/relaycast-api'),
+ type: 'text/html',
+ title: 'Relaycast API reference',
+ },
+ {
+ href: absoluteUrl('/docs/markdown/relaycast-api.md'),
+ type: 'text/markdown',
+ title: 'Relaycast API reference (markdown)',
+ },
+ ],
+ 'service-meta': [
+ {
+ href: absoluteUrl('/docs/authentication'),
+ type: 'text/html',
+ title: 'Authentication: token types and scopes',
+ },
+ ],
+ },
+ {
+ anchor: 'https://api.agentrelay.com/relayfile/v1',
+ 'service-desc': [
+ {
+ href: RELAYFILE_OPENAPI_URL,
+ type: 'application/yaml',
+ title: 'Relayfile v1 OpenAPI description',
+ },
+ ],
+ 'service-doc': [
+ {
+ href: absoluteUrl('/docs/file/api-reference'),
+ type: 'text/html',
+ title: 'Relayfile API reference',
+ },
+ ],
+ },
+ ],
+ };
+}
+
+/**
+ * MCP server manifest. `agent-relay mcp` is a stdio server shipped in the CLI,
+ * so the manifest describes how to launch it rather than a remote URL.
+ */
+export function getMcpManifest() {
+ return {
+ name: 'agent-relay',
+ description:
+ 'Agent Relay messaging as MCP tools: channels, DMs, threads, reactions, inbox, and Zod-backed actions.',
+ documentation: absoluteUrl('/docs/agent-relay-mcp'),
+ website: SITE_URL,
+ servers: [
+ {
+ name: 'agent-relay',
+ transport: 'stdio',
+ command: 'agent-relay',
+ args: ['mcp'],
+ install: {
+ registry: 'npm',
+ package: 'agent-relay',
+ command: 'npm install -g agent-relay',
+ },
+ env: [
+ {
+ name: 'RELAY_WORKSPACE_KEY',
+ description: 'Workspace join secret (rk_live_*). Create one with `agent-relay workspace create`.',
+ required: true,
+ },
+ {
+ name: 'RELAY_BASE_URL',
+ description: 'API base URL override, for self-hosted @relaycast/engine deployments.',
+ required: false,
+ },
+ ],
+ },
+ ],
+ };
+}
diff --git a/web/lib/test/agent-discovery.test.ts b/web/lib/test/agent-discovery.test.ts
new file mode 100644
index 0000000..f06a3e6
--- /dev/null
+++ b/web/lib/test/agent-discovery.test.ts
@@ -0,0 +1,45 @@
+import { describe, expect, it } from 'vitest';
+
+import { getApiCatalog, getMcpManifest, getSecurityTxt, securityTxtExpiry } from '../agent-discovery';
+
+describe('security.txt', () => {
+ it('carries the RFC 9116 required fields', () => {
+ const txt = getSecurityTxt(new Date('2026-08-21T00:00:00Z'));
+
+ expect(txt).toContain('Contact: mailto:hello@agentrelay.com');
+ expect(txt).toContain('Expires: 2027-08-21T00:00:00Z');
+ expect(txt).toContain('Canonical: https://agentrelay.com/.well-known/security.txt');
+ expect(txt).toContain('Preferred-Languages: en');
+ });
+
+ it('always expires in the future', () => {
+ const now = new Date('2026-08-21T00:00:00Z');
+ expect(Date.parse(securityTxtExpiry(now))).toBeGreaterThan(now.getTime());
+ });
+});
+
+describe('api-catalog', () => {
+ it('links each API to an OpenAPI description and human docs', () => {
+ const { linkset } = getApiCatalog();
+
+ expect(linkset.length).toBeGreaterThan(0);
+ for (const entry of linkset) {
+ expect(entry.anchor).toMatch(/^https:\/\//);
+ expect(entry['service-desc']?.[0]?.href).toMatch(/^https:\/\//);
+ expect(entry['service-doc']?.[0]?.href).toMatch(/^https:\/\/agentrelay\.com\//);
+ }
+ });
+});
+
+describe('mcp.json', () => {
+ it('describes the stdio server well enough to launch it', () => {
+ const manifest = getMcpManifest();
+ const [server] = manifest.servers;
+
+ expect(manifest.documentation).toBe('https://agentrelay.com/docs/agent-relay-mcp');
+ expect(server.transport).toBe('stdio');
+ expect(server.command).toBe('agent-relay');
+ expect(server.args).toEqual(['mcp']);
+ expect(server.env.find((e) => e.name === 'RELAY_WORKSPACE_KEY')?.required).toBe(true);
+ });
+});
diff --git a/web/next.config.mjs b/web/next.config.mjs
index 08fe1c1..fdf4431 100644
--- a/web/next.config.mjs
+++ b/web/next.config.mjs
@@ -31,11 +31,59 @@ const nextConfig = {
return config;
},
+ async headers() {
+ // Machine-readable surfaces an agent may fetch from another origin (or from
+ // a browser-based agent). Without CORS these are unreadable to anything that
+ // isn't a server-side crawler.
+ const agentReadable = [
+ '/llms.txt',
+ '/llms-full.txt',
+ '/llm.txt',
+ '/agents.md',
+ '/skill.md',
+ '/feed.xml',
+ '/sitemap.xml',
+ '/robots.txt',
+ '/docs/llms.txt',
+ '/docs/markdown.md',
+ '/docs/markdown/:path*',
+ '/docs/agents/markdown/:path*',
+ '/docs/factory/markdown/:path*',
+ '/docs/file/markdown/:path*',
+ '/docs/loop/markdown/:path*',
+ '/docs/:slug([^/]+\\.md)',
+ '/.well-known/:path*',
+ ];
+
+ return [
+ {
+ source: '/:path*',
+ headers: [{ key: 'X-Content-Type-Options', value: 'nosniff' }],
+ },
+ ...agentReadable.map((source) => ({
+ source,
+ headers: [
+ { key: 'Access-Control-Allow-Origin', value: '*' },
+ { key: 'Access-Control-Allow-Methods', value: 'GET, HEAD' },
+ ],
+ })),
+ {
+ // The internal path the /.well-known rewrite targets; keep it out of
+ // indexes so the canonical dot-prefixed URL is the only one advertised.
+ source: '/well-known/:path*',
+ headers: [{ key: 'X-Robots-Tag', value: 'noindex' }],
+ },
+ ];
+ },
async rewrites() {
return {
afterFiles: [
// Conventional llms.txt path under /docs resolves to the root route.
{ source: '/docs/llms.txt', destination: '/llms.txt' },
+ // The App Router skips dot-prefixed directories, so the /.well-known
+ // documents are implemented under app/well-known/ and surfaced here at
+ // their canonical paths.
+ { source: '/.well-known/:path*', destination: '/well-known/:path*' },
// Append .md to any docs URL to get its markdown mirror. afterFiles
// runs after static routes (so /docs/markdown.md is untouched) but
// before the /docs/[slug] dynamic page.
From 77f74cd74e645e607c925262a320ebf59d518726 Mon Sep 17 00:00:00 2001
From: Will
Date: Sat, 22 Aug 2026 13:00:45 +0000
Subject: [PATCH 2/3] Point API references at hosts that actually exist
api.agentrelay.com does not exist. It was in the api-catalog anchors added by
the previous commit, and in two marketing snippets that predate it.
Replaced with the hosts the v8 docs name as authoritative:
- Relaycast -> cast.agentrelay.com/v1, per the base-URL table in
content/docs/relaycast-api.mdx. content/docs/loop/sources.mdx still pointed
at api.relaycast.dev, which only survives in the archived 7.1.1 docs and on
the standalone /message page.
- Relayfile -> file.agentrelay.com/v1, the domain content/docs/file/cloud.mdx
names for the hosted data plane. The SDK, Python SDK and CLI pages were
sending users to api.relayfile.dev; the /file page also carried a bogus
/relayfile/v1 path prefix, where the API reference documents /v1/workspaces.
The homepage webhook snippet posted to a POST /v1/webhooks endpoint that does
not exist, with a {channel,text} body and no auth. Inbound webhook URLs are
minted per webhook by relay.webhooks.createInbound(), so the snippet now shows
the opaque URL as an environment variable with the documented {message,author}
body and bearer token.
The api-catalog test now pins both anchors so a fabricated host fails the build
rather than shipping.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_01VV1b4tXaTcM2EqLLwKpL4u
---
web/app/file/RelayfileContent.tsx | 6 +++---
web/components/home/ContextCapabilities.tsx | 9 ++++++---
web/content/docs/file/cli.mdx | 4 ++--
web/content/docs/file/introduction.mdx | 2 +-
web/content/docs/file/python-sdk.mdx | 4 ++--
web/content/docs/loop/sources.mdx | 2 +-
web/lib/agent-discovery.ts | 4 ++--
web/lib/test/agent-discovery.test.ts | 6 +++++-
8 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/web/app/file/RelayfileContent.tsx b/web/app/file/RelayfileContent.tsx
index 724c3a4..3fc520f 100644
--- a/web/app/file/RelayfileContent.tsx
+++ b/web/app/file/RelayfileContent.tsx
@@ -162,13 +162,13 @@ await run(agent, 'Review /digests/yesterday.md and file Linear follow-ups.');`,
curl: {
label: 'cURL',
language: 'curl',
- code: `curl "https://api.agentrelay.com/relayfile/v1/workspaces/rw_123/fs/tree?path=/" \\
+ code: `curl "https://file.agentrelay.com/v1/workspaces/rw_123/fs/tree?path=/" \\
-H "authorization: Bearer $RELAYFILE_TOKEN"
-curl "https://api.agentrelay.com/relayfile/v1/workspaces/rw_123/fs/file?path=/digests/yesterday.md" \\
+curl "https://file.agentrelay.com/v1/workspaces/rw_123/fs/file?path=/digests/yesterday.md" \\
-H "authorization: Bearer $RELAYFILE_TOKEN"
-curl -X PUT "https://api.agentrelay.com/relayfile/v1/workspaces/rw_123/fs/file?path=/linear/issues/AGE-12.json" \\
+curl -X PUT "https://file.agentrelay.com/v1/workspaces/rw_123/fs/file?path=/linear/issues/AGE-12.json" \\
-H "authorization: Bearer $RELAYFILE_TOKEN" \\
-H "content-type: application/json" \\
-d '{"contentType":"application/json","content":"{\\"state\\":\\"In Review\\"}"}'`,
diff --git a/web/components/home/ContextCapabilities.tsx b/web/components/home/ContextCapabilities.tsx
index 46379e1..6178071 100644
--- a/web/components/home/ContextCapabilities.tsx
+++ b/web/components/home/ContextCapabilities.tsx
@@ -4,10 +4,13 @@ import { SearchPreviewAnimation } from '../../app/SearchPreviewAnimation';
import s from '../../app/landing.module.css';
import { WaveDivider } from './icons';
-const WEBHOOK_SNIPPET = `curl -X POST \\
- https://api.agentrelay.com/v1/webhooks \\
+// relay.webhooks.createInbound() mints the URL and token per webhook, so there
+// is no fixed path to show here — the payload shape and bearer auth are what
+// the snippet is illustrating. See content/docs/webhooks.mdx.
+const WEBHOOK_SNIPPET = `curl -X POST "$RELAY_INBOUND_WEBHOOK_URL" \\
+ -H "Authorization: Bearer $RELAY_WEBHOOK_TOKEN" \\
-H "Content-Type: application/json" \\
- -d '{"channel":"#alerts","text":"Deploy finished"}'`;
+ -d '{"message":"Deploy finished","author":"github-actions[bot]"}'`;
/**
* The "build the right context" band: real-time events, webhooks, and search.
diff --git a/web/content/docs/file/cli.mdx b/web/content/docs/file/cli.mdx
index 6104b5b..331b64f 100644
--- a/web/content/docs/file/cli.mdx
+++ b/web/content/docs/file/cli.mdx
@@ -17,7 +17,7 @@ The CLI resolves a token in priority order, first match wins:
For the cloud-hosted path, login is owned by the `agent-relay` CLI: `agent-relay login`, then `agent-relay workspace switch `, then any `relayfile` command. For self-hosted servers, use the API-key path:
```bash
-relayfile login --api-key --server https://api.relayfile.dev
+relayfile login --api-key --server https://file.agentrelay.com
```
If no token is found, the CLI prints: `Error: not authenticated. Run 'agent-relay login' for Cloud or set RELAYFILE_TOKEN.`
@@ -49,7 +49,7 @@ Authenticate through the canonical relay session, or the self-hosted API-key pat
```bash
relayfile login [--no-open]
-relayfile login --api-key --server https://api.relayfile.dev
+relayfile login --api-key --server https://file.agentrelay.com
```
The default path delegates to `agent-relay login`. `--api-key` keeps the self-hosted compatibility path and writes `~/.relayfile/credentials.json` with `0600` permissions.
diff --git a/web/content/docs/file/introduction.mdx b/web/content/docs/file/introduction.mdx
index 287590e..7d50dcc 100644
--- a/web/content/docs/file/introduction.mdx
+++ b/web/content/docs/file/introduction.mdx
@@ -52,7 +52,7 @@ from relayfile import RelayFileClient
# The Python SDK is the data-plane client — connect a provider with the CLI
# above, then point the client at the workspace token to read and write:
client = RelayFileClient(
- "https://api.relayfile.dev",
+ "https://file.agentrelay.com",
lambda: os.environ["RELAYFILE_TOKEN"],
)
```
diff --git a/web/content/docs/file/python-sdk.mdx b/web/content/docs/file/python-sdk.mdx
index 13495a6..4695c9e 100644
--- a/web/content/docs/file/python-sdk.mdx
+++ b/web/content/docs/file/python-sdk.mdx
@@ -19,7 +19,7 @@ Construct a client with a base URL and a token (a string, or a callable that ret
from relayfile import RelayFileClient
client = RelayFileClient(
- "https://api.relayfile.dev",
+ "https://file.agentrelay.com",
lambda: os.environ["RELAYFILE_TOKEN"],
)
```
@@ -63,7 +63,7 @@ except RevisionConflictError:
```python
from relayfile import AsyncRelayFileClient
-async with AsyncRelayFileClient("https://api.relayfile.dev", token) as client:
+async with AsyncRelayFileClient("https://file.agentrelay.com", token) as client:
page = await client.read_file("rw_123", "/linear/issues/AGE-12__fix-login-bug.json")
```
diff --git a/web/content/docs/loop/sources.mdx b/web/content/docs/loop/sources.mdx
index bc96f1d..fd626b4 100644
--- a/web/content/docs/loop/sources.mdx
+++ b/web/content/docs/loop/sources.mdx
@@ -13,7 +13,7 @@ Relayloop's value comes from aggregating history that is otherwise scattered acr
| Codex CLI | `codex` | `~/.codex/history.jsonl` |
| Cursor | `cursor` | Per-session JSONL under `~/.cursor/projects/...` |
| Grok | `grok` | Per-session JSONL under `~/.grok/sessions/...` |
-| Agent Relay | `relay` | Relaycast API (`https://api.relaycast.dev/v1`) |
+| Agent Relay | `relay` | Relaycast API (`https://cast.agentrelay.com/v1`) |
| Trajectories | `trajectory` | Compacted per-run JSON files |
| OpenCode | `opencode` | Local SQLite (`~/.local/share/opencode/opencode.db`) |
diff --git a/web/lib/agent-discovery.ts b/web/lib/agent-discovery.ts
index 11c6baf..1a0c963 100644
--- a/web/lib/agent-discovery.ts
+++ b/web/lib/agent-discovery.ts
@@ -54,7 +54,7 @@ export function getApiCatalog(): { linkset: LinksetEntry[] } {
return {
linkset: [
{
- anchor: 'https://api.agentrelay.com/v1',
+ anchor: 'https://cast.agentrelay.com/v1',
'service-desc': [
{
href: RELAYCAST_OPENAPI_URL,
@@ -83,7 +83,7 @@ export function getApiCatalog(): { linkset: LinksetEntry[] } {
],
},
{
- anchor: 'https://api.agentrelay.com/relayfile/v1',
+ anchor: 'https://file.agentrelay.com/v1',
'service-desc': [
{
href: RELAYFILE_OPENAPI_URL,
diff --git a/web/lib/test/agent-discovery.test.ts b/web/lib/test/agent-discovery.test.ts
index f06a3e6..89c4771 100644
--- a/web/lib/test/agent-discovery.test.ts
+++ b/web/lib/test/agent-discovery.test.ts
@@ -22,7 +22,11 @@ describe('api-catalog', () => {
it('links each API to an OpenAPI description and human docs', () => {
const { linkset } = getApiCatalog();
- expect(linkset.length).toBeGreaterThan(0);
+ // Anchors must be API hosts that actually exist — api.agentrelay.com does not.
+ expect(linkset.map((e) => e.anchor)).toEqual([
+ 'https://cast.agentrelay.com/v1',
+ 'https://file.agentrelay.com/v1',
+ ]);
for (const entry of linkset) {
expect(entry.anchor).toMatch(/^https:\/\//);
expect(entry['service-desc']?.[0]?.href).toMatch(/^https:\/\//);
From ef7e6b9d2b0db6a592424dd90d03777c7648989a Mon Sep 17 00:00:00 2001
From: Will
Date: Sat, 22 Aug 2026 13:12:43 +0000
Subject: [PATCH 3/3] Replace remaining fabricated API hosts
None of the *.dev API domains the site advertised exist. Sweeping them,
leaving the archived 7.1.1 docs alone since those record what that release
actually shipped with.
Where a real host is documented, references now point at it:
- /message (Relaycast) -> cast.agentrelay.com/v1, per the base-URL table in
content/docs/relaycast-api.mdx.
- The homepage A2A agent card showed relay.dev/a2a/scout; the A2A gateway is
served at the engine root without /v1, so it is cast.agentrelay.com/a2a/scout.
- content/docs/observer.mdx -> observer.agentrelay.com, matching the
cast/file/history subdomain pattern. The surrounding section is about
pointing the CLI at a non-production deployment.
Where no host exists, the curl examples take a base-URL environment variable
instead of naming one, the way content/docs/file/api-reference.mdx already
does with RELAYFILE_BASE_URL:
- /auth -> $RELAYAUTH_BASE_URL
- /schedule -> $RELAY_CRON_BASE_URL, matching the RELAY_CRON_API_KEY already
in that snippet.
Two clickable CTAs pointed at app.agentcron.dev. Both now link the RelayCron
repo, which content/blog/just-give-the-agent-files.mdx already references and
which matches the relayauth/relayfile/relaycast repo links on the same
primitives page. The /schedule CTA gained target/rel, which the equivalent
link on the primitives page already had.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_01VV1b4tXaTcM2EqLLwKpL4u
---
web/app/auth/RelayauthContent.tsx | 12 ++++++------
web/app/message/RelaycastContent.tsx | 10 +++++-----
web/app/primitives/PrimitivesContent.tsx | 2 +-
web/app/schedule/ScheduleContent.tsx | 9 +++++++--
web/components/home/A2AFeature.tsx | 2 +-
web/content/docs/observer.mdx | 2 +-
6 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/web/app/auth/RelayauthContent.tsx b/web/app/auth/RelayauthContent.tsx
index 017f6c7..0ffec38 100644
--- a/web/app/auth/RelayauthContent.tsx
+++ b/web/app/auth/RelayauthContent.tsx
@@ -69,7 +69,7 @@ const whyCards = [
const getStartedSteps = [
{
title: 'Create an identity',
- code: `curl -X POST https://api.relayauth.dev/v1/identities \\
+ code: `curl -X POST "$RELAYAUTH_BASE_URL/v1/identities" \\
-H "content-type: application/json" \\
-d '{
"name": "billing-bot",
@@ -80,7 +80,7 @@ const getStartedSteps = [
},
{
title: 'Issue a token',
- code: `curl -X POST https://api.relayauth.dev/v1/tokens \\
+ code: `curl -X POST "$RELAYAUTH_BASE_URL/v1/tokens" \\
-H "content-type: application/json" \\
-d '{
"identity_id": "agent_8x2k",
@@ -90,7 +90,7 @@ const getStartedSteps = [
},
{
title: 'Verify at the edge',
- code: `curl https://api.relayauth.dev/.well-known/jwks.json
+ code: `curl "$RELAYAUTH_BASE_URL/.well-known/jwks.json"
# Then validate locally and enforce:
# stripe:orders:read`,
@@ -150,7 +150,7 @@ auth.authorize(
token=token["access_token"],
scope="stripe:orders:read",
)`,
- curl: `curl -X POST https://api.relayauth.dev/v1/identities \\
+ curl: `curl -X POST "$RELAYAUTH_BASE_URL/v1/identities" \\
-H "authorization: Bearer $RELAYAUTH_API_KEY" \\
-H "content-type: application/json" \\
-d '{
@@ -160,7 +160,7 @@ auth.authorize(
"sponsor_id": "user_jane"
}'
-curl -X POST https://api.relayauth.dev/v1/tokens \\
+curl -X POST "$RELAYAUTH_BASE_URL/v1/tokens" \\
-H "authorization: Bearer $RELAYAUTH_API_KEY" \\
-H "content-type: application/json" \\
-d '{
@@ -169,7 +169,7 @@ curl -X POST https://api.relayauth.dev/v1/tokens \\
"ttl": "1h"
}'
-curl https://api.relayauth.dev/.well-known/jwks.json`,
+curl "$RELAYAUTH_BASE_URL/.well-known/jwks.json"`,
};
function highlight(code: string, tab: SdkTab) {
diff --git a/web/app/message/RelaycastContent.tsx b/web/app/message/RelaycastContent.tsx
index 643ccf1..1b4913f 100644
--- a/web/app/message/RelaycastContent.tsx
+++ b/web/app/message/RelaycastContent.tsx
@@ -98,12 +98,12 @@ for event in client.events.stream(channel="dev"):
},
curl: {
label: 'cURL',
- code: `TOKEN=$(curl -s -X POST https://api.relaycast.dev/v1/agents \\
+ code: `TOKEN=$(curl -s -X POST https://cast.agentrelay.com/v1/agents \\
-H "Authorization: Bearer rk_live_..." \\
-H "Content-Type: application/json" \\
-d '{"name":"Bot","type":"agent"}' | jq -r .data.token)
-curl -X POST https://api.relaycast.dev/v1/channels/general/messages \\
+curl -X POST https://cast.agentrelay.com/v1/channels/general/messages \\
-H "Authorization: Bearer $TOKEN" \\
-H "Content-Type: application/json" \\
-d '{"text":"Hello from cURL!"}'`,
@@ -134,14 +134,14 @@ const steps = [
{
step: '01',
title: 'Create a workspace',
- code: `curl -X POST https://api.relaycast.dev/v1/workspaces \\
+ code: `curl -X POST https://cast.agentrelay.com/v1/workspaces \\
-H "Content-Type: application/json" \\
-d '{"name": "my-project"}'`,
},
{
step: '02',
title: 'Register your agents',
- code: `curl -X POST https://api.relaycast.dev/v1/agents \\
+ code: `curl -X POST https://cast.agentrelay.com/v1/agents \\
-H "Authorization: Bearer rk_live_YOUR_KEY" \\
-H "Content-Type: application/json" \\
-d '{"name": "Alice", "type": "agent"}'`,
@@ -149,7 +149,7 @@ const steps = [
{
step: '03',
title: 'Start talking',
- code: `curl -X POST https://api.relaycast.dev/v1/channels/general/messages \\
+ code: `curl -X POST https://cast.agentrelay.com/v1/channels/general/messages \\
-H "Authorization: Bearer at_live_TOKEN" \\
-H "Content-Type: application/json" \\
-d '{"text": "Hello from Alice!"}'`,
diff --git a/web/app/primitives/PrimitivesContent.tsx b/web/app/primitives/PrimitivesContent.tsx
index d5ed569..469330f 100644
--- a/web/app/primitives/PrimitivesContent.tsx
+++ b/web/app/primitives/PrimitivesContent.tsx
@@ -148,7 +148,7 @@ const primitives = [
},
],
docsHref: '/docs',
- githubHref: 'https://app.agentcron.dev',
+ githubHref: 'https://github.com/AgentWorkforce/relaycron',
},
];
diff --git a/web/app/schedule/ScheduleContent.tsx b/web/app/schedule/ScheduleContent.tsx
index 4373b7c..b73d9e3 100644
--- a/web/app/schedule/ScheduleContent.tsx
+++ b/web/app/schedule/ScheduleContent.tsx
@@ -67,7 +67,7 @@ cron.schedules.create(
for event in cron.events():
print(f"Job fired: {event.schedule_name} at {event.fired_at}")`,
curl: `# Create a schedule via REST API
-curl -X POST https://api.agentcron.dev/v1/schedules \\
+curl -X POST "$RELAY_CRON_BASE_URL/v1/schedules" \\
-H "Authorization: Bearer $RELAY_CRON_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
@@ -108,7 +108,12 @@ curl -X POST https://api.agentcron.dev/v1/schedules \\
on Cloudflare Durable Objects.
-
+
Start building free
diff --git a/web/components/home/A2AFeature.tsx b/web/components/home/A2AFeature.tsx
index 2320ef1..e3665b4 100644
--- a/web/components/home/A2AFeature.tsx
+++ b/web/components/home/A2AFeature.tsx
@@ -30,7 +30,7 @@ const AGENT_CARD_TOKENS: ReadonlyArray<{ text: string; kind?: TokenKind }> = [
{ text: ',\n ' },
{ text: '"url"', kind: 'var' },
{ text: ': ' },
- { text: '"https://relay.dev/a2a/scout"', kind: 'str' },
+ { text: '"https://cast.agentrelay.com/a2a/scout"', kind: 'str' },
{ text: ',\n ' },
{ text: '"capabilities"', kind: 'var' },
{ text: ': { ' },
diff --git a/web/content/docs/observer.mdx b/web/content/docs/observer.mdx
index 80db7e9..2ebc73a 100644
--- a/web/content/docs/observer.mdx
+++ b/web/content/docs/observer.mdx
@@ -74,7 +74,7 @@ Point the command at a different observer deployment with `--observer-url`, or
set `RELAY_OBSERVER_URL`:
```bash
-agent-relay observer --observer-url https://observer.relaycast.dev
+agent-relay observer --observer-url https://observer.agentrelay.com
```
## When to use it