From 640a0cf92f1dd14e0a21e8981d50314588292dd3 Mon Sep 17 00:00:00 2001
From: khaliqgant
Date: Sun, 25 Oct 2026 23:44:18 -0700
Subject: [PATCH 1/3] Add a minimal /pricing page for the hosted free tier
split
States local and self-hosted as free and unlimited, the hosted anonymous
trial (100 messages, one time), signed-in Free (10,000 messages a month),
and Pro/Enterprise with "Contact us" until paid prices are confirmed. Links
sign-in at /login, explains the plan_limit_exceeded error and its
upgrade_url, and adds /pricing to the sitemap.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
web/app/pricing/page.tsx | 109 +++++++++++++++++++++++++++++
web/app/sitemap.ts | 6 ++
web/lib/pricing.ts | 62 ++++++++++++++++
web/lib/test/pricing-page.test.tsx | 43 ++++++++++++
4 files changed, 220 insertions(+)
create mode 100644 web/app/pricing/page.tsx
create mode 100644 web/lib/pricing.ts
create mode 100644 web/lib/test/pricing-page.test.tsx
diff --git a/web/app/pricing/page.tsx b/web/app/pricing/page.tsx
new file mode 100644
index 00000000..7af0397c
--- /dev/null
+++ b/web/app/pricing/page.tsx
@@ -0,0 +1,109 @@
+import type { Metadata } from 'next';
+import Link from 'next/link';
+
+import { GitHubStarsBadge } from '../../components/GitHubStars';
+import { SiteFooter } from '../../components/SiteFooter';
+import { SiteNav } from '../../components/SiteNav';
+import { RELAY_PLANS, SIGN_IN_PATH } from '../../lib/pricing';
+import { absoluteUrl, SITE_EMAIL } from '../../lib/site';
+import s from '../legal.module.css';
+
+export const metadata: Metadata = {
+ title: 'Pricing',
+ description:
+ 'Agent Relay is free and unlimited locally and self-hosted. The hosted service has a 100-message trial without signing in and 10,000 messages a month free when you sign in.',
+ alternates: {
+ canonical: absoluteUrl('/pricing'),
+ },
+};
+
+export default function PricingPage() {
+ return (
+
+ } />
+
+
+
Pricing
+
+ Relay is free and unlimited when you run it yourself. The hosted service is free to try and free to keep
+ using once you sign in.
+
+
+
+
Plans
+
+
+
+
+
Plan
+
Messages
+
Other limits
+
Price
+
+
+
+ {RELAY_PLANS.map((plan) => (
+
+
+ {plan.name}
+
+ {plan.who}
+
+
{plan.messages}
+
{plan.limits}
+
{plan.price}
+
+ ))}
+
+
+
+
+ Paid prices are not published yet.{' '}
+
+ Contact us
+ {' '}
+ for Pro or{' '}
+
+ Enterprise
+
+ .
+
+
+
+
+
Local and self-hosted
+
+ Running Relay on your own machine or servers has no message, agent, or API limits, and no account is
+ needed.
+
+
+
+
+
Hosted trial, then sign in
+
+ A hosted workspace created without signing in gets 100 messages, once. That is enough to prove agents
+ on two machines can talk. When it runs out, sends fail with a limit error that links to sign-in.
+
+
+
+ Sign in
+ {' '}
+ (free), or run agent-relay login, to get a workspace with 10,000 messages a month.
+
+
+
+
+
When you reach a limit
+
+ The API answers with HTTP 429 and the error code plan_limit_exceeded. The error names the
+ limit and carries an upgrade_url: the sign-in page for a trial workspace, or billing
+ settings for a signed-in one. Monthly limits reset at the start of each UTC month; the trial allowance
+ does not reset.
+
+
+
+
+
+
+ );
+}
diff --git a/web/app/sitemap.ts b/web/app/sitemap.ts
index 21b8901c..2e543338 100644
--- a/web/app/sitemap.ts
+++ b/web/app/sitemap.ts
@@ -138,6 +138,12 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: 'yearly',
priority: 0.4,
},
+ {
+ url: absoluteUrl('/pricing'),
+ lastModified: now,
+ changeFrequency: 'monthly',
+ priority: 0.7,
+ },
{
url: absoluteUrl('/support'),
lastModified: now,
diff --git a/web/lib/pricing.ts b/web/lib/pricing.ts
new file mode 100644
index 00000000..51961f4d
--- /dev/null
+++ b/web/lib/pricing.ts
@@ -0,0 +1,62 @@
+/**
+ * Relay plans as the hosted service enforces them. Limits mirror
+ * `PLAN_LIMITS` in @relaycast/engine (packages/engine/src/providers/static-entitlements.ts);
+ * keep them in step when the engine changes.
+ *
+ * Paid prices are not set yet. `price` stays "Contact us" until they are
+ * confirmed — do not fill in numbers here without that decision.
+ */
+export interface RelayPlan {
+ id: 'selfhost' | 'trial' | 'free' | 'pro' | 'enterprise';
+ name: string;
+ who: string;
+ messages: string;
+ limits: string;
+ price: string;
+}
+
+export const RELAY_PLANS: readonly RelayPlan[] = [
+ {
+ id: 'selfhost',
+ name: 'Local and self-hosted',
+ who: 'Run Relay on your own machine or servers',
+ messages: 'Unlimited',
+ limits: 'No limits',
+ price: 'Free',
+ },
+ {
+ id: 'trial',
+ name: 'Hosted trial',
+ who: 'A hosted workspace created without signing in',
+ messages: '100 messages, one time',
+ limits: 'Enough to connect agents on two machines',
+ price: 'Free',
+ },
+ {
+ id: 'free',
+ name: 'Hosted Free',
+ who: 'Sign in with your account',
+ messages: '10,000 messages a month',
+ limits: '5 agents, 100,000 API calls a month, 300 requests a minute',
+ price: 'Free',
+ },
+ {
+ id: 'pro',
+ name: 'Pro',
+ who: 'Teams running agents every day',
+ messages: '1,000,000 messages a month',
+ limits: '100 agents, 10,000,000 API calls a month, 6,000 requests a minute',
+ price: 'Contact us',
+ },
+ {
+ id: 'enterprise',
+ name: 'Enterprise',
+ who: 'Custom limits, security review, and support',
+ messages: 'Unlimited',
+ limits: 'Unlimited agents and API calls, 30,000 requests a minute',
+ price: 'Contact us',
+ },
+];
+
+/** Where an anonymous hosted workspace signs in (the trial's limit error links here). */
+export const SIGN_IN_PATH = '/login';
diff --git a/web/lib/test/pricing-page.test.tsx b/web/lib/test/pricing-page.test.tsx
new file mode 100644
index 00000000..8b6c46f6
--- /dev/null
+++ b/web/lib/test/pricing-page.test.tsx
@@ -0,0 +1,43 @@
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it, vi } from 'vitest';
+
+vi.mock('../../components/GitHubStars', () => ({ GitHubStarsBadge: () => null }));
+vi.mock('../../components/SiteFooter', () => ({ SiteFooter: () => null }));
+vi.mock('../../components/SiteNav', () => ({ SiteNav: () => null }));
+
+import PricingPage from '../../app/pricing/page';
+import sitemap from '../../app/sitemap';
+import { RELAY_PLANS, SIGN_IN_PATH } from '../pricing';
+import { absoluteUrl } from '../site';
+
+const html = renderToStaticMarkup();
+
+describe('pricing page', () => {
+ it('states local and self-hosted use as unlimited and free', () => {
+ expect(html).toContain('Local and self-hosted');
+ expect(html).toMatch(/Local and self-hosted[\s\S]*?Unlimited[\s\S]*?Free/);
+ });
+
+ it('states the hosted trial and the signed-in free tier', () => {
+ expect(html).toContain('100 messages, one time');
+ expect(html).toContain('10,000 messages a month');
+ expect(html).toContain(`href="${SIGN_IN_PATH}"`);
+ expect(html).toContain('agent-relay login');
+ });
+
+ it('lists paid plans without inventing prices', () => {
+ for (const plan of RELAY_PLANS.filter((p) => p.id === 'pro' || p.id === 'enterprise')) {
+ expect(plan.price).toBe('Contact us');
+ }
+ expect(html).not.toMatch(/\$\d/);
+ });
+
+ it('explains the limit error and its upgrade URL', () => {
+ expect(html).toContain('plan_limit_exceeded');
+ expect(html).toContain('upgrade_url');
+ });
+
+ it('is canonical and in the sitemap', () => {
+ expect(sitemap().map((entry) => entry.url)).toContain(absoluteUrl('/pricing'));
+ });
+});
From ad78a3b3192375e954ff8656c6c5f0c95af8689e Mon Sep 17 00:00:00 2001
From: khaliqgant
Date: Mon, 26 Oct 2026 01:04:23 -0700
Subject: [PATCH 2/3] Pricing: monthly message figures are guidance; trial is
the gate
Free and Pro now read "up to" 10,000 / 1,000,000 messages a month and the page
says monthly message figures are not hard caps, while API-call and rate limits
are enforced. The 100-message hosted trial is marked as the hard limit, and the
trial section (anchor #trial) explains that signing in, free, with
`agent-relay login` continues past it; the trial's limit error links here.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
web/app/pricing/page.tsx | 25 ++++++++++++++++---------
web/lib/pricing.ts | 15 +++++++++------
web/lib/test/pricing-page.test.tsx | 14 +++++++++++---
3 files changed, 36 insertions(+), 18 deletions(-)
diff --git a/web/app/pricing/page.tsx b/web/app/pricing/page.tsx
index 7af0397c..c3e1c715 100644
--- a/web/app/pricing/page.tsx
+++ b/web/app/pricing/page.tsx
@@ -11,7 +11,7 @@ import s from '../legal.module.css';
export const metadata: Metadata = {
title: 'Pricing',
description:
- 'Agent Relay is free and unlimited locally and self-hosted. The hosted service has a 100-message trial without signing in and 10,000 messages a month free when you sign in.',
+ 'Agent Relay is free and unlimited locally and self-hosted. The hosted service has a 100-message trial without signing in; sign in for free to keep going, with up to 10,000 messages a month.',
alternates: {
canonical: absoluteUrl('/pricing'),
},
@@ -57,6 +57,10 @@ export default function PricingPage() {
+
+ Monthly message figures are sizing guidance, not hard caps. The only hard message limit is the hosted
+ trial’s 100 messages. API-call and per-minute request limits are enforced.
+
A hosted workspace created without signing in gets 100 messages, once. That is enough to prove agents
- on two machines can talk. When it runs out, sends fail with a limit error that links to sign-in.
+ on two machines can talk. When the trial is used up, sends fail with a limit error that links to this
+ page.
+ Signing in is free and continues past the trial: run agent-relay login or{' '}
- Sign in
- {' '}
- (free), or run agent-relay login, to get a workspace with 10,000 messages a month.
+ sign in
+
+ , and keep going on Hosted Free with up to 10,000 messages a month.
@@ -96,9 +102,10 @@ export default function PricingPage() {
When you reach a limit
The API answers with HTTP 429 and the error code plan_limit_exceeded. The error names the
- limit and carries an upgrade_url: the sign-in page for a trial workspace, or billing
- settings for a signed-in one. Monthly limits reset at the start of each UTC month; the trial allowance
- does not reset.
+ limit and carries an upgrade_url: this page for a used-up trial (with{' '}
+ upgrade_action: sign_in, meaning run agent-relay login), or billing settings
+ for a signed-in workspace. Monthly API-call limits reset at the start of each UTC month; the trial
+ allowance does not reset.
diff --git a/web/lib/pricing.ts b/web/lib/pricing.ts
index 51961f4d..b6e12bc7 100644
--- a/web/lib/pricing.ts
+++ b/web/lib/pricing.ts
@@ -1,5 +1,8 @@
/**
- * Relay plans as the hosted service enforces them. Limits mirror
+ * Relay plans. Monthly message figures are sizing guidance, not hard caps:
+ * Relaycast does not enforce a monthly message limit. The hard message gate is
+ * the hosted trial's one-time 100 messages. API-call and rate limits are
+ * enforced. Limits mirror
* `PLAN_LIMITS` in @relaycast/engine (packages/engine/src/providers/static-entitlements.ts);
* keep them in step when the engine changes.
*
@@ -28,15 +31,15 @@ export const RELAY_PLANS: readonly RelayPlan[] = [
id: 'trial',
name: 'Hosted trial',
who: 'A hosted workspace created without signing in',
- messages: '100 messages, one time',
- limits: 'Enough to connect agents on two machines',
+ messages: '100 messages, one time (hard limit)',
+ limits: 'Enough to connect agents on two machines. Sign in (free) to keep going',
price: 'Free',
},
{
id: 'free',
name: 'Hosted Free',
who: 'Sign in with your account',
- messages: '10,000 messages a month',
+ messages: 'Up to 10,000 messages a month',
limits: '5 agents, 100,000 API calls a month, 300 requests a minute',
price: 'Free',
},
@@ -44,7 +47,7 @@ export const RELAY_PLANS: readonly RelayPlan[] = [
id: 'pro',
name: 'Pro',
who: 'Teams running agents every day',
- messages: '1,000,000 messages a month',
+ messages: 'Up to 1,000,000 messages a month',
limits: '100 agents, 10,000,000 API calls a month, 6,000 requests a minute',
price: 'Contact us',
},
@@ -58,5 +61,5 @@ export const RELAY_PLANS: readonly RelayPlan[] = [
},
];
-/** Where an anonymous hosted workspace signs in (the trial's limit error links here). */
+/** Where an anonymous hosted workspace signs in. The trial's limit error links to /pricing, which links here. */
export const SIGN_IN_PATH = '/login';
diff --git a/web/lib/test/pricing-page.test.tsx b/web/lib/test/pricing-page.test.tsx
index 8b6c46f6..b759b64f 100644
--- a/web/lib/test/pricing-page.test.tsx
+++ b/web/lib/test/pricing-page.test.tsx
@@ -18,11 +18,19 @@ describe('pricing page', () => {
expect(html).toMatch(/Local and self-hosted[\s\S]*?Unlimited[\s\S]*?Free/);
});
- it('states the hosted trial and the signed-in free tier', () => {
- expect(html).toContain('100 messages, one time');
- expect(html).toContain('10,000 messages a month');
+ it('makes the 100-message trial the hard gate and signing in the way past it', () => {
+ expect(html).toContain('100 messages, one time (hard limit)');
+ expect(html).toContain('Signing in is free and continues past the trial');
expect(html).toContain(`href="${SIGN_IN_PATH}"`);
expect(html).toContain('agent-relay login');
+ expect(html).toContain('id="trial"');
+ });
+
+ it('does not promise a hard monthly message cap', () => {
+ expect(html).toContain('Up to 10,000 messages a month');
+ expect(html).toContain('Up to 1,000,000 messages a month');
+ expect(html).toContain('not hard caps');
+ expect(html).not.toMatch(/(? {
From 400487a4b5b52bf2c695f3c51c0a569c779b5b7d Mon Sep 17 00:00:00 2001
From: khaliqgant
Date: Mon, 26 Oct 2026 01:22:17 -0700
Subject: [PATCH 3/3] Pricing: say sign-in means a new workspace; scope the
limit error
Address review feedback: signing in gives a new account-bound workspace (there
is no claim flow), so the trial section tells users to connect their agents to
it. The limit-error section covers only the trial and monthly plan quotas, and
self-hosted states its 30,000-requests-a-minute ceiling instead of "No limits".
Co-Authored-By: Claude Opus 5.5 (1M context)
---
web/app/pricing/page.tsx | 16 +++++++++++-----
web/lib/pricing.ts | 2 +-
web/lib/test/pricing-page.test.tsx | 8 ++++++++
3 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/web/app/pricing/page.tsx b/web/app/pricing/page.tsx
index c3e1c715..d0714263 100644
--- a/web/app/pricing/page.tsx
+++ b/web/app/pricing/page.tsx
@@ -77,8 +77,9 @@ export default function PricingPage() {
Local and self-hosted
- Running Relay on your own machine or servers has no message, agent, or API limits, and no account is
- needed.
+ Running Relay on your own machine or servers has no message, agent, or monthly API-call limits, and no
+ account is needed. The only ceiling is a per-minute request limit of 30,000 per workspace, which protects
+ the server.
@@ -96,13 +97,18 @@ export default function PricingPage() {
, and keep going on Hosted Free with up to 10,000 messages a month.
+
+ Signing in gives you a new hosted workspace bound to your account; it does not convert the trial
+ workspace. Connect your agents to the new workspace to keep sending. The trial workspace stays capped at
+ 100 messages.
+
-
When you reach a limit
+
When you reach a plan limit
- The API answers with HTTP 429 and the error code plan_limit_exceeded. The error names the
- limit and carries an upgrade_url: this page for a used-up trial (with{' '}
+ When a workspace uses up the trial or a monthly plan quota, the API answers with HTTP 429 and the error
+ code plan_limit_exceeded. The error names the limit and carries an upgrade_url: this page for a used-up trial (with{' '}
upgrade_action: sign_in, meaning run agent-relay login), or billing settings
for a signed-in workspace. Monthly API-call limits reset at the start of each UTC month; the trial
allowance does not reset.
diff --git a/web/lib/pricing.ts b/web/lib/pricing.ts
index b6e12bc7..3e35384d 100644
--- a/web/lib/pricing.ts
+++ b/web/lib/pricing.ts
@@ -24,7 +24,7 @@ export const RELAY_PLANS: readonly RelayPlan[] = [
name: 'Local and self-hosted',
who: 'Run Relay on your own machine or servers',
messages: 'Unlimited',
- limits: 'No limits',
+ limits: 'Unlimited agents and API calls, 30,000 requests a minute',
price: 'Free',
},
{
diff --git a/web/lib/test/pricing-page.test.tsx b/web/lib/test/pricing-page.test.tsx
index b759b64f..5274a4af 100644
--- a/web/lib/test/pricing-page.test.tsx
+++ b/web/lib/test/pricing-page.test.tsx
@@ -24,6 +24,14 @@ describe('pricing page', () => {
expect(html).toContain(`href="${SIGN_IN_PATH}"`);
expect(html).toContain('agent-relay login');
expect(html).toContain('id="trial"');
+ // No claim flow: signing in means a new workspace, not the trial one.
+ expect(html).toContain('does not convert the trial');
+ });
+
+ it('scopes plan_limit_exceeded to plan quotas and states the self-hosted request ceiling', () => {
+ expect(html).toContain('When a workspace uses up the trial or a monthly plan quota');
+ expect(html).not.toContain('No limits');
+ expect(html).toContain('30,000 requests a minute');
});
it('does not promise a hard monthly message cap', () => {