@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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  <p align="center">
7
7
  <img src="https://img.shields.io/badge/language-TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" />
8
8
  <img src="https://img.shields.io/badge/node-%3E%3D16.0.0-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node 16+" />
9
- <img src="https://img.shields.io/badge/version-1.0.0-green?style=flat-square" alt="Version" />
9
+ <img src="https://img.shields.io/badge/version-1.2.0-green?style=flat-square" alt="Version" />
10
10
  <img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License" />
11
11
  <img src="https://img.shields.io/badge/npm-%40iteradian%2Fsdk-CB3837?style=flat-square&logo=npm&logoColor=white" alt="npm" />
12
12
  <img src="https://img.shields.io/badge/deps-zero-brightgreen?style=flat-square" alt="Zero Dependencies" />
@@ -57,11 +57,15 @@ const client = new IteradianClient({
57
57
  });
58
58
 
59
59
  // Login — sets the access token automatically
60
- const tokens = await client.auth.login({
60
+ const login = await client.auth.login({
61
61
  email: "user@example.com",
62
62
  password: "password123",
63
63
  });
64
- console.log(`Logged in as ${tokens.user.email}`);
64
+ if (login.requires2FA) {
65
+ // Two-factor auth is on: log in again with a code (see Authentication).
66
+ throw new Error("2FA code required");
67
+ }
68
+ console.log(`Logged in as ${login.user?.email}`);
65
69
 
66
70
  // List organizations
67
71
  const orgs = await client.organizations.list();
@@ -81,12 +85,13 @@ if (orgs.length > 0) {
81
85
  name: "Production Key",
82
86
  environment: "live",
83
87
  });
84
- console.log(`Created key: ${key.prefix}`);
88
+ // The full key is shown once, in `secret`.
89
+ console.log(`Created key: ${key.prefix} (${key.secret})`);
85
90
 
86
91
  // Query logs
87
92
  const logs = await client.logs.query(orgId, {
88
93
  status: "error",
89
- pageSize: 50,
94
+ limit: 50,
90
95
  });
91
96
  console.log(`Total logs: ${logs.total}`);
92
97
  }
@@ -163,14 +168,26 @@ client.clearAccessToken();
163
168
  ### Authentication (`client.auth`)
164
169
 
165
170
  ```typescript
166
- // Login (token set automatically)
167
- const tokens = await client.auth.login({ email: "...", password: "..." });
171
+ // Login (the access token is set automatically when the answer has one)
172
+ const login = await client.auth.login({ email: "...", password: "..." });
173
+
174
+ // With two-factor auth on, a login without a code answers
175
+ // { requires2FA: true } and no tokens. Log in again with a code:
176
+ if (login.requires2FA) {
177
+ await client.auth.login({ email: "...", password: "...", twoFactorCode: "123456" });
178
+ // or, with a one-time backup code:
179
+ // await client.auth.login({ email: "...", password: "...", backupCode: "..." });
180
+ }
168
181
 
169
- // Register
170
- const tokens = await client.auth.register({
182
+ // Register — does NOT sign in. Answers { message, email } and emails a
183
+ // confirmation link; log in once the address is confirmed.
184
+ // acceptedTerms is required: pass true only after the user agreed to the
185
+ // Terms of Service, Privacy Policy and DPA (https://iteradian.com/terms,
186
+ // /privacy, /dpa). The server rejects registration (400) otherwise.
187
+ const { message } = await client.auth.register({
171
188
  email: "...",
172
189
  password: "...",
173
- name: "...",
190
+ acceptedTerms: true,
174
191
  });
175
192
 
176
193
  // Refresh token
@@ -179,19 +196,25 @@ const tokens = await client.auth.refresh(refreshToken);
179
196
  // Logout
180
197
  await client.auth.logout();
181
198
 
199
+ // Email verification
200
+ await client.auth.verifyEmail(token);
201
+ await client.auth.resendVerification("user@example.com");
202
+
182
203
  // Two-Factor Authentication
183
- const setup = await client.auth.enable2FA({ password: "..." });
204
+ const setup = await client.auth.enable2FA("password");
184
205
  console.log(`Secret: ${setup.secret}`);
185
206
  console.log(`QR Code: ${setup.qrCode}`);
186
- await client.auth.verify2FA({ code: "123456" });
187
- await client.auth.disable2FA({ password: "...", code: "123456" });
207
+ const { backupCodes } = await client.auth.verify2FA("123456");
208
+ const fresh = await client.auth.regenerateBackupCodes("password");
209
+ await client.auth.disable2FA("password", "123456"); // password + current 6-digit TOTP code
188
210
 
189
211
  // Magic Link
190
- await client.auth.sendMagicLink({ email: "user@example.com" });
212
+ await client.auth.requestMagicLink("user@example.com");
213
+ await client.auth.loginWithMagicLink(tokenFromEmail);
191
214
 
192
215
  // Password Reset
193
- await client.auth.forgotPassword({ email: "user@example.com" });
194
- await client.auth.resetPassword({ token: "...", newPassword: "..." });
216
+ await client.auth.forgotPassword("user@example.com");
217
+ await client.auth.resetPassword(tokenFromEmail, "new-password");
195
218
  ```
