@genlobe/mcp-server 2.2.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.
- package/README.md +116 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +3402 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3402 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server for Multi-tenant SaaS API
|
|
4
|
+
*
|
|
5
|
+
* This server provides AI assistants with comprehensive API documentation
|
|
6
|
+
* and tools to understand how to build frontends using this API.
|
|
7
|
+
*
|
|
8
|
+
* The API has TWO types of endpoints:
|
|
9
|
+
* 1. TENANT/DASHBOARD endpoints - For tenant admins (JWT auth from dashboard login)
|
|
10
|
+
* 2. END-USER endpoints - For end-users of tenants (API Key + optional JWT)
|
|
11
|
+
*
|
|
12
|
+
* When building a frontend for END-USERS of a tenant, you should ONLY use
|
|
13
|
+
* the End-user endpoints, NOT the Tenant/Dashboard endpoints.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* npx @multiagent/mcp-server
|
|
17
|
+
*
|
|
18
|
+
* Or configure in VS Code MCP settings with SAAS_API_URL environment variable.
|
|
19
|
+
*/
|
|
20
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Configuration
|
|
25
|
+
// =============================================================================
|
|
26
|
+
const API_URL = process.env.SAAS_API_URL || process.env.API_URL || "https://api.example.com";
|
|
27
|
+
const API_KEY = process.env.SAAS_API_KEY || process.env.API_KEY || "";
|
|
28
|
+
// Cache for API responses
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// API Documentation - Embedded for reliability
|
|
33
|
+
// =============================================================================
|
|
34
|
+
/**
|
|
35
|
+
* CRITICAL: This embedded documentation ensures AI assistants have accurate
|
|
36
|
+
* information even if the API is not accessible.
|
|
37
|
+
*/
|
|
38
|
+
const API_OVERVIEW = {
|
|
39
|
+
name: "Multi-tenant SaaS API",
|
|
40
|
+
version: "2.2.0",
|
|
41
|
+
description: `
|
|
42
|
+
This is a Backend-as-a-Service API for building SaaS applications.
|
|
43
|
+
|
|
44
|
+
## Architecture Overview
|
|
45
|
+
|
|
46
|
+
There are TWO types of authentication contexts:
|
|
47
|
+
|
|
48
|
+
### 1. TENANT CONTEXT (Dashboard)
|
|
49
|
+
- Used by: Tenant owners/admins managing their SaaS account
|
|
50
|
+
- Authentication: JWT from /v1/tenant-auth/login
|
|
51
|
+
- Purpose: Manage API keys, team members, view analytics, configure settings
|
|
52
|
+
- These endpoints are NOT for end-user frontends
|
|
53
|
+
|
|
54
|
+
### 2. END-USER CONTEXT (API)
|
|
55
|
+
- Used by: End-users of applications built on top of this API
|
|
56
|
+
- Authentication: API Key (X-API-Key header) + optional User JWT
|
|
57
|
+
- Purpose: User auth, organizations, subscriptions, billing
|
|
58
|
+
- These endpoints ARE for building end-user frontends
|
|
59
|
+
|
|
60
|
+
## API Key Types
|
|
61
|
+
- \`pk_live_*\`: Public key - safe to expose in frontend code (LIMITED to auth endpoints only)
|
|
62
|
+
- \`sk_live_*\`: Secret key - backend only, never expose (FULL API access)
|
|
63
|
+
|
|
64
|
+
## Security Features (for public keys)
|
|
65
|
+
|
|
66
|
+
### Origin Validation
|
|
67
|
+
Public keys (pk_live_*) require Origin header validation:
|
|
68
|
+
- When creating a public key, the tenant specifies allowed origins
|
|
69
|
+
- Requests from other origins are rejected
|
|
70
|
+
- This prevents stolen API keys from being used on other domains
|
|
71
|
+
|
|
72
|
+
### Rate Limiting (per IP)
|
|
73
|
+
- Register: 3/hour (blocked 30 min after exceeding)
|
|
74
|
+
- Login: 10/15 min (blocked 15 min after exceeding)
|
|
75
|
+
- Forgot password: 3/hour (blocked 30 min after exceeding)
|
|
76
|
+
- Resend verification: 3/hour (blocked 30 min after exceeding)
|
|
77
|
+
|
|
78
|
+
## When Building a Frontend
|
|
79
|
+
If you're building a frontend application for end-users, you should:
|
|
80
|
+
1. Use ONLY the End-user endpoints (not Tenant/Dashboard endpoints)
|
|
81
|
+
2. Use the public API key (pk_live_*) for client-side requests
|
|
82
|
+
3. Include X-API-Key header in ALL requests
|
|
83
|
+
4. After login, also include Authorization: Bearer <jwt> for authenticated requests
|
|
84
|
+
`,
|
|
85
|
+
base_url: API_URL,
|
|
86
|
+
documentation_urls: {
|
|
87
|
+
swagger: `${API_URL}/docs`,
|
|
88
|
+
redoc: `${API_URL}/redoc`,
|
|
89
|
+
openapi: `${API_URL}/v1/developer/openapi.json`
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// End-User Endpoints (for building frontends)
|
|
94
|
+
// =============================================================================
|
|
95
|
+
const END_USER_ENDPOINTS = {
|
|
96
|
+
authentication: {
|
|
97
|
+
description: "User authentication endpoints - login, register, password reset. Protected by rate limiting.",
|
|
98
|
+
endpoints: [
|
|
99
|
+
{
|
|
100
|
+
method: "POST",
|
|
101
|
+
path: "/v1/auth/register",
|
|
102
|
+
summary: "Register a new user",
|
|
103
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
104
|
+
rate_limit: "3 per hour per IP (blocked 30 min after exceeding)",
|
|
105
|
+
request_body: {
|
|
106
|
+
email: "string (required) - valid email",
|
|
107
|
+
password: "string (required) - min 8 chars, 1 uppercase, 1 lowercase, 1 digit",
|
|
108
|
+
display_name: "string (optional)",
|
|
109
|
+
locale: "string (optional, default: 'en')",
|
|
110
|
+
return_to: "string (optional) - frontend URL for email verification redirect (e.g. https://app.example.com/verify-email)"
|
|
111
|
+
},
|
|
112
|
+
response: "CONDITIONAL - depends on tenant email verification setting",
|
|
113
|
+
response_when_no_verification: {
|
|
114
|
+
access_token: "string - JWT access token (expires in 1 hour)",
|
|
115
|
+
refresh_token: "string - JWT refresh token",
|
|
116
|
+
token_type: "Bearer",
|
|
117
|
+
expires_in: "number - seconds until expiration",
|
|
118
|
+
user: {
|
|
119
|
+
id: "uuid",
|
|
120
|
+
email: "string",
|
|
121
|
+
display_name: "string | null",
|
|
122
|
+
avatar_url: "string | null",
|
|
123
|
+
is_active: "boolean",
|
|
124
|
+
created_at: "ISO datetime"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
response_when_verification_required: {
|
|
128
|
+
message: "Registration successful. Please check your email to verify your account.",
|
|
129
|
+
verification_required: "true"
|
|
130
|
+
},
|
|
131
|
+
note: "The response depends on whether the tenant has email verification enabled. When verification IS required, NO tokens are returned — the user must verify their email first, then login. Check for 'verification_required: true' in the response to show the appropriate UI.",
|
|
132
|
+
example_request: `{
|
|
133
|
+
"email": "user@example.com",
|
|
134
|
+
"password": "SecurePass123",
|
|
135
|
+
"display_name": "John Doe",
|
|
136
|
+
"return_to": "https://myapp.com/verify-email"
|
|
137
|
+
}`
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
method: "POST",
|
|
141
|
+
path: "/v1/auth/login",
|
|
142
|
+
summary: "Login with email and password",
|
|
143
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
144
|
+
rate_limit: "10 per 15 min per IP (blocked 15 min after exceeding)",
|
|
145
|
+
request_body: {
|
|
146
|
+
email: "string (required)",
|
|
147
|
+
password: "string (required)"
|
|
148
|
+
},
|
|
149
|
+
response: "Same as register",
|
|
150
|
+
example_request: `{
|
|
151
|
+
"email": "user@example.com",
|
|
152
|
+
"password": "SecurePass123"
|
|
153
|
+
}`
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
method: "POST",
|
|
157
|
+
path: "/v1/auth/refresh",
|
|
158
|
+
summary: "Refresh access token using refresh token",
|
|
159
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
160
|
+
request_body: {
|
|
161
|
+
refresh_token: "string (required)"
|
|
162
|
+
},
|
|
163
|
+
response: "Same as login (with same refresh_token returned)"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
method: "GET",
|
|
167
|
+
path: "/v1/auth/me",
|
|
168
|
+
summary: "Get current authenticated user info",
|
|
169
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
170
|
+
response: {
|
|
171
|
+
id: "uuid",
|
|
172
|
+
email: "string",
|
|
173
|
+
display_name: "string | null",
|
|
174
|
+
avatar_url: "string | null",
|
|
175
|
+
is_active: "boolean",
|
|
176
|
+
created_at: "ISO datetime"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
method: "POST",
|
|
181
|
+
path: "/v1/auth/logout",
|
|
182
|
+
summary: "Logout (invalidates token on client side)",
|
|
183
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
184
|
+
response: { message: "Successfully logged out" }
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
method: "POST",
|
|
188
|
+
path: "/v1/auth/forgot-password",
|
|
189
|
+
summary: "Request password reset email",
|
|
190
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
191
|
+
rate_limit: "3 per hour per IP (blocked 30 min after exceeding)",
|
|
192
|
+
request_body: {
|
|
193
|
+
email: "string (required)"
|
|
194
|
+
},
|
|
195
|
+
response: { message: "Password reset email sent" },
|
|
196
|
+
note: "Always returns success even if email doesn't exist (security)"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
method: "POST",
|
|
200
|
+
path: "/v1/auth/reset-password",
|
|
201
|
+
summary: "Reset password using token from email",
|
|
202
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
203
|
+
request_body: {
|
|
204
|
+
token: "string (required) - from email link",
|
|
205
|
+
new_password: "string (required) - same requirements as register"
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
method: "POST",
|
|
210
|
+
path: "/v1/auth/change-password",
|
|
211
|
+
summary: "Change password for authenticated user",
|
|
212
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
213
|
+
rate_limit: "5 per hour per user (blocked 30 min after exceeding)",
|
|
214
|
+
request_body: {
|
|
215
|
+
current_password: "string (required)",
|
|
216
|
+
new_password: "string (required) - same requirements as register"
|
|
217
|
+
},
|
|
218
|
+
response: { message: "Password changed successfully" }
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
method: "POST",
|
|
222
|
+
path: "/v1/auth/verify-email",
|
|
223
|
+
summary: "Verify end-user email with token (requires API Key)",
|
|
224
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
225
|
+
request_body: {
|
|
226
|
+
token: "string (required) - from verification email link"
|
|
227
|
+
},
|
|
228
|
+
response: { message: "Email verified successfully" },
|
|
229
|
+
note: "Use when the verification link is opened in a context that has the tenant API key (e.g. end-user app)."
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
method: "POST",
|
|
233
|
+
path: "/v1/auth/verify-email-with-token",
|
|
234
|
+
summary: "Verify end-user email with token only (no API Key)",
|
|
235
|
+
auth: { api_key: "none", jwt: false },
|
|
236
|
+
rate_limit: "20 per hour per IP",
|
|
237
|
+
request_body: {
|
|
238
|
+
token: "string (required) - from verification email link"
|
|
239
|
+
},
|
|
240
|
+
response: { message: "Email verified successfully" },
|
|
241
|
+
note: "Use when the verification link is opened from a page without API key (e.g. dashboard domain). Rate limited by IP."
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
method: "POST",
|
|
245
|
+
path: "/v1/auth/resend-verification",
|
|
246
|
+
summary: "Resend verification email for end-user",
|
|
247
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
248
|
+
rate_limit: "3 per hour per IP (blocked 30 min after exceeding)",
|
|
249
|
+
request_body: {
|
|
250
|
+
email: "string (required) - valid email address"
|
|
251
|
+
},
|
|
252
|
+
response: { message: "Verification email sent" },
|
|
253
|
+
note: "Always returns success even if email doesn't exist (security). Rate limited to prevent abuse."
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
method: "GET",
|
|
257
|
+
path: "/v1/auth/providers",
|
|
258
|
+
summary: "List enabled OAuth providers for this tenant",
|
|
259
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
260
|
+
response: {
|
|
261
|
+
providers: "[ { provider: 'google', name: 'Google' }, ... ]"
|
|
262
|
+
},
|
|
263
|
+
note: "Use on login page to show/hide 'Login with Google' button. Only returns providers with is_enabled in tenant config."
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
method: "GET",
|
|
267
|
+
path: "/v1/auth/google/url",
|
|
268
|
+
summary: "Get Google OAuth authorization URL",
|
|
269
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
270
|
+
query_params: {
|
|
271
|
+
redirect_uri: "string (required) - frontend URL where Google redirects after auth (must match tenant config)"
|
|
272
|
+
},
|
|
273
|
+
response: { authorization_url: "string - redirect user to this URL" }
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
method: "GET",
|
|
277
|
+
path: "/v1/auth/google/callback",
|
|
278
|
+
summary: "Google OAuth callback (called by Google, not by your frontend)",
|
|
279
|
+
auth: { api_key: "none", jwt: false },
|
|
280
|
+
query_params: {
|
|
281
|
+
code: "from Google",
|
|
282
|
+
state: "from Google (HMAC-signed, contains tenant_id + redirect_uri)"
|
|
283
|
+
},
|
|
284
|
+
response: "Redirects to frontend redirect_uri with tokens in URL fragment (#): access_token, refresh_token, expires_in. Errors redirect with ?error=oauth_failed&error_description=..."
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
organizations: {
|
|
289
|
+
description: "Organization management - create orgs, manage members, invitations, subscriptions, usage",
|
|
290
|
+
endpoints: [
|
|
291
|
+
{
|
|
292
|
+
method: "GET",
|
|
293
|
+
path: "/v1/organizations/my-organizations",
|
|
294
|
+
summary: "Get all organizations the current user belongs to",
|
|
295
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
296
|
+
response: {
|
|
297
|
+
organizations: [
|
|
298
|
+
{
|
|
299
|
+
id: "uuid",
|
|
300
|
+
name: "string",
|
|
301
|
+
slug: "string",
|
|
302
|
+
description: "string | null",
|
|
303
|
+
logo_url: "string | null",
|
|
304
|
+
role: "owner | admin | member | developer",
|
|
305
|
+
created_at: "ISO datetime"
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
method: "POST",
|
|
312
|
+
path: "/v1/organizations",
|
|
313
|
+
summary: "Create a new organization (user becomes owner)",
|
|
314
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
315
|
+
request_body: {
|
|
316
|
+
name: "string (required)",
|
|
317
|
+
slug: "string (required) - URL-friendly identifier",
|
|
318
|
+
description: "string (optional)"
|
|
319
|
+
},
|
|
320
|
+
example_request: `{
|
|
321
|
+
"name": "My Company",
|
|
322
|
+
"slug": "my-company",
|
|
323
|
+
"description": "Our awesome company"
|
|
324
|
+
}`
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
method: "GET",
|
|
328
|
+
path: "/v1/organizations/{organization_id}",
|
|
329
|
+
summary: "Get organization details (must be a member)",
|
|
330
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true }
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
method: "PUT",
|
|
334
|
+
path: "/v1/organizations/{organization_id}",
|
|
335
|
+
summary: "Update organization (owner/admin only)",
|
|
336
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
337
|
+
request_body: {
|
|
338
|
+
name: "string (optional)",
|
|
339
|
+
description: "string (optional)",
|
|
340
|
+
logo_url: "string (optional)"
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
method: "GET",
|
|
345
|
+
path: "/v1/organizations/{organization_id}/departments",
|
|
346
|
+
summary: "List organization departments",
|
|
347
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true }
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
method: "GET",
|
|
351
|
+
path: "/v1/organizations/{organization_id}/members",
|
|
352
|
+
summary: "List all members of the organization",
|
|
353
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
354
|
+
response: [
|
|
355
|
+
{
|
|
356
|
+
user_id: "uuid",
|
|
357
|
+
email: "string",
|
|
358
|
+
display_name: "string | null",
|
|
359
|
+
role: "owner | admin | member | developer",
|
|
360
|
+
joined_at: "ISO datetime"
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
method: "PUT",
|
|
366
|
+
path: "/v1/organizations/{organization_id}/members/{user_id}",
|
|
367
|
+
summary: "Update member role (owner/admin only)",
|
|
368
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
369
|
+
request_body: {
|
|
370
|
+
role: "admin | member | developer (required)"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
method: "DELETE",
|
|
375
|
+
path: "/v1/organizations/{organization_id}/members/{user_id}",
|
|
376
|
+
summary: "Remove a member from the organization (owner/admin only)",
|
|
377
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true }
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
method: "POST",
|
|
381
|
+
path: "/v1/organizations/{organization_id}/members/{user_id}/deactivate",
|
|
382
|
+
summary: "Deactivate an organization member",
|
|
383
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true }
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
method: "POST",
|
|
387
|
+
path: "/v1/organizations/{organization_id}/invite",
|
|
388
|
+
summary: "Invite a user to the organization (owner/admin only)",
|
|
389
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
390
|
+
request_body: {
|
|
391
|
+
email: "string (required)",
|
|
392
|
+
role: "admin | member | developer (required)"
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
method: "POST",
|
|
397
|
+
path: "/v1/organizations/{organization_id}/invitations/{invitation_id}/resend",
|
|
398
|
+
summary: "Resend an organization invitation",
|
|
399
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true }
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
method: "DELETE",
|
|
403
|
+
path: "/v1/organizations/{organization_id}/invitations/{invitation_id}",
|
|
404
|
+
summary: "Cancel/delete an organization invitation",
|
|
405
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true }
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
method: "POST",
|
|
409
|
+
path: "/v1/organizations/accept-invitation",
|
|
410
|
+
summary: "Accept an organization invitation",
|
|
411
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
412
|
+
request_body: {
|
|
413
|
+
token: "string (required) - from invitation email"
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
method: "POST",
|
|
418
|
+
path: "/v1/organizations/reject-invitation",
|
|
419
|
+
summary: "Reject an organization invitation (public, no auth required)",
|
|
420
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
421
|
+
request_body: {
|
|
422
|
+
token: "string (required) - from invitation email"
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
method: "GET",
|
|
427
|
+
path: "/v1/organizations/invitations/pending",
|
|
428
|
+
summary: "Get pending invitations for an email",
|
|
429
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
430
|
+
query_params: {
|
|
431
|
+
email: "string (required) - email to check"
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
method: "GET",
|
|
436
|
+
path: "/v1/organizations/invitations/{invitation_token}",
|
|
437
|
+
summary: "Get invitation details by token (public)",
|
|
438
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false }
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
method: "POST",
|
|
442
|
+
path: "/v1/organizations/{organization_id}/set-default",
|
|
443
|
+
summary: "Set default organization for the current user",
|
|
444
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true }
|
|
445
|
+
},
|
|
446
|
+
// Organization subscription management
|
|
447
|
+
{
|
|
448
|
+
method: "GET",
|
|
449
|
+
path: "/v1/organizations/{organization_id}/subscription",
|
|
450
|
+
summary: "Get organization's current subscription with plan details",
|
|
451
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
452
|
+
response: "SubscriptionWithPlanResponse - includes subscription + embedded plan object",
|
|
453
|
+
note: "Requires BILLING_READ permission. Returns null if no active subscription."
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
method: "POST",
|
|
457
|
+
path: "/v1/organizations/{organization_id}/subscription/{plan_id}",
|
|
458
|
+
summary: "Subscribe organization to a plan",
|
|
459
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
460
|
+
response: "SubscriptionResponse",
|
|
461
|
+
note: "Requires BILLING_MANAGE permission (owner/admin)."
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
method: "PUT",
|
|
465
|
+
path: "/v1/organizations/{organization_id}/subscription",
|
|
466
|
+
summary: "Update organization subscription",
|
|
467
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
468
|
+
request_body: {
|
|
469
|
+
plan_id: "uuid (optional)",
|
|
470
|
+
billing_cycle: "string (optional) - 'monthly' or 'yearly'",
|
|
471
|
+
subscription_metadata: "object (optional)"
|
|
472
|
+
},
|
|
473
|
+
response: "SubscriptionResponse",
|
|
474
|
+
note: "Requires BILLING_MANAGE permission."
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
method: "DELETE",
|
|
478
|
+
path: "/v1/organizations/{organization_id}/subscription",
|
|
479
|
+
summary: "Cancel organization subscription",
|
|
480
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
481
|
+
query_params: {
|
|
482
|
+
cancel_at_period_end: "boolean (default: true) - If true, access continues until period end"
|
|
483
|
+
},
|
|
484
|
+
response: {
|
|
485
|
+
message: "string",
|
|
486
|
+
subscription: "SubscriptionResponse"
|
|
487
|
+
},
|
|
488
|
+
note: "Requires BILLING_MANAGE permission."
|
|
489
|
+
},
|
|
490
|
+
// Organization usage
|
|
491
|
+
{
|
|
492
|
+
method: "GET",
|
|
493
|
+
path: "/v1/organizations/{organization_id}/usage",
|
|
494
|
+
summary: "Get organization usage statistics",
|
|
495
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
496
|
+
response: {
|
|
497
|
+
usage_stats: "object - Aggregated usage for the organization"
|
|
498
|
+
},
|
|
499
|
+
note: "Requires ORG_READ permission. Must be a member of the organization."
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
method: "GET",
|
|
503
|
+
path: "/v1/organizations/{organization_id}/usage/user/{target_user_id}",
|
|
504
|
+
summary: "Get usage statistics for a specific user in the organization",
|
|
505
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
506
|
+
response: {
|
|
507
|
+
usage: "object - User's usage within this organization"
|
|
508
|
+
},
|
|
509
|
+
note: "Requires MEMBERS_READ permission."
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
method: "POST",
|
|
513
|
+
path: "/v1/organizations/{organization_id}/usage/check",
|
|
514
|
+
summary: "Check if organization would exceed usage limits",
|
|
515
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
516
|
+
query_params: {
|
|
517
|
+
additional_messages: "number (default: 0) - Additional messages to check",
|
|
518
|
+
additional_tokens: "number (default: 0) - Additional tokens to check"
|
|
519
|
+
},
|
|
520
|
+
response: {
|
|
521
|
+
would_exceed: "boolean - Whether limits would be exceeded",
|
|
522
|
+
limits: "object - Current limit details"
|
|
523
|
+
},
|
|
524
|
+
note: "Pre-validate before making API calls. Requires ORG_READ permission."
|
|
525
|
+
}
|
|
526
|
+
]
|
|
527
|
+
},
|
|
528
|
+
subscriptions: {
|
|
529
|
+
description: "Subscription and billing management with Stripe",
|
|
530
|
+
endpoints: [
|
|
531
|
+
{
|
|
532
|
+
method: "GET",
|
|
533
|
+
path: "/v1/subscriptions/plans",
|
|
534
|
+
summary: "List all available subscription plans",
|
|
535
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
536
|
+
response: [
|
|
537
|
+
{
|
|
538
|
+
id: "uuid",
|
|
539
|
+
name: "string",
|
|
540
|
+
description: "string",
|
|
541
|
+
price_monthly: "number",
|
|
542
|
+
price_yearly: "number",
|
|
543
|
+
currency: "string (e.g., 'usd')",
|
|
544
|
+
features: "array of strings",
|
|
545
|
+
is_active: "boolean",
|
|
546
|
+
is_free: "boolean",
|
|
547
|
+
max_cost_usd_per_month: "number | null - Maximum AI usage cost per month",
|
|
548
|
+
max_messages_per_month: "number | null - Maximum AI requests per month",
|
|
549
|
+
max_tokens_per_month: "number | null - DEPRECATED: Use max_cost_usd_per_month",
|
|
550
|
+
stripe_product_id: "string",
|
|
551
|
+
stripe_price_id_monthly: "string",
|
|
552
|
+
stripe_price_id_yearly: "string",
|
|
553
|
+
created_at: "ISO datetime",
|
|
554
|
+
updated_at: "ISO datetime"
|
|
555
|
+
}
|
|
556
|
+
],
|
|
557
|
+
example_response: `[
|
|
558
|
+
{
|
|
559
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
560
|
+
"name": "Pro Plan",
|
|
561
|
+
"description": "For growing teams",
|
|
562
|
+
"price_monthly": 29.99,
|
|
563
|
+
"price_yearly": 299.99,
|
|
564
|
+
"currency": "usd",
|
|
565
|
+
"features": ["API Access", "Priority Support", "Advanced Analytics"],
|
|
566
|
+
"is_active": true,
|
|
567
|
+
"is_free": false,
|
|
568
|
+
"max_cost_usd_per_month": 100.00,
|
|
569
|
+
"max_messages_per_month": 1000,
|
|
570
|
+
"max_tokens_per_month": null,
|
|
571
|
+
"stripe_product_id": "prod_xxxxx",
|
|
572
|
+
"stripe_price_id_monthly": "price_xxxxx",
|
|
573
|
+
"stripe_price_id_yearly": "price_xxxxx",
|
|
574
|
+
"created_at": "2024-01-15T10:30:00Z",
|
|
575
|
+
"updated_at": "2024-01-15T10:30:00Z"
|
|
576
|
+
}
|
|
577
|
+
]`
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
method: "GET",
|
|
581
|
+
path: "/v1/subscriptions/current",
|
|
582
|
+
summary: "Get current user's active subscription",
|
|
583
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
584
|
+
response: {
|
|
585
|
+
id: "uuid",
|
|
586
|
+
plan_id: "uuid",
|
|
587
|
+
organization_id: "uuid",
|
|
588
|
+
tenant_id: "uuid",
|
|
589
|
+
stripe_customer_id: "string",
|
|
590
|
+
stripe_subscription_id: "string",
|
|
591
|
+
billing_cycle: "monthly | yearly",
|
|
592
|
+
status: "active | cancelled | past_due | incomplete | trialing | unpaid",
|
|
593
|
+
current_period_start: "ISO datetime",
|
|
594
|
+
current_period_end: "ISO datetime",
|
|
595
|
+
cancel_at_period_end: "boolean",
|
|
596
|
+
canceled_at: "ISO datetime | null",
|
|
597
|
+
trial_start: "ISO datetime | null",
|
|
598
|
+
trial_end: "ISO datetime | null",
|
|
599
|
+
subscription_metadata: "object - custom metadata",
|
|
600
|
+
created_at: "ISO datetime",
|
|
601
|
+
updated_at: "ISO datetime"
|
|
602
|
+
},
|
|
603
|
+
example_response: `{
|
|
604
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
605
|
+
"plan_id": "660e8400-e29b-41d4-a716-446655440001",
|
|
606
|
+
"organization_id": "770e8400-e29b-41d4-a716-446655440002",
|
|
607
|
+
"tenant_id": "880e8400-e29b-41d4-a716-446655440003",
|
|
608
|
+
"stripe_customer_id": "cus_xxxxx",
|
|
609
|
+
"stripe_subscription_id": "sub_xxxxx",
|
|
610
|
+
"billing_cycle": "monthly",
|
|
611
|
+
"status": "active",
|
|
612
|
+
"current_period_start": "2024-01-01T00:00:00Z",
|
|
613
|
+
"current_period_end": "2024-02-01T00:00:00Z",
|
|
614
|
+
"cancel_at_period_end": false,
|
|
615
|
+
"canceled_at": null,
|
|
616
|
+
"trial_start": null,
|
|
617
|
+
"trial_end": null,
|
|
618
|
+
"subscription_metadata": {},
|
|
619
|
+
"created_at": "2024-01-01T00:00:00Z",
|
|
620
|
+
"updated_at": "2024-01-01T00:00:00Z"
|
|
621
|
+
}`,
|
|
622
|
+
note: "Does NOT include plan_name or price. Use plan_id to fetch plan details if needed."
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
method: "POST",
|
|
626
|
+
path: "/v1/subscriptions/checkout",
|
|
627
|
+
summary: "Create Stripe checkout session for subscription",
|
|
628
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
629
|
+
request_body: {
|
|
630
|
+
plan_id: "uuid (required)",
|
|
631
|
+
success_url: "string (required) - URL to redirect after success",
|
|
632
|
+
cancel_url: "string (required) - URL to redirect on cancel",
|
|
633
|
+
organization_id: "uuid (optional) - for org subscriptions"
|
|
634
|
+
},
|
|
635
|
+
response: {
|
|
636
|
+
checkout_url: "string - Stripe checkout URL",
|
|
637
|
+
session_id: "string"
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
method: "GET",
|
|
642
|
+
path: "/v1/subscriptions/plans/{plan_id}",
|
|
643
|
+
summary: "Get plan details by ID",
|
|
644
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
645
|
+
response: "PlanResponse (same as /v1/subscriptions/plans list item)",
|
|
646
|
+
note: "Tenant-isolated: plan must belong to tenant or be a global plan."
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
method: "POST",
|
|
650
|
+
path: "/v1/subscriptions/subscriptions",
|
|
651
|
+
summary: "Create a new subscription",
|
|
652
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
653
|
+
request_body: {
|
|
654
|
+
plan_id: "uuid (required)",
|
|
655
|
+
organization_id: "uuid (required)",
|
|
656
|
+
billing_cycle: "string (default: 'monthly') - 'monthly' or 'yearly'",
|
|
657
|
+
subscription_metadata: "object (optional) - custom metadata"
|
|
658
|
+
},
|
|
659
|
+
response: "SubscriptionResponse (same as /v1/subscriptions/current)"
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
method: "GET",
|
|
663
|
+
path: "/v1/subscriptions/subscriptions/{subscription_id}",
|
|
664
|
+
summary: "Get subscription details by ID",
|
|
665
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
666
|
+
response: "SubscriptionResponse",
|
|
667
|
+
note: "User must own the subscription."
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
method: "PUT",
|
|
671
|
+
path: "/v1/subscriptions/subscriptions/{subscription_id}",
|
|
672
|
+
summary: "Update a subscription",
|
|
673
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
674
|
+
request_body: {
|
|
675
|
+
plan_id: "uuid (optional)",
|
|
676
|
+
billing_cycle: "string (optional) - 'monthly' or 'yearly'",
|
|
677
|
+
subscription_metadata: "object (optional)"
|
|
678
|
+
},
|
|
679
|
+
response: "SubscriptionResponse",
|
|
680
|
+
note: "User must own the subscription."
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
method: "DELETE",
|
|
684
|
+
path: "/v1/subscriptions/subscriptions/{subscription_id}",
|
|
685
|
+
summary: "Cancel/delete a subscription",
|
|
686
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
687
|
+
query_params: {
|
|
688
|
+
cancel_at_period_end: "boolean (default: true) - if true, access continues until period end"
|
|
689
|
+
},
|
|
690
|
+
response: "SubscriptionResponse",
|
|
691
|
+
note: "Returns 403 if user doesn't own the subscription."
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
method: "GET",
|
|
695
|
+
path: "/v1/subscriptions/users/{user_id}/usage",
|
|
696
|
+
summary: "Get user plan usage statistics",
|
|
697
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
698
|
+
response: {
|
|
699
|
+
id: "uuid",
|
|
700
|
+
organization_id: "uuid",
|
|
701
|
+
user_id: "uuid",
|
|
702
|
+
plan_id: "uuid | null",
|
|
703
|
+
period_start: "ISO datetime",
|
|
704
|
+
period_end: "ISO datetime",
|
|
705
|
+
messages_count: "number",
|
|
706
|
+
tokens_count: "number",
|
|
707
|
+
usage_metadata: "object",
|
|
708
|
+
created_at: "ISO datetime",
|
|
709
|
+
updated_at: "ISO datetime"
|
|
710
|
+
},
|
|
711
|
+
note: "User can only access own usage. Returns 404 if no active plan."
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
method: "GET",
|
|
715
|
+
path: "/v1/subscriptions/users/{user_id}/usage/limits",
|
|
716
|
+
summary: "Check user usage limits",
|
|
717
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
718
|
+
response: {
|
|
719
|
+
limits: "object with plan limit details"
|
|
720
|
+
},
|
|
721
|
+
note: "User can only check own limits."
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
method: "PUT",
|
|
725
|
+
path: "/v1/subscriptions/users/{user_id}/plan/{plan_id}",
|
|
726
|
+
summary: "Assign a plan to a user",
|
|
727
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
728
|
+
response: {
|
|
729
|
+
success: "boolean",
|
|
730
|
+
user_id: "uuid",
|
|
731
|
+
plan_id: "uuid"
|
|
732
|
+
},
|
|
733
|
+
note: "User can only assign plans to themselves. Typically done via subscriptions."
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
method: "POST",
|
|
737
|
+
path: "/v1/subscriptions/organizations/{organization_id}/resume",
|
|
738
|
+
summary: "Resume a cancelled organization subscription",
|
|
739
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
740
|
+
query_params: {
|
|
741
|
+
user_id: "uuid (required) - User requesting the resume"
|
|
742
|
+
},
|
|
743
|
+
response: "SubscriptionResponse"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
method: "GET",
|
|
747
|
+
path: "/v1/subscriptions/organizations/{organization_id}/invoices",
|
|
748
|
+
summary: "Get organization invoices from Stripe",
|
|
749
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
750
|
+
query_params: {
|
|
751
|
+
user_id: "uuid (required) - User requesting invoices"
|
|
752
|
+
},
|
|
753
|
+
response: "array of Stripe invoice objects"
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
method: "DELETE",
|
|
757
|
+
path: "/v1/subscriptions/organizations/{organization_id}/subscription",
|
|
758
|
+
summary: "Cancel organization subscription",
|
|
759
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
760
|
+
query_params: {
|
|
761
|
+
user_id: "uuid (required)",
|
|
762
|
+
cancel_at_period_end: "boolean (default: true)"
|
|
763
|
+
},
|
|
764
|
+
response: "SubscriptionResponse"
|
|
765
|
+
}
|
|
766
|
+
]
|
|
767
|
+
},
|
|
768
|
+
billing: {
|
|
769
|
+
description: "Billing operations - checkout, plan changes, cancellation. All endpoints require SECRET key (sk_live_*) only.",
|
|
770
|
+
endpoints: [
|
|
771
|
+
{
|
|
772
|
+
method: "GET",
|
|
773
|
+
path: "/v1/billing/available-plans",
|
|
774
|
+
summary: "List available billing plans for upgrade/downgrade",
|
|
775
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
776
|
+
response: {
|
|
777
|
+
success: "boolean",
|
|
778
|
+
plans: "array of plan objects with: id (uuid), name (string), price_cents (int), price_display (string), currency (string), features (array), is_free (boolean), stripe_price_id (string | null)"
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
method: "POST",
|
|
783
|
+
path: "/v1/billing/create-checkout-session",
|
|
784
|
+
summary: "Create Stripe checkout session for billing",
|
|
785
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
786
|
+
request_body: {
|
|
787
|
+
user_id: "uuid (required) - End-user ID",
|
|
788
|
+
organization_id: "uuid (required) - Organization to subscribe",
|
|
789
|
+
plan_id: "uuid (required) - Target plan",
|
|
790
|
+
success_url: "string (required) - Redirect URL after successful checkout",
|
|
791
|
+
cancel_url: "string (required) - Redirect URL on cancel"
|
|
792
|
+
},
|
|
793
|
+
response: {
|
|
794
|
+
checkout_url: "string - Stripe hosted checkout URL",
|
|
795
|
+
session_id: "string - Stripe session ID",
|
|
796
|
+
plan_name: "string - Name of selected plan",
|
|
797
|
+
price: "string - Display price",
|
|
798
|
+
message: "string | null"
|
|
799
|
+
},
|
|
800
|
+
example_request: `{
|
|
801
|
+
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
802
|
+
"organization_id": "660e8400-e29b-41d4-a716-446655440001",
|
|
803
|
+
"plan_id": "770e8400-e29b-41d4-a716-446655440002",
|
|
804
|
+
"success_url": "https://myapp.com/billing/success",
|
|
805
|
+
"cancel_url": "https://myapp.com/billing/cancel"
|
|
806
|
+
}`
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
method: "POST",
|
|
810
|
+
path: "/v1/billing/change-plan",
|
|
811
|
+
summary: "Change current subscription plan (upgrade/downgrade)",
|
|
812
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
813
|
+
request_body: {
|
|
814
|
+
subscription_id: "uuid (required) - Current subscription ID",
|
|
815
|
+
new_plan_id: "uuid (required) - Target plan ID"
|
|
816
|
+
},
|
|
817
|
+
response: {
|
|
818
|
+
success: "boolean",
|
|
819
|
+
message: "string",
|
|
820
|
+
plan_name: "string",
|
|
821
|
+
subscription_id: "uuid | null",
|
|
822
|
+
stripe_subscription_id: "string | null",
|
|
823
|
+
status: "string | null",
|
|
824
|
+
cancel_at_period_end: "boolean | null"
|
|
825
|
+
},
|
|
826
|
+
note: "Upgrades create Stripe prorations. Downgrades cancel at period end and schedule the new plan.",
|
|
827
|
+
example_request: `{
|
|
828
|
+
"subscription_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
829
|
+
"new_plan_id": "660e8400-e29b-41d4-a716-446655440001"
|
|
830
|
+
}`
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
method: "POST",
|
|
834
|
+
path: "/v1/billing/cancel-subscription",
|
|
835
|
+
summary: "Cancel current subscription",
|
|
836
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
837
|
+
query_params: {
|
|
838
|
+
subscription_id: "uuid (required) - Subscription to cancel",
|
|
839
|
+
cancel_at_period_end: "boolean (default: true) - If true, access continues until period end"
|
|
840
|
+
},
|
|
841
|
+
response: {
|
|
842
|
+
success: "boolean",
|
|
843
|
+
message: "string",
|
|
844
|
+
cancel_at_period_end: "boolean"
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
method: "GET",
|
|
849
|
+
path: "/v1/billing/billing-history-org/{organization_id}",
|
|
850
|
+
summary: "Get billing history (Stripe invoices) for organization",
|
|
851
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
852
|
+
query_params: {
|
|
853
|
+
limit: "number (default: 20) - Number of invoices to return"
|
|
854
|
+
},
|
|
855
|
+
response: {
|
|
856
|
+
success: "boolean",
|
|
857
|
+
organization_id: "string",
|
|
858
|
+
invoices: "array of Stripe invoice objects",
|
|
859
|
+
total_invoices: "number"
|
|
860
|
+
},
|
|
861
|
+
note: "Returns 404 if organization not found or doesn't belong to tenant."
|
|
862
|
+
}
|
|
863
|
+
]
|
|
864
|
+
},
|
|
865
|
+
usage: {
|
|
866
|
+
description: "Usage tracking and limits. Endpoints map to /v1/api/usage/* (current/history) and /v1/usage/* (stats/check/consume).",
|
|
867
|
+
endpoints: [
|
|
868
|
+
{
|
|
869
|
+
method: "GET",
|
|
870
|
+
path: "/v1/api/usage/current",
|
|
871
|
+
summary: "Get current usage statistics for authenticated user",
|
|
872
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
873
|
+
query_params: {
|
|
874
|
+
organization_id: "uuid (required) - Organization ID to check usage for"
|
|
875
|
+
},
|
|
876
|
+
response: {
|
|
877
|
+
period_start: "ISO datetime",
|
|
878
|
+
period_end: "ISO datetime",
|
|
879
|
+
plan_name: "string",
|
|
880
|
+
plan_id: "uuid",
|
|
881
|
+
usage: {
|
|
882
|
+
messages: {
|
|
883
|
+
current: "number - Messages used in current period",
|
|
884
|
+
max: "number | 'unlimited' - Maximum allowed messages",
|
|
885
|
+
remaining: "number | 'unlimited' - Remaining messages",
|
|
886
|
+
percentage: "number - Usage percentage (0-100)"
|
|
887
|
+
},
|
|
888
|
+
cost_usd: {
|
|
889
|
+
current: "number - Current AI cost in USD",
|
|
890
|
+
max: "number | 'unlimited' - Maximum allowed cost",
|
|
891
|
+
remaining: "number | 'unlimited' - Remaining budget",
|
|
892
|
+
percentage: "number - Usage percentage (0-100)"
|
|
893
|
+
},
|
|
894
|
+
tokens: {
|
|
895
|
+
total: "number - Total tokens used",
|
|
896
|
+
input: "number - Input tokens",
|
|
897
|
+
output: "number - Output tokens"
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
error: "string | null - Error message if any"
|
|
901
|
+
},
|
|
902
|
+
example_response: `{
|
|
903
|
+
"period_start": "2024-01-01T00:00:00Z",
|
|
904
|
+
"period_end": "2024-02-01T00:00:00Z",
|
|
905
|
+
"plan_name": "Pro Plan",
|
|
906
|
+
"plan_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
907
|
+
"usage": {
|
|
908
|
+
"messages": {
|
|
909
|
+
"current": 450,
|
|
910
|
+
"max": 1000,
|
|
911
|
+
"remaining": 550,
|
|
912
|
+
"percentage": 45.0
|
|
913
|
+
},
|
|
914
|
+
"cost_usd": {
|
|
915
|
+
"current": 23.50,
|
|
916
|
+
"max": 100.0,
|
|
917
|
+
"remaining": 76.50,
|
|
918
|
+
"percentage": 23.5
|
|
919
|
+
},
|
|
920
|
+
"tokens": {
|
|
921
|
+
"total": 125000,
|
|
922
|
+
"input": 75000,
|
|
923
|
+
"output": 50000
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
"error": null
|
|
927
|
+
}`
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
method: "GET",
|
|
931
|
+
path: "/v1/api/usage/history",
|
|
932
|
+
summary: "Get usage history",
|
|
933
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
934
|
+
query_params: {
|
|
935
|
+
organization_id: "uuid (required) - Organization ID",
|
|
936
|
+
limit: "number (default: 20) - Number of records to return",
|
|
937
|
+
offset: "number (default: 0) - Pagination offset"
|
|
938
|
+
},
|
|
939
|
+
response: "array of usage records with period dates, usage counts, and metadata"
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
method: "GET",
|
|
943
|
+
path: "/v1/api/usage/entity-usage",
|
|
944
|
+
summary: "Get custom entity usage statistics",
|
|
945
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
946
|
+
query_params: {
|
|
947
|
+
organization_id: "uuid (required) - Organization ID"
|
|
948
|
+
},
|
|
949
|
+
response: "object with entity usage statistics (schemas count, records count, etc.)"
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
method: "GET",
|
|
953
|
+
path: "/v1/usage/stats",
|
|
954
|
+
summary: "Get comprehensive usage statistics for user and organization",
|
|
955
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
956
|
+
query_params: {
|
|
957
|
+
organization_id: "uuid (required) - Organization ID to check usage for"
|
|
958
|
+
},
|
|
959
|
+
response: {
|
|
960
|
+
period_start: "string | null",
|
|
961
|
+
period_end: "string | null",
|
|
962
|
+
plan_name: "string | null",
|
|
963
|
+
plan_id: "string | null",
|
|
964
|
+
usage: "object - Usage stats by type",
|
|
965
|
+
error: "string | null"
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
method: "POST",
|
|
970
|
+
path: "/v1/usage/check",
|
|
971
|
+
summary: "Check if usage is allowed without consuming",
|
|
972
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
973
|
+
request_body: {
|
|
974
|
+
tokens: "number (required, gt 0) - Amount to check",
|
|
975
|
+
usage_type: "string (default: 'tokens') - 'tokens' or 'messages'",
|
|
976
|
+
user_id: "uuid (required) - Must match authenticated user",
|
|
977
|
+
organization_id: "uuid (required)"
|
|
978
|
+
},
|
|
979
|
+
response: {
|
|
980
|
+
allowed: "boolean",
|
|
981
|
+
current_usage: "number",
|
|
982
|
+
max_limit: "number | 'unlimited'",
|
|
983
|
+
remaining: "number | 'unlimited'",
|
|
984
|
+
requested: "number | null",
|
|
985
|
+
error: "string | null"
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
method: "POST",
|
|
990
|
+
path: "/v1/usage/consume",
|
|
991
|
+
summary: "Consume usage tokens/messages",
|
|
992
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
993
|
+
request_body: {
|
|
994
|
+
tokens: "number (required, gt 0) - Amount to consume",
|
|
995
|
+
usage_type: "string (default: 'tokens') - 'tokens' or 'messages'",
|
|
996
|
+
user_id: "uuid (required) - Must match authenticated user",
|
|
997
|
+
organization_id: "uuid (required)"
|
|
998
|
+
},
|
|
999
|
+
response: {
|
|
1000
|
+
success: "boolean",
|
|
1001
|
+
consumed: "number | object | null",
|
|
1002
|
+
usage_type: "string | null",
|
|
1003
|
+
allowed: "boolean",
|
|
1004
|
+
limits: "object - Current limits state",
|
|
1005
|
+
error: "string | null"
|
|
1006
|
+
},
|
|
1007
|
+
note: "Returns 402 Payment Required if usage limits exceeded."
|
|
1008
|
+
}
|
|
1009
|
+
]
|
|
1010
|
+
},
|
|
1011
|
+
agents: {
|
|
1012
|
+
description: "AI Agents - Interact with AI agents created by the tenant",
|
|
1013
|
+
endpoints: [
|
|
1014
|
+
{
|
|
1015
|
+
method: "GET",
|
|
1016
|
+
path: "/v1/user/agents/available",
|
|
1017
|
+
summary: "List available agents for end-users",
|
|
1018
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1019
|
+
query_params: {
|
|
1020
|
+
skip: "number (default: 0)",
|
|
1021
|
+
limit: "number (default: 50, max: 100)"
|
|
1022
|
+
},
|
|
1023
|
+
response: [
|
|
1024
|
+
{
|
|
1025
|
+
id: "uuid",
|
|
1026
|
+
tenant_id: "uuid",
|
|
1027
|
+
name: "string",
|
|
1028
|
+
description: "string | null",
|
|
1029
|
+
system_prompt: "string",
|
|
1030
|
+
response_type: "text | audio | video",
|
|
1031
|
+
rag_provider: "NONE | GOOGLE_FILE_SEARCH",
|
|
1032
|
+
is_active: "boolean",
|
|
1033
|
+
rag_config: "object | null",
|
|
1034
|
+
mcp_servers: "array of MCP server configs",
|
|
1035
|
+
created_by: "uuid",
|
|
1036
|
+
created_at: "ISO datetime",
|
|
1037
|
+
updated_at: "ISO datetime"
|
|
1038
|
+
}
|
|
1039
|
+
],
|
|
1040
|
+
note: "Returns only active agents that the tenant has made available"
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
method: "GET",
|
|
1044
|
+
path: "/v1/user/agents/{agent_id}",
|
|
1045
|
+
summary: "Get agent details by ID",
|
|
1046
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1047
|
+
response: "AgentResponse (same as list)"
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
method: "POST",
|
|
1051
|
+
path: "/v1/user/agents/{agent_id}/chat",
|
|
1052
|
+
summary: "Chat with an agent (execute)",
|
|
1053
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1054
|
+
rate_limit: "60 per minute per user",
|
|
1055
|
+
request_body: {
|
|
1056
|
+
messages: "array (optional) - Chat messages [{'role': 'user', 'content': '...'}]",
|
|
1057
|
+
input: "string (optional) - Alternative to messages for single input",
|
|
1058
|
+
conversation_id: "uuid (optional) - Conversation ID for persistent history. If provided, previous messages are auto-injected and new messages are saved.",
|
|
1059
|
+
model: "string (optional) - LLM model override",
|
|
1060
|
+
temperature: "number (optional)",
|
|
1061
|
+
max_tokens: "number (optional)",
|
|
1062
|
+
use_tools: "boolean (default: true) - Enable agent tools",
|
|
1063
|
+
use_rag: "boolean (default: true) - Enable RAG retrieval"
|
|
1064
|
+
},
|
|
1065
|
+
response: {
|
|
1066
|
+
id: "uuid - Execution ID",
|
|
1067
|
+
agent_id: "uuid",
|
|
1068
|
+
tenant_id: "uuid",
|
|
1069
|
+
user_id: "uuid - Your user ID",
|
|
1070
|
+
organization_id: "uuid - Your organization ID",
|
|
1071
|
+
input_text: "string - User input",
|
|
1072
|
+
output_text: "string - Agent response",
|
|
1073
|
+
tokens_used: "number",
|
|
1074
|
+
execution_time_ms: "number",
|
|
1075
|
+
status: "SUCCESS | FAILED | PENDING",
|
|
1076
|
+
error_message: "string | null",
|
|
1077
|
+
execution_metadata: "object | null",
|
|
1078
|
+
created_at: "ISO datetime"
|
|
1079
|
+
},
|
|
1080
|
+
example_request: `{
|
|
1081
|
+
"messages": [
|
|
1082
|
+
{"role": "user", "content": "Hello, how can you help me?"}
|
|
1083
|
+
],
|
|
1084
|
+
"temperature": 0.7
|
|
1085
|
+
}`,
|
|
1086
|
+
example_response: `{
|
|
1087
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
1088
|
+
"agent_id": "660e8400-e29b-41d4-a716-446655440001",
|
|
1089
|
+
"tenant_id": "770e8400-e29b-41d4-a716-446655440002",
|
|
1090
|
+
"user_id": "880e8400-e29b-41d4-a716-446655440003",
|
|
1091
|
+
"organization_id": "990e8400-e29b-41d4-a716-446655440004",
|
|
1092
|
+
"input_text": "Hello, how can you help me?",
|
|
1093
|
+
"output_text": "I'm an AI assistant designed to help you with...",
|
|
1094
|
+
"tokens_used": 150,
|
|
1095
|
+
"execution_time_ms": 1234,
|
|
1096
|
+
"status": "SUCCESS",
|
|
1097
|
+
"error_message": null,
|
|
1098
|
+
"execution_metadata": {"model": "gpt-4o", "temperature": 0.7},
|
|
1099
|
+
"created_at": "2024-01-30T10:30:00Z"
|
|
1100
|
+
}`,
|
|
1101
|
+
note: "Usage is tracked against your plan limits. Use /v1/api/usage/current to check remaining usage."
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
method: "GET",
|
|
1105
|
+
path: "/v1/user/agents/{agent_id}/history",
|
|
1106
|
+
summary: "Get your conversation history with an agent",
|
|
1107
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1108
|
+
query_params: {
|
|
1109
|
+
skip: "number (default: 0)",
|
|
1110
|
+
limit: "number (default: 20, max: 50)"
|
|
1111
|
+
},
|
|
1112
|
+
response: "Array of AgentExecutionResponse (same structure as chat response)",
|
|
1113
|
+
note: "Returns only YOUR past interactions with this agent"
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
method: "POST",
|
|
1117
|
+
path: "/v1/user/agents/{agent_id}/conversations",
|
|
1118
|
+
summary: "Create a new conversation session with an agent",
|
|
1119
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1120
|
+
request_body: {
|
|
1121
|
+
title: "string (optional) - Conversation title"
|
|
1122
|
+
},
|
|
1123
|
+
response: {
|
|
1124
|
+
id: "uuid",
|
|
1125
|
+
tenant_id: "uuid",
|
|
1126
|
+
user_id: "uuid",
|
|
1127
|
+
agent_id: "uuid",
|
|
1128
|
+
title: "string | null",
|
|
1129
|
+
status: "ACTIVE | ENDED",
|
|
1130
|
+
created_at: "ISO datetime",
|
|
1131
|
+
updated_at: "ISO datetime"
|
|
1132
|
+
},
|
|
1133
|
+
note: "Use the returned conversation ID in the chat endpoint's conversation_id field to persist messages"
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
method: "GET",
|
|
1137
|
+
path: "/v1/user/agents/{agent_id}/conversations",
|
|
1138
|
+
summary: "List your conversations with an agent",
|
|
1139
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1140
|
+
query_params: {
|
|
1141
|
+
skip: "number (default: 0)",
|
|
1142
|
+
limit: "number (default: 20, max: 50)"
|
|
1143
|
+
},
|
|
1144
|
+
response: "Array of ConversationResponse (same as create response)"
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
method: "GET",
|
|
1148
|
+
path: "/v1/user/agents/{agent_id}/conversations/{conversation_id}",
|
|
1149
|
+
summary: "Get a conversation with all messages",
|
|
1150
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1151
|
+
response: {
|
|
1152
|
+
id: "uuid",
|
|
1153
|
+
tenant_id: "uuid",
|
|
1154
|
+
user_id: "uuid",
|
|
1155
|
+
agent_id: "uuid",
|
|
1156
|
+
title: "string | null",
|
|
1157
|
+
status: "ACTIVE | ENDED",
|
|
1158
|
+
created_at: "ISO datetime",
|
|
1159
|
+
updated_at: "ISO datetime",
|
|
1160
|
+
messages: "Array of { id, role, content, tokens_used, message_metadata, created_at }"
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
method: "DELETE",
|
|
1165
|
+
path: "/v1/user/agents/{agent_id}/conversations/{conversation_id}",
|
|
1166
|
+
summary: "End a conversation (soft close)",
|
|
1167
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1168
|
+
response: "204 No Content",
|
|
1169
|
+
note: "Marks the conversation as ENDED. No new messages can be added."
|
|
1170
|
+
}
|
|
1171
|
+
],
|
|
1172
|
+
important_notes: [
|
|
1173
|
+
"All agent endpoints require end-user authentication (API Key + JWT)",
|
|
1174
|
+
"Usage is tracked for billing against your subscription plan",
|
|
1175
|
+
"Agents with tools may take longer to respond as tools are executed",
|
|
1176
|
+
"RAG-enabled agents include context from knowledge bases in responses",
|
|
1177
|
+
"Use conversations to maintain chat history across multiple requests - create a conversation, then pass its ID to the chat endpoint"
|
|
1178
|
+
]
|
|
1179
|
+
},
|
|
1180
|
+
users: {
|
|
1181
|
+
description: "User management - CRUD, settings, activation/deactivation. Dual auth: sk_live_* for admin ops, pk_live_* + JWT for self-service.",
|
|
1182
|
+
endpoints: [
|
|
1183
|
+
{
|
|
1184
|
+
method: "POST",
|
|
1185
|
+
path: "/v1/users",
|
|
1186
|
+
summary: "Create a new user (admin, JWT required)",
|
|
1187
|
+
auth: { api_key: "sk_live_* ONLY", jwt: true },
|
|
1188
|
+
request_body: {
|
|
1189
|
+
sub: "string (required) - Unique user identifier",
|
|
1190
|
+
email: "string (optional) - valid email",
|
|
1191
|
+
display_name: "string (optional, max 100)",
|
|
1192
|
+
avatar_url: "string (optional)",
|
|
1193
|
+
locale: "string (optional, default: 'en')",
|
|
1194
|
+
tenant_id: "uuid (optional)",
|
|
1195
|
+
is_email_verified: "boolean (default: false)",
|
|
1196
|
+
status: "string (default: 'pending')",
|
|
1197
|
+
is_active: "boolean (default: true)"
|
|
1198
|
+
},
|
|
1199
|
+
response: {
|
|
1200
|
+
id: "uuid",
|
|
1201
|
+
email: "string",
|
|
1202
|
+
display_name: "string | null",
|
|
1203
|
+
avatar_url: "string | null",
|
|
1204
|
+
locale: "string",
|
|
1205
|
+
is_active: "boolean",
|
|
1206
|
+
tenant_id: "uuid",
|
|
1207
|
+
sub: "string",
|
|
1208
|
+
is_email_verified: "boolean",
|
|
1209
|
+
status: "string",
|
|
1210
|
+
current_plan_id: "uuid | null",
|
|
1211
|
+
default_organization_id: "uuid | null",
|
|
1212
|
+
created_at: "ISO datetime",
|
|
1213
|
+
updated_at: "ISO datetime",
|
|
1214
|
+
settings: "UserSettingsResponse | null"
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
method: "GET",
|
|
1219
|
+
path: "/v1/users",
|
|
1220
|
+
summary: "List all users (SECRET KEY ONLY)",
|
|
1221
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
1222
|
+
query_params: {
|
|
1223
|
+
page: "number (default: 1)",
|
|
1224
|
+
per_page: "number (default: 20, max: 100)",
|
|
1225
|
+
include_inactive: "boolean (default: false)"
|
|
1226
|
+
},
|
|
1227
|
+
response: {
|
|
1228
|
+
users: "array of UserResponse",
|
|
1229
|
+
total: "number",
|
|
1230
|
+
page: "number",
|
|
1231
|
+
per_page: "number",
|
|
1232
|
+
has_next: "boolean",
|
|
1233
|
+
has_prev: "boolean"
|
|
1234
|
+
},
|
|
1235
|
+
note: "Returns only users belonging to the secret key's tenant."
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
method: "GET",
|
|
1239
|
+
path: "/v1/users/stats",
|
|
1240
|
+
summary: "Get user statistics (SECRET KEY ONLY)",
|
|
1241
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
1242
|
+
response: {
|
|
1243
|
+
total_users: "number",
|
|
1244
|
+
active_users: "number",
|
|
1245
|
+
inactive_users: "number"
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
method: "GET",
|
|
1250
|
+
path: "/v1/users/{user_id}",
|
|
1251
|
+
summary: "Get user by ID (sk_live_* or self with pk_live_*)",
|
|
1252
|
+
auth: { api_key: "pk_live_* (self) or sk_live_*", jwt: "optional" },
|
|
1253
|
+
query_params: {
|
|
1254
|
+
include_settings: "boolean (default: false) - Include user settings in response"
|
|
1255
|
+
},
|
|
1256
|
+
response: {
|
|
1257
|
+
id: "uuid",
|
|
1258
|
+
email: "string",
|
|
1259
|
+
display_name: "string | null",
|
|
1260
|
+
avatar_url: "string | null",
|
|
1261
|
+
locale: "string",
|
|
1262
|
+
is_active: "boolean",
|
|
1263
|
+
tenant_id: "uuid",
|
|
1264
|
+
sub: "string",
|
|
1265
|
+
is_email_verified: "boolean",
|
|
1266
|
+
status: "string",
|
|
1267
|
+
current_plan_id: "uuid | null",
|
|
1268
|
+
default_organization_id: "uuid | null",
|
|
1269
|
+
created_at: "ISO datetime",
|
|
1270
|
+
updated_at: "ISO datetime",
|
|
1271
|
+
settings: "UserSettingsResponse | null (only if include_settings=true)"
|
|
1272
|
+
},
|
|
1273
|
+
note: "With sk_live_*: can view any user in tenant. With pk_live_* + JWT: can only view own profile (returns 404 for other users)."
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
method: "PUT",
|
|
1277
|
+
path: "/v1/users/{user_id}",
|
|
1278
|
+
summary: "Update user profile",
|
|
1279
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1280
|
+
request_body: {
|
|
1281
|
+
display_name: "string (optional, max 100)",
|
|
1282
|
+
avatar_url: "string (optional)",
|
|
1283
|
+
locale: "string (optional)"
|
|
1284
|
+
},
|
|
1285
|
+
response: "UserResponse (same fields as GET)",
|
|
1286
|
+
note: "With sk_live_*: can update any user. With pk_live_* + JWT: can only update own profile."
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
method: "PUT",
|
|
1290
|
+
path: "/v1/users/{user_id}/settings",
|
|
1291
|
+
summary: "Update user settings",
|
|
1292
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1293
|
+
request_body: "UserSettingsUpdate object (extensible, currently empty placeholder for future settings)",
|
|
1294
|
+
response: "UserWithSettingsResponse (UserResponse + settings object)",
|
|
1295
|
+
note: "With sk_live_*: can update any user's settings. With pk_live_* + JWT: can only update own settings."
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
method: "POST",
|
|
1299
|
+
path: "/v1/users/{user_id}/deactivate",
|
|
1300
|
+
summary: "Deactivate a user (soft delete)",
|
|
1301
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1302
|
+
response: "UserResponse",
|
|
1303
|
+
note: "Sets is_active=false. With sk_live_*: can deactivate any user. With pk_live_* + JWT: can deactivate own account."
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
method: "POST",
|
|
1307
|
+
path: "/v1/users/{user_id}/activate",
|
|
1308
|
+
summary: "Activate a user (SECRET KEY ONLY)",
|
|
1309
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
1310
|
+
response: "UserResponse",
|
|
1311
|
+
note: "Sets is_active=true. Admin operation only."
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
method: "DELETE",
|
|
1315
|
+
path: "/v1/users/{user_id}",
|
|
1316
|
+
summary: "Delete a user (SECRET KEY ONLY, soft delete)",
|
|
1317
|
+
auth: { api_key: "sk_live_* ONLY", jwt: false },
|
|
1318
|
+
response: "204 No Content",
|
|
1319
|
+
note: "Performs soft delete (deactivation). Returns 404 if user not found."
|
|
1320
|
+
}
|
|
1321
|
+
]
|
|
1322
|
+
},
|
|
1323
|
+
plans: {
|
|
1324
|
+
description: "Plan listing - browse available subscription plans. All endpoints require JWT authentication.",
|
|
1325
|
+
endpoints: [
|
|
1326
|
+
{
|
|
1327
|
+
method: "GET",
|
|
1328
|
+
path: "/v1/plans",
|
|
1329
|
+
summary: "List all plans (paginated)",
|
|
1330
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1331
|
+
query_params: {
|
|
1332
|
+
skip: "number (default: 0) - Pagination offset",
|
|
1333
|
+
limit: "number (default: 100, max: 1000) - Items per page",
|
|
1334
|
+
include_inactive: "boolean (default: false) - Include deactivated plans"
|
|
1335
|
+
},
|
|
1336
|
+
response: {
|
|
1337
|
+
plans: "array of PlanResponse",
|
|
1338
|
+
total: "number",
|
|
1339
|
+
page: "number",
|
|
1340
|
+
per_page: "number",
|
|
1341
|
+
has_next: "boolean",
|
|
1342
|
+
has_prev: "boolean"
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
method: "GET",
|
|
1347
|
+
path: "/v1/plans/active",
|
|
1348
|
+
summary: "List active plans only",
|
|
1349
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1350
|
+
response: "array of PlanResponse",
|
|
1351
|
+
note: "Useful for displaying plan options on pricing pages."
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
method: "GET",
|
|
1355
|
+
path: "/v1/plans/free",
|
|
1356
|
+
summary: "List free plans only",
|
|
1357
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1358
|
+
response: "array of PlanResponse",
|
|
1359
|
+
note: "Useful for signup flows — auto-assign a free plan."
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
method: "GET",
|
|
1363
|
+
path: "/v1/plans/{plan_id}",
|
|
1364
|
+
summary: "Get plan details by ID",
|
|
1365
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1366
|
+
response: {
|
|
1367
|
+
id: "uuid",
|
|
1368
|
+
name: "string",
|
|
1369
|
+
description: "string | null",
|
|
1370
|
+
price_monthly: "number",
|
|
1371
|
+
price_yearly: "number",
|
|
1372
|
+
currency: "string (default: 'USD')",
|
|
1373
|
+
max_messages_per_month: "number | null",
|
|
1374
|
+
max_cost_usd_per_month: "number | null",
|
|
1375
|
+
features: "array of strings",
|
|
1376
|
+
is_free: "boolean",
|
|
1377
|
+
is_active: "boolean",
|
|
1378
|
+
stripe_product_id: "string | null",
|
|
1379
|
+
stripe_price_id_monthly: "string | null",
|
|
1380
|
+
stripe_price_id_yearly: "string | null",
|
|
1381
|
+
created_at: "ISO datetime",
|
|
1382
|
+
updated_at: "ISO datetime"
|
|
1383
|
+
},
|
|
1384
|
+
note: "Returns 404 if plan not found."
|
|
1385
|
+
}
|
|
1386
|
+
]
|
|
1387
|
+
},
|
|
1388
|
+
ai: {
|
|
1389
|
+
description: "AI services (LLM completions)",
|
|
1390
|
+
endpoints: [
|
|
1391
|
+
{
|
|
1392
|
+
method: "POST",
|
|
1393
|
+
path: "/v1/ai/completions",
|
|
1394
|
+
summary: "Generate AI completion (streaming supported)",
|
|
1395
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1396
|
+
request_body: {
|
|
1397
|
+
model: "string (e.g., 'gpt-4', 'claude-3-opus')",
|
|
1398
|
+
messages: [
|
|
1399
|
+
{ role: "system | user | assistant", content: "string" }
|
|
1400
|
+
],
|
|
1401
|
+
max_tokens: "number (optional)",
|
|
1402
|
+
temperature: "number (optional, 0-2)",
|
|
1403
|
+
stream: "boolean (optional)"
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
]
|
|
1407
|
+
},
|
|
1408
|
+
entities: {
|
|
1409
|
+
description: "Entity management (Custom Data) - define schemas and manage records. Two contexts: End-User API (API Key + JWT + X-Organization-Id) and Dashboard (Tenant JWT).",
|
|
1410
|
+
endpoints: [
|
|
1411
|
+
// ── End-User Schema Endpoints ──
|
|
1412
|
+
{
|
|
1413
|
+
method: "GET",
|
|
1414
|
+
path: "/v1/entity/schemas",
|
|
1415
|
+
summary: "List all entity schemas in the organization",
|
|
1416
|
+
auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
|
|
1417
|
+
query_params: {
|
|
1418
|
+
page: "number (default 1)",
|
|
1419
|
+
size: "number (default 20, max 100)"
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
method: "POST",
|
|
1424
|
+
path: "/v1/entity/schemas",
|
|
1425
|
+
summary: "Create a new entity schema",
|
|
1426
|
+
auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
|
|
1427
|
+
request_body: {
|
|
1428
|
+
name: "string (required) - unique per organization",
|
|
1429
|
+
slug: "string (required) - unique per organization, lowercase/hyphens only",
|
|
1430
|
+
description: "string (optional)",
|
|
1431
|
+
fields_definition: "object (required) - field definitions with type, required, constraints"
|
|
1432
|
+
},
|
|
1433
|
+
notes: "Supported field types: string, integer, float, boolean, datetime, text, enum, email, url, phone. Returns 409 if name or slug already exists."
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
method: "GET",
|
|
1437
|
+
path: "/v1/entity/schemas/{schema_id}",
|
|
1438
|
+
summary: "Get a specific schema by ID",
|
|
1439
|
+
auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" }
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
method: "PUT",
|
|
1443
|
+
path: "/v1/entity/schemas/{schema_id}",
|
|
1444
|
+
summary: "Update an existing schema",
|
|
1445
|
+
auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
|
|
1446
|
+
request_body: {
|
|
1447
|
+
name: "string (optional)",
|
|
1448
|
+
slug: "string (optional)",
|
|
1449
|
+
description: "string (optional)",
|
|
1450
|
+
fields_definition: "object (optional) - version auto-increments if changed",
|
|
1451
|
+
is_active: "boolean (optional)"
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
method: "DELETE",
|
|
1456
|
+
path: "/v1/entity/schemas/{schema_id}",
|
|
1457
|
+
summary: "Delete a schema (may fail if records exist)",
|
|
1458
|
+
auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" }
|
|
1459
|
+
},
|
|
1460
|
+
// ── End-User Record Endpoints ──
|
|
1461
|
+
{
|
|
1462
|
+
method: "POST",
|
|
1463
|
+
path: "/v1/entity/records",
|
|
1464
|
+
summary: "Create a new entity record",
|
|
1465
|
+
auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" },
|
|
1466
|
+
request_body: {
|
|
1467
|
+
schema_id: "uuid (required)",
|
|
1468
|
+
data: "object (required) - validated against schema fields_definition"
|
|
1469
|
+
},
|
|
1470
|
+
notes: "Sets created_by_id to the authenticated end-user."
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
method: "GET",
|
|
1474
|
+
path: "/v1/entity/records/{record_id}",
|
|
1475
|
+
summary: "Get a specific record by ID",
|
|
1476
|
+
auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" }
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
method: "PUT",
|
|
1480
|
+
path: "/v1/entity/records/{record_id}",
|
|
1481
|
+
summary: "Update a record",
|
|
1482
|
+
auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" },
|
|
1483
|
+
request_body: {
|
|
1484
|
+
data: "object (required) - validated against schema fields_definition"
|
|
1485
|
+
},
|
|
1486
|
+
notes: "Sets updated_by_id to the authenticated end-user."
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
method: "DELETE",
|
|
1490
|
+
path: "/v1/entity/records/{record_id}",
|
|
1491
|
+
summary: "Delete a record",
|
|
1492
|
+
auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" }
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
method: "POST",
|
|
1496
|
+
path: "/v1/entity/records/search",
|
|
1497
|
+
summary: "Advanced search for entity records with filters and ordering",
|
|
1498
|
+
auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
|
|
1499
|
+
request_body: {
|
|
1500
|
+
schema_id: "uuid (optional) - filter by schema",
|
|
1501
|
+
query: "object (optional) - filters: {field: value} for equality, {field: {operator: 'gt', value: 100}} for operators"
|
|
1502
|
+
},
|
|
1503
|
+
query_params: {
|
|
1504
|
+
page: "number (default 1)",
|
|
1505
|
+
size: "number (default 20, max 100)",
|
|
1506
|
+
order_by: "string (default 'created_at')",
|
|
1507
|
+
order_dir: "'asc' | 'desc' (default 'desc')"
|
|
1508
|
+
},
|
|
1509
|
+
notes: "Supported operators: eq, ne, gt, gte, lt, lte, like, ilike, in, not_in",
|
|
1510
|
+
example_request: `{
|
|
1511
|
+
"schema_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
1512
|
+
"query": {
|
|
1513
|
+
"price": { "operator": "gt", "value": 100 },
|
|
1514
|
+
"category": "electronics",
|
|
1515
|
+
"status": { "operator": "in", "value": ["active", "pending"] }
|
|
1516
|
+
}
|
|
1517
|
+
}`
|
|
1518
|
+
}
|
|
1519
|
+
]
|
|
1520
|
+
},
|
|
1521
|
+
departments: {
|
|
1522
|
+
description: "Department management within organizations",
|
|
1523
|
+
endpoints: [
|
|
1524
|
+
{
|
|
1525
|
+
method: "GET",
|
|
1526
|
+
path: "/v1/departments/{department_id}",
|
|
1527
|
+
summary: "Get department details by ID",
|
|
1528
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
1529
|
+
query_params: {
|
|
1530
|
+
user_id: "uuid (required) - User ID for membership verification"
|
|
1531
|
+
},
|
|
1532
|
+
response: {
|
|
1533
|
+
id: "uuid",
|
|
1534
|
+
organization_id: "uuid",
|
|
1535
|
+
name: "string",
|
|
1536
|
+
description: "string | null",
|
|
1537
|
+
is_default: "boolean",
|
|
1538
|
+
is_active: "boolean",
|
|
1539
|
+
settings: "object",
|
|
1540
|
+
created_at: "ISO datetime",
|
|
1541
|
+
updated_at: "ISO datetime"
|
|
1542
|
+
},
|
|
1543
|
+
note: "User must be a member of the organization this department belongs to."
|
|
1544
|
+
}
|
|
1545
|
+
]
|
|
1546
|
+
},
|
|
1547
|
+
files: {
|
|
1548
|
+
description: "End-user Drive - private file storage with folders. Users can upload/manage their own files and access shared tenant files (read-only). Shared files are controlled by the tenant's is_shared flag — only files explicitly marked as shared are visible to end-users.",
|
|
1549
|
+
endpoints: [
|
|
1550
|
+
{
|
|
1551
|
+
method: "POST",
|
|
1552
|
+
path: "/v1/user/files",
|
|
1553
|
+
summary: "Upload a file to user's Drive",
|
|
1554
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1555
|
+
query_params: {
|
|
1556
|
+
description: "string (optional) - File description",
|
|
1557
|
+
folder_id: "uuid (optional) - Target folder ID (null = root)"
|
|
1558
|
+
},
|
|
1559
|
+
request_body: "multipart/form-data with 'file' field",
|
|
1560
|
+
response: {
|
|
1561
|
+
message: "string",
|
|
1562
|
+
file: {
|
|
1563
|
+
id: "uuid",
|
|
1564
|
+
user_id: "uuid",
|
|
1565
|
+
tenant_id: "uuid",
|
|
1566
|
+
folder_id: "uuid | null",
|
|
1567
|
+
filename: "string",
|
|
1568
|
+
original_filename: "string",
|
|
1569
|
+
file_size: "integer",
|
|
1570
|
+
content_type: "string",
|
|
1571
|
+
storage_provider: "string",
|
|
1572
|
+
description: "string | null",
|
|
1573
|
+
extra_metadata: "object | null",
|
|
1574
|
+
created_at: "ISO datetime",
|
|
1575
|
+
updated_at: "ISO datetime"
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
method: "GET",
|
|
1581
|
+
path: "/v1/user/files",
|
|
1582
|
+
summary: "List user's files with optional folder filter",
|
|
1583
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1584
|
+
query_params: {
|
|
1585
|
+
folder_id: "uuid (optional) - Filter by folder (null = root files)",
|
|
1586
|
+
skip: "integer (default: 0)",
|
|
1587
|
+
limit: "integer (default: 20, max: 100)"
|
|
1588
|
+
},
|
|
1589
|
+
response: {
|
|
1590
|
+
files: "UserFileResponse[]",
|
|
1591
|
+
total: "integer",
|
|
1592
|
+
skip: "integer",
|
|
1593
|
+
limit: "integer"
|
|
1594
|
+
}
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
method: "GET",
|
|
1598
|
+
path: "/v1/user/files/shared",
|
|
1599
|
+
summary: "List shared tenant Drive files (read-only)",
|
|
1600
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1601
|
+
query_params: {
|
|
1602
|
+
skip: "integer (default: 0)",
|
|
1603
|
+
limit: "integer (default: 20, max: 100)"
|
|
1604
|
+
},
|
|
1605
|
+
response: {
|
|
1606
|
+
files: "TenantFileResponse[]",
|
|
1607
|
+
total: "integer",
|
|
1608
|
+
skip: "integer",
|
|
1609
|
+
limit: "integer"
|
|
1610
|
+
},
|
|
1611
|
+
note: "Only returns files explicitly marked as shared by the tenant (is_shared=true). Files are private by default."
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
method: "GET",
|
|
1615
|
+
path: "/v1/user/files/shared/{file_id}/download",
|
|
1616
|
+
summary: "Download a shared tenant file",
|
|
1617
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1618
|
+
response: "Binary file content with Content-Disposition header",
|
|
1619
|
+
note: "Returns 404 if the file is not marked as shared by the tenant."
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
method: "GET",
|
|
1623
|
+
path: "/v1/user/files/{file_id}",
|
|
1624
|
+
summary: "Get own file metadata",
|
|
1625
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1626
|
+
response: "UserFileResponse"
|
|
1627
|
+
},
|
|
1628
|
+
{
|
|
1629
|
+
method: "GET",
|
|
1630
|
+
path: "/v1/user/files/{file_id}/download",
|
|
1631
|
+
summary: "Download own file",
|
|
1632
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1633
|
+
response: "Binary file content with Content-Disposition header"
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
method: "DELETE",
|
|
1637
|
+
path: "/v1/user/files/{file_id}",
|
|
1638
|
+
summary: "Delete own file",
|
|
1639
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1640
|
+
response: "204 No Content"
|
|
1641
|
+
},
|
|
1642
|
+
{
|
|
1643
|
+
method: "PATCH",
|
|
1644
|
+
path: "/v1/user/files/{file_id}/move",
|
|
1645
|
+
summary: "Move file to folder (or root)",
|
|
1646
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1647
|
+
request_body: {
|
|
1648
|
+
folder_id: "uuid | null - Target folder (null = move to root)"
|
|
1649
|
+
},
|
|
1650
|
+
response: "UserFileResponse"
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
method: "POST",
|
|
1654
|
+
path: "/v1/user/files/folders",
|
|
1655
|
+
summary: "Create a folder in user's Drive",
|
|
1656
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1657
|
+
request_body: {
|
|
1658
|
+
name: "string (required, max 255 chars)",
|
|
1659
|
+
parent_id: "uuid (optional) - Parent folder for nesting"
|
|
1660
|
+
},
|
|
1661
|
+
response: {
|
|
1662
|
+
id: "uuid",
|
|
1663
|
+
tenant_id: "uuid",
|
|
1664
|
+
user_id: "uuid",
|
|
1665
|
+
parent_id: "uuid | null",
|
|
1666
|
+
name: "string",
|
|
1667
|
+
path: "string - materialized path e.g. /Documents/Reports",
|
|
1668
|
+
created_at: "ISO datetime",
|
|
1669
|
+
updated_at: "ISO datetime"
|
|
1670
|
+
}
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
method: "GET",
|
|
1674
|
+
path: "/v1/user/files/folders",
|
|
1675
|
+
summary: "List folders (root or children of parent)",
|
|
1676
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1677
|
+
query_params: {
|
|
1678
|
+
parent_id: "uuid (optional) - List children of this folder (null = root)"
|
|
1679
|
+
},
|
|
1680
|
+
response: {
|
|
1681
|
+
folders: "FolderResponse[]"
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
method: "PATCH",
|
|
1686
|
+
path: "/v1/user/files/folders/{folder_id}",
|
|
1687
|
+
summary: "Rename a folder",
|
|
1688
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1689
|
+
request_body: {
|
|
1690
|
+
name: "string (required, max 255 chars)"
|
|
1691
|
+
},
|
|
1692
|
+
response: "FolderResponse"
|
|
1693
|
+
},
|
|
1694
|
+
{
|
|
1695
|
+
method: "DELETE",
|
|
1696
|
+
path: "/v1/user/files/folders/{folder_id}",
|
|
1697
|
+
summary: "Delete an empty folder (409 if non-empty)",
|
|
1698
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
|
|
1699
|
+
response: "204 No Content",
|
|
1700
|
+
note: "Folder must be empty (no files or subfolders). Move or delete contents first."
|
|
1701
|
+
}
|
|
1702
|
+
]
|
|
1703
|
+
},
|
|
1704
|
+
external_agents: {
|
|
1705
|
+
description: "External agent call tracking. Used by external agents (e.g., N8N workflows) to report their usage back to the platform for analytics and cost tracking.",
|
|
1706
|
+
endpoints: [
|
|
1707
|
+
{
|
|
1708
|
+
method: "POST",
|
|
1709
|
+
path: "/v1/external-agents/{agent_id}/calls",
|
|
1710
|
+
summary: "Register an external agent call",
|
|
1711
|
+
auth: { api_key: "sk_live_*", jwt: true },
|
|
1712
|
+
rate_limit: "120 per minute per IP",
|
|
1713
|
+
path_params: {
|
|
1714
|
+
agent_id: "uuid (required) - The external agent ID"
|
|
1715
|
+
},
|
|
1716
|
+
request_body: {
|
|
1717
|
+
user_id: "uuid (optional) - End-user who triggered the call. Defaults to authenticated user",
|
|
1718
|
+
organization_id: "uuid (optional) - Organization context. Defaults to user's default organization",
|
|
1719
|
+
input_tokens: "integer (optional, default: 0) - Number of input tokens consumed",
|
|
1720
|
+
output_tokens: "integer (optional, default: 0) - Number of output tokens consumed",
|
|
1721
|
+
total_tokens: "integer (optional) - Total tokens. If not provided, calculated as input + output",
|
|
1722
|
+
cost_usd: "number (optional, default: 0.0) - Cost in USD",
|
|
1723
|
+
duration_ms: "integer (optional, default: 0) - Call duration in milliseconds",
|
|
1724
|
+
status: "string (optional, default: 'SUCCESS') - 'SUCCESS' or 'ERROR'",
|
|
1725
|
+
error_message: "string (optional) - Error message if status is ERROR",
|
|
1726
|
+
call_metadata: "object (optional) - Arbitrary metadata about the call"
|
|
1727
|
+
},
|
|
1728
|
+
response: {
|
|
1729
|
+
id: "uuid",
|
|
1730
|
+
external_agent_id: "uuid",
|
|
1731
|
+
tenant_id: "uuid",
|
|
1732
|
+
user_id: "uuid | null",
|
|
1733
|
+
organization_id: "uuid | null",
|
|
1734
|
+
input_tokens: "integer",
|
|
1735
|
+
output_tokens: "integer",
|
|
1736
|
+
total_tokens: "integer",
|
|
1737
|
+
cost_usd: "number",
|
|
1738
|
+
duration_ms: "integer",
|
|
1739
|
+
status: "string - SUCCESS or ERROR",
|
|
1740
|
+
error_message: "string | null",
|
|
1741
|
+
call_metadata: "object | null",
|
|
1742
|
+
created_at: "ISO datetime"
|
|
1743
|
+
},
|
|
1744
|
+
note: "Does NOT add to tenant billing — the tenant pays the AI provider directly. This endpoint is for tracking/analytics only."
|
|
1745
|
+
}
|
|
1746
|
+
]
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
// =============================================================================
|
|
1750
|
+
// TypeScript/JavaScript SDK Template
|
|
1751
|
+
// =============================================================================
|
|
1752
|
+
const SDK_TEMPLATE_TYPESCRIPT = `/**
|
|
1753
|
+
* Multi-tenant SaaS API Client
|
|
1754
|
+
*
|
|
1755
|
+
* Usage:
|
|
1756
|
+
* const api = new ApiClient('pk_live_your_key_here', 'https://api.yoursaas.com');
|
|
1757
|
+
*
|
|
1758
|
+
* // Login
|
|
1759
|
+
* const { access_token, user } = await api.auth.login('email@example.com', 'password');
|
|
1760
|
+
*
|
|
1761
|
+
* // Set token for authenticated requests
|
|
1762
|
+
* api.setAccessToken(access_token);
|
|
1763
|
+
*
|
|
1764
|
+
* // Get user's organizations
|
|
1765
|
+
* const orgs = await api.organizations.getMyOrganizations();
|
|
1766
|
+
*/
|
|
1767
|
+
|
|
1768
|
+
interface TokenResponse {
|
|
1769
|
+
access_token: string;
|
|
1770
|
+
refresh_token: string;
|
|
1771
|
+
token_type: string;
|
|
1772
|
+
expires_in: number;
|
|
1773
|
+
user: {
|
|
1774
|
+
id: string;
|
|
1775
|
+
email: string;
|
|
1776
|
+
display_name: string | null;
|
|
1777
|
+
avatar_url: string | null;
|
|
1778
|
+
is_active: boolean;
|
|
1779
|
+
created_at: string;
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
interface Organization {
|
|
1784
|
+
id: string;
|
|
1785
|
+
name: string;
|
|
1786
|
+
slug: string;
|
|
1787
|
+
description?: string;
|
|
1788
|
+
logo_url?: string;
|
|
1789
|
+
role: 'owner' | 'admin' | 'member' | 'developer';
|
|
1790
|
+
created_at: string;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
interface ApiError {
|
|
1794
|
+
detail: string;
|
|
1795
|
+
error?: string;
|
|
1796
|
+
error_description?: string;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
class ApiClient {
|
|
1800
|
+
private apiKey: string;
|
|
1801
|
+
private baseUrl: string;
|
|
1802
|
+
private accessToken: string | null = null;
|
|
1803
|
+
private refreshToken: string | null = null;
|
|
1804
|
+
|
|
1805
|
+
constructor(apiKey: string, baseUrl: string) {
|
|
1806
|
+
if (!apiKey) throw new Error('API key is required');
|
|
1807
|
+
if (!baseUrl) throw new Error('Base URL is required');
|
|
1808
|
+
this.apiKey = apiKey;
|
|
1809
|
+
this.baseUrl = baseUrl.replace(/\\/$/, ''); // Remove trailing slash
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
setAccessToken(token: string) {
|
|
1813
|
+
this.accessToken = token;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
setRefreshToken(token: string) {
|
|
1817
|
+
this.refreshToken = token;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
private organizationId: string | null = null;
|
|
1821
|
+
|
|
1822
|
+
setOrganizationId(orgId: string) {
|
|
1823
|
+
this.organizationId = orgId;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
private async request<T>(
|
|
1827
|
+
method: string,
|
|
1828
|
+
endpoint: string,
|
|
1829
|
+
body?: any,
|
|
1830
|
+
requiresAuth = false
|
|
1831
|
+
): Promise<T> {
|
|
1832
|
+
const headers: Record<string, string> = {
|
|
1833
|
+
'X-API-Key': this.apiKey,
|
|
1834
|
+
'Content-Type': 'application/json',
|
|
1835
|
+
};
|
|
1836
|
+
|
|
1837
|
+
if (requiresAuth && this.accessToken) {
|
|
1838
|
+
headers['Authorization'] = \`Bearer \${this.accessToken}\`;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
if (this.organizationId) {
|
|
1842
|
+
headers['X-Organization-Id'] = this.organizationId;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
const response = await fetch(\`\${this.baseUrl}\${endpoint}\`, {
|
|
1846
|
+
method,
|
|
1847
|
+
headers,
|
|
1848
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
if (!response.ok) {
|
|
1852
|
+
const error: ApiError = await response.json();
|
|
1853
|
+
throw new Error(error.detail || \`Request failed: \${response.status}\`);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
return response.json();
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// Auth endpoints
|
|
1860
|
+
auth = {
|
|
1861
|
+
register: async (email: string, password: string, displayName?: string) => {
|
|
1862
|
+
const result = await this.request<TokenResponse>('POST', '/v1/auth/register', {
|
|
1863
|
+
email,
|
|
1864
|
+
password,
|
|
1865
|
+
display_name: displayName,
|
|
1866
|
+
});
|
|
1867
|
+
this.accessToken = result.access_token;
|
|
1868
|
+
this.refreshToken = result.refresh_token;
|
|
1869
|
+
return result;
|
|
1870
|
+
},
|
|
1871
|
+
|
|
1872
|
+
login: async (email: string, password: string) => {
|
|
1873
|
+
const result = await this.request<TokenResponse>('POST', '/v1/auth/login', {
|
|
1874
|
+
email,
|
|
1875
|
+
password,
|
|
1876
|
+
});
|
|
1877
|
+
this.accessToken = result.access_token;
|
|
1878
|
+
this.refreshToken = result.refresh_token;
|
|
1879
|
+
return result;
|
|
1880
|
+
},
|
|
1881
|
+
|
|
1882
|
+
logout: async () => {
|
|
1883
|
+
await this.request('POST', '/v1/auth/logout', undefined, true);
|
|
1884
|
+
this.accessToken = null;
|
|
1885
|
+
this.refreshToken = null;
|
|
1886
|
+
},
|
|
1887
|
+
|
|
1888
|
+
me: async () => {
|
|
1889
|
+
return this.request<TokenResponse['user']>('GET', '/v1/auth/me', undefined, true);
|
|
1890
|
+
},
|
|
1891
|
+
|
|
1892
|
+
refresh: async () => {
|
|
1893
|
+
if (!this.refreshToken) throw new Error('No refresh token available');
|
|
1894
|
+
const result = await this.request<TokenResponse>('POST', '/v1/auth/refresh', {
|
|
1895
|
+
refresh_token: this.refreshToken,
|
|
1896
|
+
});
|
|
1897
|
+
this.accessToken = result.access_token;
|
|
1898
|
+
return result;
|
|
1899
|
+
},
|
|
1900
|
+
|
|
1901
|
+
forgotPassword: async (email: string) => {
|
|
1902
|
+
return this.request('POST', '/v1/auth/forgot-password', { email });
|
|
1903
|
+
},
|
|
1904
|
+
|
|
1905
|
+
resetPassword: async (token: string, newPassword: string) => {
|
|
1906
|
+
return this.request('POST', '/v1/auth/reset-password', {
|
|
1907
|
+
token,
|
|
1908
|
+
new_password: newPassword,
|
|
1909
|
+
});
|
|
1910
|
+
},
|
|
1911
|
+
|
|
1912
|
+
changePassword: async (currentPassword: string, newPassword: string) => {
|
|
1913
|
+
return this.request('POST', '/v1/auth/change-password', {
|
|
1914
|
+
current_password: currentPassword,
|
|
1915
|
+
new_password: newPassword,
|
|
1916
|
+
});
|
|
1917
|
+
},
|
|
1918
|
+
|
|
1919
|
+
verifyEmail: async (token: string) => {
|
|
1920
|
+
return this.request('POST', '/v1/auth/verify-email', { token });
|
|
1921
|
+
},
|
|
1922
|
+
|
|
1923
|
+
resendVerification: async (email: string) => {
|
|
1924
|
+
return this.request('POST', '/v1/auth/resend-verification', { email });
|
|
1925
|
+
},
|
|
1926
|
+
|
|
1927
|
+
getProviders: async () => {
|
|
1928
|
+
return this.request<{ providers: Array<{ provider: string; name: string }> }>(
|
|
1929
|
+
'GET', '/v1/auth/providers'
|
|
1930
|
+
);
|
|
1931
|
+
},
|
|
1932
|
+
|
|
1933
|
+
getGoogleAuthUrl: async (redirectUri: string) => {
|
|
1934
|
+
return this.request<{ authorization_url: string }>(
|
|
1935
|
+
'GET', \`/v1/auth/google/url?redirect_uri=\${encodeURIComponent(redirectUri)}\`
|
|
1936
|
+
);
|
|
1937
|
+
},
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
// Agent endpoints
|
|
1941
|
+
agents = {
|
|
1942
|
+
getAvailable: async (skip = 0, limit = 50) => {
|
|
1943
|
+
return this.request(
|
|
1944
|
+
'GET',
|
|
1945
|
+
\`/v1/user/agents/available?skip=\${skip}&limit=\${limit}\`,
|
|
1946
|
+
undefined,
|
|
1947
|
+
true
|
|
1948
|
+
);
|
|
1949
|
+
},
|
|
1950
|
+
|
|
1951
|
+
get: async (agentId: string) => {
|
|
1952
|
+
return this.request('GET', \`/v1/user/agents/\${agentId}\`, undefined, true);
|
|
1953
|
+
},
|
|
1954
|
+
|
|
1955
|
+
chat: async (agentId: string, messages: Array<{ role: string; content: string }>, options?: { temperature?: number; model?: string; conversation_id?: string }) => {
|
|
1956
|
+
return this.request('POST', \`/v1/user/agents/\${agentId}/chat\`, {
|
|
1957
|
+
messages,
|
|
1958
|
+
...options,
|
|
1959
|
+
}, true);
|
|
1960
|
+
},
|
|
1961
|
+
|
|
1962
|
+
getHistory: async (agentId: string, skip = 0, limit = 20) => {
|
|
1963
|
+
return this.request(
|
|
1964
|
+
'GET',
|
|
1965
|
+
\`/v1/user/agents/\${agentId}/history?skip=\${skip}&limit=\${limit}\`,
|
|
1966
|
+
undefined,
|
|
1967
|
+
true
|
|
1968
|
+
);
|
|
1969
|
+
},
|
|
1970
|
+
|
|
1971
|
+
createConversation: async (agentId: string, title?: string) => {
|
|
1972
|
+
return this.request('POST', \`/v1/user/agents/\${agentId}/conversations\`, {
|
|
1973
|
+
title,
|
|
1974
|
+
}, true);
|
|
1975
|
+
},
|
|
1976
|
+
|
|
1977
|
+
listConversations: async (agentId: string, skip = 0, limit = 20) => {
|
|
1978
|
+
return this.request(
|
|
1979
|
+
'GET',
|
|
1980
|
+
\`/v1/user/agents/\${agentId}/conversations?skip=\${skip}&limit=\${limit}\`,
|
|
1981
|
+
undefined,
|
|
1982
|
+
true
|
|
1983
|
+
);
|
|
1984
|
+
},
|
|
1985
|
+
|
|
1986
|
+
getConversation: async (agentId: string, conversationId: string) => {
|
|
1987
|
+
return this.request(
|
|
1988
|
+
'GET',
|
|
1989
|
+
\`/v1/user/agents/\${agentId}/conversations/\${conversationId}\`,
|
|
1990
|
+
undefined,
|
|
1991
|
+
true
|
|
1992
|
+
);
|
|
1993
|
+
},
|
|
1994
|
+
|
|
1995
|
+
endConversation: async (agentId: string, conversationId: string) => {
|
|
1996
|
+
return this.request(
|
|
1997
|
+
'DELETE',
|
|
1998
|
+
\`/v1/user/agents/\${agentId}/conversations/\${conversationId}\`,
|
|
1999
|
+
undefined,
|
|
2000
|
+
true
|
|
2001
|
+
);
|
|
2002
|
+
},
|
|
2003
|
+
};
|
|
2004
|
+
|
|
2005
|
+
// Organization endpoints
|
|
2006
|
+
organizations = {
|
|
2007
|
+
getMyOrganizations: async () => {
|
|
2008
|
+
return this.request<{ organizations: Organization[] }>(
|
|
2009
|
+
'GET',
|
|
2010
|
+
'/v1/organizations/my-organizations',
|
|
2011
|
+
undefined,
|
|
2012
|
+
true
|
|
2013
|
+
);
|
|
2014
|
+
},
|
|
2015
|
+
|
|
2016
|
+
create: async (name: string, slug: string, description?: string) => {
|
|
2017
|
+
return this.request<Organization>(
|
|
2018
|
+
'POST',
|
|
2019
|
+
'/v1/organizations',
|
|
2020
|
+
{ name, slug, description },
|
|
2021
|
+
true
|
|
2022
|
+
);
|
|
2023
|
+
},
|
|
2024
|
+
|
|
2025
|
+
get: async (id: string) => {
|
|
2026
|
+
return this.request<Organization>(
|
|
2027
|
+
'GET',
|
|
2028
|
+
\`/v1/organizations/\${id}\`,
|
|
2029
|
+
undefined,
|
|
2030
|
+
true
|
|
2031
|
+
);
|
|
2032
|
+
},
|
|
2033
|
+
|
|
2034
|
+
update: async (id: string, data: { name?: string; description?: string }) => {
|
|
2035
|
+
return this.request<Organization>(
|
|
2036
|
+
'PUT',
|
|
2037
|
+
\`/v1/organizations/\${id}\`,
|
|
2038
|
+
data,
|
|
2039
|
+
true
|
|
2040
|
+
);
|
|
2041
|
+
},
|
|
2042
|
+
|
|
2043
|
+
getMembers: async (id: string) => {
|
|
2044
|
+
return this.request(
|
|
2045
|
+
'GET',
|
|
2046
|
+
\`/v1/organizations/\${id}/members\`,
|
|
2047
|
+
undefined,
|
|
2048
|
+
true
|
|
2049
|
+
);
|
|
2050
|
+
},
|
|
2051
|
+
|
|
2052
|
+
invite: async (id: string, email: string, role: 'admin' | 'member' | 'developer') => {
|
|
2053
|
+
return this.request(
|
|
2054
|
+
'POST',
|
|
2055
|
+
\`/v1/organizations/\${id}/invite\`,
|
|
2056
|
+
{ email, role },
|
|
2057
|
+
true
|
|
2058
|
+
);
|
|
2059
|
+
},
|
|
2060
|
+
|
|
2061
|
+
acceptInvitation: async (token: string) => {
|
|
2062
|
+
return this.request(
|
|
2063
|
+
'POST',
|
|
2064
|
+
'/v1/organizations/accept-invitation',
|
|
2065
|
+
{ token },
|
|
2066
|
+
true
|
|
2067
|
+
);
|
|
2068
|
+
},
|
|
2069
|
+
|
|
2070
|
+
rejectInvitation: async (token: string) => {
|
|
2071
|
+
return this.request(
|
|
2072
|
+
'POST',
|
|
2073
|
+
'/v1/organizations/reject-invitation',
|
|
2074
|
+
{ token }
|
|
2075
|
+
);
|
|
2076
|
+
},
|
|
2077
|
+
|
|
2078
|
+
updateMemberRole: async (orgId: string, userId: string, role: 'admin' | 'member' | 'developer') => {
|
|
2079
|
+
return this.request(
|
|
2080
|
+
'PUT',
|
|
2081
|
+
\`/v1/organizations/\${orgId}/members/\${userId}\`,
|
|
2082
|
+
{ role },
|
|
2083
|
+
true
|
|
2084
|
+
);
|
|
2085
|
+
},
|
|
2086
|
+
|
|
2087
|
+
removeMember: async (orgId: string, userId: string) => {
|
|
2088
|
+
return this.request(
|
|
2089
|
+
'DELETE',
|
|
2090
|
+
\`/v1/organizations/\${orgId}/members/\${userId}\`,
|
|
2091
|
+
undefined,
|
|
2092
|
+
true
|
|
2093
|
+
);
|
|
2094
|
+
},
|
|
2095
|
+
|
|
2096
|
+
deactivateMember: async (orgId: string, userId: string) => {
|
|
2097
|
+
return this.request(
|
|
2098
|
+
'POST',
|
|
2099
|
+
\`/v1/organizations/\${orgId}/members/\${userId}/deactivate\`,
|
|
2100
|
+
undefined,
|
|
2101
|
+
true
|
|
2102
|
+
);
|
|
2103
|
+
},
|
|
2104
|
+
|
|
2105
|
+
setDefault: async (orgId: string) => {
|
|
2106
|
+
return this.request(
|
|
2107
|
+
'POST',
|
|
2108
|
+
\`/v1/organizations/\${orgId}/set-default\`,
|
|
2109
|
+
undefined,
|
|
2110
|
+
true
|
|
2111
|
+
);
|
|
2112
|
+
},
|
|
2113
|
+
|
|
2114
|
+
getSubscription: async (orgId: string) => {
|
|
2115
|
+
return this.request('GET', \`/v1/organizations/\${orgId}/subscription\`, undefined, true);
|
|
2116
|
+
},
|
|
2117
|
+
|
|
2118
|
+
subscribe: async (orgId: string, planId: string) => {
|
|
2119
|
+
return this.request('POST', \`/v1/organizations/\${orgId}/subscription/\${planId}\`, undefined, true);
|
|
2120
|
+
},
|
|
2121
|
+
|
|
2122
|
+
cancelSubscription: async (orgId: string) => {
|
|
2123
|
+
return this.request('DELETE', \`/v1/organizations/\${orgId}/subscription\`, undefined, true);
|
|
2124
|
+
},
|
|
2125
|
+
|
|
2126
|
+
getUsage: async (orgId: string) => {
|
|
2127
|
+
return this.request('GET', \`/v1/organizations/\${orgId}/usage\`, undefined, true);
|
|
2128
|
+
},
|
|
2129
|
+
};
|
|
2130
|
+
|
|
2131
|
+
// Subscription endpoints
|
|
2132
|
+
subscriptions = {
|
|
2133
|
+
getPlans: async () => {
|
|
2134
|
+
return this.request('GET', '/v1/subscriptions/plans', undefined, true);
|
|
2135
|
+
},
|
|
2136
|
+
|
|
2137
|
+
getCurrent: async () => {
|
|
2138
|
+
return this.request('GET', '/v1/subscriptions/current', undefined, true);
|
|
2139
|
+
},
|
|
2140
|
+
|
|
2141
|
+
checkout: async (planId: string, successUrl: string, cancelUrl: string) => {
|
|
2142
|
+
return this.request<{ checkout_url: string; session_id: string }>(
|
|
2143
|
+
'POST',
|
|
2144
|
+
'/v1/subscriptions/checkout',
|
|
2145
|
+
{
|
|
2146
|
+
plan_id: planId,
|
|
2147
|
+
success_url: successUrl,
|
|
2148
|
+
cancel_url: cancelUrl,
|
|
2149
|
+
},
|
|
2150
|
+
true
|
|
2151
|
+
);
|
|
2152
|
+
},
|
|
2153
|
+
|
|
2154
|
+
getById: async (subscriptionId: string) => {
|
|
2155
|
+
return this.request('GET', \`/v1/subscriptions/subscriptions/\${subscriptionId}\`, undefined, true);
|
|
2156
|
+
},
|
|
2157
|
+
|
|
2158
|
+
cancel: async (subscriptionId: string, cancelAtPeriodEnd = true) => {
|
|
2159
|
+
return this.request(
|
|
2160
|
+
'DELETE',
|
|
2161
|
+
\`/v1/subscriptions/subscriptions/\${subscriptionId}?cancel_at_period_end=\${cancelAtPeriodEnd}\`,
|
|
2162
|
+
undefined,
|
|
2163
|
+
true
|
|
2164
|
+
);
|
|
2165
|
+
},
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
// Billing endpoints (sk_live_* only)
|
|
2169
|
+
billing = {
|
|
2170
|
+
getAvailablePlans: async () => {
|
|
2171
|
+
return this.request('GET', '/v1/billing/available-plans', undefined, true);
|
|
2172
|
+
},
|
|
2173
|
+
|
|
2174
|
+
createCheckoutSession: async (data: { user_id: string; organization_id: string; plan_id: string; success_url: string; cancel_url: string }) => {
|
|
2175
|
+
return this.request<{ checkout_url: string; session_id: string; plan_name: string; price: string }>('POST', '/v1/billing/create-checkout-session', data, true);
|
|
2176
|
+
},
|
|
2177
|
+
|
|
2178
|
+
changePlan: async (data: { subscription_id: string; new_plan_id: string }) => {
|
|
2179
|
+
return this.request('POST', '/v1/billing/change-plan', data, true);
|
|
2180
|
+
},
|
|
2181
|
+
|
|
2182
|
+
cancelSubscription: async (subscriptionId: string, cancelAtPeriodEnd = true) => {
|
|
2183
|
+
return this.request('POST', \`/v1/billing/cancel-subscription?subscription_id=\${subscriptionId}&cancel_at_period_end=\${cancelAtPeriodEnd}\`, undefined, true);
|
|
2184
|
+
},
|
|
2185
|
+
|
|
2186
|
+
getBillingHistory: async (organizationId: string, limit = 20) => {
|
|
2187
|
+
return this.request('GET', \`/v1/billing/billing-history-org/\${organizationId}?limit=\${limit}\`, undefined, true);
|
|
2188
|
+
},
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
// Plans endpoints (JWT required)
|
|
2192
|
+
plans = {
|
|
2193
|
+
list: async (skip = 0, limit = 100, includeInactive = false) => {
|
|
2194
|
+
return this.request('GET', \`/v1/plans?skip=\${skip}&limit=\${limit}&include_inactive=\${includeInactive}\`, undefined, true);
|
|
2195
|
+
},
|
|
2196
|
+
|
|
2197
|
+
getActive: async () => {
|
|
2198
|
+
return this.request('GET', '/v1/plans/active', undefined, true);
|
|
2199
|
+
},
|
|
2200
|
+
|
|
2201
|
+
getFree: async () => {
|
|
2202
|
+
return this.request('GET', '/v1/plans/free', undefined, true);
|
|
2203
|
+
},
|
|
2204
|
+
|
|
2205
|
+
getById: async (planId: string) => {
|
|
2206
|
+
return this.request('GET', \`/v1/plans/\${planId}\`, undefined, true);
|
|
2207
|
+
},
|
|
2208
|
+
};
|
|
2209
|
+
|
|
2210
|
+
// User management endpoints
|
|
2211
|
+
users = {
|
|
2212
|
+
list: async (page = 1, perPage = 20, includeInactive = false) => {
|
|
2213
|
+
return this.request('GET', \`/v1/users?page=\${page}&per_page=\${perPage}&include_inactive=\${includeInactive}\`, undefined, true);
|
|
2214
|
+
},
|
|
2215
|
+
|
|
2216
|
+
stats: async () => {
|
|
2217
|
+
return this.request('GET', '/v1/users/stats', undefined, true);
|
|
2218
|
+
},
|
|
2219
|
+
|
|
2220
|
+
get: async (userId: string, includeSettings = false) => {
|
|
2221
|
+
return this.request('GET', \`/v1/users/\${userId}?include_settings=\${includeSettings}\`, undefined, true);
|
|
2222
|
+
},
|
|
2223
|
+
|
|
2224
|
+
update: async (userId: string, data: { display_name?: string; avatar_url?: string; locale?: string }) => {
|
|
2225
|
+
return this.request('PUT', \`/v1/users/\${userId}\`, data, true);
|
|
2226
|
+
},
|
|
2227
|
+
|
|
2228
|
+
updateSettings: async (userId: string, settings: any) => {
|
|
2229
|
+
return this.request('PUT', \`/v1/users/\${userId}/settings\`, settings, true);
|
|
2230
|
+
},
|
|
2231
|
+
|
|
2232
|
+
deactivate: async (userId: string) => {
|
|
2233
|
+
return this.request('POST', \`/v1/users/\${userId}/deactivate\`, undefined, true);
|
|
2234
|
+
},
|
|
2235
|
+
|
|
2236
|
+
activate: async (userId: string) => {
|
|
2237
|
+
return this.request('POST', \`/v1/users/\${userId}/activate\`, undefined, true);
|
|
2238
|
+
},
|
|
2239
|
+
|
|
2240
|
+
delete: async (userId: string) => {
|
|
2241
|
+
return this.request('DELETE', \`/v1/users/\${userId}\`, undefined, true);
|
|
2242
|
+
},
|
|
2243
|
+
};
|
|
2244
|
+
|
|
2245
|
+
// Usage endpoints
|
|
2246
|
+
usage = {
|
|
2247
|
+
getCurrent: async (organizationId: string) => {
|
|
2248
|
+
return this.request('GET', \`/v1/api/usage/current?organization_id=\${organizationId}\`, undefined, true);
|
|
2249
|
+
},
|
|
2250
|
+
|
|
2251
|
+
getHistory: async (organizationId: string, limit = 20, offset = 0) => {
|
|
2252
|
+
return this.request(
|
|
2253
|
+
'GET',
|
|
2254
|
+
\`/v1/api/usage/history?organization_id=\${organizationId}&limit=\${limit}&offset=\${offset}\`,
|
|
2255
|
+
undefined,
|
|
2256
|
+
true
|
|
2257
|
+
);
|
|
2258
|
+
},
|
|
2259
|
+
|
|
2260
|
+
getEntityUsage: async (organizationId: string) => {
|
|
2261
|
+
return this.request('GET', \`/v1/api/usage/entity-usage?organization_id=\${organizationId}\`, undefined, true);
|
|
2262
|
+
},
|
|
2263
|
+
|
|
2264
|
+
getStats: async (organizationId: string) => {
|
|
2265
|
+
return this.request('GET', \`/v1/usage/stats?organization_id=\${organizationId}\`, undefined, true);
|
|
2266
|
+
},
|
|
2267
|
+
|
|
2268
|
+
check: async (data: { tokens: number; usage_type?: string; user_id: string; organization_id: string }) => {
|
|
2269
|
+
return this.request('POST', '/v1/usage/check', data, true);
|
|
2270
|
+
},
|
|
2271
|
+
|
|
2272
|
+
consume: async (data: { tokens: number; usage_type?: string; user_id: string; organization_id: string }) => {
|
|
2273
|
+
return this.request('POST', '/v1/usage/consume', data, true);
|
|
2274
|
+
},
|
|
2275
|
+
};
|
|
2276
|
+
|
|
2277
|
+
// Entity Schema endpoints
|
|
2278
|
+
entitySchemas = {
|
|
2279
|
+
list: async (page = 1, size = 20) => {
|
|
2280
|
+
return this.request('GET', \`/v1/entity/schemas?page=\${page}&size=\${size}\`, undefined, true);
|
|
2281
|
+
},
|
|
2282
|
+
|
|
2283
|
+
create: async (data: { name: string; slug: string; description?: string; fields_definition: any }) => {
|
|
2284
|
+
return this.request('POST', '/v1/entity/schemas', data, true);
|
|
2285
|
+
},
|
|
2286
|
+
|
|
2287
|
+
get: async (schemaId: string) => {
|
|
2288
|
+
return this.request('GET', \`/v1/entity/schemas/\${schemaId}\`, undefined, true);
|
|
2289
|
+
},
|
|
2290
|
+
|
|
2291
|
+
update: async (schemaId: string, data: { name?: string; description?: string; fields_definition?: any; is_active?: boolean }) => {
|
|
2292
|
+
return this.request('PUT', \`/v1/entity/schemas/\${schemaId}\`, data, true);
|
|
2293
|
+
},
|
|
2294
|
+
|
|
2295
|
+
delete: async (schemaId: string) => {
|
|
2296
|
+
return this.request('DELETE', \`/v1/entity/schemas/\${schemaId}\`, undefined, true);
|
|
2297
|
+
},
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
// Entity Record endpoints
|
|
2301
|
+
entityRecords = {
|
|
2302
|
+
create: async (schemaId: string, data: any) => {
|
|
2303
|
+
return this.request('POST', '/v1/entity/records', { schema_id: schemaId, data }, true);
|
|
2304
|
+
},
|
|
2305
|
+
|
|
2306
|
+
get: async (recordId: string) => {
|
|
2307
|
+
return this.request('GET', \`/v1/entity/records/\${recordId}\`, undefined, true);
|
|
2308
|
+
},
|
|
2309
|
+
|
|
2310
|
+
update: async (recordId: string, data: any) => {
|
|
2311
|
+
return this.request('PUT', \`/v1/entity/records/\${recordId}\`, { data }, true);
|
|
2312
|
+
},
|
|
2313
|
+
|
|
2314
|
+
delete: async (recordId: string) => {
|
|
2315
|
+
return this.request('DELETE', \`/v1/entity/records/\${recordId}\`, undefined, true);
|
|
2316
|
+
},
|
|
2317
|
+
|
|
2318
|
+
search: async (searchReq: { schema_id?: string; query?: any; page?: number; size?: number; order_by?: string; order_dir?: string }) => {
|
|
2319
|
+
const params = new URLSearchParams();
|
|
2320
|
+
if (searchReq.page) params.set('page', String(searchReq.page));
|
|
2321
|
+
if (searchReq.size) params.set('size', String(searchReq.size));
|
|
2322
|
+
if (searchReq.order_by) params.set('order_by', searchReq.order_by);
|
|
2323
|
+
if (searchReq.order_dir) params.set('order_dir', searchReq.order_dir);
|
|
2324
|
+
return this.request('POST', \`/v1/entity/records/search?\${params}\`, { schema_id: searchReq.schema_id, query: searchReq.query }, true);
|
|
2325
|
+
},
|
|
2326
|
+
};
|
|
2327
|
+
|
|
2328
|
+
// Department endpoints
|
|
2329
|
+
departments = {
|
|
2330
|
+
get: async (departmentId: string, userId: string) => {
|
|
2331
|
+
return this.request('GET', \`/v1/departments/\${departmentId}?user_id=\${userId}\`, undefined, true);
|
|
2332
|
+
},
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
export { ApiClient, TokenResponse, Organization, ApiError };
|
|
2337
|
+
`;
|
|
2338
|
+
// =============================================================================
|
|
2339
|
+
// Request Headers Helper
|
|
2340
|
+
// =============================================================================
|
|
2341
|
+
const REQUEST_HEADERS = `
|
|
2342
|
+
## Required Headers for ALL Requests
|
|
2343
|
+
|
|
2344
|
+
\`\`\`
|
|
2345
|
+
X-API-Key: pk_live_xxxxx
|
|
2346
|
+
Content-Type: application/json
|
|
2347
|
+
\`\`\`
|
|
2348
|
+
|
|
2349
|
+
## For Authenticated Requests (after login)
|
|
2350
|
+
|
|
2351
|
+
\`\`\`
|
|
2352
|
+
X-API-Key: pk_live_xxxxx
|
|
2353
|
+
Content-Type: application/json
|
|
2354
|
+
Authorization: Bearer <access_token>
|
|
2355
|
+
\`\`\`
|
|
2356
|
+
|
|
2357
|
+
## Header Details
|
|
2358
|
+
|
|
2359
|
+
| Header | Required | Description |
|
|
2360
|
+
|--------|----------|-------------|
|
|
2361
|
+
| X-API-Key | Always | Your API key (pk_live_* for frontend) |
|
|
2362
|
+
| Content-Type | For POST/PUT/PATCH | Always use application/json |
|
|
2363
|
+
| Authorization | After login | Bearer token from login response |
|
|
2364
|
+
| X-Organization-Id | For org-scoped endpoints | Organization UUID (entities, usage) |
|
|
2365
|
+
`;
|
|
2366
|
+
// =============================================================================
|
|
2367
|
+
// API Client
|
|
2368
|
+
// =============================================================================
|
|
2369
|
+
async function fetchFromApi(endpoint) {
|
|
2370
|
+
const cacheKey = endpoint;
|
|
2371
|
+
const cached = cache.get(cacheKey);
|
|
2372
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
2373
|
+
return cached.data;
|
|
2374
|
+
}
|
|
2375
|
+
const headers = {
|
|
2376
|
+
"Content-Type": "application/json",
|
|
2377
|
+
};
|
|
2378
|
+
if (API_KEY) {
|
|
2379
|
+
headers["X-API-Key"] = API_KEY;
|
|
2380
|
+
}
|
|
2381
|
+
try {
|
|
2382
|
+
const response = await fetch(`${API_URL}${endpoint}`, { headers });
|
|
2383
|
+
if (!response.ok) {
|
|
2384
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
2385
|
+
}
|
|
2386
|
+
const data = await response.json();
|
|
2387
|
+
cache.set(cacheKey, { data, timestamp: Date.now() });
|
|
2388
|
+
return data;
|
|
2389
|
+
}
|
|
2390
|
+
catch (error) {
|
|
2391
|
+
if (cached) {
|
|
2392
|
+
console.error(`API fetch failed, using cached data: ${error}`);
|
|
2393
|
+
return cached.data;
|
|
2394
|
+
}
|
|
2395
|
+
throw error;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
// =============================================================================
|
|
2399
|
+
// MCP Server Implementation
|
|
2400
|
+
// =============================================================================
|
|
2401
|
+
const server = new Server({
|
|
2402
|
+
name: "saas-multi-agent-api",
|
|
2403
|
+
version: "2.2.0",
|
|
2404
|
+
}, {
|
|
2405
|
+
capabilities: {
|
|
2406
|
+
tools: {},
|
|
2407
|
+
},
|
|
2408
|
+
});
|
|
2409
|
+
// List available tools
|
|
2410
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2411
|
+
return {
|
|
2412
|
+
tools: [
|
|
2413
|
+
{
|
|
2414
|
+
name: "get_api_overview",
|
|
2415
|
+
description: `Get a high-level overview of the Multi-tenant SaaS API.
|
|
2416
|
+
|
|
2417
|
+
IMPORTANT: This API has TWO types of endpoints:
|
|
2418
|
+
1. TENANT/DASHBOARD endpoints - For tenant admins (NOT for end-user frontends)
|
|
2419
|
+
2. END-USER endpoints - For building frontend applications
|
|
2420
|
+
|
|
2421
|
+
KEY FEATURES:
|
|
2422
|
+
- User authentication (login, register, password reset)
|
|
2423
|
+
- Organizations and team management
|
|
2424
|
+
- Subscriptions and billing (Stripe)
|
|
2425
|
+
- AI Agents: Chat with AI agents created by the tenant
|
|
2426
|
+
- External Agents: Track usage from external agent integrations (e.g., N8N)
|
|
2427
|
+
- Usage tracking
|
|
2428
|
+
|
|
2429
|
+
Call this first to understand the API architecture and authentication model.`,
|
|
2430
|
+
inputSchema: {
|
|
2431
|
+
type: "object",
|
|
2432
|
+
properties: {},
|
|
2433
|
+
required: [],
|
|
2434
|
+
},
|
|
2435
|
+
},
|
|
2436
|
+
{
|
|
2437
|
+
name: "get_end_user_endpoints",
|
|
2438
|
+
description: `Get detailed documentation for END-USER API endpoints.
|
|
2439
|
+
|
|
2440
|
+
Use this when building a frontend for end-users. These endpoints use:
|
|
2441
|
+
- API Key (X-API-Key header) - required for ALL requests
|
|
2442
|
+
- JWT token (Authorization: Bearer) - required AFTER login
|
|
2443
|
+
|
|
2444
|
+
Categories: authentication, organizations, subscriptions, billing, usage, agents, users, plans, ai, entities, departments, external_agents`,
|
|
2445
|
+
inputSchema: {
|
|
2446
|
+
type: "object",
|
|
2447
|
+
properties: {
|
|
2448
|
+
category: {
|
|
2449
|
+
type: "string",
|
|
2450
|
+
description: "Filter by category. Leave empty for all endpoints.",
|
|
2451
|
+
enum: ["authentication", "organizations", "subscriptions", "billing", "usage", "agents", "users", "plans", "ai", "entities", "departments", "files", "external_agents"],
|
|
2452
|
+
},
|
|
2453
|
+
},
|
|
2454
|
+
required: [],
|
|
2455
|
+
},
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
name: "get_request_headers",
|
|
2459
|
+
description: "Get detailed information about required HTTP headers for API requests.",
|
|
2460
|
+
inputSchema: {
|
|
2461
|
+
type: "object",
|
|
2462
|
+
properties: {},
|
|
2463
|
+
required: [],
|
|
2464
|
+
},
|
|
2465
|
+
},
|
|
2466
|
+
{
|
|
2467
|
+
name: "get_sdk_template",
|
|
2468
|
+
description: `Get a complete TypeScript/JavaScript SDK template for the API.
|
|
2469
|
+
|
|
2470
|
+
This template includes:
|
|
2471
|
+
- Typed interfaces for all responses
|
|
2472
|
+
- Auth methods (login, register, refresh, logout)
|
|
2473
|
+
- Organization methods
|
|
2474
|
+
- Subscription methods
|
|
2475
|
+
- Usage methods
|
|
2476
|
+
|
|
2477
|
+
You can use this as a starting point for your frontend.`,
|
|
2478
|
+
inputSchema: {
|
|
2479
|
+
type: "object",
|
|
2480
|
+
properties: {},
|
|
2481
|
+
required: [],
|
|
2482
|
+
},
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
name: "get_openapi_spec",
|
|
2486
|
+
description: "Get the complete OpenAPI specification (JSON) from the live API. Use this for detailed schema information.",
|
|
2487
|
+
inputSchema: {
|
|
2488
|
+
type: "object",
|
|
2489
|
+
properties: {},
|
|
2490
|
+
required: [],
|
|
2491
|
+
},
|
|
2492
|
+
},
|
|
2493
|
+
{
|
|
2494
|
+
name: "search_endpoints",
|
|
2495
|
+
description: "Search for endpoints by keyword. Searches path, summary, and description.",
|
|
2496
|
+
inputSchema: {
|
|
2497
|
+
type: "object",
|
|
2498
|
+
properties: {
|
|
2499
|
+
query: {
|
|
2500
|
+
type: "string",
|
|
2501
|
+
description: "Search keyword (e.g., 'login', 'organization', 'checkout')",
|
|
2502
|
+
},
|
|
2503
|
+
},
|
|
2504
|
+
required: ["query"],
|
|
2505
|
+
},
|
|
2506
|
+
},
|
|
2507
|
+
{
|
|
2508
|
+
name: "get_endpoint_details",
|
|
2509
|
+
description: "Get detailed information about a specific endpoint including request/response schemas.",
|
|
2510
|
+
inputSchema: {
|
|
2511
|
+
type: "object",
|
|
2512
|
+
properties: {
|
|
2513
|
+
path: {
|
|
2514
|
+
type: "string",
|
|
2515
|
+
description: "The endpoint path (e.g., '/v1/auth/login')",
|
|
2516
|
+
},
|
|
2517
|
+
method: {
|
|
2518
|
+
type: "string",
|
|
2519
|
+
description: "HTTP method",
|
|
2520
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
2521
|
+
},
|
|
2522
|
+
},
|
|
2523
|
+
required: ["path"],
|
|
2524
|
+
},
|
|
2525
|
+
},
|
|
2526
|
+
{
|
|
2527
|
+
name: "get_authentication_flow",
|
|
2528
|
+
description: `Get a step-by-step guide for implementing authentication in your frontend.
|
|
2529
|
+
|
|
2530
|
+
Covers:
|
|
2531
|
+
- Registration flow (with conditional email verification)
|
|
2532
|
+
- Login flow
|
|
2533
|
+
- Token refresh
|
|
2534
|
+
- Password reset
|
|
2535
|
+
- Email verification flow
|
|
2536
|
+
- Google OAuth flow
|
|
2537
|
+
- Logout`,
|
|
2538
|
+
inputSchema: {
|
|
2539
|
+
type: "object",
|
|
2540
|
+
properties: {},
|
|
2541
|
+
required: [],
|
|
2542
|
+
},
|
|
2543
|
+
},
|
|
2544
|
+
{
|
|
2545
|
+
name: "get_common_patterns",
|
|
2546
|
+
description: `Get common implementation patterns for frontend development.
|
|
2547
|
+
|
|
2548
|
+
Includes:
|
|
2549
|
+
- Token storage best practices
|
|
2550
|
+
- Auto token refresh
|
|
2551
|
+
- Error handling
|
|
2552
|
+
- API request wrapper
|
|
2553
|
+
- OAuth callback handling
|
|
2554
|
+
- Environment variables`,
|
|
2555
|
+
inputSchema: {
|
|
2556
|
+
type: "object",
|
|
2557
|
+
properties: {},
|
|
2558
|
+
required: [],
|
|
2559
|
+
},
|
|
2560
|
+
},
|
|
2561
|
+
],
|
|
2562
|
+
};
|
|
2563
|
+
});
|
|
2564
|
+
// Handle tool calls
|
|
2565
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2566
|
+
const { name, arguments: args } = request.params;
|
|
2567
|
+
try {
|
|
2568
|
+
switch (name) {
|
|
2569
|
+
case "get_api_overview": {
|
|
2570
|
+
return {
|
|
2571
|
+
content: [
|
|
2572
|
+
{
|
|
2573
|
+
type: "text",
|
|
2574
|
+
text: `# Multi-tenant SaaS API Overview
|
|
2575
|
+
|
|
2576
|
+
**API URL:** ${API_URL}
|
|
2577
|
+
**Version:** 2.1.0
|
|
2578
|
+
|
|
2579
|
+
${API_OVERVIEW.description}
|
|
2580
|
+
|
|
2581
|
+
## Quick Links
|
|
2582
|
+
- Swagger UI: ${API_URL}/docs
|
|
2583
|
+
- ReDoc: ${API_URL}/redoc
|
|
2584
|
+
- OpenAPI JSON: ${API_URL}/v1/developer/openapi.json
|
|
2585
|
+
|
|
2586
|
+
## Getting Started
|
|
2587
|
+
|
|
2588
|
+
1. Get your API key from the tenant dashboard
|
|
2589
|
+
2. Use \`pk_live_*\` key for frontend development
|
|
2590
|
+
3. Include \`X-API-Key\` header in ALL requests
|
|
2591
|
+
4. After login, include \`Authorization: Bearer <token>\` for authenticated requests
|
|
2592
|
+
|
|
2593
|
+
## Important Notes
|
|
2594
|
+
|
|
2595
|
+
⚠️ **When building a frontend for end-users:**
|
|
2596
|
+
- ONLY use endpoints from the "End-user" category
|
|
2597
|
+
- DO NOT use /v1/tenant-auth/* or /v1/dashboard/* endpoints
|
|
2598
|
+
- Those are for tenant admin dashboards, not end-user apps`,
|
|
2599
|
+
},
|
|
2600
|
+
],
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
case "get_end_user_endpoints": {
|
|
2604
|
+
const category = args.category;
|
|
2605
|
+
let endpoints = END_USER_ENDPOINTS;
|
|
2606
|
+
if (category && category in END_USER_ENDPOINTS) {
|
|
2607
|
+
endpoints = { [category]: END_USER_ENDPOINTS[category] };
|
|
2608
|
+
}
|
|
2609
|
+
return {
|
|
2610
|
+
content: [
|
|
2611
|
+
{
|
|
2612
|
+
type: "text",
|
|
2613
|
+
text: `# End-User API Endpoints${category ? ` - ${category}` : ''}
|
|
2614
|
+
|
|
2615
|
+
These endpoints are for building frontend applications for end-users.
|
|
2616
|
+
|
|
2617
|
+
${JSON.stringify(endpoints, null, 2)}
|
|
2618
|
+
|
|
2619
|
+
## Quick Reference
|
|
2620
|
+
|
|
2621
|
+
### Authentication (No JWT required)
|
|
2622
|
+
- POST /v1/auth/register - Register new user
|
|
2623
|
+
- POST /v1/auth/login - Login user
|
|
2624
|
+
- POST /v1/auth/refresh - Refresh access token
|
|
2625
|
+
- POST /v1/auth/forgot-password - Request password reset
|
|
2626
|
+
- POST /v1/auth/reset-password - Reset password
|
|
2627
|
+
- POST /v1/auth/verify-email - Verify email (with API Key)
|
|
2628
|
+
- POST /v1/auth/verify-email-with-token - Verify email with token only (no API Key)
|
|
2629
|
+
- POST /v1/auth/resend-verification - Resend verification email
|
|
2630
|
+
- GET /v1/auth/providers - List enabled OAuth providers (e.g. Google)
|
|
2631
|
+
- GET /v1/auth/google/url?redirect_uri=... - Get Google OAuth URL
|
|
2632
|
+
- GET /v1/auth/google/callback - OAuth callback (redirects with tokens in URL fragment #)
|
|
2633
|
+
|
|
2634
|
+
### Authenticated Endpoints (JWT required)
|
|
2635
|
+
- GET /v1/auth/me - Get current user
|
|
2636
|
+
- POST /v1/auth/logout - Logout
|
|
2637
|
+
- POST /v1/auth/change-password - Change password (authenticated)
|
|
2638
|
+
- GET /v1/organizations/my-organizations - Get user's organizations
|
|
2639
|
+
- POST /v1/organizations - Create organization
|
|
2640
|
+
- GET /v1/organizations/{id}/departments - List departments
|
|
2641
|
+
- GET /v1/organizations/{id}/members - List members
|
|
2642
|
+
- PUT /v1/organizations/{id}/members/{user_id} - Update member role
|
|
2643
|
+
- DELETE /v1/organizations/{id}/members/{user_id} - Remove member
|
|
2644
|
+
- POST /v1/organizations/{id}/invite - Invite member
|
|
2645
|
+
- POST /v1/organizations/accept-invitation - Accept invitation
|
|
2646
|
+
- POST /v1/organizations/reject-invitation - Reject invitation
|
|
2647
|
+
- GET /v1/organizations/invitations/pending?email= - Check pending invitations
|
|
2648
|
+
- GET /v1/organizations/invitations/{token} - Get invitation details
|
|
2649
|
+
- POST /v1/organizations/{id}/set-default - Set default org
|
|
2650
|
+
- GET /v1/organizations/{id}/subscription - Get org subscription
|
|
2651
|
+
- POST /v1/organizations/{id}/subscription/{plan_id} - Subscribe to plan
|
|
2652
|
+
- DELETE /v1/organizations/{id}/subscription - Cancel subscription
|
|
2653
|
+
- GET /v1/organizations/{id}/usage - Get org usage
|
|
2654
|
+
|
|
2655
|
+
### Plans (JWT required)
|
|
2656
|
+
- GET /v1/plans - List all plans (paginated)
|
|
2657
|
+
- GET /v1/plans/active - List active plans
|
|
2658
|
+
- GET /v1/plans/free - List free plans
|
|
2659
|
+
- GET /v1/plans/{plan_id} - Get plan details
|
|
2660
|
+
|
|
2661
|
+
### Subscriptions (JWT required)
|
|
2662
|
+
- GET /v1/subscriptions/plans - List plans
|
|
2663
|
+
- GET /v1/subscriptions/plans/{plan_id} - Get plan by ID
|
|
2664
|
+
- GET /v1/subscriptions/current - Get current subscription
|
|
2665
|
+
- POST /v1/subscriptions/subscriptions - Create subscription
|
|
2666
|
+
- GET /v1/subscriptions/subscriptions/{id} - Get subscription by ID
|
|
2667
|
+
- PUT /v1/subscriptions/subscriptions/{id} - Update subscription
|
|
2668
|
+
- DELETE /v1/subscriptions/subscriptions/{id} - Cancel subscription
|
|
2669
|
+
- POST /v1/subscriptions/checkout - Start checkout
|
|
2670
|
+
- GET /v1/subscriptions/users/{user_id}/usage - Get user plan usage
|
|
2671
|
+
- GET /v1/subscriptions/users/{user_id}/usage/limits - Check user limits
|
|
2672
|
+
- PUT /v1/subscriptions/users/{user_id}/plan/{plan_id} - Assign plan
|
|
2673
|
+
- POST /v1/subscriptions/organizations/{org_id}/resume - Resume org subscription (sk_live_*)
|
|
2674
|
+
- GET /v1/subscriptions/organizations/{org_id}/invoices - Get org invoices (sk_live_*)
|
|
2675
|
+
- DELETE /v1/subscriptions/organizations/{org_id}/subscription - Cancel org subscription (sk_live_*)
|
|
2676
|
+
|
|
2677
|
+
### Billing (sk_live_* ONLY)
|
|
2678
|
+
- GET /v1/billing/available-plans - List available plans
|
|
2679
|
+
- POST /v1/billing/create-checkout-session - Create checkout session
|
|
2680
|
+
- POST /v1/billing/change-plan - Change plan (upgrade/downgrade)
|
|
2681
|
+
- POST /v1/billing/cancel-subscription - Cancel subscription
|
|
2682
|
+
- GET /v1/billing/billing-history-org/{id} - Billing history
|
|
2683
|
+
|
|
2684
|
+
### Users
|
|
2685
|
+
- POST /v1/users - Create user (sk_live_* + JWT)
|
|
2686
|
+
- GET /v1/users - List users (sk_live_* only, paginated)
|
|
2687
|
+
- GET /v1/users/stats - User stats (sk_live_* only)
|
|
2688
|
+
- GET /v1/users/{id} - Get user (self or sk_live_*)
|
|
2689
|
+
- PUT /v1/users/{id} - Update user
|
|
2690
|
+
- PUT /v1/users/{id}/settings - Update settings
|
|
2691
|
+
- POST /v1/users/{id}/deactivate - Deactivate user (soft delete)
|
|
2692
|
+
- POST /v1/users/{id}/activate - Activate user (sk_live_* only)
|
|
2693
|
+
- DELETE /v1/users/{id} - Delete user (sk_live_* only, soft delete)
|
|
2694
|
+
|
|
2695
|
+
### Usage (JWT required)
|
|
2696
|
+
- GET /v1/api/usage/current - Current usage stats
|
|
2697
|
+
- GET /v1/api/usage/history - Usage history (limit/offset pagination)
|
|
2698
|
+
- GET /v1/api/usage/entity-usage - Entity usage stats
|
|
2699
|
+
- GET /v1/usage/stats - Comprehensive usage statistics
|
|
2700
|
+
- POST /v1/usage/check - Check if usage allowed (without consuming)
|
|
2701
|
+
- POST /v1/usage/consume - Consume usage tokens/messages
|
|
2702
|
+
|
|
2703
|
+
### AI Agents (JWT required)
|
|
2704
|
+
- GET /v1/user/agents/available - List available agents
|
|
2705
|
+
- GET /v1/user/agents/{agent_id} - Get agent details
|
|
2706
|
+
- POST /v1/user/agents/{agent_id}/chat - Chat with an agent
|
|
2707
|
+
- GET /v1/user/agents/{agent_id}/history - Get conversation history
|
|
2708
|
+
|
|
2709
|
+
### Custom Entities (API Key + JWT + X-Organization-Id)
|
|
2710
|
+
- GET /v1/entity/schemas - List schemas
|
|
2711
|
+
- POST /v1/entity/schemas - Create schema
|
|
2712
|
+
- GET /v1/entity/schemas/{id} - Get schema
|
|
2713
|
+
- PUT /v1/entity/schemas/{id} - Update schema
|
|
2714
|
+
- DELETE /v1/entity/schemas/{id} - Delete schema
|
|
2715
|
+
- POST /v1/entity/records - Create record
|
|
2716
|
+
- GET /v1/entity/records/{id} - Get record
|
|
2717
|
+
- PUT /v1/entity/records/{id} - Update record
|
|
2718
|
+
- DELETE /v1/entity/records/{id} - Delete record
|
|
2719
|
+
- POST /v1/entity/records/search - Search records with filters
|
|
2720
|
+
|
|
2721
|
+
### Departments
|
|
2722
|
+
- GET /v1/departments/{department_id} - Get department details`,
|
|
2723
|
+
},
|
|
2724
|
+
],
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
case "get_request_headers": {
|
|
2728
|
+
return {
|
|
2729
|
+
content: [
|
|
2730
|
+
{
|
|
2731
|
+
type: "text",
|
|
2732
|
+
text: REQUEST_HEADERS,
|
|
2733
|
+
},
|
|
2734
|
+
],
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
case "get_sdk_template": {
|
|
2738
|
+
return {
|
|
2739
|
+
content: [
|
|
2740
|
+
{
|
|
2741
|
+
type: "text",
|
|
2742
|
+
text: `# TypeScript/JavaScript SDK Template
|
|
2743
|
+
|
|
2744
|
+
\`\`\`typescript
|
|
2745
|
+
${SDK_TEMPLATE_TYPESCRIPT}
|
|
2746
|
+
\`\`\`
|
|
2747
|
+
|
|
2748
|
+
## Usage Example
|
|
2749
|
+
|
|
2750
|
+
\`\`\`typescript
|
|
2751
|
+
import { ApiClient } from './api-client';
|
|
2752
|
+
|
|
2753
|
+
const api = new ApiClient('pk_live_your_key', 'https://api.yoursaas.com');
|
|
2754
|
+
|
|
2755
|
+
// Register
|
|
2756
|
+
const { user } = await api.auth.register('email@example.com', 'Password123', 'John');
|
|
2757
|
+
|
|
2758
|
+
// Login
|
|
2759
|
+
const { access_token, user } = await api.auth.login('email@example.com', 'Password123');
|
|
2760
|
+
|
|
2761
|
+
// Get organizations
|
|
2762
|
+
const { organizations } = await api.organizations.getMyOrganizations();
|
|
2763
|
+
|
|
2764
|
+
// Create organization
|
|
2765
|
+
const org = await api.organizations.create('My Org', 'my-org');
|
|
2766
|
+
|
|
2767
|
+
// Get subscription plans
|
|
2768
|
+
const plans = await api.subscriptions.getPlans();
|
|
2769
|
+
\`\`\``,
|
|
2770
|
+
},
|
|
2771
|
+
],
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
case "get_openapi_spec": {
|
|
2775
|
+
try {
|
|
2776
|
+
const spec = await fetchFromApi("/v1/developer/openapi.json");
|
|
2777
|
+
return {
|
|
2778
|
+
content: [
|
|
2779
|
+
{
|
|
2780
|
+
type: "text",
|
|
2781
|
+
text: JSON.stringify(spec, null, 2),
|
|
2782
|
+
},
|
|
2783
|
+
],
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
catch (error) {
|
|
2787
|
+
return {
|
|
2788
|
+
content: [
|
|
2789
|
+
{
|
|
2790
|
+
type: "text",
|
|
2791
|
+
text: `Error fetching OpenAPI spec: ${error}
|
|
2792
|
+
|
|
2793
|
+
The API might not be accessible. Try accessing ${API_URL}/docs directly.
|
|
2794
|
+
|
|
2795
|
+
In the meantime, use the get_end_user_endpoints tool for endpoint documentation.`,
|
|
2796
|
+
},
|
|
2797
|
+
],
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
case "search_endpoints": {
|
|
2802
|
+
const query = (args.query || "").toLowerCase();
|
|
2803
|
+
const results = [];
|
|
2804
|
+
// Search in embedded endpoints
|
|
2805
|
+
for (const [category, data] of Object.entries(END_USER_ENDPOINTS)) {
|
|
2806
|
+
for (const endpoint of data.endpoints || []) {
|
|
2807
|
+
const searchText = `${endpoint.path} ${endpoint.summary} ${endpoint.method}`.toLowerCase();
|
|
2808
|
+
if (searchText.includes(query)) {
|
|
2809
|
+
results.push({
|
|
2810
|
+
category,
|
|
2811
|
+
method: endpoint.method,
|
|
2812
|
+
path: endpoint.path,
|
|
2813
|
+
summary: endpoint.summary,
|
|
2814
|
+
auth: endpoint.auth,
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
if (results.length === 0) {
|
|
2820
|
+
return {
|
|
2821
|
+
content: [
|
|
2822
|
+
{
|
|
2823
|
+
type: "text",
|
|
2824
|
+
text: `No endpoints found matching "${query}".
|
|
2825
|
+
|
|
2826
|
+
Try searching for:
|
|
2827
|
+
- auth, login, register, password
|
|
2828
|
+
- organization, member, invite
|
|
2829
|
+
- subscription, plan, checkout
|
|
2830
|
+
- billing, usage`,
|
|
2831
|
+
},
|
|
2832
|
+
],
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
return {
|
|
2836
|
+
content: [
|
|
2837
|
+
{
|
|
2838
|
+
type: "text",
|
|
2839
|
+
text: `# Endpoints matching "${query}"
|
|
2840
|
+
|
|
2841
|
+
${results.map(r => `## ${r.method} ${r.path}
|
|
2842
|
+
**Category:** ${r.category}
|
|
2843
|
+
**Summary:** ${r.summary}
|
|
2844
|
+
**Auth:** API Key: ${r.auth.api_key}, JWT: ${r.auth.jwt ? 'Required' : 'Not required'}
|
|
2845
|
+
`).join('\n')}`,
|
|
2846
|
+
},
|
|
2847
|
+
],
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
case "get_endpoint_details": {
|
|
2851
|
+
const path = args.path;
|
|
2852
|
+
const method = (args.method || "GET").toUpperCase();
|
|
2853
|
+
// Search in embedded endpoints
|
|
2854
|
+
for (const [category, data] of Object.entries(END_USER_ENDPOINTS)) {
|
|
2855
|
+
for (const endpoint of data.endpoints || []) {
|
|
2856
|
+
if (endpoint.path === path && endpoint.method === method) {
|
|
2857
|
+
return {
|
|
2858
|
+
content: [
|
|
2859
|
+
{
|
|
2860
|
+
type: "text",
|
|
2861
|
+
text: `# ${method} ${path}
|
|
2862
|
+
|
|
2863
|
+
**Category:** ${category}
|
|
2864
|
+
**Summary:** ${endpoint.summary}
|
|
2865
|
+
|
|
2866
|
+
## Authentication
|
|
2867
|
+
- API Key: ${endpoint.auth.api_key}
|
|
2868
|
+
- JWT Token: ${endpoint.auth.jwt ? 'Required' : 'Not required'}
|
|
2869
|
+
|
|
2870
|
+
${endpoint.request_body ? `## Request Body
|
|
2871
|
+
\`\`\`json
|
|
2872
|
+
${JSON.stringify(endpoint.request_body, null, 2)}
|
|
2873
|
+
\`\`\`` : ''}
|
|
2874
|
+
|
|
2875
|
+
${endpoint.example_request ? `## Example Request
|
|
2876
|
+
\`\`\`json
|
|
2877
|
+
${endpoint.example_request}
|
|
2878
|
+
\`\`\`` : ''}
|
|
2879
|
+
|
|
2880
|
+
${endpoint.response ? `## Response
|
|
2881
|
+
\`\`\`json
|
|
2882
|
+
${JSON.stringify(endpoint.response, null, 2)}
|
|
2883
|
+
\`\`\`` : ''}
|
|
2884
|
+
|
|
2885
|
+
${endpoint.note ? `## Note
|
|
2886
|
+
${endpoint.note}` : ''}`,
|
|
2887
|
+
},
|
|
2888
|
+
],
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
return {
|
|
2894
|
+
content: [
|
|
2895
|
+
{
|
|
2896
|
+
type: "text",
|
|
2897
|
+
text: `Endpoint "${method} ${path}" not found in end-user endpoints.
|
|
2898
|
+
|
|
2899
|
+
Make sure you're using the correct path format (e.g., /v1/auth/login).
|
|
2900
|
+
|
|
2901
|
+
Use search_endpoints tool to find available endpoints.`,
|
|
2902
|
+
},
|
|
2903
|
+
],
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
case "get_authentication_flow": {
|
|
2907
|
+
return {
|
|
2908
|
+
content: [
|
|
2909
|
+
{
|
|
2910
|
+
type: "text",
|
|
2911
|
+
text: `# Authentication Flow Guide
|
|
2912
|
+
|
|
2913
|
+
## 1. Registration Flow
|
|
2914
|
+
|
|
2915
|
+
\`\`\`typescript
|
|
2916
|
+
// 1. Register user
|
|
2917
|
+
const response = await fetch('/v1/auth/register', {
|
|
2918
|
+
method: 'POST',
|
|
2919
|
+
headers: {
|
|
2920
|
+
'X-API-Key': 'pk_live_your_key',
|
|
2921
|
+
'Content-Type': 'application/json',
|
|
2922
|
+
},
|
|
2923
|
+
body: JSON.stringify({
|
|
2924
|
+
email: 'user@example.com',
|
|
2925
|
+
password: 'SecurePass123', // min 8 chars, 1 upper, 1 lower, 1 digit
|
|
2926
|
+
display_name: 'John Doe', // optional
|
|
2927
|
+
}),
|
|
2928
|
+
});
|
|
2929
|
+
|
|
2930
|
+
const { access_token, refresh_token, user } = await response.json();
|
|
2931
|
+
|
|
2932
|
+
// 2. Store tokens securely
|
|
2933
|
+
localStorage.setItem('access_token', access_token);
|
|
2934
|
+
localStorage.setItem('refresh_token', refresh_token);
|
|
2935
|
+
|
|
2936
|
+
// User is now logged in
|
|
2937
|
+
\`\`\`
|
|
2938
|
+
|
|
2939
|
+
## 2. Login Flow
|
|
2940
|
+
|
|
2941
|
+
\`\`\`typescript
|
|
2942
|
+
const response = await fetch('/v1/auth/login', {
|
|
2943
|
+
method: 'POST',
|
|
2944
|
+
headers: {
|
|
2945
|
+
'X-API-Key': 'pk_live_your_key',
|
|
2946
|
+
'Content-Type': 'application/json',
|
|
2947
|
+
},
|
|
2948
|
+
body: JSON.stringify({
|
|
2949
|
+
email: 'user@example.com',
|
|
2950
|
+
password: 'SecurePass123',
|
|
2951
|
+
}),
|
|
2952
|
+
});
|
|
2953
|
+
|
|
2954
|
+
if (!response.ok) {
|
|
2955
|
+
const error = await response.json();
|
|
2956
|
+
// Handle error: error.detail contains the message
|
|
2957
|
+
throw new Error(error.detail);
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
const { access_token, refresh_token, user } = await response.json();
|
|
2961
|
+
\`\`\`
|
|
2962
|
+
|
|
2963
|
+
## 3. Token Refresh Flow
|
|
2964
|
+
|
|
2965
|
+
\`\`\`typescript
|
|
2966
|
+
// Call this when access_token expires (expires_in seconds from login)
|
|
2967
|
+
const response = await fetch('/v1/auth/refresh', {
|
|
2968
|
+
method: 'POST',
|
|
2969
|
+
headers: {
|
|
2970
|
+
'X-API-Key': 'pk_live_your_key',
|
|
2971
|
+
'Content-Type': 'application/json',
|
|
2972
|
+
},
|
|
2973
|
+
body: JSON.stringify({
|
|
2974
|
+
refresh_token: localStorage.getItem('refresh_token'),
|
|
2975
|
+
}),
|
|
2976
|
+
});
|
|
2977
|
+
|
|
2978
|
+
const { access_token } = await response.json();
|
|
2979
|
+
localStorage.setItem('access_token', access_token);
|
|
2980
|
+
\`\`\`
|
|
2981
|
+
|
|
2982
|
+
## 4. Password Reset Flow
|
|
2983
|
+
|
|
2984
|
+
\`\`\`typescript
|
|
2985
|
+
// Step 1: Request reset email
|
|
2986
|
+
await fetch('/v1/auth/forgot-password', {
|
|
2987
|
+
method: 'POST',
|
|
2988
|
+
headers: {
|
|
2989
|
+
'X-API-Key': 'pk_live_your_key',
|
|
2990
|
+
'Content-Type': 'application/json',
|
|
2991
|
+
},
|
|
2992
|
+
body: JSON.stringify({ email: 'user@example.com' }),
|
|
2993
|
+
});
|
|
2994
|
+
// Always succeeds (security)
|
|
2995
|
+
|
|
2996
|
+
// Step 2: User clicks link in email with token
|
|
2997
|
+
|
|
2998
|
+
// Step 3: Reset password with token
|
|
2999
|
+
await fetch('/v1/auth/reset-password', {
|
|
3000
|
+
method: 'POST',
|
|
3001
|
+
headers: {
|
|
3002
|
+
'X-API-Key': 'pk_live_your_key',
|
|
3003
|
+
'Content-Type': 'application/json',
|
|
3004
|
+
},
|
|
3005
|
+
body: JSON.stringify({
|
|
3006
|
+
token: 'token-from-email-link',
|
|
3007
|
+
new_password: 'NewSecurePass123',
|
|
3008
|
+
}),
|
|
3009
|
+
});
|
|
3010
|
+
\`\`\`
|
|
3011
|
+
|
|
3012
|
+
## 5. Change Password Flow (Authenticated)
|
|
3013
|
+
|
|
3014
|
+
\`\`\`typescript
|
|
3015
|
+
await fetch('/v1/auth/change-password', {
|
|
3016
|
+
method: 'POST',
|
|
3017
|
+
headers: {
|
|
3018
|
+
'X-API-Key': 'pk_live_your_key',
|
|
3019
|
+
'Authorization': 'Bearer your_jwt_token',
|
|
3020
|
+
'Content-Type': 'application/json',
|
|
3021
|
+
},
|
|
3022
|
+
body: JSON.stringify({
|
|
3023
|
+
current_password: 'OldPass123!',
|
|
3024
|
+
new_password: 'NewPass456!',
|
|
3025
|
+
}),
|
|
3026
|
+
});
|
|
3027
|
+
\`\`\`
|
|
3028
|
+
|
|
3029
|
+
## 6. Email Verification Flow
|
|
3030
|
+
|
|
3031
|
+
\`\`\`typescript
|
|
3032
|
+
// Registration may require email verification depending on tenant config.
|
|
3033
|
+
// Check the register response for verification_required: true
|
|
3034
|
+
|
|
3035
|
+
const registerResult = await response.json();
|
|
3036
|
+
|
|
3037
|
+
if (registerResult.verification_required) {
|
|
3038
|
+
// 1. Show "Check your email" message — NO tokens returned
|
|
3039
|
+
// 2. User clicks link in email → opens /verify-email?token=xxx
|
|
3040
|
+
|
|
3041
|
+
// 3. Verify the email token
|
|
3042
|
+
const verifyResponse = await fetch('/v1/auth/verify-email', {
|
|
3043
|
+
method: 'POST',
|
|
3044
|
+
headers: {
|
|
3045
|
+
'X-API-Key': 'pk_live_your_key',
|
|
3046
|
+
'Content-Type': 'application/json',
|
|
3047
|
+
},
|
|
3048
|
+
body: JSON.stringify({ token: tokenFromUrl }),
|
|
3049
|
+
});
|
|
3050
|
+
// Returns: { message: "Email verified successfully" }
|
|
3051
|
+
|
|
3052
|
+
// 4. If token expired, user can request a new one
|
|
3053
|
+
await fetch('/v1/auth/resend-verification', {
|
|
3054
|
+
method: 'POST',
|
|
3055
|
+
headers: {
|
|
3056
|
+
'X-API-Key': 'pk_live_your_key',
|
|
3057
|
+
'Content-Type': 'application/json',
|
|
3058
|
+
},
|
|
3059
|
+
body: JSON.stringify({ email: 'user@example.com' }),
|
|
3060
|
+
});
|
|
3061
|
+
// Rate limited: 3/hour
|
|
3062
|
+
|
|
3063
|
+
// 5. After verification, user can login normally via /v1/auth/login
|
|
3064
|
+
} else {
|
|
3065
|
+
// No verification required — tokens returned directly from register
|
|
3066
|
+
localStorage.setItem('access_token', registerResult.access_token);
|
|
3067
|
+
localStorage.setItem('refresh_token', registerResult.refresh_token);
|
|
3068
|
+
}
|
|
3069
|
+
\`\`\`
|
|
3070
|
+
|
|
3071
|
+
## 7. Google OAuth Flow
|
|
3072
|
+
|
|
3073
|
+
\`\`\`typescript
|
|
3074
|
+
// 1. Check if Google OAuth is enabled for this tenant
|
|
3075
|
+
const providersRes = await fetch('/v1/auth/providers', {
|
|
3076
|
+
headers: { 'X-API-Key': 'pk_live_your_key' },
|
|
3077
|
+
});
|
|
3078
|
+
const { providers } = await providersRes.json();
|
|
3079
|
+
const googleEnabled = providers.some((p: any) => p.provider === 'google');
|
|
3080
|
+
|
|
3081
|
+
// 2. Get Google authorization URL
|
|
3082
|
+
const redirectUri = window.location.origin + '/oauth/callback';
|
|
3083
|
+
const urlRes = await fetch(
|
|
3084
|
+
\`/v1/auth/google/url?redirect_uri=\${encodeURIComponent(redirectUri)}\`,
|
|
3085
|
+
{ headers: { 'X-API-Key': 'pk_live_your_key' } }
|
|
3086
|
+
);
|
|
3087
|
+
const { authorization_url } = await urlRes.json();
|
|
3088
|
+
|
|
3089
|
+
// 3. Redirect user to Google
|
|
3090
|
+
window.location.href = authorization_url;
|
|
3091
|
+
|
|
3092
|
+
// 4. Google redirects to /v1/auth/google/callback (backend handles this)
|
|
3093
|
+
// 5. Backend redirects to YOUR redirect_uri with tokens in URL FRAGMENT (#)
|
|
3094
|
+
|
|
3095
|
+
// 6. In your /oauth/callback page, read tokens from hash (NOT query params)
|
|
3096
|
+
const hash = window.location.hash.substring(1);
|
|
3097
|
+
const params = new URLSearchParams(hash);
|
|
3098
|
+
const accessToken = params.get('access_token');
|
|
3099
|
+
const refreshToken = params.get('refresh_token');
|
|
3100
|
+
const expiresIn = params.get('expires_in');
|
|
3101
|
+
|
|
3102
|
+
// Also check query params for errors
|
|
3103
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
3104
|
+
const error = urlParams.get('error');
|
|
3105
|
+
if (error) {
|
|
3106
|
+
console.error('OAuth failed:', urlParams.get('error_description'));
|
|
3107
|
+
}
|
|
3108
|
+
\`\`\`
|
|
3109
|
+
|
|
3110
|
+
## 8. Authenticated Requests
|
|
3111
|
+
|
|
3112
|
+
\`\`\`typescript
|
|
3113
|
+
// After login, include Authorization header
|
|
3114
|
+
const response = await fetch('/v1/auth/me', {
|
|
3115
|
+
headers: {
|
|
3116
|
+
'X-API-Key': 'pk_live_your_key',
|
|
3117
|
+
'Authorization': \`Bearer \${localStorage.getItem('access_token')}\`,
|
|
3118
|
+
},
|
|
3119
|
+
});
|
|
3120
|
+
|
|
3121
|
+
const user = await response.json();
|
|
3122
|
+
\`\`\`
|
|
3123
|
+
|
|
3124
|
+
## 9. Logout
|
|
3125
|
+
|
|
3126
|
+
\`\`\`typescript
|
|
3127
|
+
await fetch('/v1/auth/logout', {
|
|
3128
|
+
method: 'POST',
|
|
3129
|
+
headers: {
|
|
3130
|
+
'X-API-Key': 'pk_live_your_key',
|
|
3131
|
+
'Authorization': \`Bearer \${localStorage.getItem('access_token')}\`,
|
|
3132
|
+
},
|
|
3133
|
+
});
|
|
3134
|
+
|
|
3135
|
+
localStorage.removeItem('access_token');
|
|
3136
|
+
localStorage.removeItem('refresh_token');
|
|
3137
|
+
\`\`\``,
|
|
3138
|
+
},
|
|
3139
|
+
],
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
case "get_common_patterns": {
|
|
3143
|
+
return {
|
|
3144
|
+
content: [
|
|
3145
|
+
{
|
|
3146
|
+
type: "text",
|
|
3147
|
+
text: `# Common Implementation Patterns
|
|
3148
|
+
|
|
3149
|
+
## 1. API Request Wrapper
|
|
3150
|
+
|
|
3151
|
+
\`\`\`typescript
|
|
3152
|
+
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
3153
|
+
const API_KEY = process.env.NEXT_PUBLIC_API_KEY; // pk_live_*
|
|
3154
|
+
|
|
3155
|
+
interface RequestOptions {
|
|
3156
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
3157
|
+
body?: any;
|
|
3158
|
+
requiresAuth?: boolean;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
async function apiRequest<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
|
|
3162
|
+
const { method = 'GET', body, requiresAuth = false } = options;
|
|
3163
|
+
|
|
3164
|
+
const headers: Record<string, string> = {
|
|
3165
|
+
'X-API-Key': API_KEY,
|
|
3166
|
+
'Content-Type': 'application/json',
|
|
3167
|
+
};
|
|
3168
|
+
|
|
3169
|
+
if (requiresAuth) {
|
|
3170
|
+
const token = localStorage.getItem('access_token');
|
|
3171
|
+
if (token) {
|
|
3172
|
+
headers['Authorization'] = \`Bearer \${token}\`;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
const response = await fetch(\`\${API_URL}\${endpoint}\`, {
|
|
3177
|
+
method,
|
|
3178
|
+
headers,
|
|
3179
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
3180
|
+
});
|
|
3181
|
+
|
|
3182
|
+
if (response.status === 401 && requiresAuth) {
|
|
3183
|
+
// Try to refresh token
|
|
3184
|
+
const refreshed = await refreshToken();
|
|
3185
|
+
if (refreshed) {
|
|
3186
|
+
// Retry request with new token
|
|
3187
|
+
headers['Authorization'] = \`Bearer \${localStorage.getItem('access_token')}\`;
|
|
3188
|
+
const retryResponse = await fetch(\`\${API_URL}\${endpoint}\`, {
|
|
3189
|
+
method,
|
|
3190
|
+
headers,
|
|
3191
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
3192
|
+
});
|
|
3193
|
+
if (!retryResponse.ok) throw new Error('Request failed after refresh');
|
|
3194
|
+
return retryResponse.json();
|
|
3195
|
+
}
|
|
3196
|
+
// Redirect to login
|
|
3197
|
+
window.location.href = '/login';
|
|
3198
|
+
throw new Error('Session expired');
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
if (!response.ok) {
|
|
3202
|
+
const error = await response.json();
|
|
3203
|
+
throw new Error(error.detail || 'Request failed');
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
return response.json();
|
|
3207
|
+
}
|
|
3208
|
+
\`\`\`
|
|
3209
|
+
|
|
3210
|
+
## 2. Auto Token Refresh
|
|
3211
|
+
|
|
3212
|
+
\`\`\`typescript
|
|
3213
|
+
async function refreshToken(): Promise<boolean> {
|
|
3214
|
+
const refreshToken = localStorage.getItem('refresh_token');
|
|
3215
|
+
if (!refreshToken) return false;
|
|
3216
|
+
|
|
3217
|
+
try {
|
|
3218
|
+
const response = await fetch(\`\${API_URL}/v1/auth/refresh\`, {
|
|
3219
|
+
method: 'POST',
|
|
3220
|
+
headers: {
|
|
3221
|
+
'X-API-Key': API_KEY,
|
|
3222
|
+
'Content-Type': 'application/json',
|
|
3223
|
+
},
|
|
3224
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
3225
|
+
});
|
|
3226
|
+
|
|
3227
|
+
if (!response.ok) return false;
|
|
3228
|
+
|
|
3229
|
+
const data = await response.json();
|
|
3230
|
+
localStorage.setItem('access_token', data.access_token);
|
|
3231
|
+
return true;
|
|
3232
|
+
} catch {
|
|
3233
|
+
return false;
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
\`\`\`
|
|
3237
|
+
|
|
3238
|
+
## 3. React Hook Example
|
|
3239
|
+
|
|
3240
|
+
\`\`\`typescript
|
|
3241
|
+
import { useState, useEffect } from 'react';
|
|
3242
|
+
|
|
3243
|
+
function useAuth() {
|
|
3244
|
+
const [user, setUser] = useState(null);
|
|
3245
|
+
const [loading, setLoading] = useState(true);
|
|
3246
|
+
|
|
3247
|
+
useEffect(() => {
|
|
3248
|
+
const token = localStorage.getItem('access_token');
|
|
3249
|
+
if (!token) {
|
|
3250
|
+
setLoading(false);
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
apiRequest('/v1/auth/me', { requiresAuth: true })
|
|
3255
|
+
.then(setUser)
|
|
3256
|
+
.catch(() => {
|
|
3257
|
+
localStorage.removeItem('access_token');
|
|
3258
|
+
localStorage.removeItem('refresh_token');
|
|
3259
|
+
})
|
|
3260
|
+
.finally(() => setLoading(false));
|
|
3261
|
+
}, []);
|
|
3262
|
+
|
|
3263
|
+
const login = async (email: string, password: string) => {
|
|
3264
|
+
const data = await apiRequest('/v1/auth/login', {
|
|
3265
|
+
method: 'POST',
|
|
3266
|
+
body: { email, password },
|
|
3267
|
+
});
|
|
3268
|
+
localStorage.setItem('access_token', data.access_token);
|
|
3269
|
+
localStorage.setItem('refresh_token', data.refresh_token);
|
|
3270
|
+
setUser(data.user);
|
|
3271
|
+
return data;
|
|
3272
|
+
};
|
|
3273
|
+
|
|
3274
|
+
const logout = async () => {
|
|
3275
|
+
await apiRequest('/v1/auth/logout', { method: 'POST', requiresAuth: true });
|
|
3276
|
+
localStorage.removeItem('access_token');
|
|
3277
|
+
localStorage.removeItem('refresh_token');
|
|
3278
|
+
setUser(null);
|
|
3279
|
+
};
|
|
3280
|
+
|
|
3281
|
+
return { user, loading, login, logout };
|
|
3282
|
+
}
|
|
3283
|
+
\`\`\`
|
|
3284
|
+
|
|
3285
|
+
## 4. Error Handling
|
|
3286
|
+
|
|
3287
|
+
\`\`\`typescript
|
|
3288
|
+
interface ApiError {
|
|
3289
|
+
detail: string;
|
|
3290
|
+
error?: string;
|
|
3291
|
+
error_description?: string;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
function handleApiError(error: ApiError): string {
|
|
3295
|
+
// Common error messages
|
|
3296
|
+
const errorMessages: Record<string, string> = {
|
|
3297
|
+
'Invalid email or password': 'The email or password you entered is incorrect.',
|
|
3298
|
+
'User already exists': 'An account with this email already exists.',
|
|
3299
|
+
'Invalid token': 'Your session has expired. Please log in again.',
|
|
3300
|
+
'Rate limit exceeded': 'Too many requests. Please try again later.',
|
|
3301
|
+
};
|
|
3302
|
+
|
|
3303
|
+
return errorMessages[error.detail] || error.detail || 'An error occurred';
|
|
3304
|
+
}
|
|
3305
|
+
\`\`\`
|
|
3306
|
+
|
|
3307
|
+
## 5. OAuth Callback Handling
|
|
3308
|
+
|
|
3309
|
+
\`\`\`typescript
|
|
3310
|
+
// After Google OAuth, tokens arrive in the URL FRAGMENT (#), not query params (?)
|
|
3311
|
+
// This is because fragments are never sent to the server — they stay client-side only
|
|
3312
|
+
|
|
3313
|
+
function parseOAuthCallback(): { access_token: string; refresh_token: string; expires_in: string } | null {
|
|
3314
|
+
// Check for errors in query params first
|
|
3315
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
3316
|
+
const error = urlParams.get('error');
|
|
3317
|
+
if (error) {
|
|
3318
|
+
console.error('OAuth error:', urlParams.get('error_description'));
|
|
3319
|
+
return null;
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// Parse tokens from URL fragment
|
|
3323
|
+
const hash = window.location.hash.substring(1); // Remove leading #
|
|
3324
|
+
const params = new URLSearchParams(hash);
|
|
3325
|
+
|
|
3326
|
+
const access_token = params.get('access_token');
|
|
3327
|
+
const refresh_token = params.get('refresh_token');
|
|
3328
|
+
const expires_in = params.get('expires_in');
|
|
3329
|
+
|
|
3330
|
+
if (!access_token || !refresh_token) return null;
|
|
3331
|
+
|
|
3332
|
+
// Clean up the URL (remove fragment)
|
|
3333
|
+
window.history.replaceState({}, '', window.location.pathname);
|
|
3334
|
+
|
|
3335
|
+
return { access_token, refresh_token, expires_in: expires_in || '3600' };
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// Usage in your OAuth callback page:
|
|
3339
|
+
// const tokens = parseOAuthCallback();
|
|
3340
|
+
// if (tokens) {
|
|
3341
|
+
// localStorage.setItem('access_token', tokens.access_token);
|
|
3342
|
+
// localStorage.setItem('refresh_token', tokens.refresh_token);
|
|
3343
|
+
// navigate('/dashboard');
|
|
3344
|
+
// }
|
|
3345
|
+
\`\`\`
|
|
3346
|
+
|
|
3347
|
+
## 6. Environment Variables (Vite / React)
|
|
3348
|
+
|
|
3349
|
+
\`\`\`env
|
|
3350
|
+
# .env
|
|
3351
|
+
VITE_API_URL=https://api.yoursaas.com
|
|
3352
|
+
VITE_API_KEY=pk_live_your_public_key_here
|
|
3353
|
+
\`\`\`
|
|
3354
|
+
|
|
3355
|
+
## 7. Environment Variables (Next.js)
|
|
3356
|
+
|
|
3357
|
+
\`\`\`env
|
|
3358
|
+
# .env.local
|
|
3359
|
+
NEXT_PUBLIC_API_URL=https://api.yoursaas.com
|
|
3360
|
+
NEXT_PUBLIC_API_KEY=pk_live_your_public_key_here
|
|
3361
|
+
\`\`\``,
|
|
3362
|
+
},
|
|
3363
|
+
],
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
default:
|
|
3367
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
catch (error) {
|
|
3371
|
+
return {
|
|
3372
|
+
content: [
|
|
3373
|
+
{
|
|
3374
|
+
type: "text",
|
|
3375
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
3376
|
+
},
|
|
3377
|
+
],
|
|
3378
|
+
isError: true,
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
// Start the server
|
|
3383
|
+
async function main() {
|
|
3384
|
+
console.error(`🚀 Multi-tenant SaaS API MCP Server v2.1.0`);
|
|
3385
|
+
console.error(`📡 API URL: ${API_URL}`);
|
|
3386
|
+
console.error(`🔑 API Key: ${API_KEY ? "Configured" : "Not configured"}`);
|
|
3387
|
+
console.error(`\nAvailable tools:`);
|
|
3388
|
+
console.error(` - get_api_overview: Understand the API architecture`);
|
|
3389
|
+
console.error(` - get_end_user_endpoints: Get endpoint documentation`);
|
|
3390
|
+
console.error(` - get_sdk_template: Get TypeScript SDK template`);
|
|
3391
|
+
console.error(` - get_authentication_flow: Auth implementation guide`);
|
|
3392
|
+
console.error(` - get_common_patterns: Best practices and patterns`);
|
|
3393
|
+
console.error(` - search_endpoints: Search for endpoints`);
|
|
3394
|
+
console.error(` - get_endpoint_details: Get specific endpoint info`);
|
|
3395
|
+
console.error(` - get_openapi_spec: Get full OpenAPI spec`);
|
|
3396
|
+
const transport = new StdioServerTransport();
|
|
3397
|
+
await server.connect(transport);
|
|
3398
|
+
}
|
|
3399
|
+
main().catch((error) => {
|
|
3400
|
+
console.error("Fatal error:", error);
|
|
3401
|
+
process.exit(1);
|
|
3402
|
+
});
|