@nordsym/apiclaw 1.0.0 → 1.1.0

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 (154) hide show
  1. package/AGENTS.md +74 -0
  2. package/HEARTBEAT.md +4 -0
  3. package/IDENTITY.md +22 -0
  4. package/README.md +197 -202
  5. package/SOUL.md +36 -0
  6. package/STATUS.md +237 -0
  7. package/TOOLS.md +36 -0
  8. package/USER.md +17 -0
  9. package/{backend/convex → convex}/_generated/api.d.ts +6 -6
  10. package/convex/credits.ts +211 -0
  11. package/convex/http.ts +490 -0
  12. package/convex/providers.ts +516 -0
  13. package/convex/purchases.ts +183 -0
  14. package/convex/schema.ts +180 -0
  15. package/convex.json +3 -0
  16. package/dist/credentials.d.ts +19 -0
  17. package/dist/credentials.d.ts.map +1 -0
  18. package/dist/credentials.js +158 -0
  19. package/dist/credentials.js.map +1 -0
  20. package/dist/credits.d.ts +14 -11
  21. package/dist/credits.d.ts.map +1 -1
  22. package/dist/credits.js +151 -99
  23. package/dist/credits.js.map +1 -1
  24. package/dist/discovery.d.ts +7 -16
  25. package/dist/discovery.d.ts.map +1 -1
  26. package/dist/discovery.js +33 -40
  27. package/dist/discovery.js.map +1 -1
  28. package/dist/execute.d.ts +19 -0
  29. package/dist/execute.d.ts.map +1 -0
  30. package/dist/execute.js +285 -0
  31. package/dist/execute.js.map +1 -0
  32. package/dist/index.js +106 -30
  33. package/dist/index.js.map +1 -1
  34. package/dist/proxy.d.ts +6 -0
  35. package/dist/proxy.d.ts.map +1 -0
  36. package/dist/proxy.js +19 -0
  37. package/dist/proxy.js.map +1 -0
  38. package/dist/registry/apis.json +95362 -202
  39. package/dist/registry/apis_expanded.json +100853 -0
  40. package/dist/stripe.d.ts +68 -0
  41. package/dist/stripe.d.ts.map +1 -0
  42. package/dist/stripe.js +196 -0
  43. package/dist/stripe.js.map +1 -0
  44. package/dist/test.d.ts +3 -2
  45. package/dist/test.d.ts.map +1 -1
  46. package/dist/test.js +105 -75
  47. package/dist/test.js.map +1 -1
  48. package/dist/types.d.ts +0 -28
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/webhook.d.ts +2 -0
  51. package/dist/webhook.d.ts.map +1 -0
  52. package/dist/webhook.js +90 -0
  53. package/dist/webhook.js.map +1 -0
  54. package/landing/DESIGN.md +343 -0
  55. package/landing/package-lock.json +1190 -40
  56. package/landing/package.json +5 -2
  57. package/landing/public/android-chrome-192x192.png +0 -0
  58. package/landing/public/android-chrome-512x512.png +0 -0
  59. package/landing/public/apple-touch-icon.png +0 -0
  60. package/landing/public/demo.gif +0 -0
  61. package/landing/public/demo.mp4 +0 -0
  62. package/landing/public/favicon-16x16.png +0 -0
  63. package/landing/public/favicon-32x32.png +0 -0
  64. package/landing/public/favicon.ico +0 -0
  65. package/landing/public/favicon.svg +3 -0
  66. package/landing/public/icon.svg +47 -0
  67. package/landing/public/logo-mono.svg +37 -0
  68. package/landing/public/logo-simple.svg +45 -0
  69. package/landing/public/logo.svg +84 -0
  70. package/landing/public/og-image.png +0 -0
  71. package/landing/public/og-template.html +184 -0
  72. package/landing/public/site.webmanifest +31 -0
  73. package/landing/scripts/generate-assets.js +284 -0
  74. package/landing/scripts/generate-pngs.js +48 -0
  75. package/landing/scripts/generate-stats.js +42 -0
  76. package/landing/src/app/admin/page.tsx +348 -0
  77. package/landing/src/app/api/auth/magic-link/route.ts +73 -0
  78. package/landing/src/app/api/auth/session/route.ts +38 -0
  79. package/landing/src/app/api/auth/verify/route.ts +43 -0
  80. package/landing/src/app/api/og/route.tsx +74 -0
  81. package/landing/src/app/globals.css +439 -100
  82. package/landing/src/app/layout.tsx +37 -9
  83. package/landing/src/app/page.tsx +640 -552
  84. package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
  85. package/landing/src/app/providers/dashboard/page.tsx +589 -0
  86. package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
  87. package/landing/src/app/providers/layout.tsx +14 -0
  88. package/landing/src/app/providers/page.tsx +402 -0
  89. package/landing/src/app/providers/register/page.tsx +670 -0
  90. package/landing/src/components/ProviderDashboard.tsx +794 -0
  91. package/landing/src/hooks/useDashboardData.ts +99 -0
  92. package/landing/src/lib/apis.json +116054 -0
  93. package/landing/src/lib/convex-client.ts +106 -0
  94. package/landing/src/lib/mock-data.ts +285 -0
  95. package/landing/src/lib/stats.json +6 -0
  96. package/landing/tailwind.config.ts +12 -11
  97. package/landing/tsconfig.tsbuildinfo +1 -0
  98. package/package.json +21 -20
  99. package/scripts/SYMBOT-FIX.md +238 -0
  100. package/scripts/demo-simulation.py +177 -0
  101. package/scripts/expand-more.py +502 -0
  102. package/scripts/expand-registry.py +434 -0
  103. package/scripts/history-sanitizer.ts +272 -0
  104. package/scripts/mass-scrape.py +1308 -0
  105. package/scripts/sync-and-deploy.sh +36 -0
  106. package/src/credentials.ts +177 -0
  107. package/src/credits.ts +190 -122
  108. package/src/discovery.ts +45 -58
  109. package/src/execute.ts +350 -0
  110. package/src/index.ts +113 -31
  111. package/src/proxy.ts +24 -0
  112. package/src/registry/apis.json +95362 -202
  113. package/src/registry/apis_expanded.json +100853 -0
  114. package/src/stripe.ts +243 -0
  115. package/src/test.ts +127 -89
  116. package/src/types.ts +0 -34
  117. package/src/webhook.ts +107 -0
  118. package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
  119. package/BRIEFING.md +0 -30
  120. package/backend/convex/apiKeys.ts +0 -75
  121. package/backend/convex/purchases.ts +0 -74
  122. package/backend/convex/schema.ts +0 -45
  123. package/backend/convex/transactions.ts +0 -57
  124. package/backend/convex/users.ts +0 -94
  125. package/backend/package-lock.json +0 -521
  126. package/backend/package.json +0 -15
  127. package/dist/registry/parse_apis.py +0 -146
  128. package/dist/revenuecat.d.ts +0 -61
  129. package/dist/revenuecat.d.ts.map +0 -1
  130. package/dist/revenuecat.js +0 -166
  131. package/dist/revenuecat.js.map +0 -1
  132. package/dist/webhooks/revenuecat.d.ts +0 -48
  133. package/dist/webhooks/revenuecat.d.ts.map +0 -1
  134. package/dist/webhooks/revenuecat.js +0 -119
  135. package/dist/webhooks/revenuecat.js.map +0 -1
  136. package/docs/revenuecat-setup.md +0 -89
  137. package/landing/src/app/api/keys/route.ts +0 -71
  138. package/landing/src/app/api/log/route.ts +0 -37
  139. package/landing/src/app/api/stats/route.ts +0 -37
  140. package/landing/src/app/page.tsx.bak +0 -567
  141. package/landing/src/components/AddKeyModal.tsx +0 -159
  142. package/newsletter-template.html +0 -71
  143. package/outreach/OUTREACH-SYSTEM.md +0 -211
  144. package/outreach/email-template.html +0 -179
  145. package/outreach/targets.md +0 -133
  146. package/src/registry/parse_apis.py +0 -146
  147. package/src/revenuecat.ts +0 -239
  148. package/src/webhooks/revenuecat.ts +0 -187
  149. /package/{backend/convex → convex}/README.md +0 -0
  150. /package/{backend/convex → convex}/_generated/api.js +0 -0
  151. /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
  152. /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
  153. /package/{backend/convex → convex}/_generated/server.js +0 -0
  154. /package/{backend/convex → convex}/tsconfig.json +0 -0