196
219
 
197
220
  ### Organizations (`client.organizations`)
@@ -199,20 +222,40 @@ await client.auth.resetPassword({ token: "...", newPassword: "..." });
199
222
  ```typescript
200
223
  // CRUD
201
224
  const orgs = await client.organizations.list();
202
- const org = await client.organizations.create({
225
+ // `name`, `planId` and `acceptedTerms` are required; `slug` and
226
+ // `billingInterval` ("month" | "year") are optional.
227
+ const created = await client.organizations.create({
203
228
  name: "My Org",
229
+ planId: "plan_free",
230
+ acceptedTerms: true,
204
231
  slug: "my-org",
205
232
  });
206
233
  const org = await client.organizations.get(orgId);
234
+ await client.organizations.update(orgId, { name: "Renamed" });
207
235
  await client.organizations.delete(orgId);
236
+ await client.organizations.leave(orgId);
237
+ const perms = await client.organizations.getMyPermissions(orgId);
208
238
 
209
239
  // Members
210
240
  const members = await client.organizations.listMembers(orgId);
211
- const member = await client.organizations.inviteMember(orgId, {
212
- email: "user@example.com",
213
- role: "member",
214
- });
241
+ await client.organizations.updateMemberRole(orgId, memberId, "viewer");
215
242
  await client.organizations.removeMember(orgId, memberId);
243
+
244
+ // Invitations — inviting creates a pending invite; the invitee becomes a
245
+ // member only after accepting it (signed in, verified email).
246
+ const invite = await client.organizations.inviteMember(orgId, {
247
+ email: "user@example.com",
248
+ role: "developer",
249
+ }); // { id, email, role, status: "pending", expiresAt, createdAt }
250
+ const open = await client.organizations.listInvites(orgId);
251
+ await client.organizations.revokeInvite(orgId, invite.id);
252
+
253
+ // As the invitee
254
+ const { invites } = await client.organizations.listMyInvites();
255
+ await client.organizations.acceptInvite(invites[0].id); // or declineInvite()
256
+ // A user who has not accepted the current terms version gets 403
257
+ // (TERMS_ACCEPTANCE_REQUIRED); once they agree, accept with:
258
+ await client.organizations.acceptInvite(invites[0].id, { acceptedTerms: true });
216
259
  ```
217
260
 
218
261
  ### API Keys (`client.apiKeys`)
@@ -223,9 +266,12 @@ const key = await client.apiKeys.create(orgId, {
223
266
  name: "Key Name",
224
267
  environment: "live",
225
268
  });
226
- await client.apiKeys.revoke(orgId, keyId);
227
- const newKey = await client.apiKeys.rotate(orgId, keyId);
228
- const analytics = await client.apiKeys.getAnalytics(orgId, keyId);
269
+ // The full key is shown once, in `secret`. `key.key` is a deprecated alias.
270
+ console.log(key.secret);
271
+ await client.apiKeys.update(orgId, key.id, { name: "Renamed" });
272
+ const newKey = await client.apiKeys.rotate(orgId, key.id); // new key in newKey.secret
273
+ const analytics = await client.apiKeys.getAnalytics(orgId, key.id, "7d");
274
+ await client.apiKeys.revoke(orgId, key.id);
229
275
  ```
230
276
 
231
277
  ### Endpoints (`client.endpoints`)
@@ -233,95 +279,135 @@ const analytics = await client.apiKeys.getAnalytics(orgId, keyId);
233
279
  ```typescript
234
280
  // Networks
235
281
  const networks = await client.endpoints.getNetworks();
282
+ const network = await client.endpoints.getNetwork(networkId);
236
283
 
237
284
  // Endpoint management
238
285
  const endpoints = await client.endpoints.list(orgId);
