@nordsym/apiclaw 1.8.6 → 1.8.7

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 (125) hide show
  1. package/README.md +4 -4
  2. package/apiclaw-README.md +12 -12
  3. package/convex/_generated/api.d.ts +4 -0
  4. package/convex/apiKeys.ts +220 -0
  5. package/convex/directCall.ts +49 -14
  6. package/convex/email.ts +5 -5
  7. package/convex/http.ts +598 -40
  8. package/convex/logs.ts +26 -22
  9. package/convex/migrateProviderWorkspaces.ts +154 -35
  10. package/convex/providers.ts +313 -203
  11. package/convex/schema.ts +50 -1
  12. package/convex/searchLogs.ts +2 -6
  13. package/convex/seedPratham.ts +1 -1
  14. package/convex/spendAlerts.ts +2 -2
  15. package/convex/stripeActions.ts +1 -1
  16. package/convex/updateAPIStatus.ts +2 -3
  17. package/convex/workspaceSettings.ts +136 -0
  18. package/dist/cli/commands/demo.js +2 -2
  19. package/dist/cli/commands/doctor.js +1 -1
  20. package/dist/cli/commands/login.js +1 -1
  21. package/dist/cli/commands/setup.js +2 -2
  22. package/dist/discovery.js +1 -1
  23. package/dist/execute.js +3 -3
  24. package/dist/index.js +13 -13
  25. package/dist/ui/errors.js +16 -16
  26. package/dist/ui/prompts.js +1 -1
  27. package/email-templates/filestack-provider-outreach.html +1 -1
  28. package/email-templates/partnership-template.html +1 -1
  29. package/email-templates/pratham-partnership-draft.html +2 -2
  30. package/package.json +2 -2
  31. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  32. package/reports/session-report-2026-04-05.html +433 -0
  33. package/src/cli/commands/demo.ts +2 -2
  34. package/src/cli/commands/doctor.ts +1 -1
  35. package/src/cli/commands/login.ts +1 -1
  36. package/src/cli/commands/setup.ts +2 -2
  37. package/src/discovery.ts +1 -1
  38. package/src/execute.ts +3 -3
  39. package/src/index.ts +14 -14
  40. package/src/ui/errors.ts +16 -16
  41. package/src/ui/prompts.ts +1 -1
  42. package/convex/adminActivate.d.ts +0 -3
  43. package/convex/adminActivate.js +0 -47
  44. package/convex/adminStats.d.ts +0 -9
  45. package/convex/adminStats.js +0 -280
  46. package/convex/agents.d.ts +0 -84
  47. package/convex/agents.js +0 -809
  48. package/convex/analytics.d.ts +0 -5
  49. package/convex/analytics.js +0 -167
  50. package/convex/backfillAnalytics.d.ts +0 -2
  51. package/convex/backfillAnalytics.js +0 -20
  52. package/convex/backfillSearchLogs.d.ts +0 -2
  53. package/convex/backfillSearchLogs.js +0 -29
  54. package/convex/billing.d.ts +0 -88
  55. package/convex/billing.js +0 -655
  56. package/convex/capabilities.d.ts +0 -9
  57. package/convex/capabilities.js +0 -145
  58. package/convex/chains.d.ts +0 -68
  59. package/convex/chains.js +0 -1105
  60. package/convex/credits.d.ts +0 -25
  61. package/convex/credits.js +0 -186
  62. package/convex/crons.d.ts +0 -3
  63. package/convex/crons.js +0 -17
  64. package/convex/debugFilestackLogs.d.ts +0 -2
  65. package/convex/debugFilestackLogs.js +0 -17
  66. package/convex/debugGetToken.d.ts +0 -2
  67. package/convex/debugGetToken.js +0 -18
  68. package/convex/directCall.d.ts +0 -72
  69. package/convex/directCall.js +0 -627
  70. package/convex/earnProgress.d.ts +0 -58
  71. package/convex/earnProgress.js +0 -649
  72. package/convex/email.d.ts +0 -14
  73. package/convex/email.js +0 -300
  74. package/convex/feedback.d.ts +0 -7
  75. package/convex/feedback.js +0 -227
  76. package/convex/http.d.ts +0 -3
  77. package/convex/http.js +0 -1408
  78. package/convex/inbound.d.ts +0 -2
  79. package/convex/inbound.js +0 -32
  80. package/convex/logs.d.ts +0 -48
  81. package/convex/logs.js +0 -621
  82. package/convex/migrateFilestack.d.ts +0 -2
  83. package/convex/migrateFilestack.js +0 -74
  84. package/convex/migratePartnersProd.d.ts +0 -8
  85. package/convex/migratePartnersProd.js +0 -165
  86. package/convex/migratePratham.d.ts +0 -2
  87. package/convex/migratePratham.js +0 -121
  88. package/convex/migrateProviderWorkspaces.d.ts +0 -2
  89. package/convex/migrateProviderWorkspaces.js +0 -55
  90. package/convex/mou.d.ts +0 -6
  91. package/convex/mou.js +0 -82
  92. package/convex/providerKeys.d.ts +0 -31
  93. package/convex/providerKeys.js +0 -257
  94. package/convex/providers.d.ts +0 -35
  95. package/convex/providers.js +0 -922
  96. package/convex/purchases.d.ts +0 -7
  97. package/convex/purchases.js +0 -157
  98. package/convex/ratelimit.d.ts +0 -4
  99. package/convex/ratelimit.js +0 -91
  100. package/convex/searchLogs.d.ts +0 -13
  101. package/convex/searchLogs.js +0 -246
  102. package/convex/seedAPILayerAPIs.d.ts +0 -7
  103. package/convex/seedAPILayerAPIs.js +0 -177
  104. package/convex/seedDirectCallConfigs.d.ts +0 -2
  105. package/convex/seedDirectCallConfigs.js +0 -324
  106. package/convex/seedPratham.d.ts +0 -6
  107. package/convex/seedPratham.js +0 -150
  108. package/convex/spendAlerts.d.ts +0 -36
  109. package/convex/spendAlerts.js +0 -380
  110. package/convex/stripeActions.d.ts +0 -19
  111. package/convex/stripeActions.js +0 -411
  112. package/convex/teams.d.ts +0 -21
  113. package/convex/teams.js +0 -215
  114. package/convex/telemetry.d.ts +0 -4
  115. package/convex/telemetry.js +0 -74
  116. package/convex/updateAPIStatus.d.ts +0 -6
  117. package/convex/updateAPIStatus.js +0 -40
  118. package/convex/usage.d.ts +0 -27
  119. package/convex/usage.js +0 -229
  120. package/convex/waitlist.d.ts +0 -4
  121. package/convex/waitlist.js +0 -49
  122. package/convex/webhooks.d.ts +0 -12
  123. package/convex/webhooks.js +0 -410
  124. package/convex/workspaces.d.ts +0 -33
  125. package/convex/workspaces.js +0 -991