package/convex/http.ts ADDED
@@ -0,0 +1,490 @@
1
+ import { httpRouter } from "convex/server";
2
+ import { httpAction } from "./_generated/server";
3
+ import { api, internal } from "./_generated/api";
4
+
5
+ const http = httpRouter();
6
+
7
+ // Provider catalog
8
+ const PROVIDERS = {
9
+ "46elks": {
10
+ name: "46elks",
11
+ description: "SMS API for EU/Nordics. GDPR compliant.",
12
+ category: "sms",
13
+ pricing: "~$0.035/SMS",
14
+ regions: ["EU", "Nordic"],
15
+ tags: ["sms", "eu", "gdpr", "nordic"],
16
+ },
17
+ twilio: {
18
+ name: "Twilio",
19
+ description: "SMS and Voice API. Global coverage.",
20
+ category: "sms",
21
+ pricing: "~$0.04/SMS, ~$0.01/min voice",
22
+ regions: ["Global"],
23
+ tags: ["sms", "voice", "global"],
24
+ },
25
+ resend: {
26
+ name: "Resend",
27
+ description: "Modern email API. Developer-friendly.",
28
+ category: "email",
29
+ pricing: "~$0.001/email",
30
+ regions: ["Global"],
31
+ tags: ["email", "transactional"],
32
+ },
33
+ brave_search: {
34
+ name: "Brave Search",
35
+ description: "Privacy-focused web search API.",
36
+ category: "search",
37
+ pricing: "~$0.005/search",
38
+ regions: ["Global"],
39
+ tags: ["search", "web", "privacy"],
40
+ },
41
+ openrouter: {
42
+ name: "OpenRouter",
43
+ description: "Multi-model LLM API. Access GPT, Claude, Llama, etc.",
44
+ category: "llm",
45
+ pricing: "Varies by model",
46
+ regions: ["Global"],
47
+ tags: ["llm", "ai", "gpt", "claude"],
48
+ },
49
+ elevenlabs: {
50
+ name: "ElevenLabs",
51
+ description: "Text-to-speech API. High quality voices.",
52
+ category: "tts",
53
+ pricing: "~$0.0003/char",
54
+ regions: ["Global"],
55
+ tags: ["tts", "voice", "audio"],
56
+ },
57
+ } as const;
58
+
59
+ // CORS headers
60
+ const corsHeaders = {
61
+ "Access-Control-Allow-Origin": "*",
62
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
63
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
64
+ };
65
+
66
+ // Helper for JSON responses
67
+ function jsonResponse(data: unknown, status = 200) {
68
+ return new Response(JSON.stringify(data), {
69
+ status,
70
+ headers: { "Content-Type": "application/json", ...corsHeaders },
71
+ });
72
+ }
73
+
74
+ // OPTIONS handler for CORS
75
+ http.route({
76
+ path: "/api/discover",
77
+ method: "OPTIONS",
78
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
79
+ });
80
+
81
+ http.route({
82
+ path: "/api/details",
83
+ method: "OPTIONS",
84
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
85
+ });
86
+
87
+ http.route({
88
+ path: "/api/balance",
89
+ method: "OPTIONS",
90
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
91
+ });
92
+
93
+ http.route({
94
+ path: "/api/purchase",
95
+ method: "OPTIONS",
96
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
97
+ });
98
+
99
+ http.route({
100
+ path: "/admin/grant-credits",
101
+ method: "OPTIONS",
102
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
103
+ });
104
+
105
+ // Discover APIs
106
+ http.route({
107
+ path: "/api/discover",
108
+ method: "POST",
109
+ handler: httpAction(async (ctx, request) => {
110
+ try {
111
+ const body = await request.json();
112
+ const query = (body.query || "").toLowerCase();
113
+
114
+ const results = Object.entries(PROVIDERS)
115
+ .filter(([id, provider]) => {
116
+ if (!query) return true;
117
+ return (
118
+ provider.name.toLowerCase().includes(query) ||
119
+ provider.description.toLowerCase().includes(query) ||
120
+ provider.category.toLowerCase().includes(query) ||
121
+ provider.tags.some((tag) => tag.includes(query))
122
+ );
123
+ })
124
+ .map(([id, provider]) => ({
125
+ providerId: id,
126
+ ...provider,
127
+ }));
128
+
129
+ return jsonResponse({ providers: results, total: results.length });
130
+ } catch (e) {
131
+ return jsonResponse({ error: "Invalid request" }, 400);
132
+ }
133
+ }),
134
+ });
135
+
136
+ // Get provider details
137
+ http.route({
138
+ path: "/api/details",
139
+ method: "POST",
140
+ handler: httpAction(async (ctx, request) => {
141
+ try {
142
+ const body = await request.json();
143
+ const { providerId } = body;
144
+
145
+ if (!providerId) {
146
+ return jsonResponse({ error: "providerId required" }, 400);
147
+ }
148
+
149
+ const provider = PROVIDERS[providerId as keyof typeof PROVIDERS];
150
+ if (!provider) {
151
+ return jsonResponse({ error: "Provider not found" }, 404);
152
+ }
153
+
154
+ return jsonResponse({
155
+ providerId,
156
+ ...provider,
157
+ creditsPerDollar: getCreditsPerDollar(providerId),
158
+ documentation: `https://apiclaw.com/docs/${providerId}`,
159
+ });
160
+ } catch (e) {
161
+ return jsonResponse({ error: "Invalid request" }, 400);
162
+ }
163
+ }),
164
+ });
165
+
166
+ // Check balance
167
+ http.route({
168
+ path: "/api/balance",
169
+ method: "GET",
170
+ handler: httpAction(async (ctx, request) => {
171
+ const url = new URL(request.url);
172
+ const agentId = url.searchParams.get("agentId");
173
+
174
+ if (!agentId) {
175
+ return jsonResponse({ error: "agentId required" }, 400);
176
+ }
177
+
178
+ const credits = await ctx.runQuery(api.credits.getAgentCredits, { agentId });
179
+
180
+ if (!credits) {
181
+ return jsonResponse({
182
+ agentId,
183
+ balanceUsd: 0,
184
+ currency: "USD",
185
+ message: "No account found. Top up to get started!",
186
+ });
187
+ }
188
+
189
+ return jsonResponse({
190
+ agentId: credits.agentId,
191
+ balanceUsd: credits.balanceUsd,
192
+ currency: credits.currency,
193
+ });
194
+ }),
195
+ });
196
+
197
+ // Purchase API access
198
+ http.route({
199
+ path: "/api/purchase",
200
+ method: "POST",
201
+ handler: httpAction(async (ctx, request) => {
202
+ try {
203
+ const body = await request.json();
204
+ const { agentId, providerId, amountUsd } = body;
205
+
206
+ if (!agentId || !providerId || !amountUsd) {
207
+ return jsonResponse(
208
+ { error: "agentId, providerId, and amountUsd required" },
209
+ 400
210
+ );
211
+ }
212
+
213
+ if (amountUsd < 1 || amountUsd > 1000) {
214
+ return jsonResponse(
215
+ { error: "amountUsd must be between 1 and 1000" },
216
+ 400
217
+ );
218
+ }
219
+
220
+ const provider = PROVIDERS[providerId as keyof typeof PROVIDERS];
221
+ if (!provider) {
222
+ return jsonResponse({ error: "Provider not found" }, 404);
223
+ }
224
+
225
+ // Check balance first
226
+ const credits = await ctx.runQuery(api.credits.getAgentCredits, { agentId });
227
+ if (!credits || credits.balanceUsd < amountUsd) {
228
+ return jsonResponse(
229
+ {
230
+ error: "Insufficient balance",
231
+ currentBalance: credits?.balanceUsd || 0,
232
+ required: amountUsd,
233
+ },
234
+ 402
235
+ );
236
+ }
237
+
238
+ // Execute purchase
239
+ const purchase = await ctx.runMutation(api.purchases.purchaseAccess, {
240
+ agentId,
241
+ providerId,
242
+ amountUsd,
243
+ credentials: generateCredentials(providerId),
244
+ });
245
+
246
+ if (!purchase) {
247
+ return jsonResponse({ error: "Purchase failed" }, 500);
248
+ }
249
+
250
+ return jsonResponse({
251
+ success: true,
252
+ purchase: {
253
+ id: purchase._id,
254
+ providerId: purchase.providerId,
255
+ amountUsd: purchase.amountUsd,
256
+ creditsGranted: purchase.creditsGranted,
257
+ status: purchase.status,
258
+ },
259
+ message: `Successfully purchased $${amountUsd} of ${provider.name} credits`,
260
+ });
261
+ } catch (e: any) {
262
+ return jsonResponse({ error: e.message || "Purchase failed" }, 400);
263
+ }
264
+ }),
265
+ });
266
+
267
+ // Admin: Grant credits
268
+ http.route({
269
+ path: "/admin/grant-credits",
270
+ method: "POST",
271
+ handler: httpAction(async (ctx, request) => {
272
+ try {
273
+ const body = await request.json();
274
+ const { agentId, amount, reason } = body;
275
+
276
+ if (!agentId || !amount) {
277
+ return jsonResponse({ error: "agentId and amount required" }, 400);
278
+ }
279
+
280
+ // TODO: Add admin auth check here
281
+ // For now, allow grants (this is for Hivr integration)
282
+
283
+ const result = await ctx.runMutation(api.credits.addCredits, {
284
+ agentId,
285
+ amountUsd: amount,
286
+ source: reason || "admin_grant",
287
+ });
288
+
289
+ return jsonResponse({
290
+ success: true,
291
+ agentId,
292
+ credited: amount,
293
+ newBalance: result?.balanceUsd,
294
+ reason,
295
+ });
296
+ } catch (e: any) {
297
+ return jsonResponse({ error: e.message || "Grant failed" }, 400);
298
+ }
299
+ }),
300
+ });
301
+
302
+ // Helper functions
303
+ function getCreditsPerDollar(providerId: string): number {
304
+ const rates: Record<string, number> = {
305
+ "46elks": 30,
306
+ twilio: 25,
307
+ resend: 1000,
308
+ brave_search: 200,
309
+ openrouter: 100,
310
+ elevenlabs: 3333,
311
+ };
312
+ return rates[providerId] || 100;
313
+ }
314
+
315
+ function generateCredentials(providerId: string): object {
316
+ // In production, this would generate or retrieve actual API keys
317
+ // For now, return placeholder indicating how to use
318
+ return {
319
+ type: "apiclaw_proxy",
320
+ endpoint: `https://brilliant-puffin-712.convex.site/proxy/${providerId}`,
321
+ note: "Use APIClaw proxy endpoint. Credentials managed automatically.",
322
+ };
323
+ }
324
+
325
+ export default http;
326
+
327
+ // ==============================================
328
+ // INSTANT CONNECT PROXY ENDPOINTS
329
+ // ==============================================
330
+
331
+ // OpenRouter proxy
332
+ http.route({
333
+ path: "/proxy/openrouter",
334
+ method: "POST",
335
+ handler: httpAction(async (ctx, request) => {
336
+ const OPENROUTER_KEY = process.env.OPENROUTER_API_KEY;
337
+ if (!OPENROUTER_KEY) {
338
+ return jsonResponse({ error: "OpenRouter not configured" }, 500);
339
+ }
340
+
341
+ try {
342
+ const body = await request.json();
343
+
344
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
345
+ method: "POST",
346
+ headers: {
347
+ "Authorization": `Bearer ${OPENROUTER_KEY}`,
348
+ "Content-Type": "application/json",
349
+ "HTTP-Referer": "https://apiclaw.nordsym.com",
350
+ "X-Title": "APIClaw",
351
+ },
352
+ body: JSON.stringify(body),
353
+ });
354
+
355
+ const data = await response.json();
356
+ return jsonResponse(data, response.status);
357
+ } catch (e: any) {
358
+ return jsonResponse({ error: e.message }, 500);
359
+ }
360
+ }),
361
+ });
362
+
363
+ // Brave Search proxy
364
+ http.route({
365
+ path: "/proxy/brave_search",
366
+ method: "POST",
367
+ handler: httpAction(async (ctx, request) => {
368
+ const BRAVE_KEY = process.env.BRAVE_API_KEY;
369
+ if (!BRAVE_KEY) {
370
+ return jsonResponse({ error: "Brave Search not configured" }, 500);
371
+ }
372
+
373
+ try {
374
+ const body = await request.json();
375
+ const { query, count = 10 } = body;
376
+
377
+ const url = new URL("https://api.search.brave.com/res/v1/web/search");
378
+ url.searchParams.set("q", query);
379
+ url.searchParams.set("count", String(count));
380
+
381
+ const response = await fetch(url.toString(), {
382
+ headers: { "X-Subscription-Token": BRAVE_KEY },
383
+ });
384
+
385
+ const data = await response.json();
386
+ return jsonResponse(data, response.status);
387
+ } catch (e: any) {
388
+ return jsonResponse({ error: e.message }, 500);
389
+ }
390
+ }),
391
+ });
392
+
393
+ // Resend email proxy
394
+ http.route({
395
+ path: "/proxy/resend",
396
+ method: "POST",
397
+ handler: httpAction(async (ctx, request) => {
398
+ const RESEND_KEY = process.env.RESEND_API_KEY;
399
+ if (!RESEND_KEY) {
400
+ return jsonResponse({ error: "Resend not configured" }, 500);
401
+ }
402
+
403
+ try {
404
+ const body = await request.json();
405
+
406
+ const response = await fetch("https://api.resend.com/emails", {
407
+ method: "POST",
408
+ headers: {
409
+ "Authorization": `Bearer ${RESEND_KEY}`,
410
+ "Content-Type": "application/json",
411
+ },
412
+ body: JSON.stringify(body),
413
+ });
414
+
415
+ const data = await response.json();
416
+ return jsonResponse(data, response.status);
417
+ } catch (e: any) {
418
+ return jsonResponse({ error: e.message }, 500);
419
+ }
420
+ }),
421
+ });
422
+
423
+ // ElevenLabs TTS proxy
424
+ http.route({
425
+ path: "/proxy/elevenlabs",
426
+ method: "POST",
427
+ handler: httpAction(async (ctx, request) => {
428
+ const ELEVENLABS_KEY = process.env.ELEVENLABS_API_KEY;
429
+ if (!ELEVENLABS_KEY) {
430
+ return jsonResponse({ error: "ElevenLabs not configured" }, 500);
431
+ }
432
+
433
+ try {
434
+ const body = await request.json();
435
+ const { text, voice_id = "21m00Tcm4TlvDq8ikWAM" } = body;
436
+
437
+ const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voice_id}`, {
438
+ method: "POST",
439
+ headers: {
440
+ "xi-api-key": ELEVENLABS_KEY,
441
+ "Content-Type": "application/json",
442
+ },
443
+ body: JSON.stringify({
444
+ text,
445
+ model_id: "eleven_monolingual_v1",
446
+ }),
447
+ });
448
+
449
+ if (!response.ok) {
450
+ const error = await response.text();
451
+ return jsonResponse({ error }, response.status);
452
+ }
453
+
454
+ // Return audio as base64
455
+ const arrayBuffer = await response.arrayBuffer();
456
+ const base64 = Buffer.from(arrayBuffer).toString("base64");
457
+
458
+ return jsonResponse({
459
+ audio_base64: base64,
460
+ content_type: "audio/mpeg",
461
+ });
462
+ } catch (e: any) {
463
+ return jsonResponse({ error: e.message }, 500);
464
+ }
465
+ }),
466
+ });
467
+
468
+ http.route({
469
+ path: "/proxy/openrouter",
470
+ method: "OPTIONS",
471
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
472
+ });
473
+
474
+ http.route({
475
+ path: "/proxy/brave_search",
476
+ method: "OPTIONS",
477
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
478
+ });
479
+
480
+ http.route({
481
+ path: "/proxy/resend",
482
+ method: "OPTIONS",
483
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
484
+ });
485
+
486
+ http.route({
487
+ path: "/proxy/elevenlabs",
488
+ method: "OPTIONS",
489
+ handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
490
+ });