239
286
  const endpoint = await client.endpoints.create(orgId, {
240
- name: "...",
287
+ name: "My endpoint",
241
288
  networkId: "...",
242
- region: "us-east-1",
289
+ region: "eu_central",
243
290
  });
244
- await client.endpoints.delete(orgId, endpointId);
245
- const paused = await client.endpoints.pause(orgId, endpointId);
246
- const resumed = await client.endpoints.resume(orgId, endpointId);
247
- const health = await client.endpoints.checkHealth(orgId, endpointId);
248
- const metrics = await client.endpoints.getMetrics(orgId, endpointId);
291
+ await client.endpoints.update(orgId, endpoint.id, { name: "Renamed" });
292
+ await client.endpoints.delete(orgId, endpoint.id);
293
+ const paused = await client.endpoints.pause(orgId, endpoint.id);
294
+ const resumed = await client.endpoints.resume(orgId, endpoint.id);
295
+ await client.endpoints.updatePriorities(orgId, [{ id: endpoint.id, priority: 2 }]);
296
+
297
+ // Health and metrics
298
+ const check = await client.endpoints.checkHealth(orgId, endpoint.id); // runs a check now
299
+ const health = await client.endpoints.getHealth(orgId, endpoint.id); // current health
300
+ const summary = await client.endpoints.getHealthOverview(orgId);
301
+ const metrics = await client.endpoints.getMetrics(orgId, endpoint.id);
302
+ const ranged = await client.endpoints.getMetrics(
303
+ orgId,
304
+ endpoint.id,
305
+ "2026-01-01T00:00:00Z",
306
+ "2026-01-02T00:00:00Z",
307
+ );
249
308
  ```
250
309
 
310
+ Decimal fields such as `avgLatencyMs`, `successRate` and `uptime` can arrive as
311
+ numeric strings; they are typed `number | string`, so use `Number(value)`.
312
+
251
313
  ### Usage & Dashboard
252
314
 
253
315
  ```typescript
254
- // Usage
255
- const usage = await client.usage.get(orgId, {
256
- from: "2024-01-01",
257
- to: "2024-01-31",
258
- });
259
-
260
- // Dashboard
261
- const data = await client.dashboard.get(orgId, { period: "7d" });
316
+ // Usage: a list of rows (one per key per day)
317
+ const rows = await client.usage.get(orgId, "2026-01-01", "2026-01-31");
318
+ const usage = await client.usage.getSummary(orgId);
319
+
320
+ // Dashboard. The optional second argument is the API timeRange:
321
+ // last_hour, last_24_hours (default), last_7_days, last_30_days or custom.
322
+ // The short forms "1h", "24h", "7d" and "30d" are translated.
323
+ const data = await client.dashboard.get(orgId, "7d");
324
+ const custom = await client.dashboard.get(orgId, "custom", { from: "2026-01-01", to: "2026-01-07" });
262
325
  const health = await client.dashboard.getHealth(orgId);
263
- const stats = await client.dashboard.getQuickStats(orgId, { period: "24h" });
326
+ const stats = await client.dashboard.getQuickStats(orgId, "24h");
327
+ const requestRate = await client.dashboard.getRequestRate(orgId); // { currentRate, timeSeries, ... }
328
+ const errorRate = await client.dashboard.getErrorRate(orgId);
329
+ const latency = await client.dashboard.getLatency(orgId); // { p50, p95, p99, ... }
330
+ const perf = await client.dashboard.getPerformance(orgId);
264
331
  ```
265
332
 
266
333
  ### Alerts (`client.alerts`)
267
334
 
268
335
  ```typescript
269
- // Alerts
336
+ // Alerts. list() returns the alerts array; overview() also has the counters,
337
+ // rules and channels.
270
338
  const alerts = await client.alerts.list(orgId);
339
+ const overview = await client.alerts.overview(orgId);
271
340
  const ack = await client.alerts.acknowledge(orgId, alertId);
272
341
  const resolved = await client.alerts.resolve(orgId, alertId);
342
+ await client.alerts.evaluate(orgId);
273
343
 
274
- // Alert Rules
344
+ // Alert Rules. type is one of high_error_rate, high_latency,
345
+ // rate_limit_exceeded, quota_warning, service_down, key_expiring,
346
+ // unusual_traffic; config holds the thresholds for that type.
275
347
  const rules = await client.alerts.listRules(orgId);
