@nordsym/apiclaw 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/AGENTS.md +50 -33
  2. package/README.md +22 -12
  3. package/SOUL.md +60 -19
  4. package/STATUS.md +91 -169
  5. package/convex/_generated/api.d.ts +6 -0
  6. package/convex/directCall.ts +598 -0
  7. package/convex/providers.ts +341 -26
  8. package/convex/schema.ts +87 -0
  9. package/convex/usage.ts +260 -0
  10. package/convex/waitlist.ts +55 -0
  11. package/data/combined-02-26.json +22102 -0
  12. package/data/night-expansion-02-26-06-batch2.json +1898 -0
  13. package/data/night-expansion-02-26-06-batch3.json +1410 -0
  14. package/data/night-expansion-02-26-06.json +3146 -0
  15. package/data/night-expansion-02-26-full.json +9726 -0
  16. package/data/night-expansion-02-26-v2.json +330 -0
  17. package/data/night-expansion-02-26.json +171 -0
  18. package/dist/crypto.d.ts +7 -0
  19. package/dist/crypto.d.ts.map +1 -0
  20. package/dist/crypto.js +67 -0
  21. package/dist/crypto.js.map +1 -0
  22. package/dist/execute-dynamic.d.ts +116 -0
  23. package/dist/execute-dynamic.d.ts.map +1 -0
  24. package/dist/execute-dynamic.js +456 -0
  25. package/dist/execute-dynamic.js.map +1 -0
  26. package/dist/execute.d.ts +2 -1
  27. package/dist/execute.d.ts.map +1 -1
  28. package/dist/execute.js +35 -5
  29. package/dist/execute.js.map +1 -1
  30. package/dist/index.js +33 -4
  31. package/dist/index.js.map +1 -1
  32. package/dist/registry/apis.json +2081 -3
  33. package/docs/PRD-customer-key-passthrough.md +184 -0
  34. package/landing/public/badges/available-on-apiclaw.svg +14 -0
  35. package/landing/scripts/generate-stats.js +75 -4
  36. package/landing/src/app/admin/page.tsx +1 -1
  37. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  38. package/landing/src/app/api/auth/session/route.ts +1 -1
  39. package/landing/src/app/api/auth/verify/route.ts +1 -1
  40. package/landing/src/app/api/og/route.tsx +5 -3
  41. package/landing/src/app/docs/page.tsx +5 -4
  42. package/landing/src/app/earn/page.tsx +14 -11
  43. package/landing/src/app/globals.css +16 -15
  44. package/landing/src/app/layout.tsx +2 -2
  45. package/landing/src/app/page.tsx +425 -254
  46. package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +600 -0
  47. package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +583 -0
  48. package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +301 -0
  49. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +659 -0
  50. package/landing/src/app/providers/dashboard/[apiId]/page.tsx +381 -0
  51. package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +418 -0
  52. package/landing/src/app/providers/dashboard/layout.tsx +292 -0
  53. package/landing/src/app/providers/dashboard/page.tsx +353 -290
  54. package/landing/src/app/providers/register/page.tsx +87 -10
  55. package/landing/src/components/AiClientDropdown.tsx +85 -0
  56. package/landing/src/components/ConfigHelperModal.tsx +113 -0
  57. package/landing/src/components/HeroTabs.tsx +187 -0
  58. package/landing/src/components/ShareIntegrationModal.tsx +198 -0
  59. package/landing/src/hooks/useDashboardData.ts +53 -1
  60. package/landing/src/lib/apis.json +46554 -174
  61. package/landing/src/lib/convex-client.ts +22 -3
  62. package/landing/src/lib/stats.json +4 -4
  63. package/landing/tsconfig.tsbuildinfo +1 -1
  64. package/night-expansion-02-26-06-batch2.py +368 -0
  65. package/night-expansion-02-26-06-batch3.py +299 -0
  66. package/night-expansion-02-26-06.py +756 -0
  67. package/package.json +1 -1
  68. package/scripts/bulk-add-public-apis-v2.py +418 -0
  69. package/scripts/night-expansion-02-26-v2.py +296 -0
  70. package/scripts/night-expansion-02-26.py +890 -0
  71. package/scripts/seed-complete-api.js +181 -0
  72. package/scripts/seed-demo-api.sh +44 -0
  73. package/src/crypto.ts +75 -0
  74. package/src/execute-dynamic.ts +589 -0
  75. package/src/execute.ts +41 -5
  76. package/src/index.ts +38 -4
  77. package/src/registry/apis.json +2081 -3