package/README.md CHANGED
@@ -11,7 +11,7 @@ The API layer for AI agents. One install. Three tiers of access.
11
11
  ## Install
12
12
 
13
13
  ```bash
14
- curl -fsSL https://apiclaw.nordsym.com/install.sh | bash
14
+ curl -fsSL https://apiclaw.cloud/install.sh | bash
15
15
  ```
16
16
 
17
17
  Restart your AI assistant. First 5 API calls are free. Register your email to unlock 50/month.
@@ -84,16 +84,16 @@ Premium APIs proxied through APIClaw. No keys needed. APIClaw handles auth, rate
84
84
  | Unregistered | Free | 5 API calls, unlimited search |
85
85
  | Free | Free | 50 calls/month, full dashboard |
86
86
  | Founding Backer | $199 one-time | Unlimited until 2027 |
87
- | Enterprise | Custom | [Book a call](https://apiclaw.nordsym.com/book) |
87
+ | Enterprise | Custom | [Book a call](https://apiclaw.cloud/book) |
88
88
 
89
89
  ## For API Providers
90
90
 
91
91
  List your APIs on APIClaw. Get discovered by AI agents. Track usage, discoveries, and calls in your dashboard.
92
92
 
93
- [Register as provider](https://apiclaw.nordsym.com/providers/register)
93
+ [Register as provider](https://apiclaw.cloud/providers/register)
94
94
 
95
95
  ---
96
96
 
97
- [Dashboard](https://apiclaw.nordsym.com) - [Docs](https://apiclaw.nordsym.com/docs) - [Book a Call](https://apiclaw.nordsym.com/book)
97
+ [Dashboard](https://apiclaw.cloud) - [Docs](https://apiclaw.cloud/docs) - [Book a Call](https://apiclaw.cloud/book)
98
98
 
99
99
  MIT License
package/apiclaw-README.md CHANGED
@@ -23,7 +23,7 @@ The API layer for AI agents. 18 providers → 1000s of capabilities. Workspace
23
23
 
24
24
  ## The Platform
25
25
 
26
- **[apiclaw.nordsym.com](https://apiclaw.nordsym.com)** — Your workspace for API-powered agents.
26
+ **[apiclaw.cloud](https://apiclaw.cloud)** — Your workspace for API-powered agents.
27
27
 
28
28
  | Layer | What You Get |
29
29
  |-------|--------------|
@@ -43,7 +43,7 @@ One `mcp-install` connects your agent to all of it.
43
43
 
44
44
  ```bash
45
45
  # Install
46
- curl -fsSL https://apiclaw.nordsym.com/install.sh | bash
46
+ curl -fsSL https://apiclaw.cloud/install.sh | bash
47
47
 
48
48
  # Restart your AI assistant
49
49
  # Immediately use any API (10 calls/week)
@@ -194,7 +194,7 @@ Public APIs, instantly callable. No API keys needed.
194
194
  - Food & Recipes
195
195
  - Science & Education
196
196
 
197
- Browse at [apiclaw.nordsym.com/open-apis](https://apiclaw.nordsym.com/open-apis)
197
+ Browse at [apiclaw.cloud/open-apis](https://apiclaw.cloud/open-apis)
198
198
 
199
199
  ---
200
200
 
@@ -209,7 +209,7 @@ Use `discover_apis` to search:
209
209
  "What APIs exist for recipe data?"
210
210
  ```
211
211
 
212
- Browse at [apiclaw.nordsym.com/discover](https://apiclaw.nordsym.com/discover)
212
+ Browse at [apiclaw.cloud/discover](https://apiclaw.cloud/discover)
213
213
 
214
214
  ---
215
215
 
@@ -218,7 +218,7 @@ Browse at [apiclaw.nordsym.com/discover](https://apiclaw.nordsym.com/discover)
218
218
  ### One-Line Install (Recommended)
219
219
 
220
220
  ```bash
221
- curl -fsSL https://apiclaw.nordsym.com/install.sh | bash
221
+ curl -fsSL https://apiclaw.cloud/install.sh | bash
222
222
  ```
223
223
 
224
224
  Auto-detects Claude Desktop or Claude Code. Configures MCP. Done.
@@ -404,7 +404,7 @@ You don't manage API keys. You don't configure auth. You don't worry about rate
404
404
  - Usage analytics in your workspace
405
405
  - Production-ready from day one
406
406
 
407
- **Want higher limits?** See [Pricing](#pricing) below or upgrade at [apiclaw.nordsym.com](https://apiclaw.nordsym.com).
407
+ **Want higher limits?** See [Pricing](#pricing) below or upgrade at [apiclaw.cloud](https://apiclaw.cloud).
408
408
 
409
409
  ---
410
410
 
@@ -456,7 +456,7 @@ APIClaw is purpose-built for AI agents and MCP clients. No glue code, no key jug
456
456
  | **Scale** | $249/mo | 25,000 | Production agents |
457
457
  | **Enterprise** | Custom | Unlimited | Large teams & custom SLAs |
458
458
 
459
- → [Upgrade at apiclaw.nordsym.com](https://apiclaw.nordsym.com) · Enterprise: [book a call](https://apiclaw.nordsym.com/contact)
459
+ → [Upgrade at apiclaw.cloud](https://apiclaw.cloud) · Enterprise: [book a call](https://apiclaw.cloud/contact)
460
460
 
461
461
  ---
462
462
 
@@ -464,9 +464,9 @@ APIClaw is purpose-built for AI agents and MCP clients. No glue code, no key jug
464
464
 
465
465
  APIClaw is optimized for discovery by AI agents and LLM tooling:
466
466
 
467
- - **llms.txt:** [apiclaw.nordsym.com/llms.txt](https://apiclaw.nordsym.com/llms.txt) — Machine-readable API index
468
- - **llms-full.txt:** [apiclaw.nordsym.com/llms-full.txt](https://apiclaw.nordsym.com/llms-full.txt) — Full capability descriptions
469
- - **ai-plugin.json:** [apiclaw.nordsym.com/.well-known/ai-plugin.json](https://apiclaw.nordsym.com/.well-known/ai-plugin.json) — OpenAI plugin manifest
467
+ - **llms.txt:** [apiclaw.cloud/llms.txt](https://apiclaw.cloud/llms.txt) — Machine-readable API index
468
+ - **llms-full.txt:** [apiclaw.cloud/llms-full.txt](https://apiclaw.cloud/llms-full.txt) — Full capability descriptions
469
+ - **ai-plugin.json:** [apiclaw.cloud/.well-known/ai-plugin.json](https://apiclaw.cloud/.well-known/ai-plugin.json) — OpenAI plugin manifest
470
470
  - **MCP:** Install with one command and any MCP-compatible agent can use APIClaw immediately
471
471
 
472
472
  ---
@@ -480,8 +480,8 @@ APIClaw is optimized for discovery by AI agents and LLM tooling:
480
480
 
481
481
  ## Links
482
482
 
483
- - **Platform:** [apiclaw.nordsym.com](https://apiclaw.nordsym.com)
484
- - **Docs:** [apiclaw.nordsym.com/docs](https://apiclaw.nordsym.com/docs)
483
+ - **Platform:** [apiclaw.cloud](https://apiclaw.cloud)
484
+ - **Docs:** [apiclaw.cloud/docs](https://apiclaw.cloud/docs)
485
485
  - **GitHub:** [github.com/nordsym/apiclaw](https://github.com/nordsym/apiclaw)
486
486
  - **npm:** [@nordsym/apiclaw](https://www.npmjs.com/package/@nordsym/apiclaw)
487
487
  - **Security:** [SECURITY.md](SECURITY.md)
@@ -12,6 +12,7 @@ import type * as adminActivate from "../adminActivate.js";
12
12
  import type * as adminStats from "../adminStats.js";
13
13
  import type * as agents from "../agents.js";
14
14
  import type * as analytics from "../analytics.js";
15
+ import type * as apiKeys from "../apiKeys.js";
15
16
  import type * as backfillAnalytics from "../backfillAnalytics.js";
16
17
  import type * as backfillSearchLogs from "../backfillSearchLogs.js";
17
18
  import type * as billing from "../billing.js";
@@ -49,6 +50,7 @@ import type * as updateAPIStatus from "../updateAPIStatus.js";
49
50
  import type * as usage from "../usage.js";
50
51
  import type * as waitlist from "../waitlist.js";
51
52
  import type * as webhooks from "../webhooks.js";
53
+ import type * as workspaceSettings from "../workspaceSettings.js";
52
54
  import type * as workspaces from "../workspaces.js";
53
55
 
54
56
  import type {
@@ -62,6 +64,7 @@ declare const fullApi: ApiFromModules<{
62
64
  adminStats: typeof adminStats;
63
65
  agents: typeof agents;
64
66
  analytics: typeof analytics;
67
+ apiKeys: typeof apiKeys;
65
68
  backfillAnalytics: typeof backfillAnalytics;
66
69
  backfillSearchLogs: typeof backfillSearchLogs;
67
70
  billing: typeof billing;
@@ -99,6 +102,7 @@ declare const fullApi: ApiFromModules<{
99
102
  usage: typeof usage;
100
103
  waitlist: typeof waitlist;
101
104
  webhooks: typeof webhooks;
105
+ workspaceSettings: typeof workspaceSettings;
102
106
  workspaces: typeof workspaces;
103
107
  }>;
104
108
 
@@ -0,0 +1,220 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query, internalQuery } from "./_generated/server";
3
+
4
+ // ============================================
5
+ // WORKSPACE API KEYS
6
+ // Generate persistent API keys for programmatic access.
7
+ // Users generate keys in the dashboard, then use them
8
+ // in any agent config, automation, or script.
9
+ // ============================================
10
+
11
+ function generateRawKey(): string {
12
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
13
+ let result = "";
14
+ for (let i = 0; i < 48; i++) {
15
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
16
+ }
17
+ return `sk-claw-${result}`;
18
+ }
19
+
20
+ // Simple hash for key lookup (SHA-256 not available in Convex runtime, use deterministic hash)
21
+ function hashKey(key: string): string {
22
+ let hash = 0;
23
+ for (let i = 0; i < key.length; i++) {
24
+ const char = key.charCodeAt(i);
25
+ hash = ((hash << 5) - hash + char) | 0;
26
+ }
27
+ // Create a longer hash by running multiple rounds with offsets
28
+ let h1 = hash;
29
+ let h2 = 0;
30
+ for (let i = 0; i < key.length; i++) {
31
+ h2 = ((h2 << 7) - h2 + key.charCodeAt(i) * 31) | 0;
32
+ }
33
+ let h3 = 0;
34
+ for (let i = 0; i < key.length; i++) {
35
+ h3 = ((h3 << 11) - h3 + key.charCodeAt(i) * 127) | 0;
36
+ }
37
+ return `${(h1 >>> 0).toString(36)}-${(h2 >>> 0).toString(36)}-${(h3 >>> 0).toString(36)}`;
38
+ }
39
+
40
+ function getKeyPrefix(key: string): string {
41
+ return `sk-claw-...${key.slice(-4)}`;
42
+ }
43
+
44
+ // ============================================
45
+ // GENERATE KEY
46
+ // ============================================
47
+
48
+ export const generateKey = mutation({
49
+ args: {
50
+ token: v.string(), // session token for auth
51
+ name: v.string(), // user label
52
+ },
53
+ handler: async (ctx, args) => {
54
+ // Auth via agentSession
55
+ const session = await ctx.db
56
+ .query("agentSessions")
57
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
58
+ .first();
59
+
60
+ if (!session) {
61
+ throw new Error("Invalid session");
62
+ }
63
+
64
+ const workspaceId = session.workspaceId;
65
+
66
+ // Limit: max 5 active keys per workspace
67
+ const existingKeys = await ctx.db
68
+ .query("workspaceApiKeys")
69
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
70
+ .collect();
71
+
72
+ const activeKeys = existingKeys.filter((k) => !k.revokedAt);
73
+ if (activeKeys.length >= 5) {
74
+ throw new Error("Maximum 5 active keys per workspace. Revoke an existing key first.");
75
+ }
76
+
77
+ // Check for duplicate name
78
+ const nameExists = activeKeys.some(
79
+ (k) => k.name.toLowerCase() === args.name.toLowerCase()
80
+ );
81
+ if (nameExists) {
82
+ throw new Error(`A key named "${args.name}" already exists.`);
83
+ }
84
+
85
+ const rawKey = generateRawKey();
86
+ const now = Date.now();
87
+
88
+ await ctx.db.insert("workspaceApiKeys", {
89
+ workspaceId,
90
+ key: "", // We don't store the raw key
91
+ keyHash: hashKey(rawKey),
92
+ keyPrefix: getKeyPrefix(rawKey),
93
+ name: args.name,
94
+ createdAt: now,
95
+ });
96
+
97
+ // Return the raw key ONCE - it won't be retrievable again
98
+ return {
99
+ key: rawKey,
100
+ keyPrefix: getKeyPrefix(rawKey),
101
+ name: args.name,
102
+ };
103
+ },
104
+ });
105
+
106
+ // ============================================
107
+ // LIST KEYS
108
+ // ============================================
109
+
110
+ export const listKeys = query({
111
+ args: {
112
+ token: v.string(),
113
+ },
114
+ handler: async (ctx, args) => {
115
+ const session = await ctx.db
116
+ .query("agentSessions")
117
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
118
+ .first();
119
+
120
+ if (!session) {
121
+ return { keys: [] };
122
+ }
123
+
124
+ const keys = await ctx.db
125
+ .query("workspaceApiKeys")
126
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
127
+ .collect();
128
+
129
+ return {
130
+ keys: keys
131
+ .filter((k) => !k.revokedAt)
132
+ .map((k) => ({
133
+ id: k._id,
134
+ name: k.name,
135
+ keyPrefix: k.keyPrefix,
136
+ lastUsedAt: k.lastUsedAt,
137
+ createdAt: k.createdAt,
138
+ }))
139
+ .sort((a, b) => b.createdAt - a.createdAt),
140
+ };
141
+ },
142
+ });
143
+
144
+ // ============================================
145
+ // REVOKE KEY
146
+ // ============================================
147
+
148
+ export const revokeKey = mutation({
149
+ args: {
150
+ token: v.string(),
151
+ keyId: v.id("workspaceApiKeys"),
152
+ },
153
+ handler: async (ctx, args) => {
154
+ const session = await ctx.db
155
+ .query("agentSessions")
156
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
157
+ .first();
158
+
159
+ if (!session) {
160
+ throw new Error("Invalid session");
161
+ }
162
+
163
+ const key = await ctx.db.get(args.keyId);
164
+ if (!key || key.workspaceId !== session.workspaceId) {
165
+ throw new Error("Key not found");
166
+ }
167
+
168
+ if (key.revokedAt) {
169
+ throw new Error("Key already revoked");
170
+ }
171
+
172
+ await ctx.db.patch(args.keyId, { revokedAt: Date.now() });
173
+ return { success: true };
174
+ },
175
+ });
176
+
177
+ // ============================================
178
+ // RESOLVE KEY (internal - used by gateway)
179
+ // ============================================
180
+
181
+ export const resolveKey = internalQuery({
182
+ args: {
183
+ rawKey: v.string(),
184
+ },
185
+ handler: async (ctx, args) => {
186
+ const keyHash = hashKey(args.rawKey);
187
+
188
+ const keyDoc = await ctx.db
189
+ .query("workspaceApiKeys")
190
+ .withIndex("by_keyHash", (q) => q.eq("keyHash", keyHash))
191
+ .first();
192
+
193
+ if (!keyDoc) {
194
+ return null;
195
+ }
196
+
197
+ if (keyDoc.revokedAt) {
198
+ return null;
199
+ }
200
+
201
+ return {
202
+ workspaceId: keyDoc.workspaceId,
203
+ keyId: keyDoc._id,
204
+ name: keyDoc.name,
205
+ };
206
+ },
207
+ });
208
+
209
+ // ============================================
210
+ // TOUCH KEY (internal - update lastUsedAt)
211
+ // ============================================
212
+
213
+ export const touchKey = mutation({
214
+ args: {
215
+ keyId: v.id("workspaceApiKeys"),
216
+ },
217
+ handler: async (ctx, args) => {
218
+ await ctx.db.patch(args.keyId, { lastUsedAt: Date.now() });
219
+ },
220
+ });
@@ -82,14 +82,31 @@ export const saveConfig = mutation({
82
82
  }),
83
83
  },
84
84
  handler: async (ctx, args) => {
85
- // Verify session
86
- const session = await ctx.db
87
- .query("sessions")
88
- .withIndex("by_token", (q) => q.eq("token", args.token))
85
+ // Verify session (unified: agentSessions first, fallback to legacy sessions)
86
+ let providerId: any = null;
87
+
88
+ const agentSession = await ctx.db
89
+ .query("agentSessions")
90
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
89
91
  .first();
90
92
 
91
- if (!session || session.expiresAt < Date.now()) {
92
- throw new Error("Invalid or expired session");
93
+ if (agentSession) {
94
+ const provider = await ctx.db
95
+ .query("providers")
96
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
97
+ .first();
98
+ if (!provider) throw new Error("No provider linked to workspace");
99
+ providerId = provider._id;
100
+ } else {
101
+ // Fallback: legacy sessions
102
+ const session = await ctx.db
103
+ .query("sessions")
104
+ .withIndex("by_token", (q) => q.eq("token", args.token))
105
+ .first();
106
+ if (!session || session.expiresAt < Date.now()) {
107
+ throw new Error("Invalid or expired session");
108
+ }
109
+ providerId = session.providerId;
93
110
  }
94
111
 
95
112
  const now = Date.now();
@@ -128,7 +145,7 @@ export const saveConfig = mutation({
128
145
 
129
146
  // Create new config
130
147
  return await ctx.db.insert("providerDirectCall", {
131
- providerId: session.providerId,
148
+ providerId,
132
149
  apiId: config.apiId as any,
133
150
  baseUrl: config.baseUrl,
134
151
  authType: config.authType,
@@ -536,13 +553,31 @@ export const testAction = mutation({
536
553
  handler: async (ctx, args) => {
537
554
  const startTime = Date.now();
538
555
 
539
- // 1. Verify provider session
540
- const session = await ctx.db
541
- .query("sessions")
542
- .withIndex("by_token", (q) => q.eq("token", args.token))
556
+ // 1. Verify provider session (unified: agentSessions first, fallback to legacy)
557
+ let sessionProviderId: any = null;
558
+
559
+ const agentSession = await ctx.db
560
+ .query("agentSessions")
561
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
543
562
  .first();
544
-
545
- if (!session || session.expiresAt < Date.now()) {
563
+
564
+ if (agentSession) {
565
+ const provider = await ctx.db
566
+ .query("providers")
567
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
568
+ .first();
569
+ if (provider) sessionProviderId = provider._id;
570
+ } else {
571
+ const session = await ctx.db
572
+ .query("sessions")
573
+ .withIndex("by_token", (q) => q.eq("token", args.token))
574
+ .first();
575
+ if (session && session.expiresAt >= Date.now()) {
576
+ sessionProviderId = session.providerId;
577
+ }
578
+ }
579
+
580
+ if (!sessionProviderId) {
546
581
  return {
547
582
  success: false,
548
583
  error: "Unauthorized - invalid or expired session",
@@ -561,7 +596,7 @@ export const testAction = mutation({
561
596
  }
562
597
 
563
598
  // Verify ownership
564
- if (config.providerId !== session.providerId) {
599
+ if (config.providerId !== sessionProviderId) {
565
600
  return {
566
601
  success: false,
567
602
  error: "Unauthorized - you don't own this config",
package/convex/email.ts CHANGED
@@ -5,8 +5,8 @@ import { action, internalAction } from "./_generated/server";
5
5
  // EMAIL TEMPLATES
6
6
  // ============================================
7
7
 
8
- const EMAIL_FROM = "APIClaw <noreply@apiclaw.nordsym.com>";
9
- const APP_URL = "https://apiclaw.nordsym.com";
8
+ const EMAIL_FROM = "APIClaw <noreply@apiclaw.cloud>";
9
+ const APP_URL = "https://apiclaw.cloud";
10
10
 
11
11
  // Base email wrapper - using string concat for Convex compatibility
12
12
  function wrapEmail(content: string): string {
@@ -37,7 +37,7 @@ function wrapEmail(content: string): string {
37
37
  '<tr>',
38
38
  '<td style="padding: 24px 32px; background-color: #fafafa; border-top: 1px solid #f0f0f0;">',
39
39
  '<p style="margin: 0; font-size: 12px; color: #737373; text-align: center;">',
40
- '<a href="https://apiclaw.nordsym.com" style="color: #ef4444; text-decoration: none;">APIClaw</a> — The API Layer for AI Agents',
40
+ '<a href="https://apiclaw.cloud" style="color: #ef4444; text-decoration: none;">APIClaw</a> — The API Layer for AI Agents',
41
41
  '</p>',
42
42
  '<p style="margin: 8px 0 0; font-size: 12px; color: #a3a3a3; text-align: center;">',
43
43
  '© ' + year + ' NordSym. Stockholm, Sweden.',
@@ -283,7 +283,7 @@ export const debugEmailTemplate = action({
283
283
  args: { email: v.string() },
284
284
  handler: async (ctx, { email }) => {
285
285
  const RESEND_API_KEY = process.env.RESEND_API_KEY;
286
- const testUrl = "https://apiclaw.nordsym.com/auth/verify?token=DEBUG_TEST";
286
+ const testUrl = "https://apiclaw.cloud/auth/verify?token=DEBUG_TEST";
287
287
 
288
288
  // Generate HTML using the template
289
289
  var html = "<!DOCTYPE html><html><head><meta charset='utf-8'></head>";
@@ -309,7 +309,7 @@ export const debugEmailTemplate = action({
309
309
  "Content-Type": "application/json",
310
310
  },
311
311
  body: JSON.stringify({
312
- from: "APIClaw <noreply@apiclaw.nordsym.com>",
312
+ from: "APIClaw <noreply@apiclaw.cloud>",
313
313
  to: email,
314
314
  subject: "DEBUG EMAIL FROM CONVEX",
315
315
  html: html,