276
348
  const rule = await client.alerts.createRule(orgId, {
277
- name: "High Latency",
278
- metric: "latency_p95",
279
- condition: "greater_than",
280
- threshold: 500,
281
- severity: "warning",
349
+ name: "High error rate",
350
+ type: "high_error_rate",
351
+ config: { threshold: 5 },
352
+ severity: "warning", // info | warning | error | critical
353
+ cooldownSeconds: 900, // at least 60
282
354
  isEnabled: true,
283
- cooldownMinutes: 15,
284
355
  });
285
- await client.alerts.deleteRule(orgId, ruleId);
286
-
287
- // Channels
288
- const channels = await client.alerts.getChannels(orgId);
289
- await client.alerts.testChannel(orgId, channelId);
356
+ await client.alerts.updateRule(orgId, rule.id, { name: "Renamed" });
357
+ await client.alerts.toggleRule(orgId, rule.id);
358
+ await client.alerts.seedDefaults(orgId);
359
+ await client.alerts.deleteRule(orgId, rule.id);
360
+
361
+ // Channels. type is email | webhook | slack.
362
+ const channels = await client.alerts.listChannels(orgId);
363
+ const channel = await client.alerts.createChannel(orgId, {
364
+ name: "Ops webhook",
365
+ type: "webhook",
366
+ config: { url: "https://example.com/hook" },
367
+ });
368
+ await client.alerts.updateChannel(orgId, channel.id, { isEnabled: false });
369
+ await client.alerts.testChannel(orgId, channel.id);
370
+ await client.alerts.deleteChannel(orgId, channel.id);
290
371
  ```
291
372
 
292
373
  ### Logs (`client.logs`)
293
374
 
294
375
  ```typescript
295
- // Query with filters
376
+ // Query with filters. Page with limit (1-200) and offset.
296
377
  const logs = await client.logs.query(orgId, {
297
- page: 1,
298
- pageSize: 50,
378
+ limit: 50,
379
+ offset: 0,
299
380
  status: "error",
300
381
  method: "eth_call",
301
- network: "ethereum",
382
+ network: "eth-mainnet",
383
+ minLatencyMs: 100,
302
384
  sortBy: "timestamp",
303
385
  sortOrder: "desc",
304
386
  });
305
- console.log(`Page ${logs.page}/${logs.totalPages}`);
387
+ console.log(`${logs.logs.length} of ${logs.total}`);
306
388
  for (const log of logs.logs) {
307
389
  console.log(` [${log.status}] ${log.method} (${log.latencyMs}ms)`);
308
390
  }
309
391
 
310
392
  // Get single log
311
- const log = await client.logs.get(orgId, logId);
393
+ const log = await client.logs.get(orgId, logs.logs[0].id);
312
394
 
313
395
  // Filter options
314
396
  const filters = await client.logs.getFilterOptions(orgId);
315
397
 
316
398
  // Stats
317
- const stats = await client.logs.getStats(orgId, { period: "24h" });
399
+ const stats = await client.logs.getStats(orgId);
318
400
  ```
319
401
 
320
402
  ### Subscriptions (`client.subscriptions`)
321
403
 
322
404
  ```typescript
323
405
  const sub = await client.subscriptions.get(orgId);
324
- const updated = await client.subscriptions.changePlan(orgId, { planId: "pro" });
406
+ // The caller confirms the user accepted the plan-change billing terms; the
407
+ // server rejects the change (400) unless acceptedTerms is true.
408
+ const updated = await client.subscriptions.changePlan(orgId, "plan_builder", {
409
+ acceptedTerms: true,
410
+ });
325
411
  const cancelled = await client.subscriptions.cancel(orgId);
326
412
  const reactivated = await client.subscriptions.reactivate(orgId);
327
413
  const invoices = await client.subscriptions.getInvoices(orgId);
@@ -337,22 +423,41 @@ const plan = await client.plans.get(planId);
337
423
  ### Support (`client.support`)
338
424
 
