From 25af6fc055d67821cfa478f419209c603dddcde0 Mon Sep 17 00:00:00 2001 From: JoesnageL Date: Thu, 18 Jun 2026 12:51:47 -0600 Subject: [PATCH] Improve paid product discovery payloads --- app/api/v1/paid/[product]/route.ts | 111 ++------ lib/paid-product-payloads.ts | 403 +++++++++++++++++++++++++++++ 2 files changed, 425 insertions(+), 89 deletions(-) create mode 100644 lib/paid-product-payloads.ts diff --git a/app/api/v1/paid/[product]/route.ts b/app/api/v1/paid/[product]/route.ts index 65076eb..528f07d 100644 --- a/app/api/v1/paid/[product]/route.ts +++ b/app/api/v1/paid/[product]/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; -import { getSeedProduct, paymentRequirement } from '@/lib/seed-products'; +import { buildPaidProductPayload } from '@/lib/paid-product-payloads'; import { verifyPyrimidPaymentTx } from '@/lib/payment-verification'; +import { getSeedProduct, paymentRequirement } from '@/lib/seed-products'; function paymentRequired(req: NextRequest, product: NonNullable>) { const requirement = paymentRequirement(product, req.url); @@ -20,81 +21,10 @@ function paymentRequired(req: NextRequest, product: NonNullable }) { const { product: productId } = await context.params; const product = getSeedProduct(productId); @@ -102,7 +32,7 @@ export async function GET(req: NextRequest, context: { params: Promise<{ product if (!product) { return NextResponse.json( { error: 'not_found', message: 'Unknown Pyrimid seed product', catalog: 'https://pyrimid.ai/api/v1/catalog' }, - { status: 404, headers: { 'Cache-Control': 'no-store' } } + { status: 404, headers: { 'Cache-Control': 'no-store' } }, ); } @@ -118,23 +48,26 @@ export async function GET(req: NextRequest, context: { params: Promise<{ product docs: 'https://pyrimid.ai/quickstart', proof: 'https://pyrimid.ai/proof', }, - { status: 403, headers: { 'Cache-Control': 'no-store' } } + { status: 403, headers: { 'Cache-Control': 'no-store' } }, ); } - return NextResponse.json({ - product_id: product.product_id, - vendor_id: product.vendor_id, - payment_tx: verification.txHash, - payment_amount: verification.amount?.toString(), - buyer: verification.buyer, - ...payload(product.product_id, req, proof), - routed_by: 'pyrimid', - links: { - docs: 'https://pyrimid.ai/quickstart', - proof: 'https://pyrimid.ai/proof', - stats: 'https://pyrimid.ai/stats', - catalog: 'https://pyrimid.ai/api/v1/catalog', + return NextResponse.json( + { + product_id: product.product_id, + vendor_id: product.vendor_id, + payment_tx: verification.txHash, + payment_amount: verification.amount?.toString(), + buyer: verification.buyer, + ...(await buildPaidProductPayload(product.product_id, req, proof)), + routed_by: 'pyrimid', + links: { + docs: 'https://pyrimid.ai/quickstart', + proof: 'https://pyrimid.ai/proof', + stats: 'https://pyrimid.ai/stats', + catalog: 'https://pyrimid.ai/api/v1/catalog', + }, }, - }, { headers: { 'Cache-Control': 'no-store' } }); + { headers: { 'Cache-Control': 'no-store' } }, + ); } diff --git a/lib/paid-product-payloads.ts b/lib/paid-product-payloads.ts new file mode 100644 index 0000000..e3da58a --- /dev/null +++ b/lib/paid-product-payloads.ts @@ -0,0 +1,403 @@ +import { type NextRequest } from 'next/server'; + +type Query = Record; + +type Lead = { + segment: string; + target: string; + url: string; + fit_score: number; + fit: 'high' | 'medium' | 'low'; + source: string; + evidence: string[]; + pitch: string; + next_action: string; +}; + +type GitHubRepo = { + full_name?: string; + html_url?: string; + description?: string | null; + language?: string | null; + stargazers_count?: number; + pushed_at?: string | null; + topics?: string[]; +}; + +const SEGMENT_QUERIES: Record = { + mcp: [ + '"model context protocol" server language:TypeScript', + '"mcp server" "tools" language:TypeScript', + ], + 'agent-frameworks': [ + '"agent framework" marketplace language:TypeScript', + '"AI agent" plugin marketplace language:TypeScript', + ], + 'api-tools': [ + '"api wrapper" "pricing" language:TypeScript', + '"data api" "usage based" language:TypeScript', + ], + x402: [ + 'x402 payment language:TypeScript', + '"X-PAYMENT" "USDC" language:TypeScript', + ], + data: [ + '"data feed" api language:TypeScript', + '"market data" api language:TypeScript', + ], +}; + +const FALLBACK_LEADS: Record = { + mcp: [ + { + segment: 'mcp', + target: 'MCP servers with expensive search, enrichment, or analytics tools', + url: 'https://github.com/search?q=%22mcp+server%22+tools+language%3ATypeScript&type=repositories', + fit_score: 82, + fit: 'high', + source: 'fallback-search-plan', + evidence: ['Tool-call interface already exists', 'Per-call value is easy to price', 'Agents can buy through x402 without a human checkout'], + pitch: 'Add an optional x402 payment gate to the highest-cost tool and list it in the Pyrimid catalog.', + next_action: 'Review repos with active commits and tools that call paid APIs, scrapers, or enrichment providers.', + }, + ], + 'agent-frameworks': [ + { + segment: 'agent-frameworks', + target: 'Agent frameworks with plugin or marketplace ecosystems', + url: 'https://github.com/search?q=%22agent+framework%22+marketplace+language%3ATypeScript&type=repositories', + fit_score: 76, + fit: 'high', + source: 'fallback-search-plan', + evidence: ['Agents already install third-party capabilities', 'Framework owners can earn affiliate revenue', 'Pyrimid adds a paid discovery layer'], + pitch: 'Let framework users discover paid MCP/API tools and share commission back to the framework affiliate ID.', + next_action: 'Find repos with plugin catalogs, tool registries, or marketplace docs.', + }, + ], + 'api-tools': [ + { + segment: 'api-tools', + target: 'AI/data APIs with usage-based costs', + url: 'https://github.com/search?q=%22api+wrapper%22+%22pricing%22+language%3ATypeScript&type=repositories', + fit_score: 72, + fit: 'medium', + source: 'fallback-search-plan', + evidence: ['Per-call costs map cleanly to x402', 'Paid data or inference can be resold to agents', 'Catalog listing creates an agent-readable product surface'], + pitch: 'Package the API call as a paid endpoint with a clear output schema and a 10-30% affiliate share.', + next_action: 'Prioritize APIs with clear pricing, public docs, and JSON outputs.', + }, + ], + x402: [ + { + segment: 'x402', + target: 'Existing x402 builders without affiliate distribution', + url: 'https://github.com/search?q=x402+payment+language%3ATypeScript&type=repositories', + fit_score: 84, + fit: 'high', + source: 'fallback-search-plan', + evidence: ['They already understand 402 payment flows', 'Integration lift is lower than cold vendors', 'Pyrimid adds commission routing'], + pitch: 'Keep the existing x402 product and add Pyrimid affiliate attribution for agents that route buyers.', + next_action: 'Look for endpoints that already emit 402 challenges but have no affiliate or catalog metadata.', + }, + ], + data: [ + { + segment: 'data', + target: 'Data feeds with freshness or collection cost', + url: 'https://github.com/search?q=%22data+feed%22+api+language%3ATypeScript&type=repositories', + fit_score: 70, + fit: 'medium', + source: 'fallback-search-plan', + evidence: ['Structured data outputs are easy for agents to consume', 'Freshness and scraping costs justify per-call pricing', 'Catalog metadata can expose schema and price'], + pitch: 'Turn one high-value feed into an agent-purchasable paid endpoint with a small x402 price.', + next_action: 'Target feeds with JSON responses, documented fields, and recurring update costs.', + }, + ], +}; + +function query(req: NextRequest): Query { + return Object.fromEntries(req.nextUrl.searchParams.entries()); +} + +function normalizeSegment(input?: string) { + const raw = (input || 'mcp').toLowerCase().trim(); + if (raw.includes('agent')) return 'agent-frameworks'; + if (raw.includes('api')) return 'api-tools'; + if (raw.includes('x402')) return 'x402'; + if (raw.includes('data')) return 'data'; + return 'mcp'; +} + +function fitFromScore(score: number): Lead['fit'] { + if (score >= 75) return 'high'; + if (score >= 55) return 'medium'; + return 'low'; +} + +function recencyScore(pushedAt?: string | null) { + if (!pushedAt) return 0; + const ageDays = (Date.now() - new Date(pushedAt).getTime()) / 86_400_000; + if (ageDays <= 30) return 15; + if (ageDays <= 180) return 8; + return 2; +} + +function keywordScore(repo: GitHubRepo, segment: string) { + const text = `${repo.full_name || ''} ${repo.description || ''} ${(repo.topics || []).join(' ')}`.toLowerCase(); + const keywords = { + mcp: ['mcp', 'model context protocol', 'tool', 'server'], + 'agent-frameworks': ['agent', 'plugin', 'marketplace', 'tool'], + 'api-tools': ['api', 'data', 'wrapper', 'pricing'], + x402: ['x402', 'payment', 'usdc', '402'], + data: ['data', 'feed', 'market', 'scrape'], + }[segment] || []; + + return keywords.reduce((score, keyword) => score + (text.includes(keyword) ? 6 : 0), 0); +} + +function leadFromRepo(repo: GitHubRepo, segment: string): Lead | null { + if (!repo.full_name || !repo.html_url) return null; + + const stars = Math.min(Math.floor((repo.stargazers_count || 0) / 25), 20); + const score = Math.min(95, 35 + stars + recencyScore(repo.pushed_at) + keywordScore(repo, segment)); + const evidence = [ + repo.language ? `Primary language: ${repo.language}` : 'Public repository with inspectable source', + repo.pushed_at ? `Recently updated: ${repo.pushed_at.slice(0, 10)}` : 'Update recency unknown', + `${repo.stargazers_count || 0} GitHub stars`, + ]; + + if (repo.description) evidence.push(repo.description.slice(0, 140)); + + return { + segment, + target: repo.full_name, + url: repo.html_url, + fit_score: score, + fit: fitFromScore(score), + source: 'github-search', + evidence, + pitch: pitchForSegment(segment), + next_action: nextActionForSegment(segment), + }; +} + +function pitchForSegment(segment: string) { + switch (segment) { + case 'agent-frameworks': + return 'Add Pyrimid catalog discovery as an optional paid-tool marketplace for framework users and earn affiliate commission on routed purchases.'; + case 'api-tools': + return 'Expose one high-value JSON API call as an x402 paid endpoint and list it with clear schema, price, and affiliateBps.'; + case 'x402': + return 'Keep the existing x402 flow, then add Pyrimid metadata so agents can route buyers and receive affiliate commission.'; + case 'data': + return 'Package the freshest or costliest data lookup as a low-price paid endpoint agents can buy per call.'; + default: + return 'Convert the most valuable MCP tool into an optional paid tool with x402 and a Pyrimid catalog listing.'; + } +} + +function nextActionForSegment(segment: string) { + switch (segment) { + case 'agent-frameworks': + return 'Check whether the framework has a plugin registry or tool marketplace page that can add Pyrimid as a paid discovery provider.'; + case 'api-tools': + return 'Confirm the API has a deterministic JSON output and choose one endpoint with obvious per-call value.'; + case 'x402': + return 'Inspect the 402 challenge body and add vendorId/productId/affiliateBps metadata where missing.'; + case 'data': + return 'Choose a feed where freshness, scraping, or licensing cost justifies a small per-call price.'; + default: + return 'Identify the most expensive MCP tool and create a paid route plus catalog metadata for it.'; + } +} + +async function fetchGitHubLeads(segment: string, limit: number): Promise { + const queries = SEGMENT_QUERIES[segment] || SEGMENT_QUERIES.mcp; + const leads: Lead[] = []; + + for (const search of queries) { + const url = new URL('https://api.github.com/search/repositories'); + url.searchParams.set('q', search); + url.searchParams.set('sort', 'updated'); + url.searchParams.set('order', 'desc'); + url.searchParams.set('per_page', String(Math.min(limit, 10))); + + const res = await fetch(url, { + headers: { + Accept: 'application/vnd.github+json', + 'User-Agent': 'PyrimidVendorLeadDiscovery', + }, + next: { revalidate: 3600 }, + }); + + if (!res.ok) continue; + + const data = await res.json(); + const repos = Array.isArray(data.items) ? data.items : []; + for (const repo of repos) { + const lead = leadFromRepo(repo, segment); + if (lead && !leads.some((item) => item.url === lead.url)) leads.push(lead); + } + } + + return leads.sort((a, b) => b.fit_score - a.fit_score).slice(0, limit); +} + +export async function buildPaidProductPayload(productId: string, req: NextRequest, proof: string) { + const queryParams = query(req); + + switch (productId) { + case 'mya-agent-enrichment': { + const agent = queryParams.agent || 'demo-agent'; + return { + enrichment: { + agent, + category: 'developer-tools', + agent_readable_summary: `${agent} can monetize API calls by exposing paid tools through x402 and listing them in the Pyrimid catalog.`, + monetization_angle: 'Package one high-value tool as a paid MCP/API endpoint priced $0.05-$0.25 per call.', + suggested_cta: 'Claim listing -> add paid tool -> route purchases through Pyrimid.', + }, + }; + } + case 'mya-category-scout': { + const category = queryParams.category || 'developer-tools'; + return { + category, + agents: [ + { name: 'MCP server vendors', fit: 'high', reason: 'Already expose tool interfaces; easiest path to paid tools.' }, + { name: 'AI API wrappers', fit: 'high', reason: 'Usage-based value maps cleanly to x402 per-call pricing.' }, + { name: 'agent directories', fit: 'medium', reason: 'Can route discovery traffic into paid vendor listings.' }, + ], + }; + } + case 'vendor-lead-discovery': + return vendorLeadDiscoveryPayload(queryParams); + case 'mcp-server-audit': + return mcpServerAuditPayload(queryParams); + case 'x402-integration-plan': { + const service = queryParams.service || 'agent-api'; + return { + plan: { + service, + route_shape: 'GET /api/paid/{tool} returns 402 until X-PAYMENT or X-PAYMENT-TX is supplied', + payment_network: 'Base USDC', + pyrimid_metadata: ['vendorId', 'productId', 'affiliateBps', 'endpoint', 'output_schema'], + launch_checklist: ['publish llms.txt', 'publish agents.txt', 'submit MCP server card', 'list product in Pyrimid catalog'], + }, + }; + } + default: + return { result: 'unknown_seed_product', proof_seen: Boolean(proof) }; + } +} + +async function vendorLeadDiscoveryPayload(queryParams: Query) { + const segment = normalizeSegment(queryParams.segment); + const limit = Math.min(Math.max(parseInt(queryParams.limit || '5', 10) || 5, 1), 10); + const leads = await fetchGitHubLeads(segment, limit); + const fallback = FALLBACK_LEADS[segment] || FALLBACK_LEADS.mcp; + const finalLeads = leads.length ? leads : fallback.slice(0, limit); + + return { + segment, + discovery: { + generated_at: new Date().toISOString(), + sources: ['github-search', 'pyrimid-segment-playbook'], + search_queries: SEGMENT_QUERIES[segment] || SEGMENT_QUERIES.mcp, + scoring: '35 base + GitHub stars + recent activity + segment keyword matches; capped at 95.', + }, + leads: finalLeads, + outreach_template: { + subject: 'Turn one existing tool/API into an agent-payable x402 product', + body: `You already have a ${segment} surface agents can use. Pyrimid can make one call paid, list it in the catalog, and pay affiliates only when they route real buyers.`, + }, + qualification_checklist: [ + 'Has a public endpoint, tool schema, or documented JSON output', + 'One call has clear value and cost basis', + 'Can return a standard x402 402 challenge before payment', + 'Can define vendorId, productId, endpoint, price_usdc, and affiliate_bps', + ], + }; +} + +function mcpServerAuditPayload(queryParams: Query) { + const inputUrl = queryParams.url || 'https://example.com/mcp'; + const parsed = parseHttpUrl(inputUrl); + const host = parsed?.hostname || 'unknown-host'; + const path = parsed?.pathname || '/mcp'; + const text = `${host} ${path}`.toLowerCase(); + + const paidTools = inferPaidTools(text); + const category = inferAuditCategory(text); + + return { + audit: { + url: inputUrl, + input_url_valid: Boolean(parsed), + host, + path, + category, + recommended_paid_tools: paidTools, + pricing: pricingForTools(paidTools), + x402_route_shape: { + unpaid: 'Return HTTP 402 with accepts[] including network=base, asset=USDC, maxAmountRequired, payTo, resource, and mimeType.', + paid: 'Verify X-PAYMENT or X-PAYMENT-TX, then return the original MCP tool result with payment receipt metadata.', + }, + pyrimid_catalog_metadata: { + vendorId: host.replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '').toLowerCase() || 'mcp-vendor', + productId: `${category}-mcp-tool`, + endpoint: inputUrl, + affiliateBps: 1000, + output_schema: { type: 'object', properties: { result: { type: 'object' }, receipt: { type: 'object' } } }, + }, + integration_steps: [ + 'Select one high-cost MCP tool instead of gating the entire server.', + 'Wrap that tool with an x402 402 challenge before execution.', + 'Verify Base USDC payment proof before running the expensive operation.', + 'Add vendorId/productId/affiliateBps metadata so Pyrimid can index and route commissions.', + 'Publish the paid tool schema in the MCP server card and in agents.txt or llms.txt.', + ], + risk_notes: [ + 'Do not fetch arbitrary URLs from this audit endpoint; inspect the target server separately to avoid SSRF.', + 'Avoid pricing free metadata/list tools. Gate only calls with real compute, data, or API cost.', + 'Set max_price or slippage protection in buyer examples.', + ], + }, + }; +} + +function parseHttpUrl(input: string) { + try { + const parsed = new URL(input); + if (!['http:', 'https:'].includes(parsed.protocol)) return null; + return parsed; + } catch { + return null; + } +} + +function inferPaidTools(text: string) { + if (/search|crawl|scrape|index/.test(text)) return ['search', 'crawl', 'extract', 'summarize']; + if (/market|price|signal|trading|crypto/.test(text)) return ['latest_signal', 'backtest', 'risk_report', 'market_snapshot']; + if (/enrich|lead|crm|company/.test(text)) return ['enrich_company', 'score_lead', 'export_contacts', 'dedupe']; + if (/audit|security|compliance|scan/.test(text)) return ['scan', 'audit_report', 'risk_score', 'export_findings']; + return ['search', 'enrich', 'analyze', 'export']; +} + +function inferAuditCategory(text: string) { + if (/market|price|signal|trading|crypto/.test(text)) return 'trading-data'; + if (/search|crawl|scrape|index/.test(text)) return 'search-scraping'; + if (/audit|security|compliance|scan/.test(text)) return 'security'; + if (/enrich|lead|crm|company/.test(text)) return 'lead-enrichment'; + return 'mcp-tools'; +} + +function pricingForTools(tools: string[]) { + return tools.map((tool, index) => ({ + tool, + suggested_price_usdc: [0.01, 0.05, 0.1, 0.25][Math.min(index, 3)], + reason: index === 0 + ? 'Low-friction trial call for agents.' + : 'Higher price reserved for calls with more compute, data, or external API cost.', + })); +}