#!/usr/bin/env npx tsx /** * REST API Template: Next.js App Router * Monetize any Next.js API route with per-call billing. * * This template uses settlegridMiddleware() — the REST equivalent of sg.wrap(). * While MCP templates use sg.wrap() to bill function calls, REST templates use * settlegridMiddleware() to bill HTTP requests. Same billing pipeline, different * integration pattern. * * Works with all 10 SettleGrid protocols — protocol detection is automatic. * * Setup: * 1. npm install @settlegrid/mcp * 2. Set SETTLEGRID_API_KEY in your env * 3. Register your tool at settlegrid.ai/dashboard/tools * 4. Place this file at app/api/company/route.ts * * Pricing: 2 cents for GET (lookup), 5 cents for POST (enrichment) * - Clearbit/PeopleDataLabs costs ~$0.01/call * - 5 cents gives you ~5x margin on enrichment * - 2 cents for simple cached lookups is competitive with market rates * * Revenue: You keep 95-100% (100% on Free tier, 95% on paid tiers) */ import { NextRequest, NextResponse } from 'next/server' import { settlegridMiddleware } from '@settlegrid/mcp/rest' // ── SettleGrid Billing Setup ──────────────────────────────────────────────── // Initialize middleware with your tool slug and per-method pricing. // The slug must match what you registered at settlegrid.ai/dashboard/tools. const billing = settlegridMiddleware({ toolSlug: 'my-company-api', // Replace with your tool slug pricing: { defaultCostCents: 5, methods: { lookup: { costCents: 2, displayName: 'Company Lookup' }, enrich: { costCents: 5, displayName: 'Company Enrichment' }, }, }, }) // ── Types ─────────────────────────────────────────────────────────────────── interface CompanyInfo { name: string domain: string industry: string employeeCount: number | null founded: number | null description: string } interface EnrichmentResult extends CompanyInfo { techStack: string[] socialLinks: Record fundingTotal: string | null } // ── Implementation ────────────────────────────────────────────────────────── async function lookupCompany(domain: string): Promise { if (!domain || domain.trim().length === 0) { throw new Error('Domain parameter is required') } const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '') if (!/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(cleanDomain)) { throw new Error('Invalid domain format') } // Replace with your actual data provider (Clearbit, PeopleDataLabs, etc.) const response = await fetch( `https://api.your-data-provider.com/v1/company?domain=${encodeURIComponent(cleanDomain)}`, { headers: { Authorization: `Bearer ${process.env.DATA_API_KEY!}` } } ) if (!response.ok) { throw new Error(`Data provider returned ${response.status}: ${response.statusText}`) } const data = await response.json() return { name: data.name ?? 'Unknown', domain: cleanDomain, industry: data.industry ?? 'Unknown', employeeCount: data.metrics?.employees ?? null, founded: data.foundedYear ?? null, description: data.description ?? '', } } async function enrichCompany(domain: string): Promise { const base = await lookupCompany(domain) // Deeper enrichment — tech stack, social, funding const enrichRes = await fetch( `https://api.your-data-provider.com/v1/company/enrich?domain=${encodeURIComponent(domain)}`, { headers: { Authorization: `Bearer ${process.env.DATA_API_KEY!}` } } ) const enrichData = enrichRes.ok ? await enrichRes.json() : {} return { ...base, techStack: enrichData.techStack ?? [], socialLinks: enrichData.social ?? {}, fundingTotal: enrichData.funding?.total ?? null, } } // ── GET: Lightweight lookup (2 cents) ─────────────────────────────────────── // Usage: GET /api/company?domain=stripe.com // Header: x-api-key: sg_live_your_key_here export async function GET(request: NextRequest) { // Run billing — extracts API key from x-api-key header, validates credits, // and meters the call. Throws on invalid key or insufficient balance. await billing(request, 'lookup') const domain = request.nextUrl.searchParams.get('domain') if (!domain) { return NextResponse.json({ error: 'Missing ?domain= parameter' }, { status: 400 }) } try { const company = await lookupCompany(domain) return NextResponse.json({ data: company }) } catch (err) { const message = err instanceof Error ? err.message : 'Internal error' return NextResponse.json({ error: message }, { status: 500 }) } } // ── POST: Full enrichment (5 cents) ──────────────────────────────────────── // Usage: POST /api/company { "domain": "stripe.com" } // Header: x-api-key: sg_live_your_key_here export async function POST(request: NextRequest) { await billing(request, 'enrich') let body: { domain?: string } try { body = await request.json() } catch { return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }) } if (!body.domain) { return NextResponse.json({ error: 'Missing "domain" in request body' }, { status: 400 }) } try { const enriched = await enrichCompany(body.domain) return NextResponse.json({ data: enriched }) } catch (err) { const message = err instanceof Error ? err.message : 'Internal error' return NextResponse.json({ error: message }, { status: 500 }) } }