339
425
  ```typescript
426
+ // listTickets() returns the tickets array.
340
427
  const tickets = await client.support.listTickets(orgId);
341
428
  const ticket = await client.support.createTicket(orgId, {
342
429
  subject: "API returning 500 errors",
343
- category: "technical",
344
- priority: "high",
345
- message: "Detailed description...",
430
+ body: "Detailed description...",
431
+ category: "technical", // optional
432
+ priority: "high", // optional: low | medium | high | urgent
346
433
  });
347
- const detail = await client.support.getTicket(orgId, ticketId);
348
- await client.support.addMessage(orgId, ticketId, {
349
- content: "Follow-up message...",
434
+ const stats = await client.support.getStats(orgId);
435
+ const detail = await client.support.getTicket(orgId, ticket.id);
436
+ await client.support.addMessage(orgId, ticket.id, "Follow-up message...");
437
+ await client.support.updateStatus(orgId, ticket.id, "resolved");
438
+ ```
439
+
440
+ ### RPC (`client.rpc`)
441
+
442
+ ```typescript
443
+ const gateway = new IteradianClient({ apiKey: "itrd_live_..." });
444
+ const networks = await gateway.rpc.networks();
445
+ const res = await gateway.rpc.call("eth-mainnet", {
446
+ jsonrpc: "2.0",
447
+ method: "eth_blockNumber",
448
+ params: [],
449
+ id: 1,
350
450
  });
451
+ const batch = await gateway.rpc.batch("eth-mainnet", [
452
+ { jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 },
453
+ { jsonrpc: "2.0", method: "eth_chainId", params: [], id: 2 },
454
+ ]);
351
455
  ```
352
456
 
353
457
  ## Type Definitions
354
458
 
355
- All types are exported from the package and defined in `src/types.ts` (538 lines):
459
+ All types are exported from the package and defined in `src/types.ts`. Response fields are optional except `id`; decimal
460
+ fields are typed `number | string` (`Numeric`):
356
461
 
357
462
  ### Configuration
358
463
 
