Skip to content

Commit 16fdb7e

Browse files
ralyodioclaude
andcommitted
Charge AI training crawlers for access (@profullstack/x402-gateway)
Training crawlers (GPTBot, ClaudeBot, CCBot, meta-externalagent, Bytespider, Applebot-Extended) get 402 Payment Required with an x402 offer, or the sales page at /crawl, and a paid pass opens the site for a day. People, search engines and retrieval crawlers pass through untouched. robots.txt is now generated from the same lists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2
1 parent 46700ba commit 16fdb7e

7 files changed

Lines changed: 65 additions & 27 deletions

File tree

.env.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,9 @@ VAPID_PRIVATE_KEY=your-vapid-private-key
159159

160160
APPLE_ID=your-developer-email
161161
APPLE_TEAM_ID=your-apple-team-id
162-
APPLE_APP_SPECIFIC_PASSWORD=your-app-specific-password
162+
APPLE_APP_SPECIFIC_PASSWORD=your-app-specific-password
163+
# Crawl gateway (@profullstack/x402-gateway): AI training crawlers pay $1/day over
164+
# x402. A SCOPED CoinPay key (payments:create) and the EVM address that receives
165+
# the USDC. Unset = crawlers still get 402, nothing sold.
166+
COINPAY_X402_KEY=
167+
CRAWL_PAY_TO=

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@profullstack/autoblog": "github:profullstack/autoblog#75e54af",
2020
"@profullstack/emailer": "^1.0.3",
2121
"@profullstack/stack": "^0.1.3",
22+
"@profullstack/x402-gateway": "^0.1.0",
2223
"@serwist/next": "^9.5.0",
2324
"@supabase/ssr": "^0.5.0",
2425
"@supabase/supabase-js": "^2.47.0",

apps/web/public/robots.txt

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { robotsRoute } from '@profullstack/x402-gateway/next';
2+
import { gateway } from '@/lib/crawl-gateway';
3+
4+
// Generated from the same crawler lists the gateway enforces: training
5+
// crawlers are refused everywhere but /crawl (where they can buy a pass),
6+
// retrieval crawlers are named as welcome, everyone else gets the rules below.
7+
export const GET = robotsRoute(gateway, {
8+
disallow: ['/api/'],
9+
});

apps/web/src/lib/crawl-gateway.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { createGateway } from '@profullstack/x402-gateway';
2+
import { x402Proxy } from '@profullstack/x402-gateway/next';
3+
4+
/**
5+
* Sells crawl access to AI training crawlers (GPTBot, ClaudeBot, CCBot,
6+
* meta-externalagent, Bytespider, Applebot-Extended, ...) by the day over
7+
* x402, settled by CoinPay in USDC. People, Googlebot and the retrieval
8+
* crawlers behind AI search pass through untouched.
9+
*
10+
* Runs inside the middleware, so nothing here may import Node-only modules.
11+
* The env is read through a non-literal key on purpose: Next inlines
12+
* `process.env.NAME` at build time, and these are runtime secrets. Without
13+
* COINPAY_X402_KEY and CRAWL_PAY_TO the gateway still answers training
14+
* crawlers with 402, just with an empty offer.
15+
*/
16+
const env = (name: string) => process.env[name];
17+
18+
export const gateway = createGateway({
19+
siteUrl: env('SITE_URL') || env('NEXT_PUBLIC_SITE_URL') || 'https://pairux.com',
20+
siteName: 'PairUX',
21+
coinpay: { apiKey: env('COINPAY_X402_KEY') },
22+
payTo: env('CRAWL_PAY_TO'),
23+
contact: 'mailto:support@pairux.com',
24+
});
25+
26+
/** Resolves to a Response for a refused crawler, or undefined to carry on. */
27+
export const gate = x402Proxy(gateway);

apps/web/src/middleware.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { gate } from '@/lib/crawl-gateway';
12
import { NextResponse, type NextRequest } from 'next/server';
23
import { updateSession } from '@/lib/supabase/middleware';
34
import { CORS_HEADERS } from '@/lib/cors';
@@ -43,6 +44,12 @@ function buildCsp(nonce: string, embeddable: boolean): string {
4344
}
4445

4546
export async function middleware(request: NextRequest) {
47+
// Crawl gateway first: AI training crawlers get 402 Payment Required (or the
48+
// sales page at /crawl) unless they present a paid pass. People, Googlebot
49+
// and retrieval crawlers fall through to everything below.
50+
const answer = await gate(request);
51+
if (answer) return answer;
52+
4653
const { pathname } = request.nextUrl;
4754

4855
const isEmbed = pathname === '/embed' || pathname.startsWith('/embed/');

pnpm-lock.yaml

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)