@@ -0,0 +1,184 @@
1
+ # PRD: Customer Key Passthrough
2
+
3
+ **Status:** DRAFT — väntar på Gustavs attestering
4
+ **Datum:** 2026-02-26
5
+ **Författare:** Symbot
6
+
7
+ ---
8
+
9
+ ## Problem
10
+
11
+ CoAccept (och liknande SaaS-providers) vill att deras slutkunder (t.ex. hyresvärdar) ska kunna styra CoAccept via AI-agent. Men:
12
+
13
+ 1. Varje slutkund har egen CoAccept API-nyckel
14
+ 2. APIClaw sparar idag EN master key per provider
15
+ 3. Ingen mekanism för "denna request kommer från Kund X"
16
+
17
+ ---
18
+
19
+ ## Mål
20
+
21
+ Möjliggöra att agent users skickar med sin egen provider-nyckel vid Direct Call — **utan sign-up friction**.
22
+
23
+ ---
24
+
25
+ ## Constraints (KRITISKA)
26
+
27
+ | Constraint | Varför |
28
+ |------------|--------|
29
+ | **Ingen sign-up för agent user** | Friktion = death. Free credits + search ska vara friktionsfritt. |
30
+ | **Search alltid gratis** | Hela databasen (19k APIs) sökbar utan login |
31
+ | **Open APIs gratis** | Discovery + open API calls = $0 |
32
+ | **Direct Call med free credits** | Agent user kan testa Direct Call utan betalning |
33
+
34
+ ---
35
+
36
+ ## Lösningsförslag
37
+
38
+ ### Option A: `customer_key` parameter (Rekommenderad)
39
+
40
+ Agent skickar med sin egen nyckel i anropet:
41
+
42
+ ```typescript
43
+ api_direct_call({
44
+ provider: "coaccept",
45
+ action: "send_reminder",
46
+ params: { tenant_id: "123" },
47
+ customer_key: "hyresvärdens-coaccept-api-key" // <-- NY
48
+ })
49
+ ```
50
+
51
+ **Flow:**
52
+ 1. Agent anropar med `customer_key`
53
+ 2. APIClaw använder `customer_key` istället för master key
54
+ 3. CoAccept ser anropet som vanlig API-request från sin kund
55
+ 4. APIClaw loggar usage (för analytics, ej billing på kundens nyckel)
56
+
57
+ **Fördelar:**
58
+ - Ingen sign-up behövs
59
+ - Agent user kontrollerar sin egen nyckel
60
+ - Provider (CoAccept) ser sin vanliga kund
61
+ - Bakåtkompatibelt (utan `customer_key` = master key används)
62
+
63
+ **Nackdelar:**
64
+ - Nyckel skickas i varje request (måste vara över HTTPS)
65
+ - Agent user måste ha/få sin nyckel från providern
66
+
67
+ ---
68
+
69
+ ### Option B: Registered customer keys (mer enterprise)
70
+
71
+ Agent user registrerar sin nyckel en gång i APIClaw dashboard, får tillbaka ett `customer_id`:
72
+
73
+ ```typescript
74
+ api_direct_call({
75
+ provider: "coaccept",
76
+ action: "send_reminder",
77
+ params: { tenant_id: "123" },
78
+ customer_id: "cust_abc123" // Referens till sparad nyckel
79
+ })
80
+ ```
81
+
82
+ **Fördelar:**
83
+ - Nyckel skickas inte i varje request
84
+ - Kan bygga usage tracking per customer
85
+ - Möjliggör billing per customer senare
86
+
87
+ **Nackdelar:**
88
+ - Kräver sign-up/registration (BRYTER CONSTRAINT)
89
+ - Mer komplext
90
+
91
+ ---
92
+
93
+ ## Rekommendation
94
+
95
+ **Option A först.**
96
+
97
+ Enklast, friktionsfritt, löser CoAccept-caset direkt. Option B kan byggas senare som "enterprise mode" för de som vill.
98
+
99
+ ---
100
+
101
+ ## Implementation (Option A)
102
+
103
+ ### 1. MCP Tool Update
104
+
105
+ ```typescript
106
+ // Befintlig
107
+ api_direct_call(provider, action, params)
108
+
109
+ // Ny
110
+ api_direct_call(provider, action, params, customer_key?)
111
+ ```
112
+
113
+ ### 2. execute-dynamic.ts
114
+
115
+ ```typescript
116
+ // I executeAction():
117
+ const apiKey = args.customer_key || config.encryptedMasterKey;
118
+ // Använd apiKey för auth header
119
+ ```
120
+
121
+ ### 3. Provider Config (optional flag)
122
+
123
+ Provider kan välja att:
124
+ - `allow_customer_keys: true` — tillåt passthrough
125
+ - `require_customer_keys: true` — ENDAST customer keys (ingen master)
126
+
127
+ CoAccept skulle sätta `require_customer_keys: true` eftersom varje hyresvärd måste autentisera sig själv.
128
+
129
+ ### 4. Logging
130
+
131
+ Logga `customer_key_used: true/false` i usage logs (inte själva nyckeln).
132
+
133
+ ---
134
+
135
+ ## Vad som INTE ändras
136
+
137
+ - ✅ Search — fortfarande gratis, ingen login
138
+ - ✅ Open APIs — fortfarande gratis
139
+ - ✅ Free credits — fortfarande tillgängliga
140
+ - ✅ Master key Direct Call — funkar som innan (default)
141
+
142
+ ---
143
+
144
+ ## Scope för V1
145
+
146
+ | Ingår | Ingår ej |
147
+ |-------|----------|
148
+ | `customer_key` parameter i MCP tool | Dashboard för att spara nycklar |
149
+ | execute-dynamic.ts uppdatering | Per-customer billing |
150
+ | Provider flag `allow_customer_keys` | Customer registration flow |
151
+ | Usage logging | |
152
+
153
+ ---
154
+
155
+ ## Effort Estimate
156
+
157
+ | Task | Tid |
158
+ |------|-----|
159
+ | MCP tool schema update | 30 min |
160
+ | execute-dynamic.ts logic | 1h |
161
+ | Provider config flag (Convex + UI) | 1h |
162
+ | Testing | 30 min |
163
+ | **Total** | ~3h |
164
+
165
+ ---
166
+
167
+ ## Öppna frågor
168
+
169
+ 1. **Ska vi validera customer_key format?** (Nej — provider's API gör det)
170
+ 2. **Rate limits på customer_key?** (V1: Nej. Providern hanterar det.)
171
+ 3. **Billing?** (V1: Ingen. Agent user betalar providern direkt.)
172
+
173
+ ---
174
+
175
+ ## Attestering
176
+
177
+ - [ ] Gustav har läst och godkänt
178
+ - [ ] Scope bekräftat
179
+ - [ ] Constraints respekterade
180
+ - [ ] Klart för implementation
181
+
182
+ ---
183
+
184
+ *Väntar på din feedback, Gustav.* 🦞
@@ -0,0 +1,14 @@
1
+ <svg width="220" height="44" viewBox="0 0 220 44" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#0a0a0a"/>
5
+ <stop offset="100%" style="stop-color:#1a1a1a"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="220" height="44" rx="8" fill="url(#bgGrad)" stroke="#333" stroke-width="1"/>
9
+ <text x="16" y="28" fill="#ef4444" font-family="system-ui, -apple-system, sans-serif" font-size="18">🦞</text>
10
+ <text x="44" y="27" fill="white" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">Available on</text>
11
+ <text x="44" y="27" fill="white" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500">
12
+ <tspan x="130" font-weight="700" fill="#ef4444">APIClaw</tspan>
13
+ </text>
14
+ </svg>
@@ -2,6 +2,75 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
 
5
+ // Category mapping (consolidate similar ones)
6
+ const categoryMap = {
7
+ 'AI': 'AI & ML',
8
+ 'AI & ML': 'AI & ML',
9
+ 'AI/ML': 'AI & ML',
10
+ 'Machine Learning': 'AI & ML',
11
+ 'Authentication': 'Auth & Security',
12
+ 'Auth': 'Auth & Security',
13
+ 'Security': 'Auth & Security',
14
+ 'Payment': 'Payments',
15
+ 'Payments': 'Payments',
16
+ 'Finance': 'Finance',
17
+ 'Cryptocurrency': 'Finance',
18
+ 'Currency': 'Finance',
19
+ 'Health': 'Health & Fitness',
20
+ 'Health & Fitness': 'Health & Fitness',
21
+ 'Healthcare': 'Health & Fitness',
22
+ 'Fitness': 'Health & Fitness',
23
+ 'Social': 'Social & Communication',
24
+ 'Social Media': 'Social & Communication',
25
+ 'Communication': 'Social & Communication',
26
+ 'Messaging': 'Social & Communication',
27
+ 'Data': 'Data & Analytics',
28
+ 'Data & Analytics': 'Data & Analytics',
29
+ 'Analytics': 'Data & Analytics',
30
+ 'Big Data': 'Data & Analytics',
31
+ 'Cloud': 'Cloud & Infrastructure',
32
+ 'Cloud Storage': 'Cloud & Infrastructure',
33
+ 'Infrastructure': 'Cloud & Infrastructure',
34
+ 'DevOps': 'Development',
35
+ 'Development': 'Development',
36
+ 'Development Tools': 'Development',
37
+ 'Testing': 'Development',
38
+ 'E-commerce': 'Commerce',
39
+ 'Commerce': 'Commerce',
40
+ 'Shopping': 'Commerce',
41
+ 'Games': 'Entertainment',
42
+ 'Gaming': 'Entertainment',
43
+ 'Entertainment': 'Entertainment',
44
+ 'Music': 'Entertainment',
45
+ 'Video': 'Media',
46
+ 'Media': 'Media',
47
+ 'Photography': 'Media',
48
+ 'News': 'News & Media',
49
+ 'News & information': 'News & Media',
50
+ 'Geo': 'Location & Maps',
51
+ 'Geocoding': 'Location & Maps',
52
+ 'Geolocation': 'Location & Maps',
53
+ 'Maps': 'Location & Maps',
54
+ 'Location': 'Location & Maps',
55
+ 'Spatial': 'Location & Maps',
56
+ 'Transportation': 'Travel & Transport',
57
+ 'Travel': 'Travel & Transport',
58
+ 'Logistics': 'Travel & Transport',
59
+ 'Delivery-Tracking': 'Travel & Transport',
60
+ 'Carsharing': 'Travel & Transport',
61
+ 'Document': 'Documents & Files',
62
+ 'Documents': 'Documents & Files',
63
+ 'File Storage and Manipulation': 'Documents & Files',
64
+ 'Web Scraping': 'Search & Scraping',
65
+ 'Search': 'Search & Scraping',
66
+ 'Text Analysis': 'Language & Text',
67
+ 'Language': 'Language & Text',
68
+ 'Dictionary': 'Language & Text',
69
+ 'Vision Analysis': 'AI & ML',
70
+ 'Art & Design': 'Design',
71
+ 'Design': 'Design',
72
+ };
73
+
5
74
  // Try local copy first (for Vercel), then parent directory (for local dev)
6
75
  const localRegistryPath = path.join(__dirname, '../src/lib/apis.json');
7
76
  const parentRegistryPath = path.join(__dirname, '../../src/registry/apis.json');
@@ -11,11 +80,13 @@ const outputPath = path.join(__dirname, '../src/lib/stats.json');
11
80
  try {
12
81
  const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
13
82
 
14
- const categories = [...new Set(registry.apis.map(api => api.category))];
83
+ // Consolidate categories using the mapping
84
+ const consolidatedCategories = registry.apis.map(api => categoryMap[api.category] || api.category);
85
+ const uniqueCategories = [...new Set(consolidatedCategories)];
15
86
 
16
87
  const stats = {
17
88
  apiCount: registry.count,
18
- categoryCount: categories.length,
89
+ categoryCount: uniqueCategories.length,
19
90
  lastUpdated: registry.lastUpdated || new Date().toISOString().split('T')[0],
20
91
  generatedAt: new Date().toISOString()
21
92
  };
@@ -32,8 +103,8 @@ try {
32
103
  console.error('Failed to generate stats:', err);
33
104
  // Write fallback stats
34
105
  const fallback = {
35
- apiCount: 4518,
36
- categoryCount: 93,
106
+ apiCount: 19176,
107
+ categoryCount: 58,
37
108
  lastUpdated: new Date().toISOString().split('T')[0],
38
109
  generatedAt: new Date().toISOString()
39
110
  };
@@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
4
4
  import { Shield, Users, Zap, TrendingUp, Check, X, Clock, ExternalLink, RefreshCw, Eye } from "lucide-react";
5
5
  import Link from "next/link";
6
6
 
7
- const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
7
+ const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://adventurous-avocet-799.convex.cloud";
8
8
 
9
9
  interface Stats {
10
10
  totalProviders: number;
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
 
3
- const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
3
+ const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://adventurous-avocet-799.convex.cloud";
4
4
 
5
5
  export async function POST(req: NextRequest) {
6
6
  try {
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
 
3
- const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
3
+ const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://adventurous-avocet-799.convex.cloud";
4
4
 
5
5
  export async function POST(req: NextRequest) {
6
6
  try {
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
 
3
- const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
3
+ const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://adventurous-avocet-799.convex.cloud";
4
4
 
5
5
  export async function POST(req: NextRequest) {
6
6
  try {
@@ -52,17 +52,19 @@ export async function GET() {
52
52
  <div
53
53
  style={{
54
54
  display: 'flex',
55
- gap: 40,
55
+ gap: 32,
56
56
  marginTop: 40,
57
57
  color: '#ef4444',
58
- fontSize: 28,
58
+ fontSize: 26,
59
59
  }}
60
60
  >
61
61
  <span>{statsData.apiCount.toLocaleString()}+ APIs</span>
62
62
  <span>•</span>
63
- <span>{statsData.categoryCount}+ Categories</span>
63
+ <span>{statsData.categoryCount} Categories</span>
64
64
  <span>•</span>
65
65
  <span>MCP Native</span>
66
+ <span>•</span>
67
+ <span>Direct Call</span>
66
68
  </div>
67
69
  <div
68
70
  style={{
@@ -35,8 +35,9 @@ export default function DocsPage() {
35
35
  </Link>
36
36
  <nav className="flex items-center gap-4 md:gap-6">
37
37
  <Link href="/" className="hidden sm:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">Home</Link>
38
- <Link href="/providers" className="hidden md:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">Providers</Link>
39
- <Link href="/earn" className="hidden md:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">Earn Credits</Link>
38
+ <span className="hidden md:block text-[var(--accent)] font-medium text-sm md:text-base">Docs</span>
39
+ <Link href="/providers/dashboard" className="hidden md:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">Providers</Link>
40
+ <Link href="/earn" className="hidden md:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">Earn</Link>
40
41
  <a
41
42
  href="https://github.com/nordsym/apiclaw"
42
43
  target="_blank"
@@ -192,7 +193,7 @@ export default function DocsPage() {
192
193
  {/* discover_apis */}
193
194
  <div className="bg-[var(--surface)] border border-[var(--border)] rounded-xl p-6">
194
195
  <h3 className="text-lg font-mono text-[var(--accent)] mb-2">discover_apis</h3>
195
- <p className="text-[var(--text-secondary)] mb-4">Search 16,000+ APIs using natural language.</p>
196
+ <p className="text-[var(--text-secondary)] mb-4">Search 19,000+ APIs using natural language.</p>
196
197
  <pre className="bg-[var(--surface-elevated)] border border-[var(--border)] rounded-lg p-4 overflow-x-auto">
197
198
  <code className="text-sm">
198
199
  <span className="text-purple-600 dark:text-purple-400">discover_apis</span>({'{\n'}
@@ -310,7 +311,7 @@ export default function DocsPage() {
310
311
  </li>
311
312
  <li>
312
313
  <strong>Email:</strong>{' '}
313
- <a href="mailto:gustav@nordsym.com" className="text-[var(--accent)] hover:underline">gustav@nordsym.com</a>
314
+ <a href="mailto:symbot@nordsym.com" className="text-[var(--accent)] hover:underline">symbot@nordsym.com</a>
314
315
  </li>
315
316
  </ul>
316
317
  </div>
@@ -13,9 +13,9 @@ const EARN_CHANNELS = [
13
13
  credits: 500,
14
14
  cta: 'Star Repository',
15
15
  href: 'https://github.com/nordsym/apiclaw',
16
- color: 'from-amber-500/20 to-yellow-500/10',
17
- borderColor: 'hover:border-amber-500/50',
18
- iconColor: 'text-amber-500',
16
+ color: 'from-red-500/20 to-orange-500/10',
17
+ borderColor: 'hover:border-red-500/50',
18
+ iconColor: 'text-red-500',
19
19
  },
20
20
  {
21
21
  id: 'twitter',
@@ -25,9 +25,9 @@ const EARN_CHANNELS = [
25
25
  credits: 250,
26
26
  cta: 'Follow Us',
27
27
  href: 'https://x.com/NordSym',
28
- color: 'from-sky-500/20 to-sky-600/10',
29
- borderColor: 'hover:border-sky-500/50',
30
- iconColor: 'text-sky-500',
28
+ color: 'from-red-400/20 to-rose-500/10',
29
+ borderColor: 'hover:border-red-400/50',
30
+ iconColor: 'text-red-400',
31
31
  },
32
32
  {
33
33
  id: 'newsletter',
@@ -37,9 +37,9 @@ const EARN_CHANNELS = [
37
37
  credits: 100,
38
38
  cta: 'Subscribe',
39
39
  href: '#newsletter',
40
- color: 'from-emerald-500/20 to-emerald-600/10',
41
- borderColor: 'hover:border-emerald-500/50',
42
- iconColor: 'text-emerald-500',
40
+ color: 'from-rose-500/20 to-red-500/10',
41
+ borderColor: 'hover:border-rose-500/50',
42
+ iconColor: 'text-rose-500',
43
43
  },
44
44
  {
45
45
  id: 'referral',
@@ -120,12 +120,15 @@ export default function EarnPage() {
120
120
  <Link href="/" className="hidden sm:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">
121
121
  Home
122
122
  </Link>
123
- <Link href="/docs" className="text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">
123
+ <Link href="/docs" className="hidden md:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">
124
124
  Docs
125
125
  </Link>
126
- <Link href="/providers" className="hidden md:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">
126
+ <Link href="/providers/dashboard" className="hidden md:block text-[var(--text-secondary)] hover:text-[var(--accent)] transition-colors text-sm md:text-base">
127
127
  Providers
128
128
  </Link>
129
+ <span className="text-[var(--accent)] font-medium text-sm md:text-base">
130
+ Earn
131
+ </span>
129
132
  <button
130
133
  onClick={toggleTheme}
131
134
  className="p-2 rounded-lg bg-[var(--surface)] border border-[var(--border)] hover:border-[var(--accent)]/50 transition-colors"
@@ -471,36 +471,37 @@ input[type="email"]::placeholder {
471
471
 
472
472
  /* Code preview */
473
473
  .code-preview {
474
- background: linear-gradient(180deg, #141414 0%, #0d0d0d 100%);
475
- border: 1px solid rgba(239, 68, 68, 0.3);
474
+ background: linear-gradient(180deg, #0f1419 0%, #080b0e 100%);
475
+ border: 1px solid rgba(0, 212, 255, 0.3);
476
476
  border-radius: 14px;
477
477
  overflow: hidden;
478
478
  font-family: 'JetBrains Mono', monospace;
479
479
  font-size: 13px;
480
480
  box-shadow:
481
- 0 0 40px rgba(239, 68, 68, 0.25),
482
- 0 0 80px rgba(239, 68, 68, 0.1),
481
+ 0 0 40px rgba(0, 212, 255, 0.15),
482
+ 0 0 80px rgba(0, 212, 255, 0.05),
483
483
  0 8px 32px rgba(0, 0, 0, 0.5),
484
484
  inset 0 1px 0 rgba(255, 255, 255, 0.05);
485
485
  transition: all 0.3s ease;
486
486
  }
487
487
 
488
488
  .code-preview:hover {
489
- border-color: rgba(239, 68, 68, 0.5);
490
- transform: translateY(-4px);
489
+ border-color: rgba(0, 212, 255, 0.5);
490
+ transform: translateY(-2px);
491
491
  box-shadow:
492
- 0 0 60px rgba(239, 68, 68, 0.35),
493
- 0 0 100px rgba(239, 68, 68, 0.15),
492
+ 0 0 60px rgba(0, 212, 255, 0.25),
493
+ 0 0 100px rgba(0, 212, 255, 0.1),
494
494
  0 16px 48px rgba(0, 0, 0, 0.6),
495
495
  inset 0 1px 0 rgba(255, 255, 255, 0.08);
496
496
  }
497
497
 
498
498
  .code-preview-header {
499
499
  padding: 10px 16px;
500
- background: #141414;
501
- border-bottom: 1px solid #262626;
502
- color: #737373;
500
+ background: rgba(0, 212, 255, 0.05);
501
+ border-bottom: 1px solid rgba(0, 212, 255, 0.15);
502
+ color: #00D4FF;
503
503
  font-size: 12px;
504
+ font-weight: 500;
504
505
  }
505
506
 
506
507
  .code-preview-body {
@@ -514,10 +515,10 @@ input[type="email"]::placeholder {
514
515
  }
515
516
 
516
517
  .code-comment { color: #6b7280; }
517
- .code-string { color: #fbbf24; }
518
- .code-keyword { color: #f472b6; }
519
- .code-function { color: #60a5fa; }
520
- .code-property { color: #34d399; }
518
+ .code-string { color: #00D4FF; }
519
+ .code-keyword { color: #9370DB; }
520
+ .code-function { color: #FF8C00; }
521
+ .code-property { color: #00D4FF; }
521
522
 
522
523
  /* Logo animation */
523
524
  .logo-float {
@@ -18,13 +18,13 @@ export const metadata: Metadata = {
18
18
  },
19
19
  openGraph: {
20
20
  title: "APIClaw | The API Layer for AI Agents",
21
- description: "Agents discover and evaluate APIs via MCP. Structured data. Ranked results. No more googling.",
21
+ description: `${statsData.apiCount.toLocaleString()}+ APIs. MCP native. Direct Call: providers self-serve their APIs for AI agents.`,
22
22
  type: "website",
23
23
  siteName: "APIClaw",
24
24
  locale: "en_US",
25
25
  images: [
26
26
  {
27
- url: "/api/og",
27
+ url: "/api/og?v=2",
28
28
  width: 1200,
29
29
  height: 630,
30
30
  alt: "APIClaw - The API layer for AI agents",