@@ -362,66 +467,75 @@ All types are exported from the package and defined in `src/types.ts` (538 lines
362
467
 
363
468
  ### Auth Types
364
469
 
365
- | Type | Fields |
366
- | ----------------- | ------------------------------------- |
367
- | `LoginRequest` | `email`, `password`, `twoFactorCode?` |
368
- | `RegisterRequest` | `email`, `password`, `name` |
369
- | `AuthTokens` | `accessToken`, `refreshToken`, `user` |
370
- | `TwoFASetup` | `secret`, `qrCode` |
470
+ | Type | Fields |
471
+ | ------------------ | ------------------------------------------------------------------- |
472
+ | `LoginRequest` | `email`, `password`, `twoFactorCode?`, `backupCode?` |
473
+ | `RegisterRequest` | `email`, `password`, `acceptedTerms` |
474
+ | `RegisterResponse` | `message`, `email` (no tokens) |
475
+ | `LoginResult` | `accessToken?`, `refreshToken?`, `user?`, `requires2FA?` |
476
+ | `AuthTokens` | `accessToken`, `refreshToken`, `user?` (refresh) |
477
+ | `TwoFASetup` | `secret`, `qrCode` |
371
478
 
372
479
  ### Organization Types
373
480
 
374
- | Type | Fields |
375
- | --------------------- | ---------------------------------------------- |
376
- | `Organization` | `id`, `name`, `slug`, `createdAt`, `updatedAt` |
377
- | `OrgMember` | `id`, `userId`, `email`, `role`, `joinedAt` |
378
- | `CreateOrgRequest` | `name`, `slug` |
379
- | `InviteMemberRequest` | `email`, `role` |
481
+ | Type | Fields |
482
+ | --------------------------- | ----------------------------------------------------------------------- |
483
+ | `Organization` | `id`, `name?`, `slug?`, `role?`, `subscription?`, `members?` |
484
+ | `OrgMember` | `id`, `userId?`, `email?`, `role?`, `joinedAt?` |
485
+ | `CreateOrganizationRequest` | `name`, `planId`, `acceptedTerms`, `slug?`, `billingInterval?` |
486
+ | `OrgInvite` | `id`, `email?`, `role?`, `status?`, `expiresAt?`, `createdAt?`, `invitedBy?` |
487
+ | `MyInvites` | `emailVerified?`, `invites` (`MyInvite[]`) |
488
+ | `InviteAcceptance` | `organizationId?`, `membershipId?`, `role?` |
380
489
 
381
490
  ### API Key Types
382
491
 
383
- | Type | Fields |
384
- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
385
- | `ApiKey` | `id`, `name`, `prefix`, `key?`, `environment`, `status`, `ipAllowlist?`, `allowedNetworks?`, `rateLimit?`, `dailyLimit?`, `createdAt`, `updatedAt`, `expiresAt?`, `lastUsedAt?` |
386
- | `CreateApiKeyRequest` | `name`, `environment`, `ipAllowlist?`, `allowedNetworks?`, `rateLimit?`, `dailyLimit?`, `expiresAt?` |
492
+ | Type | Fields |
493
+ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
494
+ | `ApiKey` | `id`, `name?`, `prefix?`, `environment?`, `status?`, `ipAllowlist?`, `allowedNetworks?`, `rateLimit?`, `dailyLimit?`, `expiresAt?`, `lastUsedAt?`, `createdAt?` |
495
+ | `ApiKeyWithSecret` | `ApiKey` plus `secret` (create and rotate) and `key` (deprecated alias of `secret`) |
496
+ | `CreateApiKeyRequest` | `name`, `environment`, `ipAllowlist?`, `allowedNetworks?`, `rateLimit?`, `dailyLimit?`, `expiresAt?`, `walletUrlAuth?` |
387
497
 
388
498
  ### Network & Endpoint Types
389
499
 
390
- | Type | Fields |
391
- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
392
- | `Network` | `id`, `slug`, `name`, `chainId`, `type`, `environment`, `isActive` |
393
- | `Endpoint` | `id`, `organizationId`, `networkId`, `region`, `name`, `priority`, `status`, `isEnabled`, `timeoutMs`, `retryCount`, metrics |
394
- | `CreateEndpointRequest` | `name`, `networkId`, `region`, `priority?`, `timeoutMs?`, `retryCount?` |
500
+ | Type | Fields |
501
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------- |
502
+ | `Network` | `id`, `name?`, `chainId?`, `type?`, `environment?`, `nativeCurrency?`, `isActive?` |
503
+ | `Endpoint` | `id`, `networkId?`, `region?`, `name?`, `priority?`, `status?`, `isEnabled?`, `timeoutMs?`, `retryCount?`, metrics |
504
+ | `CreateEndpointRequest` | `name`, `networkId`, `region`, `priority?`, `timeoutMs?`, `retryCount?`, `rateLimitOverride?` |
505
+ | `EndpointHealth` | `status?`, `nodeCount?`, `healthyNodeCount?`, `latencyMs?`, `lastCheck?` |
395
506
 
396
507
  ### Logging Types
397
508
 
398
- | Type | Fields |
399
- | ---------------- | ----------------------------------------------------------------------------------------------------------- |
400
- | `RequestLog` | `id`, `organizationId`, `method`, `status`, `statusCode?`, `latencyMs?`, `network?`, `region?`, `timestamp` |
401
- | `LogsResponse` | `logs`, `total`, `page`, `pageSize`, `totalPages` |
402
- | `LogQueryParams` | `page?`, `pageSize?`, `status?`, `method?`, `network?`, `region?`, `from?`, `to?`, `sortBy?`, `sortOrder?` |
509
+ | Type | Fields |
510
+ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
511
+ | `RequestLog` | `id`, `method?`, `status?`, `statusCode?`, `latencyMs?`, `network?`, `region?`, `timestamp?` |
512
+ | `LogsResponse` | `logs`, `total?`, `stats?` |
513
+ | `LogQueryParams` | `limit?`, `offset?`, `status?`, `method?`, `apiKeyId?`, `network?`, `region?`, `search?`, `statusCode?`, `minLatencyMs?`, `maxLatencyMs?`, `from?`, `to?`, `sortBy?`, `sortOrder?` |
403
514
 
404
515
  ### Alert Types
405
516
 
406
- | Type | Fields |
407
- | ------------------------ | -------------------------------------------------------------------------------------------------------------- |
408
- | `Alert` | `id`, `organizationId`, `ruleId?`, `severity`, `status`, `title`, `message`, `metadata?` |
409
- | `AlertRule` | `id`, `organizationId`, `name`, `metric`, `condition`, `threshold`, `severity`, `isEnabled`, `cooldownMinutes` |
410
- | `CreateAlertRuleRequest` | `name`, `metric`, `condition`, `threshold`, `severity`, `isEnabled`, `cooldownMinutes` |
517
+ | Type | Fields |
518
+ | -------------------------- | -------------------------------------------------------------------------------------------- |
519
+ | `Alert` | `id`, `ruleId?`, `type?`, `severity?`, `status?`, `title?`, `message?`, `firedAt?`, `metadata?` |
520
+ | `AlertRule` | `id`, `name?`, `type?`, `severity?`, `isEnabled?`, `config?`, `cooldownSeconds?` |
521
+ | `CreateAlertRuleRequest` | `name`, `type`, `config`, `severity?`, `cooldownSeconds?`, `isEnabled?` |
522
+ | `AlertChannel` | `id`, `name?`, `type?`, `isEnabled?`, `config?` |
523
+ | `CreateAlertChannelRequest` | `name`, `type`, `config`, `isEnabled?` |
411
524
 
412
525
  ### Billing Types
413
526
 
414
- | Type | Fields |
415
- | -------------- | --------------------------------------------------------------------------------------- |
416
- | `Plan` | `id`, `name`, `slug`, `price`, `currency`, `interval`, `features`, `limits`, `isActive` |
417
- | `Subscription` | `id`, `organizationId`, `planId`, `status`, `cancelAtPeriodEnd`, `plan?` |
527
+ | Type | Fields |
528
+ | -------------- | ------------------------------------------------------------------------------------------------------- |
529
+ | `Plan` | `id`, `name?`, `displayName?`, `priceMonthly?`, `limits?`, `features?`, `isActive?` |
530
+ | `Subscription` | `id`, `status?`, `plan?`, `billingInterval?`, `currentPeriodEnd?`, `cancelAtPeriodEnd?`, `recentInvoices?` |
418
531
 
419
532
  ### Support Types
420
533
 
421
- | Type | Fields |
422
- | --------------------- | ------------------------------------------------------------------- |
423
- | `SupportTicket` | `id`, `organizationId`, `subject`, `category`, `priority`, `status` |
424
- | `CreateTicketRequest` | `subject`, `category`, `priority`, `message` |
534
+ | Type | Fields |
535
+ | --------------------- | ------------------------------------------------------------------------------ |
536
+ | `SupportTicket` | `id`, `subject?`, `category?`, `priority?`, `status?`, `messages?`, `createdBy?` |
537
+ | `CreateTicketRequest` | `subject`, `body`, `category?`, `priority?` |
538
+ | `TicketMessage` | `id`, `body?`, `isStaff?`, `author?`, `createdAt?` |
425
539
 
426
540
  ### Error Types
427
541
 
@@ -441,7 +555,7 @@ try {
441
555
  } catch (err) {
442
556
  if (err instanceof IteradianError) {
443
557
  console.log(`Status: ${err.statusCode}`); // 401
444
- console.log(`Message: ${err.message}`); // "Invalid credentials"
558
+ console.log(`Message: ${err.message}`); // "Invalid credentials" (validation errors: messages joined with ", ")
445
559
  console.log(`Error: ${err.error}`); // Error code
446
560
  } else {
447
561
  console.log(`Unexpected error: ${err}`);
@@ -449,6 +563,16 @@ try {
449
563
  }
450
564
  ```
451
565
 
566
+ ### Terms acceptance (403 `TERMS_ACCEPTANCE_REQUIRED`)
567
+
568
+ After Iteradian publishes a new version of the Terms of Service, Privacy
569
+ Policy or DPA, any write (POST/PUT/PATCH/DELETE) made with a user JWT may
570
+ answer `IteradianError` with `statusCode` 403 until the user accepts the new
571
+ version in the dashboard (the response body carries
572
+ `code: "TERMS_ACCEPTANCE_REQUIRED"`, `termsVersion` and `termsUrl`). API keys
573
+ and agent tokens are unaffected. `organizations.acceptInvite(id, {
574
+ acceptedTerms: true })` records the acceptance as part of joining.
575
+
452
576
  On a gateway call the same error also carries the JSON-RPC detail, which is how
453
577
  you tell "wait a second" from "you are out of quota until the period rolls
454
578
  over":
@@ -465,6 +589,9 @@ try {
465
589
  if (err instanceof IteradianError) {
466
590
  if (err.isRateLimited) {
467
591
  // -32005. Wait err.retryAfter seconds and try again.
592
+ } else if (err.isDailyLimitReached) {
593
+ // -32008. This API key's daily limit is used up. err.data carries
594
+ // { dailyLimit, used, cost, resetsAt }; resetsAt is the next 00:00 UTC.
468
595
  } else if (err.isQuotaExhausted) {
469
596
  // -32006 or -32007. Retrying will not help: err.data.resetsAt says when
470
597
  // the allowance returns, and the cap needs a plan change.
@@ -489,8 +616,9 @@ The SDK automatically retries:
489
616
  - **5xx Server Errors** — with exponential backoff
490
617
  - **429 Too Many Requests**, but **only** `-32005` (rate or concurrency limit).
491
618
  `-32006` (monthly quota exhausted) and `-32007` (overage spend cap) do not
492
- recover until the billing period rolls over, so retrying them burns your time
493
- for a guaranteed 429.
619
+ recover until the billing period rolls over, and `-32008` (per-API-key daily
620
+ limit) not until `error.data.resetsAt` (the next 00:00 UTC), so retrying
621
+ them burns your time for a guaranteed 429.
494
622
 
495
623
  Backoff formula: `min(1000 * 2^(attempt-1), 10000)` ms, or the gateway's own
496
624
  `retryAfter` hint when it gives one — capped at 30 seconds either way.
@@ -622,10 +750,61 @@ This SDK takes both, and defaults the gateway to `https://rpc.iteradian.com`.
622
750
  organization holds. Creating more keys does not raise them.
623
751
  - The gateway returns HTTP 429 with a JSON-RPC error body:
624
752
  `-32005` rate limited (retry after `error.data.retryAfter`), `-32006` monthly
625
- quota exhausted, `-32007` overage spend cap reached. The last two do not
626
- recover until the billing period rolls over, so retrying them is wasted work.
753
+ quota exhausted, `-32007` overage spend cap reached, `-32008` the API key's
754
+ own daily limit reached (`error.data = { dailyLimit, used, cost, resetsAt }`).
755
+ None of the last three recover before `resetsAt` (the billing period roll-over,
756
+ or the next 00:00 UTC for `-32008`), so retrying them is wasted work.
757
+
758
+ Plans (defaults; an admin can change them): Free 25 rps / 3M per month with no
759
+ overage (requests stop at the quota, no card needed), Developer 100 rps / 5M
760
+ with overage at $6 per million, Professional 500 rps / 30M at $4.50 per million,
761
+ Dedicated per contract from $4 per million. Trials are offered on Developer
762
+ only; a trial that ends unpaid falls back to Free. See `sdks/SDK_CONTRACT.md`
763
+ for the full table.
764
+
765
+ ### WebSocket
766
+
767
+ The gateway also serves JSON-RPC over WebSocket at
768
+ `wss://rpc.iteradian.com/ws/v1/{network}`. This SDK does not wrap it on
769
+ purpose: connect with any WebSocket client, such as ethers
770
+ `WebSocketProvider`, viem's `webSocket()` transport, or the built-in
771
+ `WebSocket` in browsers and Node 22+. Authenticate with the
772
+ `X-Api-Key` header, `Authorization: Bearer <key>`, or `?apiKey=<key>` on the
773
+ URL (browsers cannot set headers on a WebSocket).
627
774
 
628
- Plans: Developer 20 rps / 5M per month, Professional 100 rps / 30M, Dedicated
629
- per contract. See `sdks/SDK_CONTRACT.md` for the full table.
775
+ ```typescript
776
+ const ws = new WebSocket(
777
+ "wss://rpc.iteradian.com/ws/v1/eth-mainnet?apiKey=itrd_live_...",
778
+ );
779
+
780
+ ws.addEventListener("open", () => {
781
+ ws.send(JSON.stringify({
782
+ jsonrpc: "2.0", id: 1, method: "eth_subscribe", params: ["newHeads"],
783
+ }));
784
+ });
785
+
786
+ ws.addEventListener("message", (event) => {
787
+ const msg = JSON.parse(event.data);
788
+ if (msg.method === "eth_subscription") {
789
+ console.log("new head", msg.params.result.number);
790
+ }
791
+ });
792
+
793
+ ws.addEventListener("close", (event) => {
794
+ // 1012: node restarted, reconnect and re-subscribe. 4003: key revoked, stop.
795
+ console.log("closed", event.code);
796
+ });
797
+ ```
630
798
 
631
- **WebSocket is not available.** The gateway serves HTTPS JSON-RPC only.
799
+ - Each message you send is charged like an HTTP request (a batch of N costs N).
800
+ Subscription notifications pushed to you are not charged.
801
+ - A message refused by a limit gets an ordinary JSON-RPC error under that
802
+ call's `id`, with the same codes as above (`-32005` to `-32008`). The socket
803
+ stays open.
804
+ - An organization can hold `max(5, floor(concurrency / 4))` streams at once;
805
+ past that the upgrade is refused with HTTP 429. Other upgrade failures: 400
806
+ bad path, 401/403 bad key, 404 unknown network, 503 no WebSocket-capable node.
807
+ - Close code `1012` means the upstream node restarted: reconnect and
808
+ re-subscribe. `4003` means the key was revoked or the organization suspended
809
+ (keys are re-checked every 60 s): do not reconnect.
810
+ - The server pings every 30 s. Messages are limited to 1 MB.