@iteradian-rpc/sdk 1.0.1 → 1.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/dist/index.mjs CHANGED
@@ -9,9 +9,21 @@ var IteradianError = class extends Error {
9
9
  this.retryAfter = rpc?.retryAfter;
10
10
  this.data = rpc?.data;
11
11
  }
12
- /** True when the request failed because the subscription is out of allowance. */
12
+ /**
13
+ * True when the request failed because an allowance is used up and retrying
14
+ * will not help until it resets: the subscription quota (-32006), the
15
+ * overage spend cap (-32007) or the API key's daily limit (-32008).
16
+ */
13
17
  get isQuotaExhausted() {
14
- return this.rpcCode === -32006 || this.rpcCode === -32007;
18
+ return this.rpcCode === -32006 || this.rpcCode === -32007 || this.rpcCode === -32008;
19
+ }
20
+ /**
21
+ * True when the API key's own daily limit is reached (-32008). `data` then
22
+ * carries `{ dailyLimit, used, cost, resetsAt }`; `resetsAt` is the next
23
+ * 00:00 UTC.
24
+ */
25
+ get isDailyLimitReached() {
26
+ return this.rpcCode === -32008;
15
27
  }
16
28
  /** True when the request was throttled and is worth retrying. */
17
29
  get isRateLimited() {
@@ -101,7 +113,11 @@ var HttpClient = class {
101
113
  if (!response.ok) {
102
114
  let errorBody;
103
115
  try {
104
- errorBody = await response.json();
116
+ const parsed = await response.json();
117
+ if (parsed === null || typeof parsed !== "object") {
118
+ throw new Error("error body is not an object");
119
+ }
120
+ errorBody = parsed;
105
121
  } catch {
106
122
  errorBody = {
107
123
  message: response.statusText || "Unknown error",
@@ -111,7 +127,7 @@ var HttpClient = class {
111
127
  const rpc = errorBody?.error;
112
128
  const rpcCode = typeof rpc?.code === "number" ? rpc.code : void 0;
113
129
  const retryAfter = typeof rpc?.data?.retryAfter === "number" ? rpc.data.retryAfter : void 0;
114
- const worthRetrying = response.status >= 500 || response.status === 429 && rpcCode !== -32006 && rpcCode !== -32007;
130
+ const worthRetrying = response.status >= 500 || response.status === 429 && rpcCode !== -32006 && rpcCode !== -32007 && rpcCode !== -32008;
115
131
  if (this.retry && attempt < this.maxRetries && worthRetrying && isReplayableBody(body) && !controller.signal.aborted) {
116
132
  const backoff = Math.min(1e3 * 2 ** (attempt - 1), 1e4);
117
133
  const delay = Math.min(
@@ -121,10 +137,14 @@ var HttpClient = class {
121
137
  await new Promise((r) => setTimeout(r, delay));
122
138
  return this.request(method, path, body, attempt + 1, base, signal);
123
139
  }
140
+ const rawMessage = errorBody.message;
141
+ const bodyMessage = Array.isArray(rawMessage) ? rawMessage.map(String).join(", ") : typeof rawMessage === "string" ? rawMessage : "";
142
+ const rpcMessage = typeof rpc?.message === "string" ? rpc.message : "";
143
+ const errorLabel = errorBody.error;
124
144
  throw new IteradianError(
125
- rpc?.message || errorBody.message || response.statusText,
126
- errorBody.statusCode || response.status,
127
- errorBody.error,
145
+ rpcMessage || bodyMessage || response.statusText || `HTTP ${response.status}`,
146
+ typeof errorBody.statusCode === "number" ? errorBody.statusCode : response.status,
147
+ typeof errorLabel === "string" ? errorLabel : void 0,
128
148
  { code: rpcCode, retryAfter, data: rpc?.data }
129
149
  );
130
150
  }
@@ -197,15 +217,40 @@ var AuthResource = class {
197
217
  constructor(http) {
198
218
  this.http = http;
199
219
  }
200
- /** Register a new user account */
220
+ /**
221
+ * Register a new user account. Does not sign in: the API answers
222
+ * `{ message, email }` (the same for an address that already has an
223
+ * account) and emails a confirmation link. Call `login()` once the address
224
+ * is confirmed.
225
+ *
226
+ * The server rejects registration (400) unless the user agreed to the
227
+ * Terms of Service, Privacy Policy and DPA, so `data.acceptedTerms` is
228
+ * required and always sent. Pass `acceptedTerms: true` only after the user
229
+ * has ticked the agreement.
230
+ */
201
231
  register(data) {
202
- return this.http.post("/auth/register", data);
232
+ return this.http.post("/auth/register", {
233
+ ...data,
234
+ acceptedTerms: data?.acceptedTerms === true
235
+ });
203
236
  }
204
- /** Login with email/password (optionally with 2FA code) */
237
+ /**
238
+ * Login with email/password (optionally with a 2FA code or a backup code).
239
+ *
240
+ * When two-factor auth is on and no code was sent, the API answers
241
+ * `{ requires2FA: true }` with no tokens. Check `requires2FA` and call
242
+ * again with `twoFactorCode` or `backupCode`. The stored access token is
243
+ * only replaced when the answer carries one.
244
+ */
205
245
  async login(data) {
206
- const tokens = await this.http.post("/auth/login", data);
207
- this.http.setAccessToken(tokens.accessToken);
208
- return tokens;
246
+ const result = await this.http.post("/auth/login", data);
247
+ this.storeToken(result);
248
+ return result;
249
+ }
250
+ storeToken(result) {
251
+ if (result && typeof result.accessToken === "string" && result.accessToken) {
252
+ this.http.setAccessToken(result.accessToken);
253
+ }
209
254
  }
210
255
  /** Logout current session */
211
256
  logout() {
@@ -216,7 +261,7 @@ var AuthResource = class {
216
261
  const tokens = await this.http.post("/auth/refresh", {
217
262
  refreshToken
218
263
  });
219
- this.http.setAccessToken(tokens.accessToken);
264
+ this.storeToken(tokens);
220
265
  return tokens;
221
266
  }
222
267
  /** Verify email with token */
@@ -241,11 +286,11 @@ var AuthResource = class {
241
286
  }
242
287
  /** Login via magic link token */
243
288
  async loginWithMagicLink(token) {
244
- const tokens = await this.http.post("/auth/magic-login", {
289
+ const result = await this.http.post("/auth/magic-login", {
245
290
  token
246
291
  });
247
- this.http.setAccessToken(tokens.accessToken);
248
- return tokens;
292
+ this.storeToken(result);
293
+ return result;
249
294
  }
250
295
  /** Enable 2FA – returns QR code image and secret */
251
296
  enable2FA(password) {
@@ -274,7 +319,11 @@ var OrganizationsResource = class {
274
319
  list() {
275
320
  return this.http.get("/orgs");
276
321
  }
277
- /** Create a new organization */
322
+ /**
323
+ * Create a new organization. `name`, `planId` and `acceptedTerms` are
324
+ * required by the API; pass `acceptedTerms: true` only after the user
325
+ * agreed to the terms.
326
+ */
278
327
  create(data) {
279
328
  return this.http.post("/orgs", data);
280
329
  }
@@ -298,10 +347,47 @@ var OrganizationsResource = class {
298
347
  listMembers(orgId) {
299
348
  return this.http.get(`/orgs/${orgId}/members`);
300
349
  }
301
- /** Invite a member to the organization */
350
+ /**
351
+ * Invite someone to the organization by email. Creates (or refreshes) a
352
+ * pending invitation — nobody becomes a member until the invitee accepts
353
+ * it while signed in with a verified address.
354
+ */
302
355
  inviteMember(orgId, data) {
303
356
  return this.http.post(`/orgs/${orgId}/members`, data);
304
357
  }
358
+ /** Open invitations of the organization (owner/admin) */
359
+ listInvites(orgId) {
360
+ return this.http.get(`/orgs/${orgId}/invites`);
361
+ }
362
+ /** Revoke an open invitation (owner/admin) */
363
+ revokeInvite(orgId, inviteId) {
364
+ return this.http.delete(`/orgs/${orgId}/invites/${inviteId}`);
365
+ }
366
+ /** Invitations addressed to the signed-in user */
367
+ listMyInvites() {
368
+ return this.http.get("/invites");
369
+ }
370
+ /**
371
+ * Accept an invitation addressed to the signed-in user.
372
+ *
373
+ * If the user has not accepted the current terms version yet, the API
374
+ * answers 403 (`TERMS_ACCEPTANCE_REQUIRED`) unless
375
+ * `{ acceptedTerms: true }` is passed — only after the user agreed to the
376
+ * Terms of Service, Privacy Policy and DPA. `acceptedTerms` is sent only
377
+ * when given.
378
+ */
379
+ acceptInvite(inviteId, options) {
380
+ if (options?.acceptedTerms !== void 0) {
381
+ return this.http.post(`/invites/${inviteId}/accept`, {
382
+ acceptedTerms: options.acceptedTerms === true
383
+ });
384
+ }
385
+ return this.http.post(`/invites/${inviteId}/accept`);
386
+ }
387
+ /** Decline an invitation addressed to the signed-in user */
388
+ declineInvite(inviteId) {
389
+ return this.http.post(`/invites/${inviteId}/decline`);
390
+ }
305
391
  /** Update a member's role */
306
392
  updateMemberRole(orgId, memberId, role) {
307
393
  return this.http.put(`/orgs/${orgId}/members/${memberId}`, { role });
@@ -317,6 +403,12 @@ var OrganizationsResource = class {
317
403
  };
318
404
 
319
405
  // src/resources/api-keys.ts
406
+ function withKeyAlias(k) {
407
+ if (k && typeof k === "object" && typeof k.secret === "string") {
408
+ k.key = k.secret;
409
+ }
410
+ return k;
411
+ }
320
412
  var ApiKeysResource = class {
321
413
  constructor(http) {
322
414
  this.http = http;
@@ -329,9 +421,16 @@ var ApiKeysResource = class {
329
421
  get(orgId, keyId) {
330
422
  return this.http.get(`/orgs/${orgId}/keys/${keyId}`);
331
423
  }
332
- /** Create a new API key (returns the full key only once) */
333
- create(orgId, data) {
334
- return this.http.post(`/orgs/${orgId}/keys`, data);
424
+ /**
425
+ * Create a new API key. The full key comes back once, in `secret`
426
+ * (`key` is a deprecated alias with the same value).
427
+ */
428
+ async create(orgId, data) {
429
+ const created = await this.http.post(
430
+ `/orgs/${orgId}/keys`,
431
+ data
432
+ );
433
+ return withKeyAlias(created);
335
434
  }
336
435
  /** Update an API key */
337
436
  update(orgId, keyId, data) {
@@ -341,9 +440,15 @@ var ApiKeysResource = class {
341
440
  revoke(orgId, keyId) {
342
441
  return this.http.delete(`/orgs/${orgId}/keys/${keyId}`);
343
442
  }
344
- /** Rotate an API key (revoke old, create new) */
345
- rotate(orgId, keyId) {
346
- return this.http.post(`/orgs/${orgId}/keys/${keyId}/rotate`);
443
+ /**
444
+ * Rotate an API key (revoke old, create new). The new full key is in
445
+ * `secret`; `key` is a deprecated alias.
446
+ */
447
+ async rotate(orgId, keyId) {
448
+ const rotated = await this.http.post(
449
+ `/orgs/${orgId}/keys/${keyId}/rotate`
450
+ );
451
+ return withKeyAlias(rotated);
347
452
  }
348
453
  /** Get analytics for an API key */
349
454
  getAnalytics(orgId, keyId, timeRange = "7d") {
@@ -398,12 +503,16 @@ var EndpointsResource = class {
398
503
  return this.http.post(`/orgs/${orgId}/endpoints/${endpointId}/resume`);
399
504
  }
400
505
  // ==================== Health & Metrics ====================
401
- /** Trigger a health check */
506
+ /** Trigger a health check (POST .../health-check) */
402
507
  checkHealth(orgId, endpointId) {
403
508
  return this.http.post(
404
509
  `/orgs/${orgId}/endpoints/${endpointId}/health-check`
405
510
  );
406
511
  }
512
+ /** Get the current health of an endpoint (GET .../health) */
513
+ getHealth(orgId, endpointId) {
514
+ return this.http.get(`/orgs/${orgId}/endpoints/${endpointId}/health`);
515
+ }
407
516
  /** Get endpoint health summary */
408
517
  getHealthOverview(orgId) {
409
518
  return this.http.get(`/orgs/${orgId}/endpoints/summary`);
@@ -413,8 +522,9 @@ var EndpointsResource = class {
413
522
  const params = new URLSearchParams();
414
523
  if (from) params.append("from", from);
415
524
  if (to) params.append("to", to);
525
+ const qs = params.toString();
416
526
  return this.http.get(
417
- `/orgs/${orgId}/endpoints/${endpointId}/metrics?${params.toString()}`
527
+ `/orgs/${orgId}/endpoints/${endpointId}/metrics${qs ? `?${qs}` : ""}`
418
528
  );
419
529
  }
420
530
  /** Update endpoint priorities */
@@ -430,9 +540,13 @@ var UsageResource = class {
430
540
  constructor(http) {
431
541
  this.http = http;
432
542
  }
433
- /** Get usage analytics for a date range */
543
+ /**
544
+ * Get usage rows (one per key per day) for a date range. `from` and `to`
545
+ * are ISO dates, e.g. `2026-01-31`.
546
+ */
434
547
  get(orgId, from, to) {
435
- return this.http.get(`/orgs/${orgId}/usage?from=${from}&to=${to}`);
548
+ const qs = new URLSearchParams({ from, to }).toString();
549
+ return this.http.get(`/orgs/${orgId}/usage?${qs}`);
436
550
  }
437
551
  /** Get usage summary */
438
552
  getSummary(orgId) {
@@ -441,13 +555,27 @@ var UsageResource = class {
441
555
  };
442
556
 
443
557
  // src/resources/dashboard.ts
558
+ var TIME_RANGES = {
559
+ "1h": "last_hour",
560
+ "24h": "last_24_hours",
561
+ "7d": "last_7_days",
562
+ "30d": "last_30_days"
563
+ };
564
+ function rangeQuery(timeRange, custom) {
565
+ const qs = new URLSearchParams();
566
+ if (timeRange) qs.set("timeRange", TIME_RANGES[timeRange] ?? timeRange);
567
+ if (custom?.from) qs.set("from", custom.from);
568
+ if (custom?.to) qs.set("to", custom.to);
569
+ const s = qs.toString();
570
+ return s ? `?${s}` : "";
571
+ }
444
572
  var DashboardResource = class {
445
573
  constructor(http) {
446
574
  this.http = http;
447
575
  }
448
576
  /** Get full dashboard data */
449
- get(orgId, period) {
450
- const params = period ? `?period=${period}` : "";
577
+ get(orgId, timeRange, custom) {
578
+ const params = rangeQuery(timeRange, custom);
451
579
  return this.http.get(`/orgs/${orgId}/dashboard${params}`);
452
580
  }
453
581
  /** Get system health overview */
@@ -455,23 +583,23 @@ var DashboardResource = class {
455
583
  return this.http.get(`/orgs/${orgId}/dashboard/health`);
456
584
  }
457
585
  /** Get quick stats */
458
- getQuickStats(orgId, period) {
459
- const params = period ? `?period=${period}` : "";
586
+ getQuickStats(orgId, timeRange, custom) {
587
+ const params = rangeQuery(timeRange, custom);
460
588
  return this.http.get(`/orgs/${orgId}/dashboard/quick-stats${params}`);
461
589
  }
462
590
  /** Get request rate time series */
463
- getRequestRate(orgId, period) {
464
- const params = period ? `?period=${period}` : "";
591
+ getRequestRate(orgId, timeRange, custom) {
592
+ const params = rangeQuery(timeRange, custom);
465
593
  return this.http.get(`/orgs/${orgId}/dashboard/request-rate${params}`);
466
594
  }
467
595
  /** Get error rate time series */
468
- getErrorRate(orgId, period) {
469
- const params = period ? `?period=${period}` : "";
596
+ getErrorRate(orgId, timeRange, custom) {
597
+ const params = rangeQuery(timeRange, custom);
470
598
  return this.http.get(`/orgs/${orgId}/dashboard/error-rate${params}`);
471
599
  }
472
600
  /** Get latency metrics */
473
- getLatency(orgId, period) {
474
- const params = period ? `?period=${period}` : "";
601
+ getLatency(orgId, timeRange, custom) {
602
+ const params = rangeQuery(timeRange, custom);
475
603
  return this.http.get(`/orgs/${orgId}/dashboard/latency${params}`);
476
604
  }
477
605
  /** Get performance metrics */
@@ -486,8 +614,17 @@ var AlertsResource = class {
486
614
  this.http = http;
487
615
  }
488
616
  // ==================== Alerts ====================
489
- /** List alerts for an organization */
490
- list(orgId) {
617
+ /**
618
+ * List alerts for an organization. The API answers an overview object
619
+ * (`alerts`, counters, rules, channels); this returns just the alerts.
620
+ * Use `overview()` for the rest.
621
+ */
622
+ async list(orgId) {
623
+ const overview = await this.overview(orgId);
624
+ return Array.isArray(overview?.alerts) ? overview.alerts : [];
625
+ }
626
+ /** The full alerts overview: alerts, counters, rules and channels. */
627
+ overview(orgId) {
491
628
  return this.http.get(`/orgs/${orgId}/alerts`);
492
629
  }
493
630
  /** Acknowledge an alert */
@@ -563,7 +700,7 @@ var LogsResource = class {
563
700
  constructor(http) {
564
701
  this.http = http;
565
702
  }
566
- /** Query request logs with filters and pagination */
703
+ /** Query request logs with filters (`limit` / `offset` paginate) */
567
704
  query(orgId, params) {
568
705
  const searchParams = new URLSearchParams();
569
706
  if (params) {
@@ -599,9 +736,19 @@ var SubscriptionsResource = class {
599
736
  get(orgId) {
600
737
  return this.http.get(`/orgs/${orgId}/subscription`);
601
738
  }
602
- /** Change subscription plan */
603
- changePlan(orgId, planId) {
604
- return this.http.put(`/orgs/${orgId}/subscription/plan`, { planId });
739
+ /**
740
+ * Change subscription plan.
741
+ *
742
+ * The server rejects a plan change unless the caller confirms the
743
+ * plan-change billing terms, so `options.acceptedTerms` is required and
744
+ * always sent. Pass `{ acceptedTerms: true }` only after the user has
745
+ * accepted the terms.
746
+ */
747
+ changePlan(orgId, planId, options) {
748
+ return this.http.put(`/orgs/${orgId}/subscription/plan`, {
749
+ planId,
750
+ acceptedTerms: options?.acceptedTerms === true
751
+ });
605
752
  }
606
753
  /** Cancel subscription */
607
754
  cancel(orgId) {
@@ -642,8 +789,16 @@ var SupportResource = class {
642
789
  createTicket(orgId, data) {
643
790
  return this.http.post(`/orgs/${orgId}/support/tickets`, data);
644
791
  }
645
- /** List support tickets */
646
- listTickets(orgId) {
792
+ /**
793
+ * List support tickets. The API answers `{ tickets, total }`; this
794
+ * returns the tickets. Use `listTicketsWithTotal()` for the total.
795
+ */
796
+ async listTickets(orgId) {
797
+ const res = await this.listTicketsWithTotal(orgId);
798
+ return Array.isArray(res?.tickets) ? res.tickets : [];
799
+ }
800
+ /** List support tickets with the total count. */
801
+ listTicketsWithTotal(orgId) {
647
802
  return this.http.get(`/orgs/${orgId}/support/tickets`);
648
803
  }
649
804
  /** Get ticket statistics */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iteradian-rpc/sdk",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Official Iteradian Control Plane SDK for JavaScript/TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "build": "tsup src/index.ts --format cjs,esm --dts",
21
21
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
22
  "lint": "tsc --noEmit",
23
- "test": "jest"
23
+ "test": "node --test test/account.test.mjs test/reality.test.mjs"
24
24
  },
25
25
  "keywords": [
26
26
  "iteradian",
@@ -33,22 +33,14 @@
33
33
  "author": "Iteradian",
34
34
  "license": "MIT",
35
35
  "devDependencies": {
36
- "tsup": "^8.5.1",
36
+ "tsup": "^9.0.0",
37
37
  "typescript": "^6.0.3",
38
- "@types/node": "^25.6.0"
38
+ "@types/node": "^26.6.5"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=16.0.0"
42
42
  },
43
- "repository": {
44
- "type": "git",
45
- "url": "git+https://github.com/IteRaDian-RPC/iteradian-control-plane.git",
46
- "directory": "sdks/typescript"
47
- },
48
- "homepage": "https://github.com/IteRaDian-RPC/iteradian-control-plane/tree/main/sdks/typescript#readme",
49
- "bugs": {
50
- "url": "https://github.com/IteRaDian-RPC/iteradian-control-plane/issues"
51
- },
43
+ "homepage": "https://iteradian.com",
52
44
  "publishConfig": {
53
45
  "access": "public"
54
46
  }